From 5a87d87eb7c01bdae71738e27933de9068d37d86 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 19 Mar 2024 21:55:04 -0500 Subject: [PATCH 01/23] Added tx simulator to maintain chain specific simulation methods --- common/config/chaintype.go | 4 +- common/txmgr/txmgr.go | 11 ++ common/txmgr/types/client.go | 1 + .../toml/defaults/Polygon_Zkevm_Cardona.toml | 1 + .../toml/defaults/Polygon_Zkevm_Goerli.toml | 1 + .../toml/defaults/Polygon_Zkevm_Mainnet.toml | 1 + core/chains/evm/txmgr/broadcaster_test.go | 38 +++---- core/chains/evm/txmgr/builder.go | 7 +- core/chains/evm/txmgr/client.go | 13 ++- core/chains/evm/txmgr/confirmer_test.go | 10 +- core/chains/evm/txmgr/evm_tx_store_test.go | 2 +- core/chains/evm/txmgr/nonce_tracker_test.go | 12 +- core/chains/evm/txmgr/resender_test.go | 6 +- core/chains/evm/txmgr/test_helpers.go | 2 + core/chains/evm/txmgr/tx_simulator.go | 105 ++++++++++++++++++ core/cmd/shell_local.go | 2 +- core/services/vrf/v2/integration_v2_test.go | 2 +- core/services/vrf/v2/listener_v2_test.go | 2 +- 18 files changed, 178 insertions(+), 42 deletions(-) create mode 100644 core/chains/evm/txmgr/tx_simulator.go diff --git a/common/config/chaintype.go b/common/config/chaintype.go index 29bab2a91e2..d519ce34216 100644 --- a/common/config/chaintype.go +++ b/common/config/chaintype.go @@ -19,6 +19,7 @@ const ( ChainScroll ChainType = "scroll" ChainWeMix ChainType = "wemix" ChainXDai ChainType = "xdai" // Deprecated: use ChainGnosis instead + ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" ) @@ -31,13 +32,14 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainOptimismBedrock), string(ChainScroll), string(ChainWeMix), + string(ChainZkEvm), string(ChainZkSync), }, ", ")) // IsValid returns true if the ChainType value is known or empty. func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainCelo, ChainGnosis, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXDai, ChainZkSync: + case "", ChainArbitrum, ChainCelo, ChainGnosis, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXDai, ChainZkEvm, ChainZkSync: return true } return false diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index 74d218915d9..d6cbc38c3c3 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -84,6 +84,7 @@ type Txm[ services.StateMachine logger logger.SugaredLogger txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] config txmgrtypes.TransactionManagerChainConfig txConfig txmgrtypes.TransactionManagerTransactionsConfig keyStore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] @@ -139,6 +140,7 @@ func NewTxm[ confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], + client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], ) *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { b := Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ logger: logger.Sugared(lggr), @@ -159,6 +161,7 @@ func NewTxm[ confirmer: confirmer, resender: resender, tracker: tracker, + client: client, } if txCfg.ResendAfterThreshold() <= 0 { @@ -603,6 +606,10 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTrans return b.txStore.CountTransactionsByState(ctx, state, b.chainID) } +func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { + return b.client.SimulateTransaction(ctx, from, to, data) +} + type NullTxManager[ CHAIN_ID types.ID, HEAD types.Head[BLOCK_HASH], @@ -682,6 +689,10 @@ func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Cou return count, errors.New(n.ErrMsg) } +func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { + return nil +} + func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) pruneQueueAndCreateTxn( ctx context.Context, txRequest txmgrtypes.TxRequest[ADDR, TX_HASH], diff --git a/common/txmgr/types/client.go b/common/txmgr/types/client.go index 759b15d6162..c4eebcb396c 100644 --- a/common/txmgr/types/client.go +++ b/common/txmgr/types/client.go @@ -73,6 +73,7 @@ type TransactionClient[ attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], blockNumber *big.Int, ) (rpcErr fmt.Stringer, extractErr error) + SimulateTransaction(ctx context.Context, from ADDR, to ADDR, data []byte) error } // ChainClient contains the interfaces for reading chain parameters (chain id, sequences, etc) diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml index 02cc322f19e..0890554142e 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml @@ -1,4 +1,5 @@ ChainID = '2442' +ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '12m' MinIncomingConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml index a259e4766f8..6a9b47190fd 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml @@ -1,4 +1,5 @@ ChainID = '1442' +ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '12m' MinIncomingConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml index e8833bc7312..79e0cb0fce5 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml @@ -1,4 +1,5 @@ ChainID = '1101' +ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '6m' MinIncomingConfirmations = 1 diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index cb40fecc55f..2f274692dad 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -67,7 +67,7 @@ func NewTestEthBroadcaster( return gas.NewFixedPriceEstimator(config.EVM().GasEstimator(), ge.BlockHistory(), lggr) }, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, keyStore, estimator) - ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync) + ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync) // Mark instance as test ethBroadcaster.XXXTestDisableUnstartedTxAutoProcessing() @@ -84,7 +84,7 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) - txmClient := txmgr.NewEvmTxmClient(ethClient) + txmClient := txmgr.NewEvmTxmClient(ethClient, "") ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) eb := txmgr.NewEvmBroadcaster( txStore, @@ -143,7 +143,7 @@ func TestEthBroadcaster_LoadNextSequenceMapFailure_StartupSuccess(t *testing.T) estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), errors.New("Getting on-chain nonce failed")) - txmClient := txmgr.NewEvmTxmClient(ethClient) + txmClient := txmgr.NewEvmTxmClient(ethClient, "") eb := txmgr.NewEvmBroadcaster( txStore, txmClient, @@ -179,7 +179,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) toAddress := gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411") @@ -380,7 +380,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { }) evmcfg = evmtest.NewChainScopedConfig(t, cfg) ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(1), nil).Once() - nonceTracker = txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker = txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb = NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) t.Run("sends transactions with type 0x2 in EIP-1559 mode", func(t *testing.T) { @@ -529,7 +529,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) checkerFactory := &testCheckerFactory{} ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) checker := txmgr.TransmitCheckerSpec{ @@ -621,7 +621,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi <-chBlock }).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil) - txmClient := txmgr.NewEvmTxmClient(ethClient) + txmClient := txmgr.NewEvmTxmClient(ethClient, "") eb := txmgr.NewEvmBroadcaster( txStore, txmClient, @@ -678,7 +678,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -759,7 +759,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved @@ -798,7 +798,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -835,7 +835,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -871,7 +871,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -909,7 +909,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -951,7 +951,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -1016,7 +1016,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - txmClient := txmgr.NewEvmTxmClient(ethClient) + txmClient := txmgr.NewEvmTxmClient(ethClient, "") nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) ctx := testutils.Context(t) @@ -1661,7 +1661,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, kst, evmcfg, &testCheckerFactory{}, false, nonceTracker) ctx := testutils.Context(t) _, err := nonceTracker.GetNextSequence(ctx, fromAddress) @@ -1710,7 +1710,7 @@ func TestEthBroadcaster_Trigger(t *testing.T) { ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) eb.Trigger(testutils.NewAddress()) @@ -1748,7 +1748,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - txmClient := txmgr.NewEvmTxmClient(ethClient) + txmClient := txmgr.NewEvmTxmClient(ethClient, "") eb := txmgr.NewEvmBroadcaster(txStore, txmClient, evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, lggr, checkerFactory, false) err := eb.Start(ctx) assert.NoError(t, err) @@ -1783,7 +1783,7 @@ func TestEthBroadcaster_NonceTracker_InProgressTx(t *testing.T) { // Tx with nonce 0 in DB will set local nonce map to value to 1 mustInsertInProgressEthTxWithAttempt(t, txStore, evmtypes.Nonce(inProgressTxNonce), fromAddress) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) // Check the local nonce map was set to 1 higher than in-progress tx nonce diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index ba5b54ad29f..1550728f0a1 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -50,7 +50,7 @@ func NewTxm( txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config - txmClient := NewEvmTxmClient(client) // wrap Evm specific client + txmClient := NewEvmTxmClient(client, chainConfig.ChainType()) // wrap Evm specific client chainID := txmClient.ConfiguredChainID() evmBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, txAttemptBuilder, lggr, checker, chainConfig.NonceAutoSync()) evmTracker := NewEvmTracker(txStore, keyStore, chainID, lggr) @@ -59,7 +59,7 @@ func NewTxm( if txConfig.ResendAfterThreshold() > 0 { evmResender = NewEvmResender(lggr, txStore, txmClient, evmTracker, keyStore, txmgr.DefaultResenderPollInterval, chainConfig, txConfig) } - txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker) + txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker, txmClient) return txm, nil } @@ -78,8 +78,9 @@ func NewEvmTxm( confirmer *Confirmer, resender *Resender, tracker *Tracker, + txmClient *evmTxmClient, ) *Txm { - return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker) + return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker, txmClient) } // NewEvmResender creates a new concrete EvmResender diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index 89375e2d017..e0eb04293c5 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils" commonclient "github.com/smartcontractkit/chainlink/v2/common/client" + "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -26,9 +27,10 @@ var _ TxmClient = (*evmTxmClient)(nil) type evmTxmClient struct { client client.Client + chainType config.ChainType } -func NewEvmTxmClient(c client.Client) *evmTxmClient { +func NewEvmTxmClient(c client.Client, chainType config.ChainType) *evmTxmClient { return &evmTxmClient{client: c} } @@ -181,3 +183,12 @@ func (c *evmTxmClient) CallContract(ctx context.Context, a TxAttempt, blockNumbe }, blockNumber) return client.ExtractRPCError(errCall) } + +func (c *evmTxmClient) SimulateTransaction(ctx context.Context, from common.Address, to common.Address, data []byte) error { + msg := ethereum.CallMsg{ + From: from, + To: &to, + Data: data, + } + return SimulateTransaction(ctx, c.client, c.chainType, msg) +} diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index 7307f5c35bb..6419a776fed 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -127,7 +127,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { ge := config.EVM().GasEstimator() feeEstimator := gas.NewWrappedEvmEstimator(lggr, newEst, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ethKeyStore, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ethKeyStore, txBuilder, lggr) ctx := testutils.Context(t) // Can't close unstarted instance @@ -1648,7 +1648,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() // Create confirmer with necessary state - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -1694,7 +1694,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -1872,7 +1872,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { var attempt1_2 txmgr.TxAttempt ethClient = evmtest.NewEthClientMockWithDefaultChain(t) - ec.XXXTestSetClient(txmgr.NewEvmTxmClient(ethClient)) + ec.XXXTestSetClient(txmgr.NewEvmTxmClient(ethClient, "")) t.Run("creates new attempt with higher gas price if transaction has an attempt older than threshold", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(20000000000) @@ -3127,7 +3127,7 @@ func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Cl return gas.NewFixedPriceEstimator(ge, ge.BlockHistory(), lggr) }, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) ec.SetResumeCallback(fn) servicetest.Run(t, ec) return ec diff --git a/core/chains/evm/txmgr/evm_tx_store_test.go b/core/chains/evm/txmgr/evm_tx_store_test.go index 9d0143d2eda..83d2381d007 100644 --- a/core/chains/evm/txmgr/evm_tx_store_test.go +++ b/core/chains/evm/txmgr/evm_tx_store_test.go @@ -1389,7 +1389,7 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { evmTxmCfg := txmgr.NewEvmTxmConfig(ccfg.EVM()) ec := evmtest.NewEthClientMockWithDefaultChain(t) txMgr := txmgr.NewEvmTxm(ec.ConfiguredChainID(), evmTxmCfg, ccfg.EVM().Transactions(), nil, logger.Test(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) err := txMgr.XXXTestAbandon(fromAddress) // mark transaction as abandoned require.NoError(t, err) diff --git a/core/chains/evm/txmgr/nonce_tracker_test.go b/core/chains/evm/txmgr/nonce_tracker_test.go index 95347c2d580..150a585c397 100644 --- a/core/chains/evm/txmgr/nonce_tracker_test.go +++ b/core/chains/evm/txmgr/nonce_tracker_test.go @@ -34,7 +34,7 @@ func TestNonceTracker_LoadSequenceMap(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr1 := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") addr2 := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781140") @@ -86,7 +86,7 @@ func TestNonceTracker_syncOnChain(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") @@ -143,7 +143,7 @@ func TestNonceTracker_SyncSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} @@ -190,7 +190,7 @@ func TestNonceTracker_GetNextSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") @@ -242,7 +242,7 @@ func TestNonceTracker_GenerateNextSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} @@ -273,7 +273,7 @@ func Test_SetNonceAfterInit(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} diff --git a/core/chains/evm/txmgr/resender_test.go b/core/chains/evm/txmgr/resender_test.go index 7cf575e8c8d..02fb4bd4696 100644 --- a/core/chains/evm/txmgr/resender_test.go +++ b/core/chains/evm/txmgr/resender_test.go @@ -65,7 +65,7 @@ func Test_EthResender_resendUnconfirmed(t *testing.T) { addr3TxesRawHex = append(addr3TxesRawHex, hexutil.Encode(etx.TxAttempts[0].SignedRawTx)) } - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) var resentHex = make(map[string]struct{}) ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(elems []rpc.BatchElem) bool { @@ -121,7 +121,7 @@ func Test_EthResender_alertUnconfirmed(t *testing.T) { txStore := cltest.NewTestTxStore(t, db, logCfg) originalBroadcastAt := time.Unix(1616509100, 0) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) t.Run("alerts only once for unconfirmed transaction attempt within the unconfirmedTxAlertDelay duration", func(t *testing.T) { _ = cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, int64(1), fromAddress, originalBroadcastAt) @@ -158,7 +158,7 @@ func Test_EthResender_Start(t *testing.T) { ctx := testutils.Context(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) originalBroadcastAt := time.Unix(1616509100, 0) etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress, originalBroadcastAt) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 8cb771943b0..5f88d71e899 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -59,6 +59,8 @@ func (e *TestEvmConfig) NonceAutoSync() bool { return true } func (e *TestEvmConfig) FinalityDepth() uint32 { return 42 } +func (e *TestEvmConfig) ChainType() commonconfig.ChainType { return "" } + type TestGasEstimatorConfig struct { bumpThreshold uint64 } diff --git a/core/chains/evm/txmgr/tx_simulator.go b/core/chains/evm/txmgr/tx_simulator.go new file mode 100644 index 00000000000..d6f0272734e --- /dev/null +++ b/core/chains/evm/txmgr/tx_simulator.go @@ -0,0 +1,105 @@ +package txmgr + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" +) + +const ErrOutOfCounters = "not enough keccak counters to continue the execution" + +// ZK Chain can return an overflow error based on the number of keccak hashes in the call +// This method allows a caller to determine if a tx would fail due to overflow error by simulating the transaction +// Used as an entry point for custom simulation across different chains +func SimulateTransaction(ctx context.Context, c client.Client, chainType config.ChainType, msg ethereum.CallMsg) error { + switch chainType { + case config.ChainZkEvm: + return simulateTransactionZkEvm(ctx, c, msg) + default: + return simulateTransactionDefault(ctx, c, msg) + } +} + +// eth_estimateGas returns out-of-counters (OOC) error if the transaction would result in an overflow +func simulateTransactionDefault(ctx context.Context, c client.Client, msg ethereum.CallMsg) error { + var result uint64 + errCall := c.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") + jsonErr, err := client.ExtractRPCError(errCall) + if err != nil { + return fmt.Errorf("failed to simulate tx: %w", err) + } + // Only return error if Zk OOC error is identified + if jsonErr.Message == ErrOutOfCounters { + return errors.New(ErrOutOfCounters) + } + return nil +} + +// zkEVM implemented a custom zkevm_estimateCounters method to detect if a transaction would result in an out-of-counters (OOC) error +func simulateTransactionZkEvm(ctx context.Context, c client.Client, msg ethereum.CallMsg) error { + var result struct { + countersUsed struct { + gasUsed int + usedKeccakHashes int + usedPoseidonHashes int + usedPoseidonPaddings int + usedMemAligns int + usedArithmetics int + usedBinaries int + usedSteps int + usedSHA256Hashes int + } + countersLimit struct { + maxGasUsed int + maxKeccakHashes int + maxPoseidonHashes int + maxPoseidonPaddings int + maxMemAligns int + maxArithmetics int + maxBinaries int + maxSteps int + maxSHA256Hashes int + } + oocError string + } + err := c.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") + if err != nil { + return fmt.Errorf("failed to simulate tx: %w", err) + } + if len(result.oocError) > 0 { + return errors.New(result.oocError) + } + return nil +} + +func toCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["input"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + if msg.GasPrice != nil { + arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) + } + if msg.GasFeeCap != nil { + arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap) + } + if msg.GasTipCap != nil { + arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap) + } + return arg +} diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 8391e3bc70b..edc99bb7985 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -635,7 +635,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), chain.Config().EVM().GasEstimator(), keyStore.Eth(), nil) cfg := txmgr.NewEvmTxmConfig(chain.Config().EVM()) feeCfg := txmgr.NewEvmTxmFeeConfig(chain.Config().EVM().GasEstimator()) - ec := txmgr.NewEvmConfirmer(orm, txmgr.NewEvmTxmClient(ethClient), cfg, feeCfg, chain.Config().EVM().Transactions(), chain.Config().Database(), keyStore.Eth(), txBuilder, chain.Logger()) + ec := txmgr.NewEvmConfirmer(orm, txmgr.NewEvmTxmClient(ethClient, ""), cfg, feeCfg, chain.Config().EVM().Transactions(), chain.Config().Database(), keyStore.Eth(), txBuilder, chain.Logger()) totalNonces := endingNonce - beginningNonce + 1 nonces := make([]evmtypes.Nonce, totalNonces) for i := int64(0); i < totalNonces; i++ { diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index 5812ee675cd..3b7cdfa0a62 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -144,7 +144,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M _, _, evmConfig := txmgr.MakeTestConfigs(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) return txm } diff --git a/core/services/vrf/v2/listener_v2_test.go b/core/services/vrf/v2/listener_v2_test.go index 661772a823b..465e3dcaca9 100644 --- a/core/services/vrf/v2/listener_v2_test.go +++ b/core/services/vrf/v2/listener_v2_test.go @@ -40,7 +40,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M ec := evmtest.NewEthClientMockWithDefaultChain(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil, nil) return txm } From bdb9fef3fd0e4301bd3d66e34c811c2a52a5e5d9 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 19 Mar 2024 21:57:13 -0500 Subject: [PATCH 02/23] Fixed linting --- common/config/chaintype.go | 2 +- common/txmgr/txmgr.go | 4 ++-- core/chains/evm/txmgr/builder.go | 6 +++--- core/chains/evm/txmgr/client.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/config/chaintype.go b/common/config/chaintype.go index d519ce34216..a14c90cb5a8 100644 --- a/common/config/chaintype.go +++ b/common/config/chaintype.go @@ -19,7 +19,7 @@ const ( ChainScroll ChainType = "scroll" ChainWeMix ChainType = "wemix" ChainXDai ChainType = "xdai" // Deprecated: use ChainGnosis instead - ChainZkEvm ChainType = "zkevm" + ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" ) diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index d6cbc38c3c3..8c44e31efc8 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -84,7 +84,7 @@ type Txm[ services.StateMachine logger logger.SugaredLogger txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] - client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] + client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] config txmgrtypes.TransactionManagerChainConfig txConfig txmgrtypes.TransactionManagerTransactionsConfig keyStore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] @@ -161,7 +161,7 @@ func NewTxm[ confirmer: confirmer, resender: resender, tracker: tracker, - client: client, + client: client, } if txCfg.ResendAfterThreshold() <= 0 { diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 1550728f0a1..a091694dc8b 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -48,9 +48,9 @@ func NewTxm( txAttemptBuilder := NewEvmTxAttemptBuilder(*client.ConfiguredChainID(), fCfg, keyStore, estimator) txStore := NewTxStore(sqlxDB, lggr, dbConfig) - txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config - feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config - txmClient := NewEvmTxmClient(client, chainConfig.ChainType()) // wrap Evm specific client + txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config + feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config + txmClient := NewEvmTxmClient(client, chainConfig.ChainType()) // wrap Evm specific client chainID := txmClient.ConfiguredChainID() evmBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, txAttemptBuilder, lggr, checker, chainConfig.NonceAutoSync()) evmTracker := NewEvmTracker(txStore, keyStore, chainID, lggr) diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index e0eb04293c5..decf1fa4468 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -26,7 +26,7 @@ import ( var _ TxmClient = (*evmTxmClient)(nil) type evmTxmClient struct { - client client.Client + client client.Client chainType config.ChainType } @@ -187,7 +187,7 @@ func (c *evmTxmClient) CallContract(ctx context.Context, a TxAttempt, blockNumbe func (c *evmTxmClient) SimulateTransaction(ctx context.Context, from common.Address, to common.Address, data []byte) error { msg := ethereum.CallMsg{ From: from, - To: &to, + To: &to, Data: data, } return SimulateTransaction(ctx, c.client, c.chainType, msg) From 46b13be3f81e5e7ff7043d97c0adeeaa9a28fcc7 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 12:01:21 -0500 Subject: [PATCH 03/23] Fixed linting and regenerated config docs --- common/txmgr/txmgr.go | 2 ++ core/services/ocr/contract_tracker.go | 2 +- docs/CONFIG.md | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index 8c44e31efc8..72b5c6912db 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -60,6 +60,8 @@ type TxManager[ FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (nullv4.Time, error) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (nullv4.Int, error) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (count uint32, err error) + // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time + CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error } type reset struct { diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index 4c3260511d5..5d74063d5b9 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -402,7 +402,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 "", config.ChainArbitrum, config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkSync: + case "", config.ChainArbitrum, config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkEvm, config.ChainZkSync: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 732ed762be3..8947afcfc5f 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -3714,6 +3714,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 @@ -4048,6 +4049,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 @@ -4215,6 +4217,7 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false +ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 From 141dfeae1c17f74b0e17f53a89cb22c8fc928400 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 12:13:16 -0500 Subject: [PATCH 04/23] Generated mock --- common/txmgr/mocks/tx_manager.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/common/txmgr/mocks/tx_manager.go b/common/txmgr/mocks/tx_manager.go index 45a3675aced..4e7de2ba3b8 100644 --- a/common/txmgr/mocks/tx_manager.go +++ b/common/txmgr/mocks/tx_manager.go @@ -23,6 +23,24 @@ type TxManager[CHAIN_ID types.ID, HEAD types.Head[BLOCK_HASH], ADDR types.Hashab mock.Mock } +// CheckTxValidity provides a mock function with given fields: ctx, from, to, data +func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { + ret := _m.Called(ctx, from, to, data) + + if len(ret) == 0 { + panic("no return value specified for CheckTxValidity") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR, []byte) error); ok { + r0 = rf(ctx, from, to, data) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Close() error { ret := _m.Called() From 48355541ed3d12b5390fa5cfb048d47118c30652 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 12:57:27 -0500 Subject: [PATCH 05/23] Fixed config tests --- core/services/chainlink/config_test.go | 4 ++-- core/services/ocrcommon/block_translator.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index 63ff68fa966..8e0f00d2e9d 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1215,7 +1215,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 6 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, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zkevm, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be equal to or greater than FinalityDepth - GasEstimator: 2 errors: - FeeCapDefault: invalid value (101 wei): must be equal to PriceMax (99 wei) since you are using FixedPrice estimation with gas bumping disabled in EIP1559 mode - PriceMax will be used as the FeeCap for transactions instead of FeeCapDefault @@ -1224,7 +1224,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, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, 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.Nodes: 5 errors: diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index bc7242a6019..101681e8d3b 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -21,7 +21,7 @@ func NewBlockTranslator(cfg Config, client evmclient.Client, lggr logger.Logger) switch cfg.ChainType() { case config.ChainArbitrum: return NewArbitrumBlockTranslator(client, lggr) - case "", config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainMetis, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkSync: + case "", config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainMetis, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkEvm, config.ChainZkSync: fallthrough default: return &l1BlockTranslator{} From befeaf0fe545e64d27ca680f6795367ca6cf82a0 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 16:59:36 -0500 Subject: [PATCH 06/23] Moved the tx simulator to the chain client --- common/txmgr/mocks/tx_manager.go | 18 --------- common/txmgr/txmgr.go | 10 ----- common/txmgr/types/client.go | 1 - core/chains/evm/client/chain_client.go | 12 +++++- core/chains/evm/client/client.go | 7 ++++ core/chains/evm/client/mocks/client.go | 18 +++++++++ core/chains/evm/client/null_client.go | 4 ++ .../evm/client/simulated_backend_client.go | 4 ++ .../evm/{txmgr => client}/tx_simulator.go | 23 ++++++----- core/chains/evm/txmgr/broadcaster_test.go | 38 +++++++++---------- core/chains/evm/txmgr/builder.go | 6 +-- core/chains/evm/txmgr/client.go | 15 +------- core/chains/evm/txmgr/confirmer_test.go | 10 ++--- core/chains/evm/txmgr/nonce_tracker_test.go | 12 +++--- core/chains/evm/txmgr/resender_test.go | 6 +-- core/cmd/shell_local.go | 2 +- 16 files changed, 96 insertions(+), 90 deletions(-) rename core/chains/evm/{txmgr => client}/tx_simulator.go (74%) diff --git a/common/txmgr/mocks/tx_manager.go b/common/txmgr/mocks/tx_manager.go index 4e7de2ba3b8..45a3675aced 100644 --- a/common/txmgr/mocks/tx_manager.go +++ b/common/txmgr/mocks/tx_manager.go @@ -23,24 +23,6 @@ type TxManager[CHAIN_ID types.ID, HEAD types.Head[BLOCK_HASH], ADDR types.Hashab mock.Mock } -// CheckTxValidity provides a mock function with given fields: ctx, from, to, data -func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { - ret := _m.Called(ctx, from, to, data) - - if len(ret) == 0 { - panic("no return value specified for CheckTxValidity") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, ADDR, ADDR, []byte) error); ok { - r0 = rf(ctx, from, to, data) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Close provides a mock function with given fields: func (_m *TxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Close() error { ret := _m.Called() diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index 72b5c6912db..8967beadacc 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -60,8 +60,6 @@ type TxManager[ FindEarliestUnconfirmedBroadcastTime(ctx context.Context) (nullv4.Time, error) FindEarliestUnconfirmedTxAttemptBlock(ctx context.Context) (nullv4.Int, error) CountTransactionsByState(ctx context.Context, state txmgrtypes.TxState) (count uint32, err error) - // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time - CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error } type reset struct { @@ -608,10 +606,6 @@ func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CountTrans return b.txStore.CountTransactionsByState(ctx, state, b.chainID) } -func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { - return b.client.SimulateTransaction(ctx, from, to, data) -} - type NullTxManager[ CHAIN_ID types.ID, HEAD types.Head[BLOCK_HASH], @@ -691,10 +685,6 @@ func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) Cou return count, errors.New(n.ErrMsg) } -func (n *NullTxManager[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) CheckTxValidity(ctx context.Context, from ADDR, to ADDR, data []byte) error { - return nil -} - func (b *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]) pruneQueueAndCreateTxn( ctx context.Context, txRequest txmgrtypes.TxRequest[ADDR, TX_HASH], diff --git a/common/txmgr/types/client.go b/common/txmgr/types/client.go index c4eebcb396c..759b15d6162 100644 --- a/common/txmgr/types/client.go +++ b/common/txmgr/types/client.go @@ -73,7 +73,6 @@ type TransactionClient[ attempt TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], blockNumber *big.Int, ) (rpcErr fmt.Stringer, extractErr error) - SimulateTransaction(ctx context.Context, from ADDR, to ADDR, data []byte) error } // ChainClient contains the interfaces for reading chain parameters (chain id, sequences, etc) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 1eb2347c474..8cf7eac665c 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -38,7 +38,8 @@ type chainClient struct { RPCClient, rpc.BatchElem, ] - logger logger.SugaredLogger + logger logger.SugaredLogger + chainType config.ChainType } func NewChainClient( @@ -269,3 +270,12 @@ func (c *chainClient) TransactionReceipt(ctx context.Context, txHash common.Hash func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, error) { return c.multiNode.LatestFinalizedBlock(ctx) } + +func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { + msg := ethereum.CallMsg{ + From: from, + To: &to, + Data: data, + } + return SimulateTransaction(ctx, c, c.chainType, msg) +} diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index ee33db97fd6..ae333d8d5a1 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -95,6 +95,9 @@ type Client interface { PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) IsL2() bool + + // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time + CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error } func ContextWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) { @@ -371,3 +374,7 @@ func (client *client) IsL2() bool { func (client *client) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, error) { return nil, pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") } + +func (client *client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { + return pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") +} diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index e6c9da1cbe9..5ddbcc827d8 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -236,6 +236,24 @@ func (_m *Client) ChainID() (*big.Int, error) { return r0, r1 } +// CheckTxValidity provides a mock function with given fields: ctx, from, to, data +func (_m *Client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { + ret := _m.Called(ctx, from, to, data) + + if len(ret) == 0 { + panic("no return value specified for CheckTxValidity") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Address, []byte) error); ok { + r0 = rf(ctx, from, to, data) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *Client) Close() { _m.Called() diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index e4bd7d1dd9a..4e505db88f1 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -231,3 +231,7 @@ func (nc *NullClient) IsL2() bool { func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, error) { return nil, nil } + +func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) error { + return nil +} diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 5750887126a..bc74a6bc066 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -774,6 +774,10 @@ func (c *SimulatedBackendClient) ethGetLogs(ctx context.Context, result interfac } } +func (c *SimulatedBackendClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { + return nil +} + func toCallMsg(params map[string]interface{}) ethereum.CallMsg { var callMsg ethereum.CallMsg toAddr, err := interfaceToAddress(params["to"]) diff --git a/core/chains/evm/txmgr/tx_simulator.go b/core/chains/evm/client/tx_simulator.go similarity index 74% rename from core/chains/evm/txmgr/tx_simulator.go rename to core/chains/evm/client/tx_simulator.go index d6f0272734e..d99a93d8074 100644 --- a/core/chains/evm/txmgr/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -1,4 +1,4 @@ -package txmgr +package client import ( "context" @@ -9,28 +9,31 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/smartcontractkit/chainlink/v2/common/config" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" ) const ErrOutOfCounters = "not enough keccak counters to continue the execution" +type SimulatorClient interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error +} + // ZK Chain can return an overflow error based on the number of keccak hashes in the call // This method allows a caller to determine if a tx would fail due to overflow error by simulating the transaction // Used as an entry point for custom simulation across different chains -func SimulateTransaction(ctx context.Context, c client.Client, chainType config.ChainType, msg ethereum.CallMsg) error { +func SimulateTransaction(ctx context.Context, client SimulatorClient, chainType config.ChainType, msg ethereum.CallMsg) error { switch chainType { case config.ChainZkEvm: - return simulateTransactionZkEvm(ctx, c, msg) + return simulateTransactionZkEvm(ctx, client, msg) default: - return simulateTransactionDefault(ctx, c, msg) + return simulateTransactionDefault(ctx, client, msg) } } // eth_estimateGas returns out-of-counters (OOC) error if the transaction would result in an overflow -func simulateTransactionDefault(ctx context.Context, c client.Client, msg ethereum.CallMsg) error { +func simulateTransactionDefault(ctx context.Context, client SimulatorClient, msg ethereum.CallMsg) error { var result uint64 - errCall := c.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") - jsonErr, err := client.ExtractRPCError(errCall) + errCall := client.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") + jsonErr, err := ExtractRPCError(errCall) if err != nil { return fmt.Errorf("failed to simulate tx: %w", err) } @@ -42,7 +45,7 @@ func simulateTransactionDefault(ctx context.Context, c client.Client, msg ethere } // zkEVM implemented a custom zkevm_estimateCounters method to detect if a transaction would result in an out-of-counters (OOC) error -func simulateTransactionZkEvm(ctx context.Context, c client.Client, msg ethereum.CallMsg) error { +func simulateTransactionZkEvm(ctx context.Context, client SimulatorClient, msg ethereum.CallMsg) error { var result struct { countersUsed struct { gasUsed int @@ -68,7 +71,7 @@ func simulateTransactionZkEvm(ctx context.Context, c client.Client, msg ethereum } oocError string } - err := c.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") + err := client.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") if err != nil { return fmt.Errorf("failed to simulate tx: %w", err) } diff --git a/core/chains/evm/txmgr/broadcaster_test.go b/core/chains/evm/txmgr/broadcaster_test.go index 52333336298..9547f7ee00e 100644 --- a/core/chains/evm/txmgr/broadcaster_test.go +++ b/core/chains/evm/txmgr/broadcaster_test.go @@ -67,7 +67,7 @@ func NewTestEthBroadcaster( return gas.NewFixedPriceEstimator(config.EVM().GasEstimator(), ge.BlockHistory(), lggr) }, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, keyStore, estimator) - ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync) + ethBroadcaster := txmgrcommon.NewBroadcaster(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(config.EVM().GasEstimator()), config.EVM().Transactions(), config.Database().Listener(), keyStore, txBuilder, nonceTracker, lggr, checkerFactory, nonceAutoSync) // Mark instance as test ethBroadcaster.XXXTestDisableUnstartedTxAutoProcessing() @@ -84,7 +84,7 @@ func TestEthBroadcaster_Lifecycle(t *testing.T) { cltest.MustInsertRandomKeyReturningState(t, ethKeyStore) estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) - txmClient := txmgr.NewEvmTxmClient(ethClient, "") + txmClient := txmgr.NewEvmTxmClient(ethClient) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), nil) eb := txmgr.NewEvmBroadcaster( txStore, @@ -143,7 +143,7 @@ func TestEthBroadcaster_LoadNextSequenceMapFailure_StartupSuccess(t *testing.T) estimator := gasmocks.NewEvmFeeEstimator(t) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), evmcfg.EVM().GasEstimator(), ethKeyStore, estimator) ethClient.On("PendingNonceAt", mock.Anything, mock.Anything).Return(uint64(0), errors.New("Getting on-chain nonce failed")) - txmClient := txmgr.NewEvmTxmClient(ethClient, "") + txmClient := txmgr.NewEvmTxmClient(ethClient) eb := txmgr.NewEvmBroadcaster( txStore, txmClient, @@ -180,7 +180,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) toAddress := gethCommon.HexToAddress("0x6C03DDA95a2AEd917EeCc6eddD4b9D16E6380411") @@ -381,7 +381,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success(t *testing.T) { }) evmcfg = evmtest.NewChainScopedConfig(t, cfg) ethClient.On("PendingNonceAt", mock.Anything, otherAddress).Return(uint64(1), nil).Once() - nonceTracker = txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker = txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) eb = NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) t.Run("sends transactions with type 0x2 in EIP-1559 mode", func(t *testing.T) { @@ -531,7 +531,7 @@ func TestEthBroadcaster_TransmitChecking(t *testing.T) { evmcfg := evmtest.NewChainScopedConfig(t, cfg) checkerFactory := &testCheckerFactory{} ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) checker := txmgr.TransmitCheckerSpec{ @@ -623,7 +623,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_OptimisticLockingOnEthTx(t *testi <-chBlock }).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil) - txmClient := txmgr.NewEvmTxmClient(ethClient, "") + txmClient := txmgr.NewEvmTxmClient(ethClient) eb := txmgr.NewEvmBroadcaster( txStore, txmClient, @@ -680,7 +680,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Success_WithMultiplier(t *testing ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) ethClient.On("SendTransactionReturnCode", mock.Anything, mock.MatchedBy(func(tx *gethTypes.Transaction) bool { @@ -762,7 +762,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved @@ -801,7 +801,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -838,7 +838,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -874,7 +874,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -912,7 +912,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -954,7 +954,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_ResumingFromCrash(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) // Crashed right after we commit the database transaction that saved the nonce to the eth_tx @@ -1019,7 +1019,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) { ethClient := evmtest.NewEthClientMockWithDefaultChain(t) ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - txmClient := txmgr.NewEvmTxmClient(ethClient, "") + txmClient := txmgr.NewEvmTxmClient(ethClient) nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmClient) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) ctx := testutils.Context(t) @@ -1664,7 +1664,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_KeystoreErrors(t *testing.T) { kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, kst, evmcfg, &testCheckerFactory{}, false, nonceTracker) ctx := testutils.Context(t) _, err := nonceTracker.GetNextSequence(ctx, fromAddress) @@ -1713,7 +1713,7 @@ func TestEthBroadcaster_Trigger(t *testing.T) { ethKeyStore := cltest.NewKeyStore(t, db, cfg.Database()).Eth() ethClient := evmtest.NewEthClientMockWithDefaultChain(t) lggr := logger.Test(t) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, &testCheckerFactory{}, false, nonceTracker) eb.Trigger(testutils.NewAddress()) @@ -1751,7 +1751,7 @@ func TestEthBroadcaster_SyncNonce(t *testing.T) { addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Once() ethClient.On("PendingNonceAt", mock.Anything, fromAddress).Return(uint64(0), nil).Once() - txmClient := txmgr.NewEvmTxmClient(ethClient, "") + txmClient := txmgr.NewEvmTxmClient(ethClient) eb := txmgr.NewEvmBroadcaster(txStore, txmClient, evmTxmCfg, txmgr.NewEvmTxmFeeConfig(ge), evmcfg.EVM().Transactions(), cfg.Database().Listener(), kst, txBuilder, lggr, checkerFactory, false) err := eb.Start(ctx) assert.NoError(t, err) @@ -1785,7 +1785,7 @@ func TestEthBroadcaster_NonceTracker_InProgressTx(t *testing.T) { // Tx with nonce 0 in DB will set local nonce map to value to 1 mustInsertInProgressEthTxWithAttempt(t, txStore, evmtypes.Nonce(inProgressTxNonce), fromAddress) - nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, "")) + nonceTracker := txmgr.NewNonceTracker(lggr, txStore, txmgr.NewEvmTxmClient(ethClient)) eb := NewTestEthBroadcaster(t, txStore, ethClient, ethKeyStore, evmcfg, checkerFactory, false, nonceTracker) // Check the local nonce map was set to 1 higher than in-progress tx nonce diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 9e969349622..26057ebb106 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -47,9 +47,9 @@ func NewTxm( // create tx attempt builder txAttemptBuilder := NewEvmTxAttemptBuilder(*client.ConfiguredChainID(), fCfg, keyStore, estimator) txStore := NewTxStore(sqlxDB, lggr) - txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config - feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config - txmClient := NewEvmTxmClient(client, chainConfig.ChainType()) // wrap Evm specific client + txmCfg := NewEvmTxmConfig(chainConfig) // wrap Evm specific config + feeCfg := NewEvmTxmFeeConfig(fCfg) // wrap Evm specific config + txmClient := NewEvmTxmClient(client) // wrap Evm specific client chainID := txmClient.ConfiguredChainID() evmBroadcaster := NewEvmBroadcaster(txStore, txmClient, txmCfg, feeCfg, txConfig, listenerConfig, keyStore, txAttemptBuilder, lggr, checker, chainConfig.NonceAutoSync()) evmTracker := NewEvmTracker(txStore, keyStore, chainID, lggr) diff --git a/core/chains/evm/txmgr/client.go b/core/chains/evm/txmgr/client.go index decf1fa4468..89375e2d017 100644 --- a/core/chains/evm/txmgr/client.go +++ b/core/chains/evm/txmgr/client.go @@ -17,7 +17,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils" commonclient "github.com/smartcontractkit/chainlink/v2/common/client" - "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/gas" evmtypes "github.com/smartcontractkit/chainlink/v2/core/chains/evm/types" @@ -26,11 +25,10 @@ import ( var _ TxmClient = (*evmTxmClient)(nil) type evmTxmClient struct { - client client.Client - chainType config.ChainType + client client.Client } -func NewEvmTxmClient(c client.Client, chainType config.ChainType) *evmTxmClient { +func NewEvmTxmClient(c client.Client) *evmTxmClient { return &evmTxmClient{client: c} } @@ -183,12 +181,3 @@ func (c *evmTxmClient) CallContract(ctx context.Context, a TxAttempt, blockNumbe }, blockNumber) return client.ExtractRPCError(errCall) } - -func (c *evmTxmClient) SimulateTransaction(ctx context.Context, from common.Address, to common.Address, data []byte) error { - msg := ethereum.CallMsg{ - From: from, - To: &to, - Data: data, - } - return SimulateTransaction(ctx, c.client, c.chainType, msg) -} diff --git a/core/chains/evm/txmgr/confirmer_test.go b/core/chains/evm/txmgr/confirmer_test.go index e11133ad8dd..d00b2d9ae3d 100644 --- a/core/chains/evm/txmgr/confirmer_test.go +++ b/core/chains/evm/txmgr/confirmer_test.go @@ -127,7 +127,7 @@ func TestEthConfirmer_Lifecycle(t *testing.T) { ge := config.EVM().GasEstimator() feeEstimator := gas.NewWrappedEvmEstimator(lggr, newEst, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ethKeyStore, feeEstimator) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ethKeyStore, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ethKeyStore, txBuilder, lggr) ctx := testutils.Context(t) // Can't close unstarted instance @@ -1650,7 +1650,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() // Create confirmer with necessary state - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -1697,7 +1697,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary_WithConnectivityCheck(t *testing txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, kst, feeEstimator) addresses := []gethCommon.Address{fromAddress} kst.On("EnabledAddressesForChain", mock.Anything, &cltest.FixtureChainID).Return(addresses, nil).Maybe() - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), ccfg.EVM(), txmgr.NewEvmTxmFeeConfig(ccfg.EVM().GasEstimator()), ccfg.EVM().Transactions(), cfg.Database(), kst, txBuilder, lggr) servicetest.Run(t, ec) currentHead := int64(30) oldEnough := int64(15) @@ -1877,7 +1877,7 @@ func TestEthConfirmer_RebroadcastWhereNecessary(t *testing.T) { var attempt1_2 txmgr.TxAttempt ethClient = evmtest.NewEthClientMockWithDefaultChain(t) - ec.XXXTestSetClient(txmgr.NewEvmTxmClient(ethClient, "")) + ec.XXXTestSetClient(txmgr.NewEvmTxmClient(ethClient)) t.Run("creates new attempt with higher gas price if transaction has an attempt older than threshold", func(t *testing.T) { expectedBumpedGasPrice := big.NewInt(20000000000) @@ -3134,7 +3134,7 @@ func newEthConfirmer(t testing.TB, txStore txmgr.EvmTxStore, ethClient client.Cl return gas.NewFixedPriceEstimator(ge, ge.BlockHistory(), lggr) }, ge.EIP1559DynamicFees(), nil) txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), ge, ks, estimator) - ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) + ec := txmgr.NewEvmConfirmer(txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTxmConfig(config.EVM()), txmgr.NewEvmTxmFeeConfig(ge), config.EVM().Transactions(), config.Database(), ks, txBuilder, lggr) ec.SetResumeCallback(fn) servicetest.Run(t, ec) return ec diff --git a/core/chains/evm/txmgr/nonce_tracker_test.go b/core/chains/evm/txmgr/nonce_tracker_test.go index c0024ee0e12..38059b82335 100644 --- a/core/chains/evm/txmgr/nonce_tracker_test.go +++ b/core/chains/evm/txmgr/nonce_tracker_test.go @@ -33,7 +33,7 @@ func TestNonceTracker_LoadSequenceMap(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr1 := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") addr2 := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781140") @@ -85,7 +85,7 @@ func TestNonceTracker_syncOnChain(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") @@ -142,7 +142,7 @@ func TestNonceTracker_SyncSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} @@ -189,7 +189,7 @@ func TestNonceTracker_GetNextSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") @@ -241,7 +241,7 @@ func TestNonceTracker_GenerateNextSequence(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} @@ -271,7 +271,7 @@ func Test_SetNonceAfterInit(t *testing.T) { client := clientmock.NewClient(t) client.On("ConfiguredChainID").Return(chainID) - nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client, "")) + nonceTracker := txmgr.NewNonceTracker(logger.Test(t), txStore, txmgr.NewEvmTxmClient(client)) addr := common.HexToAddress("0xd5e099c71b797516c10ed0f0d895f429c2781142") enabledAddresses := []common.Address{addr} diff --git a/core/chains/evm/txmgr/resender_test.go b/core/chains/evm/txmgr/resender_test.go index 85fe46688a1..57605c61785 100644 --- a/core/chains/evm/txmgr/resender_test.go +++ b/core/chains/evm/txmgr/resender_test.go @@ -65,7 +65,7 @@ func Test_EthResender_resendUnconfirmed(t *testing.T) { addr3TxesRawHex = append(addr3TxesRawHex, hexutil.Encode(etx.TxAttempts[0].SignedRawTx)) } - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) var resentHex = make(map[string]struct{}) ethClient.On("BatchCallContextAll", mock.Anything, mock.MatchedBy(func(elems []rpc.BatchElem) bool { @@ -121,7 +121,7 @@ func Test_EthResender_alertUnconfirmed(t *testing.T) { txStore := cltest.NewTestTxStore(t, db) originalBroadcastAt := time.Unix(1616509100, 0) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) t.Run("alerts only once for unconfirmed transaction attempt within the unconfirmedTxAlertDelay duration", func(t *testing.T) { _ = cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, int64(1), fromAddress, originalBroadcastAt) @@ -158,7 +158,7 @@ func Test_EthResender_Start(t *testing.T) { ctx := testutils.Context(t) ethClient := evmtest.NewEthClientMockWithDefaultChain(t) - er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient, ""), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) + er := txmgr.NewEvmResender(lggr, txStore, txmgr.NewEvmTxmClient(ethClient), txmgr.NewEvmTracker(txStore, ethKeyStore, big.NewInt(0), lggr), ethKeyStore, 100*time.Millisecond, ccfg.EVM(), ccfg.EVM().Transactions()) originalBroadcastAt := time.Unix(1616509100, 0) etx := cltest.MustInsertUnconfirmedEthTxWithBroadcastLegacyAttempt(t, txStore, 0, fromAddress, originalBroadcastAt) diff --git a/core/cmd/shell_local.go b/core/cmd/shell_local.go index 4af4b8da8c7..afdf6c6fdb0 100644 --- a/core/cmd/shell_local.go +++ b/core/cmd/shell_local.go @@ -635,7 +635,7 @@ func (s *Shell) RebroadcastTransactions(c *cli.Context) (err error) { txBuilder := txmgr.NewEvmTxAttemptBuilder(*ethClient.ConfiguredChainID(), chain.Config().EVM().GasEstimator(), keyStore.Eth(), nil) cfg := txmgr.NewEvmTxmConfig(chain.Config().EVM()) feeCfg := txmgr.NewEvmTxmFeeConfig(chain.Config().EVM().GasEstimator()) - ec := txmgr.NewEvmConfirmer(orm, txmgr.NewEvmTxmClient(ethClient, ""), cfg, feeCfg, chain.Config().EVM().Transactions(), chain.Config().Database(), keyStore.Eth(), txBuilder, chain.Logger()) + ec := txmgr.NewEvmConfirmer(orm, txmgr.NewEvmTxmClient(ethClient), cfg, feeCfg, chain.Config().EVM().Transactions(), chain.Config().Database(), keyStore.Eth(), txBuilder, chain.Logger()) totalNonces := endingNonce - beginningNonce + 1 nonces := make([]evmtypes.Nonce, totalNonces) for i := int64(0); i < totalNonces; i++ { From fbdd110cee0ebaacfa29c9d8bcca52d07ab7a0a0 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 17:02:41 -0500 Subject: [PATCH 07/23] Removed client from Txm struct --- common/txmgr/txmgr.go | 3 --- core/chains/evm/txmgr/builder.go | 5 ++--- core/chains/evm/txmgr/evm_tx_store_test.go | 2 +- core/services/vrf/v2/integration_v2_test.go | 2 +- core/services/vrf/v2/listener_v2_test.go | 2 +- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/common/txmgr/txmgr.go b/common/txmgr/txmgr.go index 8967beadacc..74d218915d9 100644 --- a/common/txmgr/txmgr.go +++ b/common/txmgr/txmgr.go @@ -84,7 +84,6 @@ type Txm[ services.StateMachine logger logger.SugaredLogger txStore txmgrtypes.TxStore[ADDR, CHAIN_ID, TX_HASH, BLOCK_HASH, R, SEQ, FEE] - client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] config txmgrtypes.TransactionManagerChainConfig txConfig txmgrtypes.TransactionManagerTransactionsConfig keyStore txmgrtypes.KeyStore[ADDR, CHAIN_ID, SEQ] @@ -140,7 +139,6 @@ func NewTxm[ confirmer *Confirmer[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], resender *Resender[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], tracker *Tracker[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], - client txmgrtypes.TxmClient[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE], ) *Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE] { b := Txm[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, R, SEQ, FEE]{ logger: logger.Sugared(lggr), @@ -161,7 +159,6 @@ func NewTxm[ confirmer: confirmer, resender: resender, tracker: tracker, - client: client, } if txCfg.ResendAfterThreshold() <= 0 { diff --git a/core/chains/evm/txmgr/builder.go b/core/chains/evm/txmgr/builder.go index 26057ebb106..b345f48a671 100644 --- a/core/chains/evm/txmgr/builder.go +++ b/core/chains/evm/txmgr/builder.go @@ -58,7 +58,7 @@ func NewTxm( if txConfig.ResendAfterThreshold() > 0 { evmResender = NewEvmResender(lggr, txStore, txmClient, evmTracker, keyStore, txmgr.DefaultResenderPollInterval, chainConfig, txConfig) } - txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker, txmClient) + txm = NewEvmTxm(chainID, txmCfg, txConfig, keyStore, lggr, checker, fwdMgr, txAttemptBuilder, txStore, evmBroadcaster, evmConfirmer, evmResender, evmTracker) return txm, nil } @@ -77,9 +77,8 @@ func NewEvmTxm( confirmer *Confirmer, resender *Resender, tracker *Tracker, - txmClient *evmTxmClient, ) *Txm { - return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker, txmClient) + return txmgr.NewTxm(chainId, cfg, txCfg, keyStore, lggr, checkerFactory, fwdMgr, txAttemptBuilder, txStore, broadcaster, confirmer, resender, tracker) } // NewEvmResender creates a new concrete EvmResender diff --git a/core/chains/evm/txmgr/evm_tx_store_test.go b/core/chains/evm/txmgr/evm_tx_store_test.go index cda512fa288..b410a775d14 100644 --- a/core/chains/evm/txmgr/evm_tx_store_test.go +++ b/core/chains/evm/txmgr/evm_tx_store_test.go @@ -1411,7 +1411,7 @@ func TestORM_UpdateTxUnstartedToInProgress(t *testing.T) { evmTxmCfg := txmgr.NewEvmTxmConfig(ccfg.EVM()) ec := evmtest.NewEthClientMockWithDefaultChain(t) txMgr := txmgr.NewEvmTxm(ec.ConfiguredChainID(), evmTxmCfg, ccfg.EVM().Transactions(), nil, logger.Test(t), nil, nil, - nil, txStore, nil, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil) err := txMgr.XXXTestAbandon(fromAddress) // mark transaction as abandoned require.NoError(t, err) diff --git a/core/services/vrf/v2/integration_v2_test.go b/core/services/vrf/v2/integration_v2_test.go index 2aadcb2545d..1a7c15a2508 100644 --- a/core/services/vrf/v2/integration_v2_test.go +++ b/core/services/vrf/v2/integration_v2_test.go @@ -143,7 +143,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M _, _, evmConfig := txmgr.MakeTestConfigs(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil) return txm } diff --git a/core/services/vrf/v2/listener_v2_test.go b/core/services/vrf/v2/listener_v2_test.go index 1478c7f2781..4e9e65bfafc 100644 --- a/core/services/vrf/v2/listener_v2_test.go +++ b/core/services/vrf/v2/listener_v2_test.go @@ -40,7 +40,7 @@ func makeTestTxm(t *testing.T, txStore txmgr.TestEvmTxStore, keyStore keystore.M ec := evmtest.NewEthClientMockWithDefaultChain(t) txmConfig := txmgr.NewEvmTxmConfig(evmConfig) txm := txmgr.NewEvmTxm(ec.ConfiguredChainID(), txmConfig, evmConfig.Transactions(), keyStore.Eth(), logger.TestLogger(t), nil, nil, - nil, txStore, nil, nil, nil, nil, nil) + nil, txStore, nil, nil, nil, nil) return txm } From 2f7078fba89cd5ea25100e7645ed08d1ff0bd7be Mon Sep 17 00:00:00 2001 From: amit-momin Date: Wed, 20 Mar 2024 17:05:49 -0500 Subject: [PATCH 08/23] Removed config from test helper --- core/chains/evm/txmgr/test_helpers.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/chains/evm/txmgr/test_helpers.go b/core/chains/evm/txmgr/test_helpers.go index 5f88d71e899..8cb771943b0 100644 --- a/core/chains/evm/txmgr/test_helpers.go +++ b/core/chains/evm/txmgr/test_helpers.go @@ -59,8 +59,6 @@ func (e *TestEvmConfig) NonceAutoSync() bool { return true } func (e *TestEvmConfig) FinalityDepth() uint32 { return 42 } -func (e *TestEvmConfig) ChainType() commonconfig.ChainType { return "" } - type TestGasEstimatorConfig struct { bumpThreshold uint64 } From fc70e7f28059d2197cb7b5c61122de9410d759b2 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 21 Mar 2024 17:37:34 -0500 Subject: [PATCH 09/23] Added tests and logging --- common/client/models.go | 1 + core/chains/evm/client/chain_client.go | 2 +- core/chains/evm/client/client.go | 2 +- core/chains/evm/client/errors.go | 16 +- core/chains/evm/client/tx_simulator.go | 79 ++++---- core/chains/evm/client/tx_simulator_test.go | 195 ++++++++++++++++++++ 6 files changed, 257 insertions(+), 38 deletions(-) create mode 100644 core/chains/evm/client/tx_simulator_test.go diff --git a/common/client/models.go b/common/client/models.go index d0cf42a3844..66f1e9cf88b 100644 --- a/common/client/models.go +++ b/common/client/models.go @@ -18,6 +18,7 @@ const ( InsufficientFunds // Tx was rejected due to insufficient funds. ExceedsMaxFee // Attempt's fee was higher than the node's limit and got rejected. FeeOutOfValidRange // This error is returned when we use a fee price suggested from an RPC, but the network rejects the attempt due to an invalid range(mostly used by L2 chains). Retry by requesting a new suggested fee price. + OutOfCounters // The error returned when a transaction is too complex to be proven by zk circuits. This error is mainly returned by zk chains. sendTxReturnCodeLen // tracks the number of errors. Must always be last ) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 8cf7eac665c..10034ca36dc 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -277,5 +277,5 @@ func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, To: &to, Data: data, } - return SimulateTransaction(ctx, c, c.chainType, msg) + return SimulateTransaction(ctx, c, c.logger, c.chainType, msg) } diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index ae333d8d5a1..6f4e2dc5fd0 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -96,7 +96,7 @@ type Client interface { IsL2() bool - // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time + // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time. It will not return an error for non-zk chains. CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error } diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 8095c122508..c730913ed57 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -63,6 +63,7 @@ const ( TransactionAlreadyMined Fatal ServiceUnavailable + OutOfCounters ) type ClientErrors = map[int]*regexp.Regexp @@ -227,7 +228,11 @@ var zkSync = ClientErrors{ Fatal: regexp.MustCompile(`(?:: |^)(?:exceeds block gas limit|intrinsic gas too low|Not enough gas for transaction validation|Failed to pay the fee to the operator|Error function_selector = 0x, data = 0x|invalid sender. can't start a transaction from a non-account|max(?: priority)? fee per (?:gas|pubdata byte) higher than 2\^64-1|oversized data. max: \d+; actual: \d+)$`), } -var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync} +var zkEvm = ClientErrors{ + OutOfCounters: regexp.MustCompile(`(?:: |^)not enough keccak counters to continue the execution$`), +} + +var clients = []ClientErrors{parity, geth, arbitrum, metis, substrate, avalanche, nethermind, harmony, besu, erigon, klaytn, celo, zkSync, zkEvm} func (s *SendError) is(errorType int) bool { if s == nil || s.err == nil { @@ -309,6 +314,11 @@ func (s *SendError) IsServiceUnavailable() bool { return s.is(ServiceUnavailable) } +// IsOutOfCounters is a zk chain specific error returned if the transaction is too complex to prove on zk circuits +func (s *SendError) IsOutOfCounters() bool { + return s.is(OutOfCounters) +} + // IsTimeout indicates if the error was caused by an exceeded context deadline func (s *SendError) IsTimeout() bool { if s == nil { @@ -511,6 +521,10 @@ func ClassifySendError(err error, lggr logger.SugaredLogger, tx *types.Transacti ) return commonclient.ExceedsMaxFee } + if sendError.IsOutOfCounters() { + lggr.Infow("Transaction encountered zk out-of-counters error", "err", sendError) + return commonclient.OutOfCounters + } lggr.Criticalw("Unknown error encountered when sending transaction", "err", err, "etx", tx) return commonclient.Unknown } diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index d99a93d8074..ecbe8ad34a2 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -7,76 +7,85 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/config" + + commonclient "github.com/smartcontractkit/chainlink/v2/common/client" ) const ErrOutOfCounters = "not enough keccak counters to continue the execution" -type SimulatorClient interface { +type simulatorClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error } // ZK Chain can return an overflow error based on the number of keccak hashes in the call // This method allows a caller to determine if a tx would fail due to overflow error by simulating the transaction // Used as an entry point for custom simulation across different chains -func SimulateTransaction(ctx context.Context, client SimulatorClient, chainType config.ChainType, msg ethereum.CallMsg) error { +func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) error { + var err error switch chainType { case config.ChainZkEvm: - return simulateTransactionZkEvm(ctx, client, msg) + err = simulateTransactionZkEvm(ctx, client, lggr, msg) default: - return simulateTransactionDefault(ctx, client, msg) + err = simulateTransactionDefault(ctx, client, msg) + } + // ClassifySendError will not have proper logging within the method due to + code := ClassifySendError(err, lggr, &types.Transaction{}, msg.From, chainType.IsL2()) + if code == commonclient.OutOfCounters { + return errors.New(ErrOutOfCounters) } + return nil } // eth_estimateGas returns out-of-counters (OOC) error if the transaction would result in an overflow -func simulateTransactionDefault(ctx context.Context, client SimulatorClient, msg ethereum.CallMsg) error { - var result uint64 +func simulateTransactionDefault(ctx context.Context, client simulatorClient, msg ethereum.CallMsg) error { + var result hexutil.Big errCall := client.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") - jsonErr, err := ExtractRPCError(errCall) - if err != nil { - return fmt.Errorf("failed to simulate tx: %w", err) - } + jsonErr, _ := ExtractRPCError(errCall) // Only return error if Zk OOC error is identified - if jsonErr.Message == ErrOutOfCounters { + if jsonErr != nil && jsonErr.Message == ErrOutOfCounters { return errors.New(ErrOutOfCounters) } return nil } // zkEVM implemented a custom zkevm_estimateCounters method to detect if a transaction would result in an out-of-counters (OOC) error -func simulateTransactionZkEvm(ctx context.Context, client SimulatorClient, msg ethereum.CallMsg) error { +func simulateTransactionZkEvm(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, msg ethereum.CallMsg) error { var result struct { - countersUsed struct { - gasUsed int - usedKeccakHashes int - usedPoseidonHashes int - usedPoseidonPaddings int - usedMemAligns int - usedArithmetics int - usedBinaries int - usedSteps int - usedSHA256Hashes int + CountersUsed struct { + GasUsed string + UsedKeccakHashes string + UsedPoseidonHashes string + UsedPoseidonPaddings string + UsedMemAligns string + UsedArithmetics string + UsedBinaries string + UsedSteps string + UsedSHA256Hashes string } - countersLimit struct { - maxGasUsed int - maxKeccakHashes int - maxPoseidonHashes int - maxPoseidonPaddings int - maxMemAligns int - maxArithmetics int - maxBinaries int - maxSteps int - maxSHA256Hashes int + CountersLimit struct { + MaxGasUsed string + MaxKeccakHashes string + MaxPoseidonHashes string + MaxPoseidonPaddings string + MaxMemAligns string + MaxArithmetics string + MaxBinaries string + MaxSteps string + MaxSHA256Hashes string } - oocError string + OocError string } err := client.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") if err != nil { return fmt.Errorf("failed to simulate tx: %w", err) } - if len(result.oocError) > 0 { - return errors.New(result.oocError) + if len(result.OocError) > 0 { + lggr.Debugw("zkevm_estimateCounters returned", "result", result) + return errors.New(result.OocError) } return nil } diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go new file mode 100644 index 00000000000..70741b24fdd --- /dev/null +++ b/core/chains/evm/client/tx_simulator_test.go @@ -0,0 +1,195 @@ +package client_test + +import ( + "testing" + + "github.com/ethereum/go-ethereum" + "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink/v2/common/config" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" +) + +func TestSimulateTx_Default(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + toAddress := testutils.NewAddress() + + t.Run("returns without error if simulation passes", func(t *testing.T) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + switch method { + case "eth_subscribe": + resp.Result = `"0x00"` + resp.Notify = headResult + return + case "eth_unsubscribe": + resp.Result = "true" + return + case "eth_estimateGas": + resp.Result = `"0x100"` + } + return + }).WSURL().String() + + ethClient := mustNewChainClient(t, wsURL) + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + msg := ethereum.CallMsg{ + From: fromAddress, + To: &toAddress, + Data: []byte("0x00"), + } + err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + require.NoError(t, err) + }) + + t.Run("returns error if simulation returns zk out-of-counters error", func(t *testing.T) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + switch method { + case "eth_subscribe": + resp.Result = `"0x00"` + resp.Notify = headResult + return + case "eth_unsubscribe": + resp.Result = "true" + return + case "eth_estimateGas": + resp.Error.Code = -32000 + resp.Result = `"0x100"` + resp.Error.Message = client.ErrOutOfCounters + } + return + }).WSURL().String() + + ethClient := mustNewChainClient(t, wsURL) + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + msg := ethereum.CallMsg{ + From: fromAddress, + To: &toAddress, + Data: []byte("0x00"), + } + err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + require.Error(t, err, client.ErrOutOfCounters) + }) +} + +func TestSimulateTx_ZkEvm(t *testing.T) { + t.Parallel() + + fromAddress := testutils.NewAddress() + toAddress := testutils.NewAddress() + + t.Run("returns without error if simulation passes", func(t *testing.T) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + switch method { + case "eth_subscribe": + resp.Result = `"0x00"` + resp.Notify = headResult + return + case "eth_unsubscribe": + resp.Result = "true" + return + case "zkevm_estimateCounters": + resp.Result = `{ + "countersUsed": { + "gasUsed": "0x5360", + "usedKeccakHashes": "0x7", + "usedPoseidonHashes": "0x2bb", + "usedPoseidonPaddings": "0x4", + "usedMemAligns": "0x0", + "usedArithmetics": "0x263", + "usedBinaries": "0x40c", + "usedSteps": "0x3288", + "usedSHA256Hashes": "0x0" + }, + "countersLimit": { + "maxGasUsed": "0x1c9c380", + "maxKeccakHashes": "0x861", + "maxPoseidonHashes": "0x3d9c5", + "maxPoseidonPaddings": "0x21017", + "maxMemAligns": "0x39c29", + "maxArithmetics": "0x39c29", + "maxBinaries": "0x73852", + "maxSteps": "0x73846a", + "maxSHA256Hashes": "0x63c" + } + }` + } + return + }).WSURL().String() + + ethClient := mustNewChainClient(t, wsURL) + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + msg := ethereum.CallMsg{ + From: fromAddress, + To: &toAddress, + Data: []byte("0x00"), + } + err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) + require.NoError(t, err) + }) + + t.Run("returns error if simulation returns zk out-of-counters error", func(t *testing.T) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + switch method { + case "eth_subscribe": + resp.Result = `"0x00"` + resp.Notify = headResult + return + case "eth_unsubscribe": + resp.Result = "true" + return + case "zkevm_estimateCounters": + resp.Result = `{ + "countersUsed": { + "gasUsed": "0x12f3bd", + "usedKeccakHashes": "0x8d3", + "usedPoseidonHashes": "0x222", + "usedPoseidonPaddings": "0x16", + "usedMemAligns": "0x1a69", + "usedArithmetics": "0x2619", + "usedBinaries": "0x2d738", + "usedSteps": "0x72e223", + "usedSHA256Hashes": "0x0" + }, + "countersLimit": { + "maxGasUsed": "0x1c9c380", + "maxKeccakHashes": "0x861", + "maxPoseidonHashes": "0x3d9c5", + "maxPoseidonPaddings": "0x21017", + "maxMemAligns": "0x39c29", + "maxArithmetics": "0x39c29", + "maxBinaries": "0x73852", + "maxSteps": "0x73846a", + "maxSHA256Hashes": "0x63c" + }, + "oocError": "not enough keccak counters to continue the execution" + }` + } + return + }).WSURL().String() + + ethClient := mustNewChainClient(t, wsURL) + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + msg := ethereum.CallMsg{ + From: fromAddress, + To: &toAddress, + Data: []byte("0x00"), + } + err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) + require.Error(t, err, client.ErrOutOfCounters) + }) +} From 389e18f3a794803b41bc06768a1968bc967720bb Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 21 Mar 2024 17:43:06 -0500 Subject: [PATCH 10/23] Added changeset --- .changeset/hungry-impalas-jog.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-impalas-jog.md diff --git a/.changeset/hungry-impalas-jog.md b/.changeset/hungry-impalas-jog.md new file mode 100644 index 00000000000..efa23edabb2 --- /dev/null +++ b/.changeset/hungry-impalas-jog.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Added a tx simulation feature to the chain client to enable testing for zk out-of-counter (OOC) errors From 37b783e0ce2b7cf610c6ce7057315715310bf8b5 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 21 Mar 2024 17:58:36 -0500 Subject: [PATCH 11/23] Fixed multinode test --- common/client/multi_node_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/client/multi_node_test.go b/common/client/multi_node_test.go index 43e4127556a..33e4a19b5de 100644 --- a/common/client/multi_node_test.go +++ b/common/client/multi_node_test.go @@ -848,6 +848,14 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { ExpectedCriticalErr: "expected at least one response on SendTransaction", ResultsByCode: map[SendTxReturnCode][]error{}, }, + { + Name: "Zk out of counter error", + ExpectedTxResult: "not enough keccak counters to continue the execution", + ExpectedCriticalErr: "", + ResultsByCode: map[SendTxReturnCode][]error{ + OutOfCounters: {errors.New("not enough keccak counters to continue the execution")}, + }, + }, } for _, testCase := range testCases { From e1268055e7ca4c8ef0c5de3ff15f66c700e0884a Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 21 Mar 2024 18:09:49 -0500 Subject: [PATCH 12/23] Fixed linting --- common/client/multi_node_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/client/multi_node_test.go b/common/client/multi_node_test.go index 33e4a19b5de..9c09bd57d70 100644 --- a/common/client/multi_node_test.go +++ b/common/client/multi_node_test.go @@ -849,8 +849,8 @@ func TestMultiNode_SendTransaction_aggregateTxResults(t *testing.T) { ResultsByCode: map[SendTxReturnCode][]error{}, }, { - Name: "Zk out of counter error", - ExpectedTxResult: "not enough keccak counters to continue the execution", + Name: "Zk out of counter error", + ExpectedTxResult: "not enough keccak counters to continue the execution", ExpectedCriticalErr: "", ResultsByCode: map[SendTxReturnCode][]error{ OutOfCounters: {errors.New("not enough keccak counters to continue the execution")}, From c7eae5068761022f39449d66a07f56f8db0ca82d Mon Sep 17 00:00:00 2001 From: amit-momin Date: Thu, 21 Mar 2024 19:51:39 -0500 Subject: [PATCH 13/23] Fixed comment --- core/chains/evm/client/tx_simulator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index ecbe8ad34a2..6a9237fb0e0 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -32,7 +32,7 @@ func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logge default: err = simulateTransactionDefault(ctx, client, msg) } - // ClassifySendError will not have proper logging within the method due to + // ClassifySendError will not have the proper fields for logging within the method due to the empty Transaction passed code := ClassifySendError(err, lggr, &types.Transaction{}, msg.From, chainType.IsL2()) if code == commonclient.OutOfCounters { return errors.New(ErrOutOfCounters) From 53efe2267bb0341ea054000860a7efb5a216c790 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Fri, 22 Mar 2024 11:10:28 -0500 Subject: [PATCH 14/23] Added test for non-OOC error --- core/chains/evm/client/tx_simulator_test.go | 31 +++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go index 70741b24fdd..539f00cc690 100644 --- a/core/chains/evm/client/tx_simulator_test.go +++ b/core/chains/evm/client/tx_simulator_test.go @@ -80,6 +80,37 @@ func TestSimulateTx_Default(t *testing.T) { err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) require.Error(t, err, client.ErrOutOfCounters) }) + + t.Run("returns without error if simulation returns non-OOC error", func(t *testing.T) { + wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { + switch method { + case "eth_subscribe": + resp.Result = `"0x00"` + resp.Notify = headResult + return + case "eth_unsubscribe": + resp.Result = "true" + return + case "eth_estimateGas": + resp.Error.Code = -32000 + resp.Result = `"0x100"` + resp.Error.Message = "something other than OOC" + } + return + }).WSURL().String() + + ethClient := mustNewChainClient(t, wsURL) + err := ethClient.Dial(testutils.Context(t)) + require.NoError(t, err) + + msg := ethereum.CallMsg{ + From: fromAddress, + To: &toAddress, + Data: []byte("0x00"), + } + err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + require.NoError(t, err) + }) } func TestSimulateTx_ZkEvm(t *testing.T) { From 8ad251090daa23b54375b4851189377f35f5fbca Mon Sep 17 00:00:00 2001 From: amit-momin Date: Fri, 22 Mar 2024 11:55:11 -0500 Subject: [PATCH 15/23] Reduced context initializations in tests --- core/chains/evm/client/tx_simulator_test.go | 22 +++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go index 539f00cc690..129bf6acb2d 100644 --- a/core/chains/evm/client/tx_simulator_test.go +++ b/core/chains/evm/client/tx_simulator_test.go @@ -20,6 +20,7 @@ func TestSimulateTx_Default(t *testing.T) { fromAddress := testutils.NewAddress() toAddress := testutils.NewAddress() + ctx := testutils.Context(t) t.Run("returns without error if simulation passes", func(t *testing.T) { wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { @@ -38,7 +39,7 @@ func TestSimulateTx_Default(t *testing.T) { }).WSURL().String() ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) + err := ethClient.Dial(ctx) require.NoError(t, err) msg := ethereum.CallMsg{ @@ -46,7 +47,7 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) require.NoError(t, err) }) @@ -69,7 +70,7 @@ func TestSimulateTx_Default(t *testing.T) { }).WSURL().String() ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) + err := ethClient.Dial(ctx) require.NoError(t, err) msg := ethereum.CallMsg{ @@ -77,7 +78,7 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) require.Error(t, err, client.ErrOutOfCounters) }) @@ -100,7 +101,7 @@ func TestSimulateTx_Default(t *testing.T) { }).WSURL().String() ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) + err := ethClient.Dial(ctx) require.NoError(t, err) msg := ethereum.CallMsg{ @@ -108,7 +109,7 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), "", msg) + err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) require.NoError(t, err) }) } @@ -118,6 +119,7 @@ func TestSimulateTx_ZkEvm(t *testing.T) { fromAddress := testutils.NewAddress() toAddress := testutils.NewAddress() + ctx := testutils.Context(t) t.Run("returns without error if simulation passes", func(t *testing.T) { wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { @@ -159,7 +161,7 @@ func TestSimulateTx_ZkEvm(t *testing.T) { }).WSURL().String() ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) + err := ethClient.Dial(ctx) require.NoError(t, err) msg := ethereum.CallMsg{ @@ -167,7 +169,7 @@ func TestSimulateTx_ZkEvm(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) + err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) require.NoError(t, err) }) @@ -212,7 +214,7 @@ func TestSimulateTx_ZkEvm(t *testing.T) { }).WSURL().String() ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(testutils.Context(t)) + err := ethClient.Dial(ctx) require.NoError(t, err) msg := ethereum.CallMsg{ @@ -220,7 +222,7 @@ func TestSimulateTx_ZkEvm(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(testutils.Context(t), ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) + err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) require.Error(t, err, client.ErrOutOfCounters) }) } From 3f53867b244913c94af3840e5f0c8cc4ba78035d Mon Sep 17 00:00:00 2001 From: amit-momin Date: Fri, 22 Mar 2024 17:12:20 -0500 Subject: [PATCH 16/23] Updated to account for all types of OOC errors --- core/chains/evm/client/errors.go | 2 +- core/chains/evm/client/tx_simulator.go | 81 +++++++++++++-------- core/chains/evm/client/tx_simulator_test.go | 4 +- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index c730913ed57..194142b7ac2 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -229,7 +229,7 @@ var zkSync = ClientErrors{ } var zkEvm = ClientErrors{ - OutOfCounters: regexp.MustCompile(`(?:: |^)not enough keccak counters to continue the execution$`), + 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} diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index 6a9237fb0e0..decfade8a33 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -15,14 +15,14 @@ import ( commonclient "github.com/smartcontractkit/chainlink/v2/common/client" ) -const ErrOutOfCounters = "not enough keccak counters to continue the execution" +const ErrOutOfCounters = "not enough counters to continue the execution" type simulatorClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error } -// ZK Chain can return an overflow error based on the number of keccak hashes in the call -// This method allows a caller to determine if a tx would fail due to overflow error by simulating the transaction +// ZK chains can return an out-of-counters error +// This method allows a caller to determine if a tx would fail due to OOC error by simulating the transaction // Used as an entry point for custom simulation across different chains func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) error { var err error @@ -34,6 +34,7 @@ func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logge } // ClassifySendError will not have the proper fields for logging within the method due to the empty Transaction passed code := ClassifySendError(err, lggr, &types.Transaction{}, msg.From, chainType.IsL2()) + // Only return error if ZK OOC error is identified if code == commonclient.OutOfCounters { return errors.New(ErrOutOfCounters) } @@ -45,51 +46,67 @@ func simulateTransactionDefault(ctx context.Context, client simulatorClient, msg var result hexutil.Big errCall := client.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") jsonErr, _ := ExtractRPCError(errCall) - // Only return error if Zk OOC error is identified - if jsonErr != nil && jsonErr.Message == ErrOutOfCounters { - return errors.New(ErrOutOfCounters) + if jsonErr != nil && len(jsonErr.Message) > 0 { + return errors.New(jsonErr.Message) } return nil } +type zkEvmEstimateCountResponse struct { + CountersUsed struct { + GasUsed string + UsedKeccakHashes string + UsedPoseidonHashes string + UsedPoseidonPaddings string + UsedMemAligns string + UsedArithmetics string + UsedBinaries string + UsedSteps string + UsedSHA256Hashes string + } + CountersLimit struct { + MaxGasUsed string + MaxKeccakHashes string + MaxPoseidonHashes string + MaxPoseidonPaddings string + MaxMemAligns string + MaxArithmetics string + MaxBinaries string + MaxSteps string + MaxSHA256Hashes string + } + OocError string +} + // zkEVM implemented a custom zkevm_estimateCounters method to detect if a transaction would result in an out-of-counters (OOC) error func simulateTransactionZkEvm(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, msg ethereum.CallMsg) error { - var result struct { - CountersUsed struct { - GasUsed string - UsedKeccakHashes string - UsedPoseidonHashes string - UsedPoseidonPaddings string - UsedMemAligns string - UsedArithmetics string - UsedBinaries string - UsedSteps string - UsedSHA256Hashes string - } - CountersLimit struct { - MaxGasUsed string - MaxKeccakHashes string - MaxPoseidonHashes string - MaxPoseidonPaddings string - MaxMemAligns string - MaxArithmetics string - MaxBinaries string - MaxSteps string - MaxSHA256Hashes string - } - OocError string - } + var result zkEvmEstimateCountResponse err := client.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") if err != nil { return fmt.Errorf("failed to simulate tx: %w", err) } - if len(result.OocError) > 0 { + if detectZkEvmCounterOverflow(result) && len(result.OocError) > 0 { lggr.Debugw("zkevm_estimateCounters returned", "result", result) return errors.New(result.OocError) } return nil } +// Helper method for zkEvm to determine if response indicates an overflow +func detectZkEvmCounterOverflow(result zkEvmEstimateCountResponse) bool { + if result.CountersUsed.UsedKeccakHashes > result.CountersLimit.MaxKeccakHashes || + result.CountersUsed.UsedPoseidonHashes > result.CountersLimit.MaxPoseidonHashes || + result.CountersUsed.UsedPoseidonPaddings > result.CountersLimit.MaxPoseidonPaddings || + result.CountersUsed.UsedMemAligns > result.CountersLimit.MaxMemAligns || + result.CountersUsed.UsedArithmetics > result.CountersLimit.MaxArithmetics || + result.CountersUsed.UsedBinaries > result.CountersLimit.MaxBinaries || + result.CountersUsed.UsedSteps > result.CountersLimit.MaxSteps || + result.CountersUsed.UsedSHA256Hashes > result.CountersLimit.MaxSHA256Hashes { + return true + } + return false +} + func toCallArg(msg ethereum.CallMsg) interface{} { arg := map[string]interface{}{ "from": msg.From, diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go index 129bf6acb2d..8996f81e20f 100644 --- a/core/chains/evm/client/tx_simulator_test.go +++ b/core/chains/evm/client/tx_simulator_test.go @@ -64,7 +64,7 @@ func TestSimulateTx_Default(t *testing.T) { case "eth_estimateGas": resp.Error.Code = -32000 resp.Result = `"0x100"` - resp.Error.Message = client.ErrOutOfCounters + resp.Error.Message = "not enough keccak counters to continue the execution" } return }).WSURL().String() @@ -95,7 +95,7 @@ func TestSimulateTx_Default(t *testing.T) { case "eth_estimateGas": resp.Error.Code = -32000 resp.Result = `"0x100"` - resp.Error.Message = "something other than OOC" + resp.Error.Message = "txpool is full" } return }).WSURL().String() From ce8a3c7ad6ac97d4dcdf4d1fd45efe9b0947195a Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 25 Mar 2024 16:34:07 -0500 Subject: [PATCH 17/23] Removed custom zk counter method and simplified error handling --- core/chains/evm/client/tx_simulator.go | 94 +++------------- core/chains/evm/client/tx_simulator_test.go | 116 +------------------- 2 files changed, 18 insertions(+), 192 deletions(-) diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index decfade8a33..4e4d976e463 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -2,17 +2,13 @@ package client import ( "context" - "errors" "fmt" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/v2/common/config" - - commonclient "github.com/smartcontractkit/chainlink/v2/common/client" ) const ErrOutOfCounters = "not enough counters to continue the execution" @@ -23,20 +19,24 @@ type simulatorClient interface { // ZK chains can return an out-of-counters error // This method allows a caller to determine if a tx would fail due to OOC error by simulating the transaction -// Used as an entry point for custom simulation across different chains +// It will also return service unavailable or timeout errors for callers to react to retryable errors +// Used as an entry point in case custom simulation is required across different chains func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) error { - var err error - switch chainType { - case config.ChainZkEvm: - err = simulateTransactionZkEvm(ctx, client, lggr, msg) - default: - err = simulateTransactionDefault(ctx, client, msg) + err := simulateTransactionDefault(ctx, client, msg) + sendErr := NewSendError(err) + if sendErr == nil { + return nil + } + // Only return select errors to the caller + // Other errors are irrelevant to tx simulation to determin OOC error + if sendErr.IsOutOfCounters() { + return fmt.Errorf("%s: %w", ErrOutOfCounters, err) + } + if sendErr.IsServiceUnavailable() { + return fmt.Errorf("service not available when performing tx simulation: %w", err) } - // ClassifySendError will not have the proper fields for logging within the method due to the empty Transaction passed - code := ClassifySendError(err, lggr, &types.Transaction{}, msg.From, chainType.IsL2()) - // Only return error if ZK OOC error is identified - if code == commonclient.OutOfCounters { - return errors.New(ErrOutOfCounters) + if sendErr.IsTimeout() { + return fmt.Errorf("timeout experienced when performing tx simulation: %w", err) } return nil } @@ -44,67 +44,7 @@ func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logge // eth_estimateGas returns out-of-counters (OOC) error if the transaction would result in an overflow func simulateTransactionDefault(ctx context.Context, client simulatorClient, msg ethereum.CallMsg) error { var result hexutil.Big - errCall := client.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") - jsonErr, _ := ExtractRPCError(errCall) - if jsonErr != nil && len(jsonErr.Message) > 0 { - return errors.New(jsonErr.Message) - } - return nil -} - -type zkEvmEstimateCountResponse struct { - CountersUsed struct { - GasUsed string - UsedKeccakHashes string - UsedPoseidonHashes string - UsedPoseidonPaddings string - UsedMemAligns string - UsedArithmetics string - UsedBinaries string - UsedSteps string - UsedSHA256Hashes string - } - CountersLimit struct { - MaxGasUsed string - MaxKeccakHashes string - MaxPoseidonHashes string - MaxPoseidonPaddings string - MaxMemAligns string - MaxArithmetics string - MaxBinaries string - MaxSteps string - MaxSHA256Hashes string - } - OocError string -} - -// zkEVM implemented a custom zkevm_estimateCounters method to detect if a transaction would result in an out-of-counters (OOC) error -func simulateTransactionZkEvm(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, msg ethereum.CallMsg) error { - var result zkEvmEstimateCountResponse - err := client.CallContext(ctx, &result, "zkevm_estimateCounters", toCallArg(msg), "pending") - if err != nil { - return fmt.Errorf("failed to simulate tx: %w", err) - } - if detectZkEvmCounterOverflow(result) && len(result.OocError) > 0 { - lggr.Debugw("zkevm_estimateCounters returned", "result", result) - return errors.New(result.OocError) - } - return nil -} - -// Helper method for zkEvm to determine if response indicates an overflow -func detectZkEvmCounterOverflow(result zkEvmEstimateCountResponse) bool { - if result.CountersUsed.UsedKeccakHashes > result.CountersLimit.MaxKeccakHashes || - result.CountersUsed.UsedPoseidonHashes > result.CountersLimit.MaxPoseidonHashes || - result.CountersUsed.UsedPoseidonPaddings > result.CountersLimit.MaxPoseidonPaddings || - result.CountersUsed.UsedMemAligns > result.CountersLimit.MaxMemAligns || - result.CountersUsed.UsedArithmetics > result.CountersLimit.MaxArithmetics || - result.CountersUsed.UsedBinaries > result.CountersLimit.MaxBinaries || - result.CountersUsed.UsedSteps > result.CountersLimit.MaxSteps || - result.CountersUsed.UsedSHA256Hashes > result.CountersLimit.MaxSHA256Hashes { - return true - } - return false + return client.CallContext(ctx, &result, "eth_estimateGas", toCallArg(msg), "pending") } func toCallArg(msg ethereum.CallMsg) interface{} { diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go index 8996f81e20f..fd9377b80b7 100644 --- a/core/chains/evm/client/tx_simulator_test.go +++ b/core/chains/evm/client/tx_simulator_test.go @@ -9,7 +9,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" - "github.com/smartcontractkit/chainlink/v2/common/config" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" "github.com/smartcontractkit/chainlink/v2/core/internal/cltest" "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" @@ -95,7 +94,7 @@ func TestSimulateTx_Default(t *testing.T) { case "eth_estimateGas": resp.Error.Code = -32000 resp.Result = `"0x100"` - resp.Error.Message = "txpool is full" + resp.Error.Message = "something went wrong" } return }).WSURL().String() @@ -113,116 +112,3 @@ func TestSimulateTx_Default(t *testing.T) { require.NoError(t, err) }) } - -func TestSimulateTx_ZkEvm(t *testing.T) { - t.Parallel() - - fromAddress := testutils.NewAddress() - toAddress := testutils.NewAddress() - ctx := testutils.Context(t) - - t.Run("returns without error if simulation passes", func(t *testing.T) { - wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { - switch method { - case "eth_subscribe": - resp.Result = `"0x00"` - resp.Notify = headResult - return - case "eth_unsubscribe": - resp.Result = "true" - return - case "zkevm_estimateCounters": - resp.Result = `{ - "countersUsed": { - "gasUsed": "0x5360", - "usedKeccakHashes": "0x7", - "usedPoseidonHashes": "0x2bb", - "usedPoseidonPaddings": "0x4", - "usedMemAligns": "0x0", - "usedArithmetics": "0x263", - "usedBinaries": "0x40c", - "usedSteps": "0x3288", - "usedSHA256Hashes": "0x0" - }, - "countersLimit": { - "maxGasUsed": "0x1c9c380", - "maxKeccakHashes": "0x861", - "maxPoseidonHashes": "0x3d9c5", - "maxPoseidonPaddings": "0x21017", - "maxMemAligns": "0x39c29", - "maxArithmetics": "0x39c29", - "maxBinaries": "0x73852", - "maxSteps": "0x73846a", - "maxSHA256Hashes": "0x63c" - } - }` - } - return - }).WSURL().String() - - ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(ctx) - require.NoError(t, err) - - msg := ethereum.CallMsg{ - From: fromAddress, - To: &toAddress, - Data: []byte("0x00"), - } - err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) - require.NoError(t, err) - }) - - t.Run("returns error if simulation returns zk out-of-counters error", func(t *testing.T) { - wsURL := testutils.NewWSServer(t, &cltest.FixtureChainID, func(method string, params gjson.Result) (resp testutils.JSONRPCResponse) { - switch method { - case "eth_subscribe": - resp.Result = `"0x00"` - resp.Notify = headResult - return - case "eth_unsubscribe": - resp.Result = "true" - return - case "zkevm_estimateCounters": - resp.Result = `{ - "countersUsed": { - "gasUsed": "0x12f3bd", - "usedKeccakHashes": "0x8d3", - "usedPoseidonHashes": "0x222", - "usedPoseidonPaddings": "0x16", - "usedMemAligns": "0x1a69", - "usedArithmetics": "0x2619", - "usedBinaries": "0x2d738", - "usedSteps": "0x72e223", - "usedSHA256Hashes": "0x0" - }, - "countersLimit": { - "maxGasUsed": "0x1c9c380", - "maxKeccakHashes": "0x861", - "maxPoseidonHashes": "0x3d9c5", - "maxPoseidonPaddings": "0x21017", - "maxMemAligns": "0x39c29", - "maxArithmetics": "0x39c29", - "maxBinaries": "0x73852", - "maxSteps": "0x73846a", - "maxSHA256Hashes": "0x63c" - }, - "oocError": "not enough keccak counters to continue the execution" - }` - } - return - }).WSURL().String() - - ethClient := mustNewChainClient(t, wsURL) - err := ethClient.Dial(ctx) - require.NoError(t, err) - - msg := ethereum.CallMsg{ - From: fromAddress, - To: &toAddress, - Data: []byte("0x00"), - } - err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), config.ChainZkEvm, msg) - require.Error(t, err, client.ErrOutOfCounters) - }) -} From d7605cc4ee3bb0600726f3e46b11b6480e2bb0fa Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 25 Mar 2024 16:50:17 -0500 Subject: [PATCH 18/23] Removed zkevm chain type --- common/config/chaintype.go | 4 +--- .../evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml | 1 - .../chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml | 1 - .../evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml | 1 - core/services/chainlink/config_test.go | 4 ++-- core/services/ocr/contract_tracker.go | 2 +- core/services/ocrcommon/block_translator.go | 2 +- docs/CONFIG.md | 3 --- 8 files changed, 5 insertions(+), 13 deletions(-) diff --git a/common/config/chaintype.go b/common/config/chaintype.go index a14c90cb5a8..29bab2a91e2 100644 --- a/common/config/chaintype.go +++ b/common/config/chaintype.go @@ -19,7 +19,6 @@ const ( ChainScroll ChainType = "scroll" ChainWeMix ChainType = "wemix" ChainXDai ChainType = "xdai" // Deprecated: use ChainGnosis instead - ChainZkEvm ChainType = "zkevm" ChainZkSync ChainType = "zksync" ) @@ -32,14 +31,13 @@ var ErrInvalidChainType = fmt.Errorf("must be one of %s or omitted", strings.Joi string(ChainOptimismBedrock), string(ChainScroll), string(ChainWeMix), - string(ChainZkEvm), string(ChainZkSync), }, ", ")) // IsValid returns true if the ChainType value is known or empty. func (c ChainType) IsValid() bool { switch c { - case "", ChainArbitrum, ChainCelo, ChainGnosis, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXDai, ChainZkEvm, ChainZkSync: + case "", ChainArbitrum, ChainCelo, ChainGnosis, ChainKroma, ChainMetis, ChainOptimismBedrock, ChainScroll, ChainWeMix, ChainXDai, ChainZkSync: return true } return false diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml index 0890554142e..02cc322f19e 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Cardona.toml @@ -1,5 +1,4 @@ ChainID = '2442' -ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '12m' MinIncomingConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml index 6a9b47190fd..a259e4766f8 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Goerli.toml @@ -1,5 +1,4 @@ ChainID = '1442' -ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '12m' MinIncomingConfirmations = 1 diff --git a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml index 79e0cb0fce5..e8833bc7312 100644 --- a/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml +++ b/core/chains/evm/config/toml/defaults/Polygon_Zkevm_Mainnet.toml @@ -1,5 +1,4 @@ ChainID = '1101' -ChainType = 'zkevm' FinalityDepth = 500 NoNewHeadsThreshold = '6m' MinIncomingConfirmations = 1 diff --git a/core/services/chainlink/config_test.go b/core/services/chainlink/config_test.go index b4a0d1b4d88..2587bbc8f5e 100644 --- a/core/services/chainlink/config_test.go +++ b/core/services/chainlink/config_test.go @@ -1217,7 +1217,7 @@ func TestConfig_Validate(t *testing.T) { - 1: 6 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, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zkevm, zksync or omitted + - ChainType: invalid value (Foo): must be one of arbitrum, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zksync or omitted - HeadTracker.HistoryDepth: invalid value (30): must be equal to or greater than FinalityDepth - GasEstimator: 2 errors: - FeeCapDefault: invalid value (101 wei): must be equal to PriceMax (99 wei) since you are using FixedPrice estimation with gas bumping disabled in EIP1559 mode - PriceMax will be used as the FeeCap for transactions instead of FeeCapDefault @@ -1226,7 +1226,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, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, zkevm, zksync or omitted + - ChainType: invalid value (Arbitrum): must be one of arbitrum, celo, gnosis, kroma, metis, optimismBedrock, scroll, wemix, 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.Nodes: 5 errors: diff --git a/core/services/ocr/contract_tracker.go b/core/services/ocr/contract_tracker.go index 1677a26410b..e4845ee3bc2 100644 --- a/core/services/ocr/contract_tracker.go +++ b/core/services/ocr/contract_tracker.go @@ -405,7 +405,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 "", config.ChainArbitrum, config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkEvm, config.ChainZkSync: + case "", config.ChainArbitrum, config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkSync: // continue } latestBlockHeight := t.getLatestBlockHeight() diff --git a/core/services/ocrcommon/block_translator.go b/core/services/ocrcommon/block_translator.go index 101681e8d3b..bc7242a6019 100644 --- a/core/services/ocrcommon/block_translator.go +++ b/core/services/ocrcommon/block_translator.go @@ -21,7 +21,7 @@ func NewBlockTranslator(cfg Config, client evmclient.Client, lggr logger.Logger) switch cfg.ChainType() { case config.ChainArbitrum: return NewArbitrumBlockTranslator(client, lggr) - case "", config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainMetis, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkEvm, config.ChainZkSync: + case "", config.ChainCelo, config.ChainGnosis, config.ChainKroma, config.ChainMetis, config.ChainOptimismBedrock, config.ChainScroll, config.ChainWeMix, config.ChainXDai, config.ChainZkSync: fallthrough default: return &l1BlockTranslator{} diff --git a/docs/CONFIG.md b/docs/CONFIG.md index a825f0ce8e9..4bcba633bcc 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -3725,7 +3725,6 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 @@ -4060,7 +4059,6 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 @@ -4228,7 +4226,6 @@ GasLimit = 5400000 AutoCreateKey = true BlockBackfillDepth = 10 BlockBackfillSkip = false -ChainType = 'zkevm' FinalityDepth = 500 FinalityTagEnabled = false LogBackfillBatchSize = 1000 From f301d4361db00d1b69339f19ab1b72b0cbaeabad Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 25 Mar 2024 17:25:48 -0500 Subject: [PATCH 19/23] Changed simulate tx method return object --- core/chains/evm/client/chain_client.go | 2 +- core/chains/evm/client/client.go | 6 +++--- core/chains/evm/client/errors.go | 4 ---- core/chains/evm/client/mocks/client.go | 12 +++++++---- core/chains/evm/client/null_client.go | 2 +- .../evm/client/simulated_backend_client.go | 2 +- core/chains/evm/client/tx_simulator.go | 20 ++----------------- core/chains/evm/client/tx_simulator_test.go | 13 ++++++------ 8 files changed, 22 insertions(+), 39 deletions(-) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 10034ca36dc..52bc1263c75 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -271,7 +271,7 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } -func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { +func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { msg := ethereum.CallMsg{ From: from, To: &to, diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 6f4e2dc5fd0..6ded2b5d8cf 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -97,7 +97,7 @@ type Client interface { IsL2() bool // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time. It will not return an error for non-zk chains. - CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error + CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError } func ContextWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) { @@ -375,6 +375,6 @@ func (client *client) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") } -func (client *client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { - return pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") +func (client *client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { + return NewSendError(pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives")) } diff --git a/core/chains/evm/client/errors.go b/core/chains/evm/client/errors.go index 194142b7ac2..26bdcee9d30 100644 --- a/core/chains/evm/client/errors.go +++ b/core/chains/evm/client/errors.go @@ -521,10 +521,6 @@ func ClassifySendError(err error, lggr logger.SugaredLogger, tx *types.Transacti ) return commonclient.ExceedsMaxFee } - if sendError.IsOutOfCounters() { - lggr.Infow("Transaction encountered zk out-of-counters error", "err", sendError) - return commonclient.OutOfCounters - } lggr.Criticalw("Unknown error encountered when sending transaction", "err", err, "etx", tx) return commonclient.Unknown } diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index 5ddbcc827d8..b3cdac3a6b6 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -7,6 +7,8 @@ import ( assets "github.com/smartcontractkit/chainlink-common/pkg/assets" + client "github.com/smartcontractkit/chainlink/v2/core/chains/evm/client" + common "github.com/ethereum/go-ethereum/common" commonclient "github.com/smartcontractkit/chainlink/v2/common/client" @@ -237,18 +239,20 @@ func (_m *Client) ChainID() (*big.Int, error) { } // CheckTxValidity provides a mock function with given fields: ctx, from, to, data -func (_m *Client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { +func (_m *Client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *client.SendError { ret := _m.Called(ctx, from, to, data) if len(ret) == 0 { panic("no return value specified for CheckTxValidity") } - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Address, []byte) error); ok { + var r0 *client.SendError + if rf, ok := ret.Get(0).(func(context.Context, common.Address, common.Address, []byte) *client.SendError); ok { r0 = rf(ctx, from, to, data) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.SendError) + } } return r0 diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 4e505db88f1..3129bcff9b0 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -232,6 +232,6 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, nil } -func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) error { +func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { return nil } diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index bc74a6bc066..9fe2ff88ba7 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -774,7 +774,7 @@ func (c *SimulatedBackendClient) ethGetLogs(ctx context.Context, result interfac } } -func (c *SimulatedBackendClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) error { +func (c *SimulatedBackendClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { return nil } diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index 4e4d976e463..2a5b5f98894 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -2,7 +2,6 @@ package client import ( "context" - "fmt" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common/hexutil" @@ -21,24 +20,9 @@ type simulatorClient interface { // This method allows a caller to determine if a tx would fail due to OOC error by simulating the transaction // It will also return service unavailable or timeout errors for callers to react to retryable errors // Used as an entry point in case custom simulation is required across different chains -func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) error { +func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) *SendError { err := simulateTransactionDefault(ctx, client, msg) - sendErr := NewSendError(err) - if sendErr == nil { - return nil - } - // Only return select errors to the caller - // Other errors are irrelevant to tx simulation to determin OOC error - if sendErr.IsOutOfCounters() { - return fmt.Errorf("%s: %w", ErrOutOfCounters, err) - } - if sendErr.IsServiceUnavailable() { - return fmt.Errorf("service not available when performing tx simulation: %w", err) - } - if sendErr.IsTimeout() { - return fmt.Errorf("timeout experienced when performing tx simulation: %w", err) - } - return nil + return NewSendError(err) } // eth_estimateGas returns out-of-counters (OOC) error if the transaction would result in an overflow diff --git a/core/chains/evm/client/tx_simulator_test.go b/core/chains/evm/client/tx_simulator_test.go index fd9377b80b7..4e270d401bf 100644 --- a/core/chains/evm/client/tx_simulator_test.go +++ b/core/chains/evm/client/tx_simulator_test.go @@ -46,8 +46,8 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) - require.NoError(t, err) + sendErr := client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) + require.Empty(t, sendErr) }) t.Run("returns error if simulation returns zk out-of-counters error", func(t *testing.T) { @@ -77,8 +77,8 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) - require.Error(t, err, client.ErrOutOfCounters) + sendErr := client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) + require.Equal(t, true, sendErr.IsOutOfCounters()) }) t.Run("returns without error if simulation returns non-OOC error", func(t *testing.T) { @@ -93,7 +93,6 @@ func TestSimulateTx_Default(t *testing.T) { return case "eth_estimateGas": resp.Error.Code = -32000 - resp.Result = `"0x100"` resp.Error.Message = "something went wrong" } return @@ -108,7 +107,7 @@ func TestSimulateTx_Default(t *testing.T) { To: &toAddress, Data: []byte("0x00"), } - err = client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) - require.NoError(t, err) + sendErr := client.SimulateTransaction(ctx, ethClient, logger.TestSugared(t), "", msg) + require.Equal(t, false, sendErr.IsOutOfCounters()) }) } From eb34bc7949621841697324178f7c7cd496b0b1fc Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 25 Mar 2024 17:34:30 -0500 Subject: [PATCH 20/23] Cleaned up stale comments --- core/chains/evm/client/client.go | 2 +- core/chains/evm/client/tx_simulator.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 6ded2b5d8cf..2758c9cf0a4 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -96,7 +96,7 @@ type Client interface { IsL2() bool - // Simulate the transaction prior to sending to catch zk out-of-counters error ahead of time. It will not return an error for non-zk chains. + // Simulate the transaction prior to sending to catch zk out-of-counters errors ahead of time CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError } diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index 2a5b5f98894..672f79f382d 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -18,7 +18,6 @@ type simulatorClient interface { // ZK chains can return an out-of-counters error // This method allows a caller to determine if a tx would fail due to OOC error by simulating the transaction -// It will also return service unavailable or timeout errors for callers to react to retryable errors // Used as an entry point in case custom simulation is required across different chains func SimulateTransaction(ctx context.Context, client simulatorClient, lggr logger.SugaredLogger, chainType config.ChainType, msg ethereum.CallMsg) *SendError { err := simulateTransactionDefault(ctx, client, msg) From 68de59475c028794fd39ba193188f4fbb68fde09 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Mon, 25 Mar 2024 18:08:40 -0500 Subject: [PATCH 21/23] Removed unused error message --- core/chains/evm/client/tx_simulator.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/chains/evm/client/tx_simulator.go b/core/chains/evm/client/tx_simulator.go index 672f79f382d..65e108bd227 100644 --- a/core/chains/evm/client/tx_simulator.go +++ b/core/chains/evm/client/tx_simulator.go @@ -10,8 +10,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/common/config" ) -const ErrOutOfCounters = "not enough counters to continue the execution" - type simulatorClient interface { CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error } From 1a2527095f5fa4f74e652d9079fdeef9a5310ebf Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 26 Mar 2024 12:48:03 -0500 Subject: [PATCH 22/23] Changed zk overflow validation method name --- core/chains/evm/client/chain_client.go | 2 +- core/chains/evm/client/client.go | 4 ++-- core/chains/evm/client/mocks/client.go | 6 +++--- core/chains/evm/client/null_client.go | 2 +- core/chains/evm/client/simulated_backend_client.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 52bc1263c75..016da44aa28 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -271,7 +271,7 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } -func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (c *chainClient) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { msg := ethereum.CallMsg{ From: from, To: &to, diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 2758c9cf0a4..27a9db9b85f 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -97,7 +97,7 @@ type Client interface { IsL2() bool // Simulate the transaction prior to sending to catch zk out-of-counters errors ahead of time - CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError + CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError } func ContextWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) { @@ -375,6 +375,6 @@ func (client *client) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") } -func (client *client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (client *client) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { return NewSendError(pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives")) } diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index b3cdac3a6b6..7a5d7f960a9 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -238,12 +238,12 @@ func (_m *Client) ChainID() (*big.Int, error) { return r0, r1 } -// CheckTxValidity provides a mock function with given fields: ctx, from, to, data -func (_m *Client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *client.SendError { +// CheckTxOverflow provides a mock function with given fields: ctx, from, to, data +func (_m *Client) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *client.SendError { ret := _m.Called(ctx, from, to, data) if len(ret) == 0 { - panic("no return value specified for CheckTxValidity") + panic("no return value specified for CheckTxOverflow") } var r0 *client.SendError diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 3129bcff9b0..98bc91a4395 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -232,6 +232,6 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, nil } -func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { +func (nc *NullClient) CheckTxOverflow(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { return nil } diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 9fe2ff88ba7..100b867c202 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -774,7 +774,7 @@ func (c *SimulatedBackendClient) ethGetLogs(ctx context.Context, result interfac } } -func (c *SimulatedBackendClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (c *SimulatedBackendClient) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { return nil } From 6798e4de6b99700aa40090b8e8a5c154c29dd687 Mon Sep 17 00:00:00 2001 From: amit-momin Date: Tue, 26 Mar 2024 14:43:33 -0500 Subject: [PATCH 23/23] Reverted method name change --- core/chains/evm/client/chain_client.go | 2 +- core/chains/evm/client/client.go | 4 ++-- core/chains/evm/client/mocks/client.go | 6 +++--- core/chains/evm/client/null_client.go | 2 +- core/chains/evm/client/simulated_backend_client.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/chains/evm/client/chain_client.go b/core/chains/evm/client/chain_client.go index 016da44aa28..52bc1263c75 100644 --- a/core/chains/evm/client/chain_client.go +++ b/core/chains/evm/client/chain_client.go @@ -271,7 +271,7 @@ func (c *chainClient) LatestFinalizedBlock(ctx context.Context) (*evmtypes.Head, return c.multiNode.LatestFinalizedBlock(ctx) } -func (c *chainClient) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (c *chainClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { msg := ethereum.CallMsg{ From: from, To: &to, diff --git a/core/chains/evm/client/client.go b/core/chains/evm/client/client.go index 27a9db9b85f..2758c9cf0a4 100644 --- a/core/chains/evm/client/client.go +++ b/core/chains/evm/client/client.go @@ -97,7 +97,7 @@ type Client interface { IsL2() bool // Simulate the transaction prior to sending to catch zk out-of-counters errors ahead of time - CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError + CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError } func ContextWithDefaultTimeout() (ctx context.Context, cancel context.CancelFunc) { @@ -375,6 +375,6 @@ func (client *client) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives") } -func (client *client) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (client *client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { return NewSendError(pkgerrors.New("not implemented. client was deprecated. New methods are added only to satisfy type constraints while we are migrating to new alternatives")) } diff --git a/core/chains/evm/client/mocks/client.go b/core/chains/evm/client/mocks/client.go index 7a5d7f960a9..b3cdac3a6b6 100644 --- a/core/chains/evm/client/mocks/client.go +++ b/core/chains/evm/client/mocks/client.go @@ -238,12 +238,12 @@ func (_m *Client) ChainID() (*big.Int, error) { return r0, r1 } -// CheckTxOverflow provides a mock function with given fields: ctx, from, to, data -func (_m *Client) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *client.SendError { +// CheckTxValidity provides a mock function with given fields: ctx, from, to, data +func (_m *Client) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *client.SendError { ret := _m.Called(ctx, from, to, data) if len(ret) == 0 { - panic("no return value specified for CheckTxOverflow") + panic("no return value specified for CheckTxValidity") } var r0 *client.SendError diff --git a/core/chains/evm/client/null_client.go b/core/chains/evm/client/null_client.go index 98bc91a4395..3129bcff9b0 100644 --- a/core/chains/evm/client/null_client.go +++ b/core/chains/evm/client/null_client.go @@ -232,6 +232,6 @@ func (nc *NullClient) LatestFinalizedBlock(_ context.Context) (*evmtypes.Head, e return nil, nil } -func (nc *NullClient) CheckTxOverflow(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { +func (nc *NullClient) CheckTxValidity(_ context.Context, _ common.Address, _ common.Address, _ []byte) *SendError { return nil } diff --git a/core/chains/evm/client/simulated_backend_client.go b/core/chains/evm/client/simulated_backend_client.go index 100b867c202..9fe2ff88ba7 100644 --- a/core/chains/evm/client/simulated_backend_client.go +++ b/core/chains/evm/client/simulated_backend_client.go @@ -774,7 +774,7 @@ func (c *SimulatedBackendClient) ethGetLogs(ctx context.Context, result interfac } } -func (c *SimulatedBackendClient) CheckTxOverflow(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { +func (c *SimulatedBackendClient) CheckTxValidity(ctx context.Context, from common.Address, to common.Address, data []byte) *SendError { return nil }