Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable Client Errors #12760

Merged
merged 35 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a2c86d1
Implement client errors
DylanTinianov Apr 9, 2024
5c99f42
Test client errors
DylanTinianov Apr 9, 2024
b5e3184
Set client errors
DylanTinianov Apr 9, 2024
0063c0f
changeset
DylanTinianov Apr 9, 2024
2ce6450
Fix race conditions
DylanTinianov Apr 9, 2024
16eec30
Fix config tests
DylanTinianov Apr 10, 2024
7518403
Enable client errors
DylanTinianov Apr 10, 2024
1203fdb
remove global map
DylanTinianov Apr 10, 2024
ba56f27
Move errors to NodePool
DylanTinianov Apr 11, 2024
b09ba3a
Rename errors
DylanTinianov Apr 11, 2024
7a27e19
Update chains-evm.toml
DylanTinianov Apr 11, 2024
5d786a2
Fix test data
DylanTinianov Apr 11, 2024
71ef74a
Fix and generate docs
DylanTinianov Apr 11, 2024
0b30073
generate testdata
DylanTinianov Apr 16, 2024
3d143af
changeset
DylanTinianov Apr 16, 2024
3998333
update changeset
DylanTinianov Apr 16, 2024
52fb2c0
update testdata
DylanTinianov Apr 16, 2024
c98cfcf
Merge branch 'develop' into BCI-1176-configurable-client-errors
DylanTinianov Apr 16, 2024
467d477
generate docs
DylanTinianov Apr 16, 2024
2c93ba7
Update errors.go
DylanTinianov Apr 16, 2024
3ccc438
Merge branch 'develop' into BCI-1176-configurable-client-errors
DylanTinianov Apr 16, 2024
f0a3d14
remove variable
DylanTinianov Apr 16, 2024
037757a
Merge branch 'develop' into BCI-1176-configurable-client-errors
DylanTinianov Apr 17, 2024
d2a8284
Merge branch 'develop' into BCI-1176-configurable-client-errors
DylanTinianov Apr 17, 2024
acc6613
Refactor ClientErrors
DylanTinianov Apr 17, 2024
c2c4702
Pass client errors
DylanTinianov Apr 18, 2024
094889a
update client errors
DylanTinianov Apr 18, 2024
f2a2108
Update config
DylanTinianov Apr 18, 2024
488ccca
Fix config
DylanTinianov Apr 18, 2024
97dcadf
Update config tests
DylanTinianov Apr 18, 2024
a6c715c
update golden files
DylanTinianov Apr 18, 2024
68f2771
Update config-multi-chain-effective.toml
DylanTinianov Apr 22, 2024
7963a4d
Add regex to toml
DylanTinianov Apr 22, 2024
da96549
Merge branch 'develop' of https://github.com/smartcontractkit/chainli…
DylanTinianov Apr 23, 2024
d07b27b
Update lovely-jeans-confess.md
DylanTinianov Apr 23, 2024
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
5 changes: 5 additions & 0 deletions .changeset/lovely-jeans-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

Enable configurable client error regexes for error classification
56 changes: 55 additions & 1 deletion core/chains/evm/client/errors.go
DylanTinianov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"regexp"
"sync"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/smartcontractkit/chainlink-common/pkg/logger"

commonclient "github.com/smartcontractkit/chainlink/v2/common/client"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/label"
)

Expand Down Expand Up @@ -233,17 +235,62 @@ var zkEvm = ClientErrors{
OutOfCounters: regexp.MustCompile(`(?:: |^)not enough .* counters to continue the execution$`),
}

var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm}
var clientsLock = sync.Mutex{}
var clients = map[string]ClientErrors{
"parity": parity,
"geth": geth,
"arbitrum": arbitrum,
"metis": metis,
"substrate": substrate,
"avalanche": avalanche,
"nethermind": nethermind,
"harmony": harmony,
"besu": besu,
"erigon": erigon,
"klaytn": klaytn,
"celo": celo,
"zkSync": zkSync,
"zkEvm": zkEvm,
}

// SetClientErrorRegexes is called on startup to set the client errors from the config
func SetClientErrorRegexes(errsRegex config.ClientErrors) {
clientsLock.Lock()
defer clientsLock.Unlock()
clients["tomlConfig"] = ClientErrors{
DylanTinianov marked this conversation as resolved.
Show resolved Hide resolved
NonceTooLow: regexp.MustCompile(errsRegex.NonceTooLow()),
NonceTooHigh: regexp.MustCompile(errsRegex.NonceTooHigh()),
ReplacementTransactionUnderpriced: regexp.MustCompile(errsRegex.ReplacementTransactionUnderpriced()),
LimitReached: regexp.MustCompile(errsRegex.LimitReached()),
TransactionAlreadyInMempool: regexp.MustCompile(errsRegex.TransactionAlreadyInMempool()),
TerminallyUnderpriced: regexp.MustCompile(errsRegex.TerminallyUnderpriced()),
InsufficientEth: regexp.MustCompile(errsRegex.InsufficientEth()),
TxFeeExceedsCap: regexp.MustCompile(errsRegex.TxFeeExceedsCap()),
L2FeeTooLow: regexp.MustCompile(errsRegex.L2FeeTooLow()),
L2FeeTooHigh: regexp.MustCompile(errsRegex.L2FeeTooHigh()),
L2Full: regexp.MustCompile(errsRegex.L2Full()),
TransactionAlreadyMined: regexp.MustCompile(errsRegex.TransactionAlreadyMined()),
Fatal: regexp.MustCompile(errsRegex.Fatal()),
ServiceUnavailable: regexp.MustCompile(errsRegex.ServiceUnavailable()),
}
}

func (s *SendError) is(errorType int) bool {
if s == nil || s.err == nil {
return false
}
str := s.CauseStr()

clientsLock.Lock()
defer clientsLock.Unlock()
for _, client := range clients {
DylanTinianov marked this conversation as resolved.
Show resolved Hide resolved
if _, ok := client[errorType]; !ok {
continue
}
if client[errorType].String() == "" {
// Skip empty regexes.
continue
}
if client[errorType].MatchString(str) {
return true
}
Expand Down Expand Up @@ -369,10 +416,17 @@ func isFatalSendError(err error) bool {
return false
}
str := pkgerrors.Cause(err).Error()

clientsLock.Lock()
defer clientsLock.Unlock()
for _, client := range clients {
if _, ok := client[Fatal]; !ok {
continue
}
if client[Fatal].String() == "" {
// Skip empty regexes.
continue
}
if client[Fatal].MatchString(str) {
return true
}
Expand Down
38 changes: 35 additions & 3 deletions core/chains/evm/client/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,35 @@ type errorCase struct {
network string
}

func NewTestClientErrors() evmclient.TestClientErrors {
clientErrors := evmclient.TestClientErrors{}
clientErrors.SetNonceTooLow("client error nonce too low")
clientErrors.SetNonceTooHigh("client error nonce too high")
clientErrors.SetReplacementTransactionUnderpriced("client error replacement underpriced")
clientErrors.SetLimitReached("client error limit reached")
clientErrors.SetTransactionAlreadyInMempool("client error transaction already in mempool")
clientErrors.SetTerminallyUnderpriced("client error terminally underpriced")
clientErrors.SetInsufficientEth("client error insufficient eth")
clientErrors.SetTxFeeExceedsCap("client error tx fee exceeds cap")
clientErrors.SetL2FeeTooLow("client error l2 fee too low")
clientErrors.SetL2FeeTooHigh("client error l2 fee too high")
clientErrors.SetL2Full("client error l2 full")
clientErrors.SetTransactionAlreadyMined("client error transaction already mined")
clientErrors.SetFatal("client error fatal")
clientErrors.SetServiceUnavailable("client error service unavailable")
return clientErrors
}

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

var err *evmclient.SendError
randomError := evmclient.NewSendErrorS("some old bollocks")

t.Run("IsNonceTooLowError", func(t *testing.T) {
assert.False(t, randomError.IsNonceTooLowError())
clientErrors := NewTestClientErrors()
evmclient.SetClientErrorRegexes(&clientErrors)

t.Run("IsNonceTooLowError", func(t *testing.T) {
tests := []errorCase{
{"nonce too low", true, "Geth"},
{"nonce too low: address 0x336394A3219e71D9d9bd18201d34E95C1Bb7122C, tx: 8089 state: 8090", true, "Arbitrum"},
Expand All @@ -41,6 +61,7 @@ func Test_Eth_Errors(t *testing.T) {
{"call failed: OldNonce", true, "Nethermind"},
{"call failed: OldNonce, Current nonce: 22, nonce of rejected tx: 17", true, "Nethermind"},
{"nonce too low. allowed nonce range: 427 - 447, actual: 426", true, "zkSync"},
{"client error nonce too low", true, "tomlConfig"},
}

for _, test := range tests {
Expand All @@ -54,14 +75,14 @@ func Test_Eth_Errors(t *testing.T) {
})

t.Run("IsNonceTooHigh", func(t *testing.T) {

tests := []errorCase{
{"call failed: NonceGap", true, "Nethermind"},
{"call failed: NonceGap, Future nonce. Expected nonce: 10", true, "Nethermind"},
{"nonce too high: address 0x336394A3219e71D9d9bd18201d34E95C1Bb7122C, tx: 8089 state: 8090", true, "Arbitrum"},
{"nonce too high", true, "Geth"},
{"nonce too high", true, "Erigon"},
{"nonce too high. allowed nonce range: 427 - 477, actual: 527", true, "zkSync"},
{"client error nonce too high", true, "tomlConfig"},
}

for _, test := range tests {
Expand All @@ -77,6 +98,7 @@ func Test_Eth_Errors(t *testing.T) {

tests := []errorCase{
{"transaction already finalized", true, "Harmony"},
{"client error transaction already mined", true, "tomlConfig"},
}

for _, test := range tests {
Expand All @@ -100,6 +122,7 @@ func Test_Eth_Errors(t *testing.T) {
{"Transaction gas price 100wei is too low. There is another transaction with same nonce in the queue with gas price 150wei. Try increasing the gas price or incrementing the nonce.", true, "Parity"},
{"There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.", false, "Parity"},
{"gas price too low", false, "Arbitrum"},
{"client error replacement underpriced", true, "tomlConfig"},
}

for _, test := range tests {
Expand Down Expand Up @@ -130,6 +153,7 @@ func Test_Eth_Errors(t *testing.T) {
{"known transaction. transaction with hash 0x6013…3053 is already in the system", true, "zkSync"},
// This seems to be an erroneous message from the zkSync client, we'll have to match it anyway
{"ErrorObject { code: ServerError(3), message: \\\"known transaction. transaction with hash 0xf016…ad63 is already in the system\\\", data: Some(RawValue(\\\"0x\\\")) }", true, "zkSync"},
{"client error transaction already in mempool", true, "tomlConfig"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand Down Expand Up @@ -159,6 +183,7 @@ func Test_Eth_Errors(t *testing.T) {
{"intrinsic gas too low", true, "Klaytn"},
{"max fee per gas less than block base fee", true, "zkSync"},
{"virtual machine entered unexpected state. please contact developers and provide transaction details that caused this error. Error description: The operator included transaction with an unacceptable gas price", true, "zkSync"},
{"client error terminally underpriced", true, "tomlConfig"},
}

for _, test := range tests {
Expand Down Expand Up @@ -204,6 +229,7 @@ func Test_Eth_Errors(t *testing.T) {
{"insufficient funds for gas * price + value + gatewayFee", true, "celo"},
{"insufficient balance for transfer", true, "zkSync"},
{"insufficient funds for gas + value. balance: 42719769622667482000, fee: 48098250000000, value: 42719769622667482000", true, "celo"},
{"client error insufficient eth", true, "tomlConfig"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand All @@ -217,6 +243,7 @@ func Test_Eth_Errors(t *testing.T) {
tests := []errorCase{
{"call failed: 503 Service Unavailable: <html>\r\n<head><title>503 Service Temporarily Unavailable</title></head>\r\n<body>\r\n<center><h1>503 Service Temporarily Unavailable</h1></center>\r\n</body>\r\n</html>\r\n", true, "Nethermind"},
{"call failed: 502 Bad Gateway: <html>\r\n<head><title>502 Bad Gateway</title></head>\r\n<body>\r\n<center><h1>502 Bad Gateway</h1></center>\r\n<hr><center>", true, "Arbitrum"},
{"client error service unavailable", true, "tomlConfig"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand All @@ -237,6 +264,7 @@ func Test_Eth_Errors(t *testing.T) {
{"max fee per gas higher than max priority fee per gas", true, "Klaytn"},
{"tx fee (1.10 of currency celo) exceeds the configured cap (1.00 celo)", true, "celo"},
{"max priority fee per gas higher than max fee per gas", true, "zkSync"},
{"client error tx fee exceeds cap", true, "tomlConfig"},
}
for _, test := range tests {
err = evmclient.NewSendErrorS(test.message)
Expand Down Expand Up @@ -299,6 +327,9 @@ func Test_Eth_Errors(t *testing.T) {
func Test_Eth_Errors_Fatal(t *testing.T) {
t.Parallel()

clientErrors := NewTestClientErrors()
evmclient.SetClientErrorRegexes(&clientErrors)

tests := []errorCase{
{"some old bollocks", false, "none"},

Expand Down Expand Up @@ -363,6 +394,7 @@ func Test_Eth_Errors_Fatal(t *testing.T) {
{"Failed to serialize transaction: max fee per pubdata byte higher than 2^64-1", true, "zkSync"},
{"Failed to serialize transaction: max priority fee per gas higher than 2^64-1", true, "zkSync"},
{"Failed to serialize transaction: oversized data. max: 1000000; actual: 1000000", true, "zkSync"},
{"client error fatal", true, "tomlConfig"},
}

for _, test := range tests {
Expand Down
67 changes: 67 additions & 0 deletions core/chains/evm/client/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,73 @@ import (
evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types"
)

type TestClientErrors struct {
nonceTooLow string
nonceTooHigh string
replacementTransactionUnderpriced string
limitReached string
transactionAlreadyInMempool string
terminallyUnderpriced string
insufficientEth string
txFeeExceedsCap string
l2FeeTooLow string
l2FeeTooHigh string
l2Full string
transactionAlreadyMined string
fatal string
serviceUnavailable string
}

func (c *TestClientErrors) SetNonceTooLow(s string) { c.nonceTooLow = s }
func (c *TestClientErrors) SetNonceTooHigh(s string) { c.nonceTooHigh = s }

func (c *TestClientErrors) SetReplacementTransactionUnderpriced(s string) {
c.replacementTransactionUnderpriced = s
}

func (c *TestClientErrors) SetLimitReached(s string) { c.limitReached = s }

func (c *TestClientErrors) SetTransactionAlreadyInMempool(s string) {
c.transactionAlreadyInMempool = s
}

func (c *TestClientErrors) SetTerminallyUnderpriced(s string) { c.terminallyUnderpriced = s }
func (c *TestClientErrors) SetInsufficientEth(s string) { c.insufficientEth = s }
func (c *TestClientErrors) SetTxFeeExceedsCap(s string) { c.txFeeExceedsCap = s }
func (c *TestClientErrors) SetL2FeeTooLow(s string) { c.l2FeeTooLow = s }
func (c *TestClientErrors) SetL2FeeTooHigh(s string) { c.l2FeeTooHigh = s }
func (c *TestClientErrors) SetL2Full(s string) { c.l2Full = s }

func (c *TestClientErrors) SetTransactionAlreadyMined(s string) {
c.transactionAlreadyMined = s
}

func (c *TestClientErrors) SetFatal(s string) { c.fatal = s }
func (c *TestClientErrors) SetServiceUnavailable(s string) { c.serviceUnavailable = s }

func (c *TestClientErrors) NonceTooLow() string { return c.nonceTooLow }
func (c *TestClientErrors) NonceTooHigh() string { return c.nonceTooHigh }

func (c *TestClientErrors) ReplacementTransactionUnderpriced() string {
return c.replacementTransactionUnderpriced
}

func (c *TestClientErrors) LimitReached() string { return c.limitReached }

func (c *TestClientErrors) TransactionAlreadyInMempool() string {
return c.transactionAlreadyInMempool
}

func (c *TestClientErrors) TerminallyUnderpriced() string { return c.terminallyUnderpriced }
func (c *TestClientErrors) InsufficientEth() string { return c.insufficientEth }
func (c *TestClientErrors) TxFeeExceedsCap() string { return c.txFeeExceedsCap }
func (c *TestClientErrors) L2FeeTooLow() string { return c.l2FeeTooLow }
func (c *TestClientErrors) L2FeeTooHigh() string { return c.l2FeeTooHigh }
func (c *TestClientErrors) L2Full() string { return c.l2Full }
func (c *TestClientErrors) TransactionAlreadyMined() string { return c.transactionAlreadyMined }
func (c *TestClientErrors) Fatal() string { return c.fatal }
func (c *TestClientErrors) ServiceUnavailable() string { return c.serviceUnavailable }

type TestNodePoolConfig struct {
NodePollFailureThreshold uint32
NodePollInterval time.Duration
Expand Down
4 changes: 4 additions & 0 deletions core/chains/evm/config/chain_scoped.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func (e *EVMConfig) Transactions() Transactions {
return &transactionsConfig{c: e.C.Transactions}
}

func (e *EVMConfig) ClientErrors() ClientErrors {
return &clientErrorsConfig{c: e.C.ClientErrors}
}

func (e *EVMConfig) HeadTracker() HeadTracker {
return &headTrackerConfig{c: e.C.HeadTracker}
}
Expand Down
52 changes: 52 additions & 0 deletions core/chains/evm/config/chain_scoped_client_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package config

import (
"github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/toml"
)

var defaultRegex = ""
DylanTinianov marked this conversation as resolved.
Show resolved Hide resolved

func derefOrDefault(s *string) string {
if s == nil {
return defaultRegex
}

return *s
}

type clientErrorsConfig struct {
c toml.ClientErrors
}

func (c *clientErrorsConfig) NonceTooLow() string { return derefOrDefault(c.c.NonceTooLow) }
func (c *clientErrorsConfig) NonceTooHigh() string { return derefOrDefault(c.c.NonceTooHigh) }

func (c *clientErrorsConfig) ReplacementTransactionUnderpriced() string {
return derefOrDefault(c.c.ReplacementTransactionUnderpriced)
}

func (c *clientErrorsConfig) LimitReached() string { return derefOrDefault(c.c.LimitReached) }

func (c *clientErrorsConfig) TransactionAlreadyInMempool() string {
return derefOrDefault(c.c.TransactionAlreadyInMempool)
}

func (c *clientErrorsConfig) TerminallyUnderpriced() string {
return derefOrDefault(c.c.TerminallyUnderpriced)
}

func (c *clientErrorsConfig) InsufficientEth() string { return derefOrDefault(c.c.InsufficientEth) }
func (c *clientErrorsConfig) TxFeeExceedsCap() string { return derefOrDefault(c.c.TxFeeExceedsCap) }
func (c *clientErrorsConfig) L2FeeTooLow() string { return derefOrDefault(c.c.L2FeeTooLow) }
func (c *clientErrorsConfig) L2FeeTooHigh() string { return derefOrDefault(c.c.L2FeeTooHigh) }
func (c *clientErrorsConfig) L2Full() string { return derefOrDefault(c.c.L2Full) }

func (c *clientErrorsConfig) TransactionAlreadyMined() string {
return derefOrDefault(c.c.TransactionAlreadyMined)
}

func (c *clientErrorsConfig) Fatal() string { return derefOrDefault(c.c.Fatal) }

func (c *clientErrorsConfig) ServiceUnavailable() string {
return derefOrDefault(c.c.ServiceUnavailable)
}
Loading
Loading