Skip to content

Commit 2d371c7

Browse files
committed
made some changes to result in a cleaner shut down process. Added dynamic gas calculation so that we can respond to times where gas prices might not to be a bit higher to get through
1 parent 93cce93 commit 2d371c7

8 files changed

Lines changed: 632 additions & 52 deletions

File tree

cmd/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"os/signal"
88
"syscall"
9+
"time"
910

1011
"github.com/joho/godotenv"
1112
"github.com/rs/zerolog"
@@ -126,6 +127,7 @@ func init() {
126127
rootCmd.Flags().Uint32("auto-unbonding-frequency", 0, "Enable automatic unbonding every N days (0 = disabled, 1 - 21 days = valid")
127128
rootCmd.Flags().Uint32("auto-unbonding-amount", 0, "Amount of tokens in loya to unbond each unbonding transaction (0 = disabled)")
128129
rootCmd.Flags().String("auto-unbonding-max-stake-percentage", "0.0", "Maximum percentage of stake to unbond each unbonding transaction (0 = disabled, 1.0 = 100%). If unbonding amount exceeds this percentage, we will skip the unbonding transaction until it exceeds this percentage again.")
130+
rootCmd.Flags().Duration("refresh-gas-estimates-interval", 12*time.Hour, "Interval for resetting cached gas estimates and gas-adjustment levels (<=0 disables)")
129131

130132
// Marking required flags
131133
if err := rootCmd.MarkFlagRequired(flags.FlagHome); err != nil {

pricefeed/client/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,14 @@ func (c *Client) start(ctx context.Context,
137137
exchangeIdToExchangeDetails map[types.ExchangeId]types.ExchangeQueryDetails,
138138
subTaskRunner SubTaskRunner,
139139
) (err error) {
140+
// Release daemonStartup on any exit before the normal "startup complete" point (avoids Stop() blocking forever).
141+
startupCompleted := false
142+
defer func() {
143+
if !startupCompleted {
144+
c.daemonStartup.Done()
145+
}
146+
}()
147+
140148
// 1. Establish connections to gRPC servers.
141149
queryConn, err := grpcClient.NewTcpConnection(ctx, grpcAddress)
142150
if err != nil {
@@ -246,6 +254,7 @@ func (c *Client) start(ctx context.Context,
246254
// Now that all persistent subtasks have been started and all tickers and stop channels are created,
247255
// signal that the startup process is complete. This needs to be called before entering the
248256
// price updater loop, which loops indefinitely until the daemon is stopped.
257+
startupCompleted = true
249258
c.daemonStartup.Done()
250259

251260
pricefeedClient := servertypes.NewPriceFeedServiceClient(daemonConn)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package client
2+
3+
import (
4+
"errors"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
daemonflags "github.com/tellor-io/layer-daemons/flags"
11+
"github.com/tellor-io/layer-daemons/mocks"
12+
"github.com/tellor-io/layer-daemons/pricefeed/client/types"
13+
"github.com/tellor-io/layer-daemons/testutil/constants"
14+
grpc_util "github.com/tellor-io/layer-daemons/testutil/grpc"
15+
16+
"cosmossdk.io/log"
17+
)
18+
19+
// TestStop_CompletesWhenStartFailsBeforeDaemonStartupDone guards the contract that
20+
// daemonStartup must be released on every early return from start(); otherwise Stop()
21+
// blocks forever on daemonStartup.Wait().
22+
func TestStop_CompletesWhenStartFailsBeforeDaemonStartupDone(t *testing.T) {
23+
t.Parallel()
24+
25+
tests := map[string]struct {
26+
mockGrpcClient *mocks.GrpcClient
27+
exchangeIdToQueryConfig map[types.ExchangeId]*types.ExchangeQueryConfig
28+
exchangeIdToExchangeDetails map[types.ExchangeId]types.ExchangeQueryDetails
29+
wantErrContains string
30+
}{
31+
"tcp_connection_fails": {
32+
mockGrpcClient: grpc_util.GenerateMockGrpcClientWithOptionalTcpConnectionErrors(
33+
errors.New(connectionFailsErrorMsg),
34+
nil,
35+
false,
36+
),
37+
wantErrContains: connectionFailsErrorMsg,
38+
},
39+
"grpc_connection_fails": {
40+
mockGrpcClient: grpc_util.GenerateMockGrpcClientWithOptionalGrpcConnectionErrors(
41+
errors.New(connectionFailsErrorMsg),
42+
nil,
43+
false,
44+
),
45+
wantErrContains: connectionFailsErrorMsg,
46+
},
47+
"empty_exchange_config": {
48+
mockGrpcClient: grpc_util.GenerateMockGrpcClientWithOptionalGrpcConnectionErrors(
49+
nil,
50+
nil,
51+
true,
52+
),
53+
exchangeIdToQueryConfig: map[types.ExchangeId]*types.ExchangeQueryConfig{},
54+
exchangeIdToExchangeDetails: map[types.ExchangeId]types.ExchangeQueryDetails{},
55+
wantErrContains: "exchangeIds must not be empty",
56+
},
57+
}
58+
59+
for name, tc := range tests {
60+
t.Run(name, func(t *testing.T) {
61+
t.Parallel()
62+
63+
cfg := tc.exchangeIdToQueryConfig
64+
details := tc.exchangeIdToExchangeDetails
65+
if cfg == nil {
66+
cfg = constants.TestExchangeQueryConfigs
67+
}
68+
if details == nil {
69+
details = constants.TestExchangeIdToExchangeQueryDetails
70+
}
71+
72+
faketaskRunner := FakeSubTaskRunner{}
73+
74+
client := newClient(log.NewNopLogger())
75+
err := client.start(
76+
grpc_util.Ctx,
77+
daemonflags.GetDefaultDaemonFlags(),
78+
grpc_util.TcpEndpoint,
79+
tc.mockGrpcClient,
80+
[]types.MarketParam{},
81+
cfg,
82+
details,
83+
&faketaskRunner,
84+
)
85+
require.Error(t, err)
86+
require.Contains(t, err.Error(), tc.wantErrContains)
87+
88+
stopDone := make(chan struct{})
89+
go func() {
90+
client.Stop()
91+
close(stopDone)
92+
}()
93+
94+
select {
95+
case <-stopDone:
96+
case <-time.After(2 * time.Second):
97+
t.Fatal("Stop() blocked — daemonStartup was likely not released on failed start()")
98+
}
99+
})
100+
}
101+
}

reporter/client/client.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ type Client struct {
7575
logger log.Logger
7676
txChan chan TxChannelInfo
7777
PriceGuard *PriceGuard
78+
// Gas estimate refresh interval; <=0 disables periodic refresh.
79+
refreshGasEstimatesInterval time.Duration
80+
gasEstimator *gasEstimateState
7881

7982
// Resources that need cleanup
8083
grpcConn *grpc.ClientConn
@@ -101,6 +104,16 @@ func NewClient(logger log.Logger, valGasMin string) *Client {
101104
logger: logger,
102105
minGasFee: valGasMin,
103106
txChan: txChan,
107+
gasEstimator: newGasEstimateState(map[string]gasBucketConfig{
108+
bridgeGasBucketKey: {
109+
levels: []float64{1.75, 2.0},
110+
baseIdx: 0,
111+
},
112+
defaultNonBridgeBucketConfigKey: {
113+
levels: []float64{1.0, 1.25, 2.0},
114+
baseIdx: 0,
115+
},
116+
}),
104117
}
105118
}
106119

@@ -190,6 +203,7 @@ func (c *Client) Start(
190203
autoUnbondingFrequency := viper.GetUint32("auto-unbonding-frequency")
191204
autoUnbondingAmount := viper.GetUint32("auto-unbonding-amount")
192205
autoUnbondingMaxStakePercentage := viper.GetString("auto-unbonding-max-stake-percentage")
206+
c.refreshGasEstimatesInterval = viper.GetDuration("refresh-gas-estimates-interval")
193207

194208
if autoUnbondingFrequency > 0 {
195209
if autoUnbondingAmount == 0 {
@@ -224,6 +238,11 @@ func (c *Client) Start(
224238
} else {
225239
c.logger.Info("Auto unbonding disabled")
226240
}
241+
if c.refreshGasEstimatesInterval > 0 {
242+
c.logger.Info("Periodic gas estimate refresh enabled", "interval", c.refreshGasEstimatesInterval.String())
243+
} else {
244+
c.logger.Info("Periodic gas estimate refresh disabled")
245+
}
227246

228247
c.cosmosCtx = c.cosmosCtx.WithChainID(chainId)
229248
c.cosmosCtx = c.cosmosCtx.WithHomeDir(homeDir)
@@ -332,9 +351,31 @@ func StartReporterDaemonTaskLoop(
332351
wg.Add(1)
333352
go client.AutoUnbondStakePeriodically(ctx, wg)
334353

354+
if client.refreshGasEstimatesInterval > 0 {
355+
wg.Add(1)
356+
go client.RefreshGasEstimatesPeriodically(ctx, wg)
357+
}
358+
335359
wg.Wait()
336360
}
337361

362+
func (c *Client) RefreshGasEstimatesPeriodically(ctx context.Context, wg *sync.WaitGroup) {
363+
defer wg.Done()
364+
ticker := time.NewTicker(c.refreshGasEstimatesInterval)
365+
defer ticker.Stop()
366+
367+
for {
368+
select {
369+
case <-ctx.Done():
370+
c.logger.Debug("RefreshGasEstimatesPeriodically: context canceled, exiting")
371+
return
372+
case <-ticker.C:
373+
c.logger.Info("Refreshing gas estimate buckets to base levels")
374+
c.resetAllGasLevelsToBase()
375+
}
376+
}
377+
}
378+
338379
func (c *Client) checkReporter(ctx context.Context) bool {
339380
c.logger.Info("Checking if reporter is created", "address", c.accAddr.String())
340381

0 commit comments

Comments
 (0)