Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion chain/evm/provider/rpcclient/multiclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math/big"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -38,6 +39,7 @@ const (

// Default timeout for health checks
RPCDefaultHealthCheckTimeout = 2 * time.Second
AlreadyKnownErrorMessage = "already known"
)

// RetryConfig configures retry behavior for RPC operations.
Expand Down Expand Up @@ -161,7 +163,14 @@ func NewMultiClient(
// chain.DeployerKey at chain load (gas_config defaults) or before signing the transaction.
func (mc *MultiClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
return mc.retryWithBackups(ctx, "SendTransaction", func(ct context.Context, client *ethclient.Client) error {
return client.SendTransaction(ct, tx)
err := client.SendTransaction(ctx, tx)
if isAlreadyKnown(err) {
// The exact signed transaction is already in this node's
// transaction pool or was previously submitted to it.
return nil
}

return err
})
}

Expand Down Expand Up @@ -561,3 +570,13 @@ func maybeDataErr(err error) error {

return err
}

func isAlreadyKnown(err error) bool {
if err == nil {
return false
}

msg := strings.ToLower(err.Error())

return strings.Contains(msg, AlreadyKnownErrorMessage)
}
49 changes: 49 additions & 0 deletions chain/evm/provider/rpcclient/multiclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,52 @@ func TestMultiClient_EstimateGas_cap(t *testing.T) {
})
}
}

func TestIsAlreadyKnown(t *testing.T) {
t.Parallel()

tests := []struct {
name string
err error
want bool
}{
{
name: "nil error",
err: nil,
want: false,
},
{
name: "generic error containing 'already known'",
err: errors.New("failed to send transaction: already known"),
want: true,
},
{
name: "error with uppercase 'Already Known'",
err: errors.New("Already Known"),
want: true,
},
{
name: "error with padded 'already known'",
err: errors.New(" already known "),
want: true,
},
{
name: "error with 'already known' in middle",
err: errors.New("operation failed: already known: reason unknown"),
want: true,
},
{
name: "error that doesn't match",
err: errors.New("some other error"),
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := isAlreadyKnown(tt.err)
assert.Equal(t, tt.want, got)
})
}
}
Loading