diff --git a/seth/README.md b/seth/README.md index 27b6f8a34..f93719836 100644 --- a/seth/README.md +++ b/seth/README.md @@ -216,7 +216,7 @@ client, err := builder. // tracing WithTracing(seth.TracingLevel_All, []string{seth.TraceOutput_Console}). // protections - WithProtections(true, true). + WithProtections(true, true, seth.MustMakeDuration(2*time.Minute)). // artifacts folder WithArtifactsFolder("some_folder"). // folder with gethwrappers for ABI decoding @@ -321,6 +321,12 @@ check_rpc_health_on_start = false It will execute a simple check of transferring 10k wei from root key to root key and check if the transaction was successful. +You can also enable pending nonce protection that will check if given key has any pending transactions. By default, we will wait 1 minute for all transactions to be mined. If any of them is still pending, we will panic. You can enable it with: +```toml +pending_nonce_protection_enabled = true +pending_nonce_protection_timeout = "5m" +``` + You can add more networks like this: ```toml diff --git a/seth/client.go b/seth/client.go index 13f2245f3..857886e3c 100644 --- a/seth/client.go +++ b/seth/client.go @@ -203,6 +203,10 @@ func ValidateConfig(cfg *Config) error { cfg.Network.DialTimeout = &Duration{D: DefaultDialTimeout} } + if cfg.PendingNonceProtectionTimeout == nil { + cfg.PendingNonceProtectionTimeout = &Duration{D: DefaultPendingNonceProtectionTimeout} + } + return nil } @@ -752,7 +756,7 @@ func (m *Client) getProposedTransactionOptions(keyNum int) (*bind.TransactOpts, var ctx context.Context if m.Cfg.PendingNonceProtectionEnabled { - if nonceStatus.PendingNonce > nonceStatus.LastNonce { + if pendingErr := m.WaitUntilNoPendingTxForKeyNum(keyNum, m.Cfg.PendingNonceProtectionTimeout.Duration()); pendingErr != nil { errMsg := ` pending nonce for key %d is higher than last nonce, there are %d pending transactions. @@ -762,7 +766,7 @@ This issue is caused by one of two things: ` err := fmt.Errorf(errMsg, keyNum, nonceStatus.PendingNonce-nonceStatus.LastNonce) m.Errors = append(m.Errors, err) - // can't return nil, otherwise RPC wrapper will panic and we might lose funds on testnets/mainnets, that's why + // can't return nil, otherwise RPC wrapper will panic, and we might lose funds on testnets/mainnets, that's why // error is passed in Context here to avoid panic, whoever is using Seth should make sure that there is no error // present in Context before using *bind.TransactOpts ctx = context.WithValue(context.Background(), ContextErrorKey{}, err) diff --git a/seth/client_builder.go b/seth/client_builder.go index 6a07d78f3..5967a30dd 100644 --- a/seth/client_builder.go +++ b/seth/client_builder.go @@ -212,11 +212,12 @@ func (c *ClientBuilder) WithTracing(level string, outputs []string) *ClientBuild return c } -// WithProtections enables or disables nonce protection (fails, when key has a pending transaction and you try to submit another one) and node health check on startup. -// Default values are false for nonce protection and true for node health check. -func (c *ClientBuilder) WithProtections(pendingNonceProtectionEnabled, nodeHealthStartupCheck bool) *ClientBuilder { +// WithProtections enables or disables nonce protection (fails, when key has a pending transaction, and you try to submit another one) and node health check on startup. +// Default values are false for nonce protection, true for node health check and 1 minute timeout. +func (c *ClientBuilder) WithProtections(pendingNonceProtectionEnabled, nodeHealthStartupCheck bool, pendingNonceProtectionTimeout *Duration) *ClientBuilder { c.config.PendingNonceProtectionEnabled = pendingNonceProtectionEnabled c.config.CheckRpcHealthOnStart = nodeHealthStartupCheck + c.config.PendingNonceProtectionTimeout = pendingNonceProtectionTimeout return c } diff --git a/seth/config.go b/seth/config.go index 2dfa5ebed..cb335f8d0 100644 --- a/seth/config.go +++ b/seth/config.go @@ -29,8 +29,9 @@ const ( NETWORK_ENV_VAR = "SETH_NETWORK" URL_ENV_VAR = "SETH_URL" - DefaultNetworkName = "Default" - DefaultDialTimeout = 1 * time.Minute + DefaultNetworkName = "Default" + DefaultDialTimeout = 1 * time.Minute + DefaultPendingNonceProtectionTimeout = 1 * time.Minute DefaultTransferGasFee = 21_000 DefaultGasPrice = 1_000_000_000 // 1 Gwei @@ -60,6 +61,7 @@ type Config struct { TracingLevel string `toml:"tracing_level"` TraceOutputs []string `toml:"trace_outputs"` PendingNonceProtectionEnabled bool `toml:"pending_nonce_protection_enabled"` + PendingNonceProtectionTimeout *Duration `toml:"pending_nonce_protection_timeout"` ConfigDir string `toml:"abs_path"` ExperimentsEnabled []string `toml:"experiments_enabled"` CheckRpcHealthOnStart bool `toml:"check_rpc_health_on_start"` diff --git a/seth/config_test.go b/seth/config_test.go index c03c27b39..8b316dfc2 100644 --- a/seth/config_test.go +++ b/seth/config_test.go @@ -67,7 +67,7 @@ func TestConfig_MaximalBuilder(t *testing.T) { // tracing WithTracing(seth.TracingLevel_All, []string{seth.TraceOutput_Console}). // protections - WithProtections(true, true). + WithProtections(true, true, seth.MustMakeDuration(2*time.Minute)). // artifacts folder WithArtifactsFolder("some_folder"). // geth wrappers folders diff --git a/seth/gas_bump_test.go b/seth/gas_bump_test.go index b0c88d984..fd5a7939a 100644 --- a/seth/gas_bump_test.go +++ b/seth/gas_bump_test.go @@ -188,7 +188,7 @@ func TestGasBumping_Contract_Deployment_Legacy_CustomBumpingFunction(t *testing. WithEIP1559DynamicFees(false). WithLegacyGasPrice(1). WithTransactionTimeout(10*time.Second). - WithProtections(false, false). + WithProtections(false, false, nil). WithGasBumping(5, 0, func(gasPrice *big.Int) *big.Int { customGasBumps++ return new(big.Int).Mul(gasPrice, big.NewInt(512)) diff --git a/seth/seth.toml b/seth/seth.toml index dc58a728c..9124f3fe7 100644 --- a/seth/seth.toml +++ b/seth/seth.toml @@ -39,6 +39,10 @@ ephemeral_addresses_number = 0 # it when running load tests. pending_nonce_protection_enabled = false +# When pending_nonce_protection_enabled is set to true, this value will be used to determine how long we should wait +# for all pending transactions to be mined (so that pending nonce and last nonce are equal). +# pending_nonce_protection_timeout = "10s" + # Amount to be left on root key/address, when we are using ephemeral addresses. It's the amount that will not # be divided into ephemeral keys. root_key_funds_buffer = 10 # 10 ether