Skip to content

Commit 7232655

Browse files
fix: add maxGasLimit parameter
1 parent ce79de8 commit 7232655

File tree

12 files changed

+152
-28
lines changed

12 files changed

+152
-28
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ clean:
3333
.PHONY: test
3434
test:
3535
@echo "\n\t$(C_GREEN)# Run test and generate new coverage.out$(C_END)"
36-
go test -short -coverprofile=coverage.out -covermode=atomic -race ./...
36+
CTF_CONFIGS=./config.toml go test -short -coverprofile=coverage.out -covermode=atomic -race ./...
3737

3838
.PHONY: coverage
3939
coverage:

cmd/start.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func startCommand() *cobra.Command {
2525

2626
nodeURL, privateKey, timelockAddress, callProxyAddress, chainFamily string
2727
fromBlock, pollPeriod, eventListenerPollPeriod int64
28-
eventListenerPollSize uint64
28+
eventListenerPollSize, maxGasLimit uint64
2929
dryRun bool
3030
)
3131

@@ -47,6 +47,7 @@ func startCommand() *cobra.Command {
4747
startCmd.Flags().StringVarP(&callProxyAddress, "call-proxy-address", "f", timelockConf.CallProxyAddress, "Address of the target CallProxyAddress contract")
4848
startCmd.Flags().StringVarP(&privateKey, "private-key", "k", timelockConf.PrivateKey, "Private key used to execute transactions")
4949
startCmd.Flags().Int64Var(&fromBlock, "from-block", timelockConf.FromBlock, "Start watching from this block")
50+
startCmd.Flags().Uint64Var(&maxGasLimit, "max-gas-limit", timelockConf.MaxGasLimit, "Network's maximum gas limit")
5051
startCmd.Flags().Int64Var(&pollPeriod, "poll-period", timelockConf.PollPeriod, "Poll period in seconds")
5152
startCmd.Flags().Int64Var(&eventListenerPollPeriod, "event-listener-poll-period", timelockConf.EventListenerPollPeriod, "Event Listener poll period in seconds")
5253
startCmd.Flags().Uint64Var(&eventListenerPollSize, "event-listener-poll-size", timelockConf.EventListenerPollSize, "Number of entries to fetch when polling logs")
@@ -110,6 +111,11 @@ func startTimelock(cmd *cobra.Command) {
110111
}
111112
}
112113

114+
maxGasLimit, err := cmd.Flags().GetUint64("max-gas-limit")
115+
if err != nil {
116+
slog.Fatalf("value of max-gas-limit not set: %s", err.Error())
117+
}
118+
113119
fromBlock, err := cmd.Flags().GetInt64("from-block")
114120
if err != nil {
115121
slog.Fatalf("value of from-block not set: %s", err.Error())
@@ -137,7 +143,7 @@ func startTimelock(cmd *cobra.Command) {
137143

138144
if chainFamily == chain_selectors.FamilyEVM {
139145
tWorker, err := timelock.NewTimelockWorkerEVM(nodeURL, timelockAddress, callProxyAddress, privateKey,
140-
big.NewInt(fromBlock), pollPeriod, eventListenerPollPeriod, eventListenerPollSize, dryRun, slog)
146+
big.NewInt(fromBlock), maxGasLimit, pollPeriod, eventListenerPollPeriod, eventListenerPollSize, dryRun, slog)
141147
if err != nil {
142148
slog.Fatalf("error creating the timelock-worker: %s", err.Error())
143149
}

pkg/cli/config.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Config struct {
1919
CallProxyAddress string `mapstructure:"CALL_PROXY_ADDRESS"`
2020
PrivateKey string `mapstructure:"PRIVATE_KEY"`
2121
FromBlock int64 `mapstructure:"FROM_BLOCK"`
22+
MaxGasLimit uint64 `mapstructure:"MAX_GAS_LIMIT"`
2223
PollPeriod int64 `mapstructure:"POLL_PERIOD"`
2324
EventListenerPollPeriod int64 `mapstructure:"EVENT_LISTENER_POLL_PERIOD"`
2425
EventListenerPollSize uint64 `mapstructure:"EVENT_LISTENER_POLL_SIZE"`
@@ -75,6 +76,15 @@ func NewTimelockCLI() (*Config, error) {
7576
c.FromBlock = int64(fb)
7677
}
7778

79+
if os.Getenv("MAX_GAS_LIMIT") != "" {
80+
mgl, err := strconv.ParseUint(os.Getenv("MAX_GAS_LIMIT"), 10, 64)
81+
if err != nil {
82+
return nil, fmt.Errorf("unable to parse MAX_GAS_LIMIT value: %w", err)
83+
}
84+
85+
c.MaxGasLimit = mgl
86+
}
87+
7888
if os.Getenv("POLL_PERIOD") != "" {
7989
pp, err := strconv.Atoi(os.Getenv("POLL_PERIOD"))
8090
if err != nil {

pkg/timelock/const_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var (
1414
testCallProxyAddress = "0x0000000000000000000000000000000000000000"
1515
testPrivateKey = "8064bf62c044d2654705b9d0cfbd666c2649fabb76ed8f4b9d8d3eb28267e3cf"
1616
testFromBlock = big.NewInt(0)
17+
testMaxGasLimit = uint64(0)
1718
testPollPeriod = 5
1819
testEventListenerPollPeriod = 1
1920
testEventListenerPollSize = uint64(10)

pkg/timelock/operations_evm.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package timelock
33
import (
44
"context"
55
"crypto/ecdsa"
6+
"errors"
67
"fmt"
78
"math/big"
89

@@ -15,6 +16,8 @@ import (
1516
contracts "github.com/smartcontractkit/ccip-owner-contracts/gethwrappers"
1617
)
1718

19+
var ErrMaxGasLimit = errors.New("transaction gas exceeds max gas limit")
20+
1821
// execute runs the CallScheduled operation if:
1922
// - The predecessor operation is finished
2023
// - The operation is ready to be executed
@@ -41,6 +44,9 @@ func (tw *WorkerEVM) execute(ctx context.Context, op []TimelockCallScheduled) {
4144
tx, err := tw.executeCallSchedule(ctx, &tw.executeContract.RBACTimelockTransactor, op, tw.privateKey)
4245
if err != nil || tx == nil {
4346
tw.logger.Errorf("execute operation %x error: %s", opId, err.Error())
47+
if errors.Is(err, ErrMaxGasLimit) {
48+
tw.scheduler.delFromScheduler(opId)
49+
}
4450
} else {
4551
tw.logger.Infof("execute operation %x success: %s", opId, tx.Hash())
4652

@@ -101,6 +107,16 @@ func (tw *WorkerEVM) executeCallSchedule(
101107
predecessor := cs[0].(*evmTimelockCallScheduled).callScheduled.Predecessor
102108
salt := cs[0].(*evmTimelockCallScheduled).callScheduled.Salt
103109

110+
if tw.maxGasLimit > 0 {
111+
gasEstimate, err := estimateGas(ctx, c, *txOpts, calls, predecessor, salt)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to estimate gas for execute batch: %w", err)
114+
}
115+
if gasEstimate >= tw.maxGasLimit {
116+
return nil, ErrMaxGasLimit
117+
}
118+
}
119+
104120
return Retry(ctx, func(rctx context.Context) (*types.Transaction, error) {
105121
txOpts.Context = rctx
106122
return c.ExecuteBatch(txOpts, calls, predecessor, salt)
@@ -150,6 +166,23 @@ func (tw *WorkerEVM) signTx(chainID *big.Int) bind.SignerFn {
150166
}
151167
}
152168

169+
func estimateGas(
170+
ctx context.Context, contract *contracts.RBACTimelockTransactor, txOpts bind.TransactOpts,
171+
calls []contracts.RBACTimelockCall, predecessor, salt [32]byte,
172+
) (uint64, error) {
173+
tx, err := Retry(ctx, func(rctx context.Context) (*types.Transaction, error) {
174+
txOpts.Context = rctx //nolint:fatcontext
175+
txOpts.NoSend = true
176+
177+
return contract.ExecuteBatch(&txOpts, calls, predecessor, salt)
178+
})
179+
if err != nil {
180+
return 0, fmt.Errorf("failed to estimate gas for execute batch: %w", err)
181+
}
182+
183+
return tx.Gas(), nil
184+
}
185+
153186
// privateKeyToAddress is an util function to calculate the addresses of a given private key.
154187
// From a private key the public key can be deducted, and with the pubkey is
155188
// trivial to calculate the addresses.

pkg/timelock/retry.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
var (
1111
retryMinDelay = 500 * time.Millisecond
12-
retryAttempts = uint(5)
1312
retryIncrementalDelays = [4]int{500, 2000, 8000, 32000}
1413
retryContextTimeout = 30 * time.Second
1514
retryOpts = func(ctx context.Context) []retry.Option {

pkg/timelock/scheduler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ type scheduler struct {
5858
func newScheduler(tick time.Duration, logger *zap.SugaredLogger, executeFn executeFn) *scheduler {
5959
s := &scheduler{
6060
ticker: time.NewTicker(tick),
61-
add: make(chan TimelockCallScheduled),
62-
del: make(chan operationKey),
61+
add: make(chan TimelockCallScheduled, 16),
62+
del: make(chan operationKey, 16),
6363
store: make(map[operationKey][]TimelockCallScheduled),
6464
busy: false,
6565
logger: logger,

pkg/timelock/worker_evm.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type WorkerEVM struct {
3434
abi *abi.ABI
3535
addresses []common.Address
3636
fromBlock *big.Int
37+
maxGasLimit uint64
3738
pollPeriod int64
3839
listenerPollPeriod int64
3940
pollSize uint64
@@ -51,7 +52,8 @@ var validNodeUrlSchemesEVM = []string{"http", "https", "ws", "wss"}
5152
// It's a singleton, so further executions will retrieve the same timelockWorker.
5253
func NewTimelockWorkerEVM(
5354
nodeURL, timelockAddress, callProxyAddress, privateKey string, fromBlock *big.Int,
54-
pollPeriod int64, listenerPollPeriod int64, pollSize uint64, dryRun bool, logger *zap.SugaredLogger,
55+
maxGasLimit uint64, pollPeriod int64, listenerPollPeriod int64, pollSize uint64, dryRun bool,
56+
logger *zap.SugaredLogger,
5557
) (*WorkerEVM, error) {
5658
// Sanity check on each provided variable before allocating more resources.
5759
u, err := url.ParseRequestURI(nodeURL)
@@ -130,6 +132,7 @@ func NewTimelockWorkerEVM(
130132
abi: timelockABI,
131133
addresses: []common.Address{common.HexToAddress(timelockAddress)},
132134
fromBlock: fromBlock,
135+
maxGasLimit: maxGasLimit,
133136
pollPeriod: pollPeriod,
134137
listenerPollPeriod: listenerPollPeriod,
135138
pollSize: pollSize,
@@ -400,7 +403,7 @@ func (tw *WorkerEVM) fetchAndDispatchLogs(
400403
for _, log := range logs {
401404
select {
402405
case logCh <- log:
403-
tw.logger.With("log", log).Debug("dispatching log")
406+
tw.logger.With("log", log).Debug("dispatched log")
404407
case <-ctx.Done():
405408
tw.logger.Debug("stopped while dispatching logs: incomplete retrieval.")
406409
return fromBlock
@@ -593,6 +596,7 @@ func (tw *WorkerEVM) startLog() {
593596

594597
tw.logger.Infof("\tEOA addresses: %v", wallet)
595598
tw.logger.Infof("\tStarting from block: %v", tw.fromBlock)
599+
tw.logger.Infof("\tNetwork's max gas limit: %v", tw.maxGasLimit)
596600
tw.logger.Infof("\tPoll Period: %v", time.Duration(tw.pollPeriod*int64(time.Second)).String())
597601
tw.logger.Infof("\tEvent Listener Poll Period: %v", time.Duration(tw.listenerPollPeriod*int64(time.Second)).String())
598602
tw.logger.Infof("\tEvent Listener Poll # Logs: %v", tw.pollSize)

pkg/timelock/worker_evm_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313

1414
func newTestTimelockWorker(
1515
t *testing.T, nodeURL, timelockAddress, callProxyAddress, privateKey string, fromBlock *big.Int,
16-
pollPeriod int64, eventListenerPollPeriod int64, eventListenerPollSize uint64, dryRun bool,
17-
logger *zap.SugaredLogger,
16+
maxGasLimit uint64, pollPeriod int64, eventListenerPollPeriod int64, eventListenerPollSize uint64,
17+
dryRun bool, logger *zap.SugaredLogger,
1818
) *WorkerEVM {
1919
assert.NotEmpty(t, nodeURL, "nodeURL is empty. Are environment variabes in const_test.go set?")
2020
assert.NotEmpty(t, timelockAddress, "nodeURL is empty. Are environment variabes in const_test.go set?")
@@ -25,7 +25,7 @@ func newTestTimelockWorker(
2525
assert.NotNil(t, logger, "logger is nil. Are environment variabes in const_test.go set?")
2626

2727
tw, err := NewTimelockWorkerEVM(nodeURL, timelockAddress, callProxyAddress, privateKey, fromBlock,
28-
pollPeriod, eventListenerPollPeriod, eventListenerPollSize, dryRun, logger)
28+
maxGasLimit, pollPeriod, eventListenerPollPeriod, eventListenerPollSize, dryRun, logger)
2929
require.NoError(t, err)
3030
require.NotNil(t, tw)
3131

@@ -43,6 +43,7 @@ func TestNewTimelockWorkerEVM(t *testing.T) {
4343
callProxyAddress string
4444
privateKey string
4545
fromBlock *big.Int
46+
maxGasLimit uint64
4647
pollPeriod int64
4748
eventListenerPollPeriod int64
4849
eventListenerPollSize uint64
@@ -125,7 +126,7 @@ func TestNewTimelockWorkerEVM(t *testing.T) {
125126
tt.setup(&args)
126127

127128
got, err := NewTimelockWorkerEVM(args.nodeURL, args.timelockAddress, args.callProxyAddress,
128-
args.privateKey, args.fromBlock, args.pollPeriod, args.eventListenerPollPeriod,
129+
args.privateKey, args.fromBlock, args.maxGasLimit, args.pollPeriod, args.eventListenerPollPeriod,
129130
args.eventListenerPollSize, args.dryRun, args.logger)
130131

131132
if tt.wantErr == "" {
@@ -144,7 +145,7 @@ func TestWorker_startLog(t *testing.T) {
144145
rpcURL := runRPCServer(t)
145146

146147
testWorker := newTestTimelockWorker(t, rpcURL, testTimelockAddress, testCallProxyAddress, testPrivateKey,
147-
testFromBlock, int64(testPollPeriod), int64(testEventListenerPollPeriod), testEventListenerPollSize,
148+
testFromBlock, testMaxGasLimit, int64(testPollPeriod), int64(testEventListenerPollPeriod), testEventListenerPollSize,
148149
testDryRun, testLogger)
149150

150151
tests := []struct {

tests/containers/geth.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ func NewGethContainer(ctx context.Context) (*GethContainer, error) {
4646
"--cache.blocklogs", "1024",
4747
"--datadir", dataDir,
4848
},
49-
LogConsumerCfg: &testcontainers.LogConsumerConfig{
50-
Opts: []testcontainers.LogProductionOption{
51-
testcontainers.WithLogProductionTimeout(10 * time.Second),
52-
},
53-
Consumers: []testcontainers.LogConsumer{&StdoutLogConsumer{Prefix: "|| "}},
54-
},
49+
// uncomment to print containers logs
50+
// LogConsumerCfg: &testcontainers.LogConsumerConfig{
51+
// Opts: []testcontainers.LogProductionOption{
52+
// testcontainers.WithLogProductionTimeout(10 * time.Second),
53+
// },
54+
// Consumers: []testcontainers.LogConsumer{&StdoutLogConsumer{Prefix: "|| "}},
55+
// },
5556
}
5657
gethContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
5758
ContainerRequest: request,

0 commit comments

Comments
 (0)