From 3785c032e60e1bf1bbca088b0b242e4f478a570b Mon Sep 17 00:00:00 2001 From: Bartek Tofel Date: Tue, 7 Jan 2025 12:43:34 +0100 Subject: [PATCH 1/6] run compatibility pipeline only on demand (#1574) --- .github/workflows/client-compatibility-tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/client-compatibility-tests.yml b/.github/workflows/client-compatibility-tests.yml index 2e27f49a39..f35b86b0de 100644 --- a/.github/workflows/client-compatibility-tests.yml +++ b/.github/workflows/client-compatibility-tests.yml @@ -1,12 +1,12 @@ name: Client Compatibility Tests on: - schedule: - - cron: "30 5 * * TUE,FRI" # Run every Tuesday and Friday at midnight + 30min EST - push: - tags: - - "*" - merge_group: - pull_request: + # schedule: + # - cron: "30 5 * * TUE,FRI" # Run every Tuesday and Friday at midnight + 30min EST + # push: + # tags: + # - "*" + # merge_group: + # pull_request: workflow_dispatch: inputs: chainlinkVersion: From ea7c20013a4c2b7a8bd414b411c86abf7083081f Mon Sep 17 00:00:00 2001 From: Sishir Giri Date: Tue, 7 Jan 2025 12:33:14 -0800 Subject: [PATCH 2/6] Sonic, HashKey toml config (#1568) ## Motivation ## Solution --- ccip/config/evm/Hashkey_Mainnet.toml | 16 ++++++++++++++++ ccip/config/evm/Hashkey_Testnet.toml | 16 ++++++++++++++++ ccip/config/evm/Sonic_Mainnet.toml | 28 ++++++++++++++++++++++++++++ ccip/config/evm/Sonic_Testnet.toml | 28 ++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 ccip/config/evm/Hashkey_Mainnet.toml create mode 100644 ccip/config/evm/Hashkey_Testnet.toml create mode 100644 ccip/config/evm/Sonic_Mainnet.toml create mode 100644 ccip/config/evm/Sonic_Testnet.toml diff --git a/ccip/config/evm/Hashkey_Mainnet.toml b/ccip/config/evm/Hashkey_Mainnet.toml new file mode 100644 index 0000000000..69450c96f8 --- /dev/null +++ b/ccip/config/evm/Hashkey_Mainnet.toml @@ -0,0 +1,16 @@ +ChainID = '177' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true + +[GasEstimator] +PriceMax = '1000 gwei' +LimitDefault = 8000000 +FeeCapDefault = '1000 gwei' + +[NodePool] +PollFailureThreshold = 2 +PollInterval = '8s' + +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' \ No newline at end of file diff --git a/ccip/config/evm/Hashkey_Testnet.toml b/ccip/config/evm/Hashkey_Testnet.toml new file mode 100644 index 0000000000..c342e503a3 --- /dev/null +++ b/ccip/config/evm/Hashkey_Testnet.toml @@ -0,0 +1,16 @@ +ChainID = '133' +ChainType = 'optimismBedrock' +FinalityTagEnabled = true + +[GasEstimator] +PriceMax = '1000 gwei' +LimitDefault = 8000000 +FeeCapDefault = '1000 gwei' + +[NodePool] +PollFailureThreshold = 2 +PollInterval = '8s' + +[GasEstimator.DAOracle] +OracleType = 'opstack' +OracleAddress = '0x420000000000000000000000000000000000000F' \ No newline at end of file diff --git a/ccip/config/evm/Sonic_Mainnet.toml b/ccip/config/evm/Sonic_Mainnet.toml new file mode 100644 index 0000000000..523a931c8d --- /dev/null +++ b/ccip/config/evm/Sonic_Mainnet.toml @@ -0,0 +1,28 @@ +ChainId = '146' +FinalityDepth = 10 +FinalityTagEnabled = false +LogPollInterval = "1s" #1s block rate +MinIncomingConfirmations = 5 +RPCBlockQueryDelay = 10 +RPCDefaultBatchSize = 100 + +[GasEstimator] +Mode = 'FeeHistory' +EIP1559DynamicFees = true +BumpPercent = 10 +LimitDefault = 8000000 # default ccip value + +[GasEstimator.FeeHistory] +CacheTimeout = '2s' + +[GasEstimator.BlockHistory] +BlockHistorySize = 100 + +[HeadTracker] +HistoryDepth = 50 + +[NodePool] +SyncThreshold = 10 + +[Transactions] +MaxQueued = 500 \ No newline at end of file diff --git a/ccip/config/evm/Sonic_Testnet.toml b/ccip/config/evm/Sonic_Testnet.toml new file mode 100644 index 0000000000..ca3ccf8f71 --- /dev/null +++ b/ccip/config/evm/Sonic_Testnet.toml @@ -0,0 +1,28 @@ +ChainId = '57054' +FinalityDepth = 10 +FinalityTagEnabled = false +LogPollInterval = "1s" #1s block rate +MinIncomingConfirmations = 5 +RPCBlockQueryDelay = 10 +RPCDefaultBatchSize = 100 + +[GasEstimator] +Mode = 'FeeHistory' +EIP1559DynamicFees = true +BumpPercent = 10 +LimitDefault = 8000000 # default ccip value + +[GasEstimator.FeeHistory] +CacheTimeout = '2s' + +[GasEstimator.BlockHistory] +BlockHistorySize = 100 + +[HeadTracker] +HistoryDepth = 50 + +[NodePool] +SyncThreshold = 10 + +[Transactions] +MaxQueued = 500 \ No newline at end of file From 6167b77f72d6fd3684fa230e844c858a024f18af Mon Sep 17 00:00:00 2001 From: Sishir Giri Date: Wed, 8 Jan 2025 09:35:34 -0800 Subject: [PATCH 3/6] Remove DA from HashKey (#1588) ## Motivation ## Solution --- ccip/config/evm/Hashkey_Mainnet.toml | 6 +----- ccip/config/evm/Hashkey_Testnet.toml | 4 ---- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/ccip/config/evm/Hashkey_Mainnet.toml b/ccip/config/evm/Hashkey_Mainnet.toml index 69450c96f8..1c82facc50 100644 --- a/ccip/config/evm/Hashkey_Mainnet.toml +++ b/ccip/config/evm/Hashkey_Mainnet.toml @@ -9,8 +9,4 @@ FeeCapDefault = '1000 gwei' [NodePool] PollFailureThreshold = 2 -PollInterval = '8s' - -[GasEstimator.DAOracle] -OracleType = 'opstack' -OracleAddress = '0x420000000000000000000000000000000000000F' \ No newline at end of file +PollInterval = '8s' \ No newline at end of file diff --git a/ccip/config/evm/Hashkey_Testnet.toml b/ccip/config/evm/Hashkey_Testnet.toml index c342e503a3..b38e7a635d 100644 --- a/ccip/config/evm/Hashkey_Testnet.toml +++ b/ccip/config/evm/Hashkey_Testnet.toml @@ -10,7 +10,3 @@ FeeCapDefault = '1000 gwei' [NodePool] PollFailureThreshold = 2 PollInterval = '8s' - -[GasEstimator.DAOracle] -OracleType = 'opstack' -OracleAddress = '0x420000000000000000000000000000000000000F' \ No newline at end of file From 9e91b405df88965be6b90142f623d90d91e9355c Mon Sep 17 00:00:00 2001 From: Dmytro Haidashenko <34754799+dhaidashenko@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:38:46 +0100 Subject: [PATCH 4/6] Custom log index for sei chain (#1592) Patch with minor adjustments from the chainlink repo [PR](https://github.com/smartcontractkit/chainlink/pull/15858). Adjustments were caused by differences between repos. It's easier to do&review them than to apply all the changes made to chainlink repo that included MultiNode abstraction with 5k lines changed. --- .changeset/clever-knives-tap.md | 5 + ccip/config/evm/Sei_Testnet_Atlantic.toml | 18 +++ core/build/platform_arch_guard.go | 3 + core/chains/evm/client/errors.go | 12 +- core/chains/evm/client/errors_test.go | 6 + core/chains/evm/client/helpers_test.go | 8 +- core/chains/evm/client/rpc_client.go | 72 +++++++++- .../evm/client/rpc_client_internal_test.go | 93 ++++++++++++ core/chains/evm/client/rpc_client_test.go | 132 +++++++++++++++++- core/chains/evm/client/sub_forwarder.go | 30 ++-- core/chains/evm/client/sub_forwarder_test.go | 48 ++++--- core/chains/evm/config/chaintype/chaintype.go | 6 +- core/services/chainlink/config_test.go | 4 +- core/services/ocr/contract_tracker.go | 2 +- core/services/ocrcommon/block_translator.go | 2 +- 15 files changed, 397 insertions(+), 44 deletions(-) create mode 100644 .changeset/clever-knives-tap.md create mode 100644 ccip/config/evm/Sei_Testnet_Atlantic.toml create mode 100644 core/build/platform_arch_guard.go create mode 100644 core/chains/evm/client/rpc_client_internal_test.go diff --git a/.changeset/clever-knives-tap.md b/.changeset/clever-knives-tap.md new file mode 100644 index 0000000000..8683e89f77 --- /dev/null +++ b/.changeset/clever-knives-tap.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#added Sei config and error mapping diff --git a/ccip/config/evm/Sei_Testnet_Atlantic.toml b/ccip/config/evm/Sei_Testnet_Atlantic.toml new file mode 100644 index 0000000000..f8c23d95c5 --- /dev/null +++ b/ccip/config/evm/Sei_Testnet_Atlantic.toml @@ -0,0 +1,18 @@ +ChainID = '1328' +ChainType = 'sei' +# finality_depth: instant +FinalityDepth = 10 +# block_time: ~0.4s, adding 1 second buffer +LogPollInterval = '2s' +# finality_depth * block_time / 60 secs = ~0.8 min (finality time) +NoNewFinalizedHeadsThreshold = '5m' +# "RPC node returned multiple missing blocks on query for block numbers [31592085 31592084] even though the WS subscription already sent us these blocks. It might help to increase EVM.RPCBlockQueryDelay (currently 1)" +RPCBlockQueryDelay = 5 + +[GasEstimator] +EIP1559DynamicFees = false +Mode = 'BlockHistory' +PriceMax = '3000 gwei' # recommended by ds&a + +[GasEstimator.BlockHistory] +BlockHistorySize = 200 diff --git a/core/build/platform_arch_guard.go b/core/build/platform_arch_guard.go new file mode 100644 index 0000000000..3a22f7df53 --- /dev/null +++ b/core/build/platform_arch_guard.go @@ -0,0 +1,3 @@ +//go:build !amd64 && !arm64 +package build +"non-64-bits architectures are not supported" diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index f92a79e203..0bac56d142 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -284,6 +284,16 @@ var gnosis = ClientErrors{ TransactionAlreadyInMempool: regexp.MustCompile(`(: |^)(alreadyknown)`), } +var sei = ClientErrors{ + // https://github.com/sei-protocol/sei-tendermint/blob/e9a22c961e83579d8a68cd045c532980d82fb2a0/types/mempool.go#L12 + TransactionAlreadyInMempool: regexp.MustCompile("tx already exists in cache"), + // https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L50 + // https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L56 + // https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/client/broadcast.go#L27 + // https://github.com/sei-protocol/sei-cosmos/blob/a4eb451c957b1ca7ca9118406682f93fe83d1f61/types/errors/errors.go#L32 + Fatal: regexp.MustCompile(`(: |^)'*out of gas|insufficient fee|Tx too large. Max size is \d+, but got \d+|: insufficient funds`), +} + const TerminallyStuckMsg = "transaction terminally stuck" // Tx.Error messages that are set internally so they are not chain or client specific @@ -291,7 +301,7 @@ var internal = ClientErrors{ TerminallyStuck: regexp.MustCompile(TerminallyStuckMsg), } -var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, hedera, gnosis, internal} +var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm, treasure, mantle, aStar, hedera, gnosis, sei, internal} // ClientErrorRegexes returns a map of compiled regexes for each error type func ClientErrorRegexes(errsRegex config.ClientErrors) *ClientErrors { diff --git a/core/chains/evm/client/errors_test.go b/core/chains/evm/client/errors_test.go index c8483972c8..7fba0ae51f 100644 --- a/core/chains/evm/client/errors_test.go +++ b/core/chains/evm/client/errors_test.go @@ -144,6 +144,7 @@ func Test_Eth_Errors(t *testing.T) { {"client error transaction already in mempool", true, "tomlConfig"}, {"alreadyknown", true, "Gnosis"}, {"failed to forward tx to sequencer, please try again. Error message: 'already known'", true, "Mantle"}, + {"tx already exists in cache", true, "Sei"}, } for _, test := range tests { err = evmclient.NewSendErrorS(test.message) @@ -420,6 +421,11 @@ func Test_Eth_Errors_Fatal(t *testing.T) { {"client error fatal", true, "tomlConfig"}, {"[Request ID: d9711488-4c1e-4af2-bc1f-7969913d7b60] Error invoking RPC: transaction 0.0.4425573@1718213476.914320044 failed precheck with status INVALID_SIGNATURE", true, "hedera"}, {"invalid chain id for signer", true, "Treasure"}, + + {": out of gas", true, "Sei"}, + {"Tx too large. Max size is 2048576, but got 2097431", true, "Sei"}, + {": insufficient funds", true, "Sei"}, + {"insufficient fee", true, "Sei"}, } for _, test := range tests { diff --git a/core/chains/evm/client/helpers_test.go b/core/chains/evm/client/helpers_test.go index 1a6090e4a0..acb8f39338 100644 --- a/core/chains/evm/client/helpers_test.go +++ b/core/chains/evm/client/helpers_test.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "net/url" + "sync" "testing" "time" @@ -219,6 +220,7 @@ const HeadResult = `{"difficulty":"0xf3a00","extraData":"0xd88301050384676574688 type mockSubscription struct { unsubscribed bool Errors chan error + unsub sync.Once } func NewMockSubscription() *mockSubscription { @@ -228,8 +230,10 @@ func NewMockSubscription() *mockSubscription { func (mes *mockSubscription) Err() <-chan error { return mes.Errors } func (mes *mockSubscription) Unsubscribe() { - mes.unsubscribed = true - close(mes.Errors) + mes.unsub.Do(func() { + mes.unsubscribed = true + close(mes.Errors) + }) } func ParseTestNodeConfigs(nodes []NodeConfig) ([]*toml.Node, error) { diff --git a/core/chains/evm/client/rpc_client.go b/core/chains/evm/client/rpc_client.go index f55c35980d..58f88bd003 100644 --- a/core/chains/evm/client/rpc_client.go +++ b/core/chains/evm/client/rpc_client.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/big" "net/url" "strconv" @@ -151,6 +152,7 @@ type rpcClient struct { latestChainInfo commonclient.ChainInfo } +// NewRPCCLient returns a new *rpcClient as commonclient.RPC // NewRPCCLient returns a new *rpcClient as commonclient.RPC func NewRPCClient( lggr logger.Logger, @@ -166,6 +168,22 @@ func NewRPCClient( rpcTimeout time.Duration, chainType chaintype.ChainType, ) RPCClient { + return newRPCClient(lggr, wsuri, httpuri, name, id, chainID, tier, finalizedBlockPollInterval, newHeadsPollInterval, largePayloadRpcTimeout, rpcTimeout, chainType) +} +func newRPCClient( + lggr logger.Logger, + wsuri *url.URL, + httpuri *url.URL, + name string, + id int, + chainID *big.Int, + tier commonclient.NodeTier, + finalizedBlockPollInterval time.Duration, + newHeadsPollInterval time.Duration, + largePayloadRpcTimeout time.Duration, + rpcTimeout time.Duration, + chainType chaintype.ChainType, +) *rpcClient { r := &rpcClient{ largePayloadRpcTimeout: largePayloadRpcTimeout, rpcTimeout: rpcTimeout, @@ -428,6 +446,10 @@ func (r *rpcClient) BatchCallContext(rootCtx context.Context, b []rpc.BatchElem) var requestedFinalizedBlock bool if r.chainType == chaintype.ChainAstar { for _, el := range b { + if el.Method == "eth_getLogs" { + r.rpcLog.Critical("evmclient.BatchCallContext: eth_getLogs is not supported") + return errors.New("evmclient.BatchCallContext: eth_getLogs is not supported") + } if !isRequestingFinalizedBlock(el) { continue } @@ -547,10 +569,10 @@ func (r *rpcClient) SubscribeNewHead(ctx context.Context, channel chan<- *evmtyp r.logResult(lggr, err, duration, r.getRPCDomain(), "EthSubscribe") err = r.wrapWS(err) }() - subForwarder := newSubForwarder(channel, func(head *evmtypes.Head) *evmtypes.Head { + subForwarder := newSubForwarder(channel, func(head *evmtypes.Head) (*evmtypes.Head, error) { head.EVMChainID = ubig.New(r.chainID) r.onNewHead(ctx, chStopInFlight, head) - return head + return head, nil }, r.wrapRPCClientError) err = subForwarder.start(ws.rpc.EthSubscribe(ctx, subForwarder.srcCh, args...)) if err != nil { @@ -602,10 +624,10 @@ func (r *rpcClient) SubscribeToHeads(ctx context.Context) (ch <-chan *evmtypes.H }() channel := make(chan *evmtypes.Head) - forwarder := newSubForwarder(channel, func(head *evmtypes.Head) *evmtypes.Head { + forwarder := newSubForwarder(channel, func(head *evmtypes.Head) (*evmtypes.Head, error) { head.EVMChainID = ubig.New(r.chainID) r.onNewHead(ctx, chStopInFlight, head) - return head + return head, nil }, r.wrapRPCClientError) err = forwarder.start(ws.rpc.EthSubscribe(ctx, forwarder.srcCh, args...)) @@ -1283,8 +1305,11 @@ func (r *rpcClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) (l [ l, err = ws.geth.FilterLogs(ctx, q) err = r.wrapWS(err) } - duration := time.Since(start) + if err == nil { + err = r.makeLogsValid(l) + } + duration := time.Since(start) r.logResult(lggr, err, duration, r.getRPCDomain(), "FilterLogs", "log", l, ) @@ -1312,7 +1337,7 @@ func (r *rpcClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQu r.logResult(lggr, err, duration, r.getRPCDomain(), "SubscribeFilterLogs") err = r.wrapWS(err) }() - sub := newSubForwarder(ch, nil, r.wrapRPCClientError) + sub := newSubForwarder(ch, r.makeLogValid, r.wrapRPCClientError) err = sub.start(ws.geth.SubscribeFilterLogs(ctx, q, sub.srcCh)) if err != nil { return @@ -1540,3 +1565,38 @@ func ToBlockNumArg(number *big.Int) string { } return hexutil.EncodeBig(number) } + +func (r *rpcClient) makeLogsValid(logs []types.Log) error { + if r.chainType != chaintype.ChainSei { + return nil + } + + for i := range logs { + var err error + logs[i], err = r.makeLogValid(logs[i]) + if err != nil { + return err + } + } + + return nil +} + +func (r *rpcClient) makeLogValid(log types.Log) (types.Log, error) { + if r.chainType != chaintype.ChainSei { + return log, nil + } + + if log.TxIndex > math.MaxUint32 { + return types.Log{}, fmt.Errorf("TxIndex of tx %s exceeds max supported value of %d", log.TxHash, math.MaxUint32) + } + + if log.Index > math.MaxUint32 { + return types.Log{}, fmt.Errorf("log's index %d of tx %s exceeds max supported value of %d", log.Index, log.TxHash, math.MaxUint32) + } + + // it's safe as we have a build guard to guarantee 64-bit system + newIndex := uint64(log.TxIndex<<32) | uint64(log.Index) + log.Index = uint(newIndex) + return log, nil +} diff --git a/core/chains/evm/client/rpc_client_internal_test.go b/core/chains/evm/client/rpc_client_internal_test.go new file mode 100644 index 0000000000..ab8333477d --- /dev/null +++ b/core/chains/evm/client/rpc_client_internal_test.go @@ -0,0 +1,93 @@ +package client + +import ( + "errors" + "math" + "testing" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config/chaintype" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestRPCClient_MakeLogsValid(t *testing.T) { + testCases := []struct { + Name string + TxIndex uint + LogIndex uint + ExpectedLogIndex uint + ExpectedError error + }{ + { + Name: "TxIndex = 0 LogIndex = 0", + TxIndex: 0, + LogIndex: 0, + ExpectedLogIndex: 0, + ExpectedError: nil, + }, + { + Name: "TxIndex = 0 LogIndex = 1", + TxIndex: 0, + LogIndex: 1, + ExpectedLogIndex: 1, + ExpectedError: nil, + }, + { + Name: "TxIndex = 0 LogIndex = MaxUint32", + TxIndex: 0, + LogIndex: math.MaxUint32, + ExpectedLogIndex: math.MaxUint32, + ExpectedError: nil, + }, + { + Name: "LogIndex = MaxUint32 + 1 => returns an error", + TxIndex: 0, + LogIndex: math.MaxUint32 + 1, + ExpectedLogIndex: 0, + ExpectedError: errors.New("log's index 4294967296 of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"), + }, + { + Name: "TxIndex = 1 LogIndex = 0", + TxIndex: 1, + LogIndex: 0, + ExpectedLogIndex: math.MaxUint32 + 1, + ExpectedError: nil, + }, + { + Name: "TxIndex = MaxUint32 LogIndex = MaxUint32", + TxIndex: math.MaxUint32, + LogIndex: math.MaxUint32, + ExpectedLogIndex: math.MaxUint64, + ExpectedError: nil, + }, + { + Name: "TxIndex = MaxUint32 + 1 => returns an error", + TxIndex: math.MaxUint32 + 1, + LogIndex: 0, + ExpectedLogIndex: 0, + ExpectedError: errors.New("TxIndex of tx 0x0000000000000000000000000000000000000000000000000000000000000000 exceeds max supported value of 4294967295"), + }, + } + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + rpc := newRPCClient(logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + log, err := rpc.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex}) + // non sei should return as is + require.NoError(t, err) + require.Equal(t, tc.TxIndex, log.TxIndex) + require.Equal(t, tc.LogIndex, log.Index) + seiRPC := newRPCClient(logger.TestLogger(t), nil, nil, "eth-primary-rpc-0", 0, nil, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainSei) + log, err = seiRPC.makeLogValid(ethtypes.Log{TxIndex: tc.TxIndex, Index: tc.LogIndex}) + if tc.ExpectedError != nil { + require.EqualError(t, err, tc.ExpectedError.Error()) + return + } + + require.Equal(t, tc.ExpectedLogIndex, log.Index) + require.Equal(t, tc.TxIndex, log.TxIndex) + }) + } +} diff --git a/core/chains/evm/client/rpc_client_test.go b/core/chains/evm/client/rpc_client_test.go index 662c757ffb..f2d8a83206 100644 --- a/core/chains/evm/client/rpc_client_test.go +++ b/core/chains/evm/client/rpc_client_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "math/big" "net/url" "sync" @@ -12,6 +13,7 @@ import ( "time" "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/assert" @@ -32,14 +34,16 @@ import ( evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" ) -func makeNewHeadWSMessage(head *evmtypes.Head) string { - asJSON, err := json.Marshal(head) +func makeNewWSMessage[T any](v T) string { + asJSON, err := json.Marshal(v) if err != nil { panic(fmt.Errorf("failed to marshal head: %w", err)) } return fmt.Sprintf(`{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x00","result":%s}}`, string(asJSON)) } +var makeNewHeadWSMessage = makeNewWSMessage[*evmtypes.Head] + func TestRPCClient_SubscribeNewHead(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(tests.Context(t), tests.WaitTimeout(t)) @@ -390,6 +394,130 @@ func TestRPCClient_SubscribeFilterLogs(t *testing.T) { t.Errorf("Expected subscription to return an error, but test timeout instead") } }) + t.Run("Log's index is properly set for Sei chain type", func(t *testing.T) { + server := testutils.NewWSServer(t, chainId, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + if method == "eth_unsubscribe" { + resp.Result = "true" + return + } else if method == "eth_subscribe" { + if assert.True(t, params.IsArray()) && assert.Equal(t, "logs", params.Array()[0].String()) { + resp.Result = `"0x00"` + } + return + } + return + }) + wsURL := server.WSURL() + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainId, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainSei) + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + ch := make(chan types.Log) + sub, err := rpc.SubscribeFilterLogs(ctx, ethereum.FilterQuery{}, ch) + require.NoError(t, err) + testCases := []struct { + TxIndex uint + Index uint + ExpectedIndex uint + }{ + { + TxIndex: 0, + Index: 0, + ExpectedIndex: 0, + }, + { + TxIndex: 0, + Index: 1, + ExpectedIndex: 1, + }, + { + TxIndex: 1, + Index: 0, + ExpectedIndex: math.MaxUint32 + 1, + }, + } + go func() { + for _, testCase := range testCases { + server.MustWriteBinaryMessageSync(t, makeNewWSMessage(types.Log{TxIndex: testCase.TxIndex, Index: testCase.Index, Topics: []common.Hash{{}}})) + } + }() + defer sub.Unsubscribe() + for _, testCase := range testCases { + select { + case <-tests.Context(t).Done(): + require.Fail(t, "context timed out") + case err := <-sub.Err(): + require.NoError(t, err) + require.Fail(t, "Did not expect error channel to be closed or return error before all testcases were consumed") + case log := <-ch: + require.Equal(t, testCase.ExpectedIndex, log.Index, "Unexpected log index %d for test case %v", log.Index, testCase) + } + } + }) +} + +func TestRPCClientFilterLogs(t *testing.T) { + t.Parallel() + + chainID := big.NewInt(123456) + lggr := logger.Test(t) + ctx, cancel := context.WithTimeout(tests.Context(t), tests.WaitTimeout(t)) + defer cancel() + t.Run("Log's index is properly set for Sei chain type", func(t *testing.T) { + testCases := []struct { + TxIndex uint + Index uint + ExpectedIndex uint + }{ + { + TxIndex: 0, + Index: 0, + ExpectedIndex: 0, + }, + { + TxIndex: 0, + Index: 1, + ExpectedIndex: 1, + }, + { + TxIndex: 1, + Index: 0, + ExpectedIndex: math.MaxUint32 + 1, + }, + } + server := testutils.NewWSServer(t, chainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + if method != "eth_getLogs" { + return + } + var logs []types.Log + for _, testCase := range testCases { + logs = append(logs, types.Log{TxIndex: testCase.TxIndex, Index: testCase.Index, Topics: []common.Hash{{}}}) + } + raw, err := json.Marshal(logs) + require.NoError(t, err) + resp.Result = string(raw) + return + }) + wsURL := server.WSURL() + seiRPC := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, chaintype.ChainSei) + defer seiRPC.Close() + require.NoError(t, seiRPC.Dial(ctx)) + logs, err := seiRPC.FilterEvents(ctx, ethereum.FilterQuery{}) + require.NoError(t, err) + for i, testCase := range testCases { + require.Equal(t, testCase.ExpectedIndex, logs[i].Index, "Unexpected log index %d for test case %v", logs[i].Index, testCase) + } + + // non sei should return index as is + rpc := client.NewRPCClient(lggr, wsURL, nil, "rpc", 1, chainID, commonclient.Primary, 0, 0, commonclient.QueryTimeout, commonclient.QueryTimeout, "") + defer rpc.Close() + require.NoError(t, rpc.Dial(ctx)) + logs, err = rpc.FilterEvents(ctx, ethereum.FilterQuery{}) + require.NoError(t, err) + for i, testCase := range testCases { + require.Equal(t, testCase.Index, logs[i].Index, "Expected non sei log to be returned as is") + require.Equal(t, testCase.TxIndex, logs[i].TxIndex, "Expected non sei log to be returned as is") + } + }) } func TestRPCClient_LatestFinalizedBlock(t *testing.T) { diff --git a/core/chains/evm/client/sub_forwarder.go b/core/chains/evm/client/sub_forwarder.go index 93e9b106b4..a9b5a97eee 100644 --- a/core/chains/evm/client/sub_forwarder.go +++ b/core/chains/evm/client/sub_forwarder.go @@ -13,7 +13,7 @@ type subForwarder[T any] struct { srcCh chan T srcSub ethereum.Subscription - interceptResult func(T) T + interceptResult func(T) (T, error) interceptError func(error) error done chan struct{} @@ -21,14 +21,14 @@ type subForwarder[T any] struct { unSub chan struct{} } -func newSubForwarder[T any](destCh chan<- T, interceptResult func(T) T, interceptError func(error) error) *subForwarder[T] { +func newSubForwarder[T any](destCh chan<- T, interceptResult func(T) (T, error), interceptError func(error) error) *subForwarder[T] { return &subForwarder[T]{ interceptResult: interceptResult, interceptError: interceptError, destCh: destCh, srcCh: make(chan T), done: make(chan struct{}), - err: make(chan error), + err: make(chan error, 1), unSub: make(chan struct{}, 1), } } @@ -44,6 +44,14 @@ func (c *subForwarder[T]) start(sub ethereum.Subscription, err error) error { return nil } +func (c *subForwarder[T]) handleError(err error) { + if c.interceptError != nil { + err = c.interceptError(err) + } + c.err <- err // err is buffered, and we never write twice, so write is not blocking + c.srcSub.Unsubscribe() +} + // forwardLoop receives from src, adds the chainID, and then sends to dest. // It also handles Unsubscribing, which may interrupt either forwarding operation. func (c *subForwarder[T]) forwardLoop() { @@ -54,19 +62,17 @@ func (c *subForwarder[T]) forwardLoop() { for { select { case err := <-c.srcSub.Err(): - if c.interceptError != nil { - err = c.interceptError(err) - } - select { - case c.err <- err: - case <-c.unSub: - c.srcSub.Unsubscribe() - } + c.handleError(err) return case h := <-c.srcCh: if c.interceptResult != nil { - h = c.interceptResult(h) + var err error + h, err = c.interceptResult(h) + if err != nil { + c.handleError(err) + return + } } select { case c.destCh <- h: diff --git a/core/chains/evm/client/sub_forwarder_test.go b/core/chains/evm/client/sub_forwarder_test.go index 1bc0122603..267fa1b846 100644 --- a/core/chains/evm/client/sub_forwarder_test.go +++ b/core/chains/evm/client/sub_forwarder_test.go @@ -21,9 +21,9 @@ func TestChainIDSubForwarder(t *testing.T) { t.Parallel() newChainIDSubForwarder := func(chainID *big.Int, ch chan<- *evmtypes.Head) *subForwarder[*evmtypes.Head] { - return newSubForwarder(ch, func(head *evmtypes.Head) *evmtypes.Head { + return newSubForwarder(ch, func(head *evmtypes.Head) (*evmtypes.Head, error) { head.EVMChainID = ubig.New(chainID) - return head + return head, nil }, nil) } @@ -54,12 +54,14 @@ func TestChainIDSubForwarder(t *testing.T) { sub := NewMockSubscription() err := forwarder.start(sub, nil) assert.NoError(t, err) - sub.Errors <- errors.New("boo") + expectedError := errors.New("boo") + sub.Errors <- expectedError forwarder.Unsubscribe() assert.True(t, sub.unsubscribed) - _, ok := <-sub.Err() - assert.False(t, ok) + err, ok := <-forwarder.Err() + assert.True(t, ok) + require.ErrorIs(t, err, expectedError) _, ok = <-forwarder.Err() assert.False(t, ok) }) @@ -117,6 +119,31 @@ func TestChainIDSubForwarder(t *testing.T) { }) } +func TestSubscriptionForwarder(t *testing.T) { + t.Run("Error returned by interceptResult is forwarded to err channel", func(t *testing.T) { + t.Parallel() + + ch := make(chan *evmtypes.Head) + expectedErr := errors.New("something went wrong during result interception") + forwarder := newSubForwarder(ch, func(head *evmtypes.Head) (*evmtypes.Head, error) { + return nil, expectedErr + }, nil) + mockedSub := NewMockSubscription() + require.NoError(t, forwarder.start(mockedSub, nil)) + + head := &evmtypes.Head{ + ID: 1, + } + forwarder.srcCh <- head + err := <-forwarder.Err() + require.ErrorIs(t, err, expectedErr) + // ensure forwarder is closed + _, ok := <-forwarder.Err() + assert.False(t, ok) + assert.True(t, mockedSub.unsubscribed) + }) +} + func TestSubscriptionErrorWrapper(t *testing.T) { t.Parallel() newSubscriptionErrorWrapper := func(t *testing.T, sub commontypes.Subscription, errorPrefix string) ethereum.Subscription { @@ -145,17 +172,6 @@ func TestSubscriptionErrorWrapper(t *testing.T) { // subsequence unsubscribe does not causes panic wrapper.Unsubscribe() }) - t.Run("Unsubscribe interrupts error delivery", func(t *testing.T) { - t.Parallel() - sub := NewMockSubscription() - const prefix = "RPC returned error" - wrapper := newSubscriptionErrorWrapper(t, sub, prefix) - sub.Errors <- fmt.Errorf("error") - - wrapper.Unsubscribe() - _, ok := <-wrapper.Err() - assert.False(t, ok) - }) t.Run("Successfully wraps error", func(t *testing.T) { t.Parallel() sub := NewMockSubscription() diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index f6b84e4655..eca8ea6062 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -17,6 +17,7 @@ const ( ChainMantle ChainType = "mantle" ChainMetis ChainType = "metis" ChainOptimismBedrock ChainType = "optimismBedrock" + ChainSei ChainType = "sei" ChainScroll ChainType = "scroll" ChainWeMix ChainType = "wemix" ChainXLayer ChainType = "xlayer" @@ -38,7 +39,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainSei, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: return true } return false @@ -64,6 +65,8 @@ func FromSlug(slug string) ChainType { return ChainMetis case "optimismBedrock": return ChainOptimismBedrock + case "sei": + return ChainSei case "scroll": return ChainScroll case "wemix": @@ -135,6 +138,7 @@ var ErrInvalid = fmt.Errorf("must be one of %s or omitted", strings.Join([]strin string(ChainMantle), string(ChainMetis), string(ChainOptimismBedrock), + string(ChainSei), string(ChainScroll), string(ChainWeMix), string(ChainXLayer), diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 9caf37ccf2..163a6de05e 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1357,7 +1357,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo @@ -1370,7 +1370,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3: 3 errors: diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index 0d0dc45c0d..ac71d8855d 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -400,7 +400,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight // care about the block height; we have no way of getting the L1 block // height anyway return 0, nil - case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: + case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index fa44d79c2d..34f6f5c908 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -22,7 +22,7 @@ func NewBlockTranslator(cfg Config, client evmclient.Client, lggr logger.Logger) switch cfg.ChainType() { case chaintype.ChainArbitrum: return NewArbitrumBlockTranslator(client, lggr) - case "", chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainKroma, chaintype.ChainMetis, chaintype.ChainOptimismBedrock, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: + case "", chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainKroma, chaintype.ChainMetis, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: fallthrough default: return &l1BlockTranslator{} From d323f98178d614e10668cc3ba5b631d2c855edc7 Mon Sep 17 00:00:00 2001 From: nogo <110664798+0xnogo@users.noreply.github.com> Date: Thu, 9 Jan 2025 02:19:45 +0400 Subject: [PATCH 5/6] Increase token data worker settings and cache duration (#1583) Adjust the number of token data workers and increase the cache duration for token data to improve performance and efficiency. Co-authored-by: dimitris Co-authored-by: Sishir Giri --- core/services/ocr2/plugins/ccip/ccipexec/initializers.go | 6 ++++-- core/services/ocr2/plugins/ccip/tokendata/bgworker.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go index d2d3d32ce9..3ad9c94dc7 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/initializers.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/initializers.go @@ -43,7 +43,9 @@ var ( // 5s for token data worker timeout is a reasonable default. tokenDataWorkerTimeout = 5 * time.Second // tokenDataWorkerNumWorkers is the number of workers that will be processing token data in parallel. - tokenDataWorkerNumWorkers = 5 + tokenDataWorkerNumWorkers = 10 + // expirationDur is the duration for which the token data will be cached. + expirationDurTokenData = 10 * time.Minute ) var defaultNewReportingPluginRetryConfig = ccipdata.RetryConfig{ @@ -169,7 +171,7 @@ func NewExecServices(ctx context.Context, lggr logger.Logger, jb job.Job, srcPro tokenDataProviders, tokenDataWorkerNumWorkers, tokenDataWorkerTimeout, - 2*tokenDataWorkerTimeout, + expirationDurTokenData, ) wrappedPluginFactory := NewExecutionReportingPluginFactory(ExecutionPluginStaticConfig{ diff --git a/core/services/ocr2/plugins/ccip/tokendata/bgworker.go b/core/services/ocr2/plugins/ccip/tokendata/bgworker.go index 1a74ab2305..5f8110b78c 100644 --- a/core/services/ocr2/plugins/ccip/tokendata/bgworker.go +++ b/core/services/ocr2/plugins/ccip/tokendata/bgworker.go @@ -60,7 +60,7 @@ func NewBackgroundWorker( return &BackgroundWorker{ tokenDataReaders: tokenDataReaders, numWorkers: numWorkers, - jobsChan: make(chan cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, numWorkers*100), + jobsChan: make(chan cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta, numWorkers*200), resultsCache: cache.New(expirationDur, expirationDur/2), timeoutDur: timeoutDur, From ecc1fb0918f536313efa4ba105e0a3e3bbf6e2c4 Mon Sep 17 00:00:00 2001 From: amit-momin <108959691+amit-momin@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:09:18 -0600 Subject: [PATCH 6/6] [Cherry Pick] Zircuit ZK Overflow detection (#1594) Support Zircuit fraud transactions detection and zk overflow detection, need dedup and unit test --------- Co-authored-by: Joe Huang Co-authored-by: stackman27 --- .changeset/chilled-plants-clap.md | 5 + .changeset/orange-humans-laugh.md | 5 + ccip/config/evm/Sei_Mainnet.toml | 18 +++ core/chains/evm/config/chaintype/chaintype.go | 6 +- .../config/toml/defaults/Zircuit_Mainnet.toml | 2 +- .../config/toml/defaults/Zircuit_Sepolia.toml | 2 +- core/chains/evm/gas/chain_specific.go | 2 +- core/chains/evm/gas/rollups/l1_oracle.go | 2 +- core/chains/evm/gas/rollups/op_l1_oracle.go | 2 +- core/chains/evm/txmgr/stuck_tx_detector.go | 110 ++++++++++++++- .../evm/txmgr/stuck_tx_detector_test.go | 128 ++++++++++++++++++ core/services/chainlink/config_test.go | 4 +- core/services/ocr/contract_tracker.go | 2 +- core/services/ocrcommon/block_translator.go | 2 +- docs/CONFIG.md | 4 +- 15 files changed, 277 insertions(+), 17 deletions(-) create mode 100644 .changeset/chilled-plants-clap.md create mode 100644 .changeset/orange-humans-laugh.md create mode 100644 ccip/config/evm/Sei_Mainnet.toml diff --git a/.changeset/chilled-plants-clap.md b/.changeset/chilled-plants-clap.md new file mode 100644 index 0000000000..2a23b0960f --- /dev/null +++ b/.changeset/chilled-plants-clap.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +The findBroadcastedAttempts in detectStuckTransactionsHeuristic can returns uninitialized struct that potentially cause nil pointer error. Changed the return type of findBroadcastedAttempts to be pointers and added nil pointer check. #bugfix diff --git a/.changeset/orange-humans-laugh.md b/.changeset/orange-humans-laugh.md new file mode 100644 index 0000000000..b9f8fa74f5 --- /dev/null +++ b/.changeset/orange-humans-laugh.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Support Zircuit fraud transactions detection and zk overflow detection #added diff --git a/ccip/config/evm/Sei_Mainnet.toml b/ccip/config/evm/Sei_Mainnet.toml new file mode 100644 index 0000000000..23977756ac --- /dev/null +++ b/ccip/config/evm/Sei_Mainnet.toml @@ -0,0 +1,18 @@ +ChainID = '1329' +ChainType = 'sei' +# finality_depth: instant +FinalityDepth = 10 +# block_time: ~0.4s, adding 1 second buffer +LogPollInterval = '2s' +# finality_depth * block_time / 60 secs = ~0.8 min (finality time) +NoNewFinalizedHeadsThreshold = '5m' +# "RPC node returned multiple missing blocks on query for block numbers [31592085 31592084] even though the WS subscription already sent us these blocks. It might help to increase EVM.RPCBlockQueryDelay (currently 1)" +RPCBlockQueryDelay = 5 + +[GasEstimator] +EIP1559DynamicFees = false +Mode = 'BlockHistory' +PriceMax = '3000 gwei' # recommended by ds&a + +[GasEstimator.BlockHistory] +BlockHistorySize = 200 diff --git a/core/chains/evm/config/chaintype/chaintype.go b/core/chains/evm/config/chaintype/chaintype.go index eca8ea6062..be3afa0ea6 100644 --- a/core/chains/evm/config/chaintype/chaintype.go +++ b/core/chains/evm/config/chaintype/chaintype.go @@ -23,6 +23,7 @@ const ( ChainXLayer ChainType = "xlayer" ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" + ChainZircuit ChainType = "zircuit" ) // IsL2 returns true if this chain is a Layer 2 chain. Notably: @@ -39,7 +40,7 @@ func (c ChainType) IsL2() bool { func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainSei, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainAstar, ChainCelo, ChainGnosis, ChainHedera, ChainKroma, ChainMantle, ChainMetis, ChainOptimismBedrock, ChainSei, ChainScroll, ChainWeMix, ChainXLayer, ChainZkEvm, ChainZkSync, ChainZircuit: return true } return false @@ -77,6 +78,8 @@ func FromSlug(slug string) ChainType { return ChainZkEvm case "zksync": return ChainZkSync + case "zircuit": + return ChainZircuit default: return ChainType(slug) } @@ -144,4 +147,5 @@ var ErrInvalid = fmt.Errorf("must be one of %s or omitted", strings.Join([]strin string(ChainXLayer), string(ChainZkEvm), string(ChainZkSync), + string(ChainZircuit), }, ", ")) diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml index 9ec1dd0d6a..8d45d9c4ef 100644 --- a/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Zircuit_Mainnet.toml @@ -1,5 +1,5 @@ ChainID = '48900' -ChainType = 'optimismBedrock' +ChainType = 'zircuit' FinalityTagEnabled = true FinalityDepth = 1000 LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a' diff --git a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml index d439d98162..538aa54a4d 100644 --- a/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml +++ b/core/chains/evm/config/toml/defaults/Zircuit_Sepolia.toml @@ -1,5 +1,5 @@ ChainID = '48899' -ChainType = 'optimismBedrock' +ChainType = 'zircuit' FinalityTagEnabled = true FinalityDepth = 1000 LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD' diff --git a/core/chains/evm/gas/chain_specific.go b/core/chains/evm/gas/chain_specific.go index bc227441e1..1c7c834d52 100644 --- a/core/chains/evm/gas/chain_specific.go +++ b/core/chains/evm/gas/chain_specific.go @@ -19,7 +19,7 @@ func chainSpecificIsUsable(tx evmtypes.Transaction, baseFee *assets.Wei, chainTy return false } } - if chainType == chaintype.ChainOptimismBedrock || chainType == chaintype.ChainKroma || chainType == chaintype.ChainScroll { + if chainType == chaintype.ChainOptimismBedrock || chainType == chaintype.ChainKroma || chainType == chaintype.ChainScroll || chainType == chaintype.ChainZircuit { // This is a special deposit transaction type introduced in Bedrock upgrade. // This is a system transaction that it will occur at least one time per block. // We should discard this type before even processing it to avoid flooding the diff --git a/core/chains/evm/gas/rollups/l1_oracle.go b/core/chains/evm/gas/rollups/l1_oracle.go index 4195175598..d3fd5d7538 100644 --- a/core/chains/evm/gas/rollups/l1_oracle.go +++ b/core/chains/evm/gas/rollups/l1_oracle.go @@ -57,7 +57,7 @@ func NewL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chai var l1Oracle L1Oracle var err error switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle: + case chaintype.ChainOptimismBedrock, chaintype.ChainKroma, chaintype.ChainScroll, chaintype.ChainMantle, chaintype.ChainZircuit: l1Oracle, err = NewOpStackL1GasOracle(lggr, ethClient, chainType) case chaintype.ChainArbitrum: l1Oracle, err = NewArbitrumL1GasOracle(lggr, ethClient) diff --git a/core/chains/evm/gas/rollups/op_l1_oracle.go b/core/chains/evm/gas/rollups/op_l1_oracle.go index 3c55844f40..a7e5e622ec 100644 --- a/core/chains/evm/gas/rollups/op_l1_oracle.go +++ b/core/chains/evm/gas/rollups/op_l1_oracle.go @@ -101,7 +101,7 @@ const ( func NewOpStackL1GasOracle(lggr logger.Logger, ethClient l1OracleClient, chainType chaintype.ChainType) (*optimismL1Oracle, error) { var precompileAddress string switch chainType { - case chaintype.ChainOptimismBedrock, chaintype.ChainMantle: + case chaintype.ChainOptimismBedrock, chaintype.ChainMantle, chaintype.ChainZircuit: precompileAddress = OPGasOracleAddress case chaintype.ChainKroma: precompileAddress = KromaGasOracleAddress diff --git a/core/chains/evm/txmgr/stuck_tx_detector.go b/core/chains/evm/txmgr/stuck_tx_detector.go index 26d7643c15..303cb47a95 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector.go +++ b/core/chains/evm/txmgr/stuck_tx_detector.go @@ -130,6 +130,8 @@ func (d *stuckTxDetector) DetectStuckTransactions(ctx context.Context, enabledAd return d.detectStuckTransactionsScroll(ctx, txs) case chaintype.ChainZkEvm, chaintype.ChainXLayer: return d.detectStuckTransactionsZkEVM(ctx, txs) + case chaintype.ChainZircuit: + return d.detectStuckTransactionsZircuit(ctx, txs, blockNum) default: return d.detectStuckTransactionsHeuristic(ctx, txs, blockNum) } @@ -215,10 +217,25 @@ func (d *stuckTxDetector) detectStuckTransactionsHeuristic(ctx context.Context, } // Tx attempts are loaded from newest to oldest oldestBroadcastAttempt, newestBroadcastAttempt, broadcastedAttemptsCount := findBroadcastedAttempts(tx) + d.lggr.Debugf("found %d broadcasted attempts for tx id %d in stuck transaction heuristic", broadcastedAttemptsCount, tx.ID) + + // attempt shouldn't be nil as we validated in FindUnconfirmedTxWithLowestNonce, but added anyway for a "belts and braces" approach + if oldestBroadcastAttempt == nil || newestBroadcastAttempt == nil { + d.lggr.Debugw("failed to find broadcast attempt for tx in stuck transaction heuristic", "tx", tx) + continue + } + + // sanity check + if oldestBroadcastAttempt.BroadcastBeforeBlockNum == nil { + d.lggr.Debugw("BroadcastBeforeBlockNum was not set for broadcast attempt in stuck transaction heuristic", "attempt", oldestBroadcastAttempt) + continue + } + // 2. Check if Threshold amount of blocks have passed since the oldest attempt's broadcast block num if *oldestBroadcastAttempt.BroadcastBeforeBlockNum > blockNum-int64(*d.cfg.Threshold()) { continue } + // 3. Check if the transaction has at least MinAttempts amount of broadcasted attempts if broadcastedAttemptsCount < *d.cfg.MinAttempts() { continue @@ -244,17 +261,18 @@ func compareGasFees(attemptGas gas.EvmFee, marketGas gas.EvmFee) int { } // Assumes tx attempts are loaded newest to oldest -func findBroadcastedAttempts(tx Tx) (oldestAttempt TxAttempt, newestAttempt TxAttempt, broadcastedCount uint32) { +func findBroadcastedAttempts(tx Tx) (oldestAttempt *TxAttempt, newestAttempt *TxAttempt, broadcastedCount uint32) { foundNewest := false - for _, attempt := range tx.TxAttempts { + for i := range tx.TxAttempts { + attempt := tx.TxAttempts[i] if attempt.State != types.TxAttemptBroadcast { continue } if !foundNewest { - newestAttempt = attempt + newestAttempt = &attempt foundNewest = true } - oldestAttempt = attempt + oldestAttempt = &attempt broadcastedCount++ } return @@ -270,6 +288,10 @@ type scrollResponse struct { Data map[string]int `json:"data"` } +type zircuitResponse struct { + IsQuarantined bool `json:"isQuarantined"` +} + // Uses the custom Scroll skipped endpoint to determine an overflow transaction func (d *stuckTxDetector) detectStuckTransactionsScroll(ctx context.Context, txs []Tx) ([]Tx, error) { if d.cfg.DetectionApiUrl() == nil { @@ -336,6 +358,84 @@ func (d *stuckTxDetector) detectStuckTransactionsScroll(ctx context.Context, txs return stuckTx, nil } +// return fraud and overflow transactions +func (d *stuckTxDetector) detectStuckTransactionsZircuit(ctx context.Context, txs []Tx, blockNum int64) ([]Tx, error) { + var err error + var fraudTxs, stuckTxs []Tx + fraudTxs, err = d.detectFraudTransactionsZircuit(ctx, txs) + if err != nil { + d.lggr.Errorf("Failed to detect zircuit fraud transactions: %v", err) + } + + stuckTxs, err = d.detectStuckTransactionsHeuristic(ctx, txs, blockNum) + if err != nil { + return txs, err + } + + // prevent duplicate transactions from the fraudTxs and stuckTxs with a map + uniqueTxs := make(map[int64]Tx) + for _, tx := range fraudTxs { + uniqueTxs[tx.ID] = tx + } + + for _, tx := range stuckTxs { + uniqueTxs[tx.ID] = tx + } + + var combinedStuckTxs []Tx + for _, tx := range uniqueTxs { + combinedStuckTxs = append(combinedStuckTxs, tx) + } + + return combinedStuckTxs, nil +} + +// Uses zirc_isQuarantined to check whether the transactions are considered as malicious by the sequencer and +// preventing their inclusion into a block +func (d *stuckTxDetector) detectFraudTransactionsZircuit(ctx context.Context, txs []Tx) ([]Tx, error) { + txReqs := make([]rpc.BatchElem, len(txs)) + txHashMap := make(map[common.Hash]Tx) + txRes := make([]*zircuitResponse, len(txs)) + + // Build batch request elems to perform + for i, tx := range txs { + latestAttemptHash := tx.TxAttempts[0].Hash + var result zircuitResponse + txReqs[i] = rpc.BatchElem{ + Method: "zirc_isQuarantined", + Args: []interface{}{ + latestAttemptHash, + }, + Result: &result, + } + txHashMap[latestAttemptHash] = tx + txRes[i] = &result + } + + // Send batch request + err := d.chainClient.BatchCallContext(ctx, txReqs) + if err != nil { + return nil, fmt.Errorf("failed to check Quarantine transactions in batch: %w", err) + } + + // If the result is not nil, the fraud transaction is flagged as quarantined + var fraudTxs []Tx + for i, req := range txReqs { + txHash := req.Args[0].(common.Hash) + if req.Error != nil { + d.lggr.Errorf("failed to check fraud transaction by hash (%s): %v", txHash.String(), req.Error) + continue + } + + result := txRes[i] + if result != nil && result.IsQuarantined { + tx := txHashMap[txHash] + fraudTxs = append(fraudTxs, tx) + } + } + return fraudTxs, nil +} + // Uses eth_getTransactionByHash to detect that a transaction has been discarded due to overflow // Currently only used by zkEVM but if other chains follow the same behavior in the future func (d *stuckTxDetector) detectStuckTransactionsZkEVM(ctx context.Context, txs []Tx) ([]Tx, error) { @@ -390,7 +490,7 @@ func (d *stuckTxDetector) detectStuckTransactionsZkEVM(ctx context.Context, txs for i, req := range txReqs { txHash := req.Args[0].(common.Hash) if req.Error != nil { - d.lggr.Debugf("failed to get transaction by hash (%s): %v", txHash.String(), req.Error) + d.lggr.Errorf("failed to get transaction by hash (%s): %v", txHash.String(), req.Error) continue } result := *txRes[i] diff --git a/core/chains/evm/txmgr/stuck_tx_detector_test.go b/core/chains/evm/txmgr/stuck_tx_detector_test.go index eb22830ef3..187e16e913 100644 --- a/core/chains/evm/txmgr/stuck_tx_detector_test.go +++ b/core/chains/evm/txmgr/stuck_tx_detector_test.go @@ -278,6 +278,117 @@ func TestStuckTxDetector_DetectStuckTransactionsHeuristic(t *testing.T) { require.NoError(t, err) require.Len(t, txs, 1) }) + + t.Run("detects stuck transaction with empty BroadcastBeforeBlockNum in attempts will be skipped without panic", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + enabledAddresses := []common.Address{fromAddress} + mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t, txStore, 0, fromAddress, autoPurgeMinAttempts, marketGasPrice.Add(oneGwei)) + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, enabledAddresses, blockNum) + require.NoError(t, err) + require.Len(t, txs, 0) + }) +} + +func TestStuckTxDetector_DetectStuckTransactionsZircuit(t *testing.T) { + t.Parallel() + + db := pgtest.NewSqlxDB(t) + txStore := cltest.NewTestTxStore(t, db) + ethKeyStore := cltest.NewKeyStore(t, db).Eth() + ctx := tests.Context(t) + + lggr := logger.Test(t) + feeEstimator := gasmocks.NewEvmFeeEstimator(t) + // Return 10 gwei as market gas price + marketGasPrice := tenGwei + fee := gas.EvmFee{Legacy: marketGasPrice} + feeEstimator.On("GetFee", mock.Anything, []byte{}, uint64(0), mock.Anything, mock.Anything, mock.Anything).Return(fee, uint64(0), nil) + ethClient := testutils.NewEthClientMockWithDefaultChain(t) + autoPurgeThreshold := uint32(5) + autoPurgeMinAttempts := uint32(3) + autoPurgeCfg := testAutoPurgeConfig{ + enabled: true, // Enable auto-purge feature for testing + threshold: &autoPurgeThreshold, + minAttempts: &autoPurgeMinAttempts, + } + blockNum := int64(100) + stuckTxDetector := txmgr.NewStuckTxDetector(lggr, testutils.FixtureChainID, chaintype.ChainZircuit, assets.NewWei(assets.NewEth(100).ToInt()), autoPurgeCfg, feeEstimator, txStore, ethClient) + + t.Run("returns empty list if no fraud or stuck transactions identified", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + tx := mustInsertUnconfirmedTxWithBroadcastAttempts(t, txStore, 0, fromAddress, 1, blockNum, tenGwei) + attempts := tx.TxAttempts[0] + // Request still returns transaction by hash, transaction not discarded by network and not considered stuck + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempts.Hash, "zirc_isQuarantined") + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + resp, err := json.Marshal(struct { + IsQuarantined bool `json:"isQuarantined"` + }{IsQuarantined: false}) + require.NoError(t, err) + elems[0].Error = json.Unmarshal(resp, elems[0].Result) + }).Once() + + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, []common.Address{fromAddress}, blockNum) + require.NoError(t, err) + require.Len(t, txs, 0) + }) + + t.Run("returns fraud transactions identified", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + tx := mustInsertUnconfirmedTxWithBroadcastAttempts(t, txStore, 0, fromAddress, 1, blockNum, tenGwei) + attempts := tx.TxAttempts[0] + // Request still returns transaction by hash, transaction not discarded by network and not considered stuck + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempts.Hash, "zirc_isQuarantined") + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + resp, err := json.Marshal(struct { + IsQuarantined bool `json:"isQuarantined"` + }{IsQuarantined: true}) + require.NoError(t, err) + elems[0].Error = json.Unmarshal(resp, elems[0].Result) + }).Once() + + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, []common.Address{fromAddress}, blockNum) + require.NoError(t, err) + require.Len(t, txs, 1) + }) + + t.Run("returns the transaction only once if it's identified as both fraud and stuck", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + tx := mustInsertUnconfirmedTxWithBroadcastAttempts(t, txStore, 0, fromAddress, autoPurgeMinAttempts, blockNum-int64(autoPurgeThreshold)+int64(autoPurgeMinAttempts-1), marketGasPrice.Add(oneGwei)) + attempts := tx.TxAttempts[0] + + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempts.Hash, "zirc_isQuarantined") + })).Return(nil).Run(func(args mock.Arguments) { + elems := args.Get(1).([]rpc.BatchElem) + resp, err := json.Marshal(struct { + IsQuarantined bool `json:"isQuarantined"` + }{IsQuarantined: true}) + require.NoError(t, err) + elems[0].Error = json.Unmarshal(resp, elems[0].Result) + }).Once() + + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, []common.Address{fromAddress}, blockNum) + require.NoError(t, err) + require.Len(t, txs, 1) + }) + t.Run("returns the stuck tx even if failed to detect fraud tx", func(t *testing.T) { + _, fromAddress := cltest.MustInsertRandomKey(t, ethKeyStore) + tx := mustInsertUnconfirmedTxWithBroadcastAttempts(t, txStore, 0, fromAddress, autoPurgeMinAttempts, blockNum-int64(autoPurgeThreshold)+int64(autoPurgeMinAttempts-1), marketGasPrice.Add(oneGwei)) + attempts := tx.TxAttempts[0] + + ethClient.On("BatchCallContext", mock.Anything, mock.MatchedBy(func(b []rpc.BatchElem) bool { + return len(b) == 1 && cltest.BatchElemMatchesParams(b[0], attempts.Hash, "zirc_isQuarantined") + })).Return(fmt.Errorf("failed to fetch rpc")) + + txs, err := stuckTxDetector.DetectStuckTransactions(ctx, []common.Address{fromAddress}, blockNum) + require.NoError(t, err) + require.Len(t, txs, 1) + }) } func TestStuckTxDetector_DetectStuckTransactionsZkEVM(t *testing.T) { @@ -435,6 +546,23 @@ func mustInsertUnconfirmedTxWithBroadcastAttempts(t *testing.T, txStore txmgr.Te return etx } +// helper function for edge case where broadcast attempt contains empty pointer +func mustInsertUnconfirmedTxWithBroadcastAttemptsContainsEmptyBroadcastBeforeBlockNum(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, numAttempts uint32, latestGasPrice *assets.Wei) txmgr.Tx { + ctx := tests.Context(t) + etx := cltest.MustInsertUnconfirmedEthTx(t, txStore, nonce, fromAddress) + // Insert attempts from oldest to newest + for i := int64(numAttempts - 1); i >= 0; i-- { + attempt := cltest.NewLegacyEthTxAttempt(t, etx.ID) + attempt.State = txmgrtypes.TxAttemptBroadcast + attempt.BroadcastBeforeBlockNum = nil + attempt.TxFee = gas.EvmFee{Legacy: latestGasPrice.Sub(assets.NewWeiI(i))} + require.NoError(t, txStore.InsertTxAttempt(ctx, &attempt)) + } + etx, err := txStore.FindTxWithAttempts(ctx, etx.ID) + require.NoError(t, err) + return etx +} + func mustInsertFatalErrorTxWithError(t *testing.T, txStore txmgr.TestEvmTxStore, nonce int64, fromAddress common.Address, blockNum int64) txmgr.Tx { etx := cltest.NewEthTx(fromAddress) etx.State = txmgrcommon.TxFatalError diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 163a6de05e..ba750eed7c 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1357,7 +1357,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 10 errors: - ChainType: invalid value (Foo): must not be set with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync, zircuit or omitted - HeadTracker.HistoryDepth: invalid value (30): must be greater than or equal to FinalizedBlockOffset - GasEstimator.BumpThreshold: invalid value (0): cannot be 0 if auto-purge feature is enabled for Foo - Transactions.AutoPurge.Threshold: missing: needs to be set if auto-purge feature is enabled for Foo @@ -1370,7 +1370,7 @@ func TestConfig_Validate(t *testing.T) { - 2: 5 errors: - ChainType: invalid value (Arbitrum): only "optimismBedrock" can be used with this chain id - Nodes: missing: must have at least one node - - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, astar, celo, gnosis, hedera, kroma, mantle, metis, optimismBedrock, sei, scroll, wemix, xlayer, zkevm, zksync, zircuit or omitted - FinalityDepth: invalid value (0): must be greater than or equal to 1 - MinIncomingConfirmations: invalid value (0): must be greater than or equal to 1 - 3: 3 errors: diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index ac71d8855d..4ccb8ae8ed 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -400,7 +400,7 @@ func (t *OCRContractTracker) LatestBlockHeight(ctx context.Context) (blockheight // care about the block height; we have no way of getting the L1 block // height anyway return 0, nil - case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: + case "", chaintype.ChainArbitrum, chaintype.ChainAstar, chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainHedera, chaintype.ChainKroma, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync, chaintype.ChainZircuit: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index 34f6f5c908..8a755f767b 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -22,7 +22,7 @@ func NewBlockTranslator(cfg Config, client evmclient.Client, lggr logger.Logger) switch cfg.ChainType() { case chaintype.ChainArbitrum: return NewArbitrumBlockTranslator(client, lggr) - case "", chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainKroma, chaintype.ChainMetis, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync: + case "", chaintype.ChainCelo, chaintype.ChainGnosis, chaintype.ChainKroma, chaintype.ChainMetis, chaintype.ChainOptimismBedrock, chaintype.ChainSei, chaintype.ChainScroll, chaintype.ChainWeMix, chaintype.ChainXLayer, chaintype.ChainZkEvm, chaintype.ChainZkSync, chaintype.ChainZircuit: fallthrough default: return &l1BlockTranslator{} diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 97744f2557..ff9ab83754 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -7846,7 +7846,7 @@ GasLimitDefault = 400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'optimismBedrock' +ChainType = 'zircuit' FinalityDepth = 1000 FinalityTagEnabled = true LinkContractAddress = '0xDEE94506570cA186BC1e3516fCf4fd719C312cCD' @@ -7952,7 +7952,7 @@ GasLimitDefault = 400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'optimismBedrock' +ChainType = 'zircuit' FinalityDepth = 1000 FinalityTagEnabled = true LinkContractAddress = '0x5D6d033B4FbD2190D99D930719fAbAcB64d2439a'