From 62e1bfd8a60fa93a8e3801fdfcbee20ac79afae3 Mon Sep 17 00:00:00 2001 From: fx0x55 <80245546+fx0x55@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:53:01 +0800 Subject: [PATCH 1/3] refactor: optimize some returns (#1628) * refactor: Optimize return delete redundant judgments * changelog --- changelog.md | 4 ++++ common/chain.go | 5 +---- rpc/namespaces/ethereum/debug/utils.go | 6 +----- x/crosschain/keeper/cctx_utils.go | 5 +---- x/crosschain/keeper/msg_server_tss_voter.go | 5 +---- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/changelog.md b/changelog.md index 1adfa29044..18632d0202 100644 --- a/changelog.md +++ b/changelog.md @@ -31,6 +31,10 @@ * [1591](https://github.com/zeta-chain/node/pull/1591) - support lower gas limit for voting on inbound and outbound transactions * [1592](https://github.com/zeta-chain/node/issues/1592) - check inbound tracker tx hash against Tss address and some refactor on inTx observation +### Refactoring + +* [1628](https://github.com/zeta-chain/node/pull/1628) optimize return and simplify code + ## Version: v12.0.0 ### Breaking Changes diff --git a/common/chain.go b/common/chain.go index d6762c1a91..cc3dde3722 100644 --- a/common/chain.go +++ b/common/chain.go @@ -23,10 +23,7 @@ type Chains []Chain // IsEqual compare two chain to see whether they represent the same chain func (chain Chain) IsEqual(c Chain) bool { - if chain.ChainId == c.ChainId { - return true - } - return false + return chain.ChainId == c.ChainId } func (chain Chain) IsZetaChain() bool { diff --git a/rpc/namespaces/ethereum/debug/utils.go b/rpc/namespaces/ethereum/debug/utils.go index a65134977d..bfd8ced432 100644 --- a/rpc/namespaces/ethereum/debug/utils.go +++ b/rpc/namespaces/ethereum/debug/utils.go @@ -30,11 +30,7 @@ import ( func isCPUProfileConfigurationActivated(ctx *server.Context) bool { // TODO: use same constants as server/start.go // constant declared in start.go cannot be imported (cyclical dependency) - const flagCPUProfile = "cpu-profile" - if cpuProfile := ctx.Viper.GetString(flagCPUProfile); cpuProfile != "" { - return true - } - return false + return ctx.Viper.GetString("cpu-profile") != "" } // ExpandHome expands home directory in file paths. diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 528139af69..7b43123cbe 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -124,8 +124,5 @@ func (k Keeper) GetRevertGasLimit(ctx sdk.Context, cctx types.CrossChainTx) (uin func IsPending(cctx types.CrossChainTx) bool { // pending inbound is not considered a "pending" state because it has not reached consensus yet - if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound || cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { - return true - } - return false + return cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound || cctx.CctxStatus.Status == types.CctxStatus_PendingRevert } diff --git a/x/crosschain/keeper/msg_server_tss_voter.go b/x/crosschain/keeper/msg_server_tss_voter.go index 13a97e58ae..29672a1a6d 100644 --- a/x/crosschain/keeper/msg_server_tss_voter.go +++ b/x/crosschain/keeper/msg_server_tss_voter.go @@ -116,8 +116,5 @@ func (k msgServer) CreateTSSVoter(goCtx context.Context, msg *types.MsgCreateTSS // IsAuthorizedNodeAccount checks whether a signer is authorized to sign , by checking their address against the observer mapper which contains the observer list for the chain and type func (k Keeper) IsAuthorizedNodeAccount(ctx sdk.Context, address string) bool { _, found := k.zetaObserverKeeper.GetNodeAccount(ctx, address) - if found { - return true - } - return false + return found } From 72f6407d3741d0d7a44ec44f3a46dd27c1cf7235 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 26 Jan 2024 13:40:29 -0500 Subject: [PATCH 2/3] refactor: add evm amount calculation for tss migration (#1619) * add evm amount calculation for tss migration * add changelog entry * add test for multiplier * Update common/gas_limits.go Co-authored-by: Lucas Bertrand * add additional checks for unit tests * rename crosschainTX to cctx --------- Co-authored-by: Lucas Bertrand --- changelog.md | 3 + common/gas_limits.go | 26 +++ .../keeper/msg_server_migrate_tss_funds.go | 13 ++ .../msg_server_migrate_tss_funds_test.go | 94 ++++++++--- x/crosschain/types/errors.go | 2 + x/crosschain/types/keys.go | 2 + zetaclient/evm_signer.go | 154 +++++++++--------- 7 files changed, 194 insertions(+), 100 deletions(-) create mode 100644 common/gas_limits.go diff --git a/changelog.md b/changelog.md index 18632d0202..bdd9866e84 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,9 @@ * [1628](https://github.com/zeta-chain/node/pull/1628) optimize return and simplify code +### Refactoring +* [1619](https://github.com/zeta-chain/node/pull/1619) - Add evm fee calculation to tss migration of evm chains + ## Version: v12.0.0 ### Breaking Changes diff --git a/common/gas_limits.go b/common/gas_limits.go new file mode 100644 index 0000000000..bbd1f0a6c5 --- /dev/null +++ b/common/gas_limits.go @@ -0,0 +1,26 @@ +package common + +import ( + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // EVMSend is the gas limit required to transfer tokens on an EVM based chain + EVMSend = 21000 + // TODO: Move gas limits from zeta-client to this file + // https://github.com/zeta-chain/node/issues/1606 +) + +// MultiplyGasPrice multiplies the median gas price by the given multiplier and returns the truncated value +func MultiplyGasPrice(medianGasPrice sdkmath.Uint, multiplierString string) (sdkmath.Uint, error) { + multiplier, err := sdk.NewDecFromStr(multiplierString) + if err != nil { + return sdkmath.ZeroUint(), err + } + gasPrice, err := sdk.NewDecFromStr(medianGasPrice.String()) + if err != nil { + return sdkmath.ZeroUint(), err + } + return sdkmath.NewUintFromString(gasPrice.Mul(multiplier).TruncateInt().String()), nil +} diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go index 081e2d6490..a552d0feb4 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go @@ -116,6 +116,19 @@ func (k Keeper) MigrateTSSFundsForChain(ctx sdk.Context, chainID int64, amount s } cctx.InboundTxParams.Sender = ethAddressOld.String() cctx.GetCurrentOutTxParam().Receiver = ethAddressNew.String() + // Tss migration is a send transaction, so the gas limit is set to 21000 + cctx.GetCurrentOutTxParam().OutboundTxGasLimit = common.EVMSend + // Multiple current gas price with standard multiplier to add some buffer + multipliedGasPrice, err := common.MultiplyGasPrice(medianGasPrice, types.TssMigrationGasMultiplierEVM) + if err != nil { + return err + } + cctx.GetCurrentOutTxParam().OutboundTxGasPrice = multipliedGasPrice.String() + evmFee := sdkmath.NewUint(cctx.GetCurrentOutTxParam().OutboundTxGasLimit).Mul(multipliedGasPrice) + if evmFee.GT(amount) { + return errorsmod.Wrap(types.ErrInsufficientFundsTssMigration, fmt.Sprintf("insufficient funds to pay for gas fee, amount: %s, gas fee: %s, chainid: %d", amount.String(), evmFee.String(), chainID)) + } + cctx.GetCurrentOutTxParam().Amount = amount.Sub(evmFee) } // Set the sender and receiver addresses for Bitcoin chain if common.IsBitcoinChain(chainID) { diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go index 436a6b69c9..4d909abdf0 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go @@ -6,7 +6,7 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" @@ -15,6 +15,33 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +func TestKeeper_MigrateTSSFundsForChain(t *testing.T) { + t.Run("test gas price multiplier", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + admin := sample.AccAddress() + setAdminPolicies(ctx, zk, admin) + msgServer := keeper.NewMsgServerImpl(*k) + chain := getValidEthChain(t) + amount := sdkmath.NewUintFromString("10000000000000000000") + indexString, _ := setupTssMigrationParams(zk, k, ctx, *chain, amount, true, true) + gp, found := k.GetMedianGasPriceInUint(ctx, chain.ChainId) + require.True(t, found) + _, err := msgServer.MigrateTssFunds(ctx, &crosschaintypes.MsgMigrateTssFunds{ + Creator: admin, + ChainId: chain.ChainId, + Amount: amount, + }) + require.NoError(t, err) + hash := crypto.Keccak256Hash([]byte(indexString)) + index := hash.Hex() + cctx, found := k.GetCrossChainTx(ctx, index) + require.True(t, found) + multipliedValue, err := common.MultiplyGasPrice(gp, crosschaintypes.TssMigrationGasMultiplierEVM) + require.NoError(t, err) + require.Equal(t, multipliedValue.String(), cctx.GetCurrentOutTxParam().OutboundTxGasPrice) + + }) +} func TestMsgServer_MigrateTssFunds(t *testing.T) { t.Run("successfully create tss migration cctx", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -22,18 +49,40 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { setAdminPolicies(ctx, zk, admin) msgServer := keeper.NewMsgServerImpl(*k) chain := getValidEthChain(t) - amount := sdkmath.NewUint(100) + amount := sdkmath.NewUintFromString("10000000000000000000") + indexString, _ := setupTssMigrationParams(zk, k, ctx, *chain, amount, true, true) + _, err := msgServer.MigrateTssFunds(ctx, &crosschaintypes.MsgMigrateTssFunds{ + Creator: admin, + ChainId: chain.ChainId, + Amount: amount, + }) + require.NoError(t, err) + hash := crypto.Keccak256Hash([]byte(indexString)) + index := hash.Hex() + cctx, found := k.GetCrossChainTx(ctx, index) + require.True(t, found) + feeCalculated := sdk.NewUint(cctx.GetCurrentOutTxParam().OutboundTxGasLimit). + Mul(sdkmath.NewUintFromString(cctx.GetCurrentOutTxParam().OutboundTxGasPrice)) + require.Equal(t, cctx.GetCurrentOutTxParam().Amount.String(), amount.Sub(feeCalculated).String()) + }) + t.Run("not enough funds in tss address for migration", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) + admin := sample.AccAddress() + setAdminPolicies(ctx, zk, admin) + msgServer := keeper.NewMsgServerImpl(*k) + chain := getValidEthChain(t) + amount := sdkmath.NewUintFromString("100") indexString, _ := setupTssMigrationParams(zk, k, ctx, *chain, amount, true, true) _, err := msgServer.MigrateTssFunds(ctx, &crosschaintypes.MsgMigrateTssFunds{ Creator: admin, ChainId: chain.ChainId, Amount: amount, }) - assert.NoError(t, err) + require.ErrorContains(t, err, crosschaintypes.ErrCannotMigrateTssFunds.Error()) hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() _, found := k.GetCrossChainTx(ctx, index) - assert.True(t, found) + require.False(t, found) }) t.Run("unable to migrate funds if new TSS is not created ", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -41,19 +90,18 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { setAdminPolicies(ctx, zk, admin) msgServer := keeper.NewMsgServerImpl(*k) chain := getValidEthChain(t) - amount := sdkmath.NewUint(100) + amount := sdkmath.NewUintFromString("10000000000000000000") indexString, _ := setupTssMigrationParams(zk, k, ctx, *chain, amount, false, true) _, err := msgServer.MigrateTssFunds(ctx, &crosschaintypes.MsgMigrateTssFunds{ Creator: admin, ChainId: chain.ChainId, Amount: amount, }) - assert.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) - assert.ErrorContains(t, err, "no new tss address has been generated") + require.ErrorContains(t, err, "no new tss address has been generated") hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() _, found := k.GetCrossChainTx(ctx, index) - assert.False(t, found) + require.False(t, found) }) t.Run("unable to migrate funds when nonce low does not match nonce high", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -61,7 +109,7 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { setAdminPolicies(ctx, zk, admin) msgServer := keeper.NewMsgServerImpl(*k) chain := getValidEthChain(t) - amount := sdkmath.NewUint(100) + amount := sdkmath.NewUintFromString("10000000000000000000") indexString, tssPubkey := setupTssMigrationParams(zk, k, ctx, *chain, amount, true, true) k.GetObserverKeeper().SetPendingNonces(ctx, observertypes.PendingNonces{ NonceLow: 1, @@ -74,12 +122,12 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { ChainId: chain.ChainId, Amount: amount, }) - assert.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) - assert.ErrorContains(t, err, "cannot migrate funds when there are pending nonces") + require.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) + require.ErrorContains(t, err, "cannot migrate funds when there are pending nonces") hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() _, found := k.GetCrossChainTx(ctx, index) - assert.False(t, found) + require.False(t, found) }) t.Run("unable to migrate funds when a pending cctx is presnt in migration info", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -87,7 +135,7 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { setAdminPolicies(ctx, zk, admin) msgServer := keeper.NewMsgServerImpl(*k) chain := getValidEthChain(t) - amount := sdkmath.NewUint(100) + amount := sdkmath.NewUintFromString("10000000000000000000") indexString, tssPubkey := setupTssMigrationParams(zk, k, ctx, *chain, amount, true, true) k.GetObserverKeeper().SetPendingNonces(ctx, observertypes.PendingNonces{ NonceLow: 1, @@ -107,14 +155,14 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { ChainId: chain.ChainId, Amount: amount, }) - assert.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) - assert.ErrorContains(t, err, "cannot migrate funds while there are pending migrations") + require.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) + require.ErrorContains(t, err, "cannot migrate funds while there are pending migrations") hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() _, found := k.GetCrossChainTx(ctx, index) - assert.False(t, found) + require.False(t, found) _, found = k.GetCrossChainTx(ctx, existingCctx.Index) - assert.True(t, found) + require.True(t, found) }) t.Run("unable to migrate funds if current TSS is not present in TSSHistory and no new TSS has been generated", func(t *testing.T) { @@ -123,10 +171,10 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { setAdminPolicies(ctx, zk, admin) msgServer := keeper.NewMsgServerImpl(*k) chain := getValidEthChain(t) - amount := sdkmath.NewUint(100) + amount := sdkmath.NewUintFromString("10000000000000000000") indexString, _ := setupTssMigrationParams(zk, k, ctx, *chain, amount, false, false) currentTss, found := k.GetObserverKeeper().GetTSS(ctx) - assert.True(t, found) + require.True(t, found) newTss := sample.Tss() newTss.FinalizedZetaHeight = currentTss.FinalizedZetaHeight - 10 newTss.KeyGenZetaHeight = currentTss.KeyGenZetaHeight - 10 @@ -136,12 +184,12 @@ func TestMsgServer_MigrateTssFunds(t *testing.T) { ChainId: chain.ChainId, Amount: amount, }) - assert.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) - assert.ErrorContains(t, err, "current tss is the latest") + require.ErrorIs(t, err, crosschaintypes.ErrCannotMigrateTssFunds) + require.ErrorContains(t, err, "current tss is the latest") hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() _, found = k.GetCrossChainTx(ctx, index) - assert.False(t, found) + require.False(t, found) }) } func setupTssMigrationParams( @@ -192,7 +240,7 @@ func setupTssMigrationParams( ChainId: chain.ChainId, Signers: nil, BlockNums: nil, - Prices: []uint64{1, 1, 1}, + Prices: []uint64{100000, 100000, 100000}, MedianIndex: 1, }) k.GetObserverKeeper().SetChainNonces(ctx, observertypes.ChainNonces{ diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 8dc589e878..12bbd4ad84 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -43,4 +43,6 @@ var ( ErrReceiverIsEmpty = errorsmod.Register(ModuleName, 1142, "receiver is empty") ErrUnsupportedStatus = errorsmod.Register(ModuleName, 1143, "unsupported status") ErrObservedTxAlreadyFinalized = errorsmod.Register(ModuleName, 1144, "observed tx already finalized") + + ErrInsufficientFundsTssMigration = errorsmod.Register(ModuleName, 1145, "insufficient funds for TSS migration") ) diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go index 8777d8df0c..4adac4d73f 100644 --- a/x/crosschain/types/keys.go +++ b/x/crosschain/types/keys.go @@ -25,6 +25,8 @@ const ( MemStoreKey = "mem_metacore" ProtocolFee = 2000000000000000000 + //TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . This is done to avoid the tss migration tx getting stuck in the mempool + TssMigrationGasMultiplierEVM = "2.5" ) func GetProtocolFee() sdk.Uint { diff --git a/zetaclient/evm_signer.go b/zetaclient/evm_signer.go index c961a627e0..204ff75cc7 100644 --- a/zetaclient/evm_signer.go +++ b/zetaclient/evm_signer.go @@ -294,7 +294,7 @@ func (signer *EVMSigner) SignCommandTx( return tx, nil } if cmd == common.CmdMigrateTssFunds { - tx := ethtypes.NewTransaction(outboundParams.OutboundTxTssNonce, to, outboundParams.Amount.BigInt(), 21000, gasPrice, nil) + tx := ethtypes.NewTransaction(outboundParams.OutboundTxTssNonce, to, outboundParams.Amount.BigInt(), outboundParams.OutboundTxGasLimit, gasPrice, nil) hashBytes := signer.ethSigner.Hash(tx).Bytes() sig, err := signer.tssSigner.Sign(hashBytes, height, outboundParams.OutboundTxTssNonce, signer.chain, "") if err != nil { @@ -318,7 +318,7 @@ func (signer *EVMSigner) SignCommandTx( } func (signer *EVMSigner) TryProcessOutTx( - send *types.CrossChainTx, + cctx *types.CrossChainTx, outTxMan *OutTxProcessorManager, outTxID string, chainclient ChainClient, @@ -327,10 +327,10 @@ func (signer *EVMSigner) TryProcessOutTx( ) { logger := signer.logger.With(). Str("outTxID", outTxID). - Str("SendHash", send.Index). + Str("SendHash", cctx.Index). Logger() logger.Info().Msgf("start processing outTxID %s", outTxID) - logger.Info().Msgf("EVM Chain TryProcessOutTx: %s, value %d to %s", send.Index, send.GetCurrentOutTxParam().Amount.BigInt(), send.GetCurrentOutTxParam().Receiver) + logger.Info().Msgf("EVM Chain TryProcessOutTx: %s, value %d to %s", cctx.Index, cctx.GetCurrentOutTxParam().Amount.BigInt(), cctx.GetCurrentOutTxParam().Receiver) defer func() { outTxMan.EndTryProcess(outTxID) @@ -339,23 +339,23 @@ func (signer *EVMSigner) TryProcessOutTx( var to ethcommon.Address var toChain *common.Chain - if send.CctxStatus.Status == types.CctxStatus_PendingRevert { - to = ethcommon.HexToAddress(send.InboundTxParams.Sender) - toChain = common.GetChainFromChainID(send.InboundTxParams.SenderChainId) + if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { + to = ethcommon.HexToAddress(cctx.InboundTxParams.Sender) + toChain = common.GetChainFromChainID(cctx.InboundTxParams.SenderChainId) if toChain == nil { - logger.Error().Msgf("Unknown chain: %d", send.InboundTxParams.SenderChainId) + logger.Error().Msgf("Unknown chain: %d", cctx.InboundTxParams.SenderChainId) return } logger.Info().Msgf("Abort: reverting inbound") - } else if send.CctxStatus.Status == types.CctxStatus_PendingOutbound { - to = ethcommon.HexToAddress(send.GetCurrentOutTxParam().Receiver) - toChain = common.GetChainFromChainID(send.GetCurrentOutTxParam().ReceiverChainId) + } else if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + to = ethcommon.HexToAddress(cctx.GetCurrentOutTxParam().Receiver) + toChain = common.GetChainFromChainID(cctx.GetCurrentOutTxParam().ReceiverChainId) if toChain == nil { - logger.Error().Msgf("Unknown chain: %d", send.GetCurrentOutTxParam().ReceiverChainId) + logger.Error().Msgf("Unknown chain: %d", cctx.GetCurrentOutTxParam().ReceiverChainId) return } } else { - logger.Info().Msgf("Transaction doesn't need to be processed status: %d", send.CctxStatus.Status) + logger.Info().Msgf("Transaction doesn't need to be processed status: %d", cctx.CctxStatus.Status) return } evmClient, ok := chainclient.(*EVMChainClient) @@ -365,8 +365,8 @@ func (signer *EVMSigner) TryProcessOutTx( } // Early return if the cctx is already processed - nonce := send.GetCurrentOutTxParam().OutboundTxTssNonce - included, confirmed, err := evmClient.IsSendOutTxProcessed(send.Index, nonce, send.GetCurrentOutTxParam().CoinType, logger) + nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce + included, confirmed, err := evmClient.IsSendOutTxProcessed(cctx.Index, nonce, cctx.GetCurrentOutTxParam().CoinType, logger) if err != nil { logger.Error().Err(err).Msg("IsSendOutTxProcessed failed") } @@ -376,27 +376,27 @@ func (signer *EVMSigner) TryProcessOutTx( } var message []byte - if send.GetCurrentOutTxParam().CoinType != common.CoinType_Cmd { - message, err = base64.StdEncoding.DecodeString(send.RelayedMessage) + if cctx.GetCurrentOutTxParam().CoinType != common.CoinType_Cmd { + message, err = base64.StdEncoding.DecodeString(cctx.RelayedMessage) if err != nil { - logger.Err(err).Msgf("decode CCTX.Message %s error", send.RelayedMessage) + logger.Err(err).Msgf("decode CCTX.Message %s error", cctx.RelayedMessage) } } - gasLimit := send.GetCurrentOutTxParam().OutboundTxGasLimit + gasLimit := cctx.GetCurrentOutTxParam().OutboundTxGasLimit if gasLimit < 100_000 { gasLimit = 100_000 - logger.Warn().Msgf("gasLimit %d is too low; set to %d", send.GetCurrentOutTxParam().OutboundTxGasLimit, gasLimit) + logger.Warn().Msgf("gasLimit %d is too low; set to %d", cctx.GetCurrentOutTxParam().OutboundTxGasLimit, gasLimit) } if gasLimit > 1_000_000 { gasLimit = 1_000_000 - logger.Warn().Msgf("gasLimit %d is too high; set to %d", send.GetCurrentOutTxParam().OutboundTxGasLimit, gasLimit) + logger.Warn().Msgf("gasLimit %d is too high; set to %d", cctx.GetCurrentOutTxParam().OutboundTxGasLimit, gasLimit) } - logger.Info().Msgf("chain %s minting %d to %s, nonce %d, finalized zeta bn %d", toChain, send.InboundTxParams.Amount, to.Hex(), nonce, send.InboundTxParams.InboundTxFinalizedZetaHeight) - sendHash, err := hex.DecodeString(send.Index[2:]) // remove the leading 0x + logger.Info().Msgf("chain %s minting %d to %s, nonce %d, finalized zeta bn %d", toChain, cctx.InboundTxParams.Amount, to.Hex(), nonce, cctx.InboundTxParams.InboundTxFinalizedZetaHeight) + sendHash, err := hex.DecodeString(cctx.Index[2:]) // remove the leading 0x if err != nil || len(sendHash) != 32 { - logger.Error().Err(err).Msgf("decode CCTX %s error", send.Index) + logger.Error().Err(err).Msgf("decode CCTX %s error", cctx.Index) return } var sendhash [32]byte @@ -408,7 +408,7 @@ func (signer *EVMSigner) TryProcessOutTx( // The code below is a fix for https://github.com/zeta-chain/node/issues/1085 // doesn't close directly the issue because we should determine if we want to keep using SuggestGasPrice if no OutboundTxGasPrice // we should possibly remove it completely and return an error if no OutboundTxGasPrice is provided because it means no fee is processed on ZetaChain - specified, ok := new(big.Int).SetString(send.GetCurrentOutTxParam().OutboundTxGasPrice, 10) + specified, ok := new(big.Int).SetString(cctx.GetCurrentOutTxParam().OutboundTxGasPrice, 10) if !ok { if common.IsEthereumChain(toChain.ChainId) { suggested, err := signer.client.SuggestGasPrice(context.Background()) @@ -418,7 +418,7 @@ func (signer *EVMSigner) TryProcessOutTx( } gasprice = roundUpToNearestGwei(suggested) } else { - logger.Error().Err(err).Msgf("cannot convert gas price %s ", send.GetCurrentOutTxParam().OutboundTxGasPrice) + logger.Error().Err(err).Msgf("cannot convert gas price %s ", cctx.GetCurrentOutTxParam().OutboundTxGasPrice) return } } else { @@ -444,138 +444,138 @@ func (signer *EVMSigner) TryProcessOutTx( var tx *ethtypes.Transaction - if send.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command - to := ethcommon.HexToAddress(send.GetCurrentOutTxParam().Receiver) + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Cmd { // admin command + to := ethcommon.HexToAddress(cctx.GetCurrentOutTxParam().Receiver) if to == (ethcommon.Address{}) { - logger.Error().Msgf("invalid receiver %s", send.GetCurrentOutTxParam().Receiver) + logger.Error().Msgf("invalid receiver %s", cctx.GetCurrentOutTxParam().Receiver) return } - msg := strings.Split(send.RelayedMessage, ":") + msg := strings.Split(cctx.RelayedMessage, ":") if len(msg) != 2 { logger.Error().Msgf("invalid message %s", msg) return } - tx, err = signer.SignCommandTx(msg[0], msg[1], to, send.GetCurrentOutTxParam(), gasLimit, gasprice, height) - } else if send.InboundTxParams.SenderChainId == zetaBridge.ZetaChain().ChainId && send.CctxStatus.Status == types.CctxStatus_PendingOutbound && flags.IsOutboundEnabled { - if send.GetCurrentOutTxParam().CoinType == common.CoinType_Gas { - logger.Info().Msgf("SignWithdrawTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + tx, err = signer.SignCommandTx(msg[0], msg[1], to, cctx.GetCurrentOutTxParam(), gasLimit, gasprice, height) + } else if cctx.InboundTxParams.SenderChainId == zetaBridge.ZetaChain().ChainId && cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound && flags.IsOutboundEnabled { + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Gas { + logger.Info().Msgf("SignWithdrawTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignWithdrawTx( to, - send.GetCurrentOutTxParam().Amount.BigInt(), - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } - if send.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 { - asset := ethcommon.HexToAddress(send.InboundTxParams.Asset) - logger.Info().Msgf("SignERC20WithdrawTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 { + asset := ethcommon.HexToAddress(cctx.InboundTxParams.Asset) + logger.Info().Msgf("SignERC20WithdrawTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignERC20WithdrawTx( to, asset, - send.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().Amount.BigInt(), gasLimit, - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } - if send.GetCurrentOutTxParam().CoinType == common.CoinType_Zeta { - logger.Info().Msgf("SignOutboundTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Zeta { + logger.Info().Msgf("SignOutboundTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignOutboundTx( - ethcommon.HexToAddress(send.InboundTxParams.Sender), - big.NewInt(send.InboundTxParams.SenderChainId), + ethcommon.HexToAddress(cctx.InboundTxParams.Sender), + big.NewInt(cctx.InboundTxParams.SenderChainId), to, - send.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().Amount.BigInt(), gasLimit, message, sendhash, - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } - } else if send.CctxStatus.Status == types.CctxStatus_PendingRevert && send.OutboundTxParams[0].ReceiverChainId == zetaBridge.ZetaChain().ChainId { - if send.GetCurrentOutTxParam().CoinType == common.CoinType_Gas { - logger.Info().Msgf("SignWithdrawTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert && cctx.OutboundTxParams[0].ReceiverChainId == zetaBridge.ZetaChain().ChainId { + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_Gas { + logger.Info().Msgf("SignWithdrawTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignWithdrawTx( to, - send.GetCurrentOutTxParam().Amount.BigInt(), - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } - if send.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 { - asset := ethcommon.HexToAddress(send.InboundTxParams.Asset) - logger.Info().Msgf("SignERC20WithdrawTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + if cctx.GetCurrentOutTxParam().CoinType == common.CoinType_ERC20 { + asset := ethcommon.HexToAddress(cctx.InboundTxParams.Asset) + logger.Info().Msgf("SignERC20WithdrawTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignERC20WithdrawTx( to, asset, - send.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().Amount.BigInt(), gasLimit, - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } - } else if send.CctxStatus.Status == types.CctxStatus_PendingRevert { - logger.Info().Msgf("SignRevertTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { + logger.Info().Msgf("SignRevertTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignRevertTx( - ethcommon.HexToAddress(send.InboundTxParams.Sender), - big.NewInt(send.OutboundTxParams[0].ReceiverChainId), + ethcommon.HexToAddress(cctx.InboundTxParams.Sender), + big.NewInt(cctx.OutboundTxParams[0].ReceiverChainId), to.Bytes(), - big.NewInt(send.GetCurrentOutTxParam().ReceiverChainId), - send.GetCurrentOutTxParam().Amount.BigInt(), + big.NewInt(cctx.GetCurrentOutTxParam().ReceiverChainId), + cctx.GetCurrentOutTxParam().Amount.BigInt(), gasLimit, message, sendhash, - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) - } else if send.CctxStatus.Status == types.CctxStatus_PendingOutbound { - logger.Info().Msgf("SignOutboundTx: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) + } else if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + logger.Info().Msgf("SignOutboundTx: %d => %s, nonce %d, gasprice %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice) tx, err = signer.SignOutboundTx( - ethcommon.HexToAddress(send.InboundTxParams.Sender), - big.NewInt(send.InboundTxParams.SenderChainId), + ethcommon.HexToAddress(cctx.InboundTxParams.Sender), + big.NewInt(cctx.InboundTxParams.SenderChainId), to, - send.GetCurrentOutTxParam().Amount.BigInt(), + cctx.GetCurrentOutTxParam().Amount.BigInt(), gasLimit, message, sendhash, - send.GetCurrentOutTxParam().OutboundTxTssNonce, + cctx.GetCurrentOutTxParam().OutboundTxTssNonce, gasprice, height, ) } if err != nil { - logger.Warn().Err(err).Msgf("signer SignOutbound error: nonce %d chain %d", send.GetCurrentOutTxParam().OutboundTxTssNonce, send.GetCurrentOutTxParam().ReceiverChainId) + logger.Warn().Err(err).Msgf("signer SignOutbound error: nonce %d chain %d", cctx.GetCurrentOutTxParam().OutboundTxTssNonce, cctx.GetCurrentOutTxParam().ReceiverChainId) return } - logger.Info().Msgf("Key-sign success: %d => %s, nonce %d", send.InboundTxParams.SenderChainId, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce) + logger.Info().Msgf("Key-sign success: %d => %s, nonce %d", cctx.InboundTxParams.SenderChainId, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce) _, err = zetaBridge.GetObserverList() if err != nil { - logger.Warn().Err(err).Msgf("unable to get observer list: chain %d observation %s", send.GetCurrentOutTxParam().OutboundTxTssNonce, observertypes.ObservationType_OutBoundTx.String()) + logger.Warn().Err(err).Msgf("unable to get observer list: chain %d observation %s", cctx.GetCurrentOutTxParam().OutboundTxTssNonce, observertypes.ObservationType_OutBoundTx.String()) } if tx != nil { outTxHash := tx.Hash().Hex() - logger.Info().Msgf("on chain %s nonce %d, outTxHash %s signer %s", signer.chain, send.GetCurrentOutTxParam().OutboundTxTssNonce, outTxHash, myID) + logger.Info().Msgf("on chain %s nonce %d, outTxHash %s signer %s", signer.chain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, outTxHash, myID) //if len(signers) == 0 || myid == signers[send.OutboundTxParams.Broadcaster] || myid == signers[int(send.OutboundTxParams.Broadcaster+1)%len(signers)] { backOff := 1000 * time.Millisecond // retry loop: 1s, 2s, 4s, 8s, 16s in case of RPC error for i := 0; i < 5; i++ { - logger.Info().Msgf("broadcasting tx %s to chain %s: nonce %d, retry %d", outTxHash, toChain, send.GetCurrentOutTxParam().OutboundTxTssNonce, i) + logger.Info().Msgf("broadcasting tx %s to chain %s: nonce %d, retry %d", outTxHash, toChain, cctx.GetCurrentOutTxParam().OutboundTxTssNonce, i) // #nosec G404 randomness is not a security issue here time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond) // FIXME: use backoff err := signer.Broadcast(tx) if err != nil { log.Warn().Err(err).Msgf("OutTx Broadcast error") - retry, report := HandleBroadcastError(err, strconv.FormatUint(send.GetCurrentOutTxParam().OutboundTxTssNonce, 10), toChain.String(), outTxHash) + retry, report := HandleBroadcastError(err, strconv.FormatUint(cctx.GetCurrentOutTxParam().OutboundTxTssNonce, 10), toChain.String(), outTxHash) if report { signer.reportToOutTxTracker(zetaBridge, toChain.ChainId, tx.Nonce(), outTxHash, logger) } @@ -585,7 +585,7 @@ func (signer *EVMSigner) TryProcessOutTx( backOff *= 2 continue } - logger.Info().Msgf("Broadcast success: nonce %d to chain %s outTxHash %s", send.GetCurrentOutTxParam().OutboundTxTssNonce, toChain, outTxHash) + logger.Info().Msgf("Broadcast success: nonce %d to chain %s outTxHash %s", cctx.GetCurrentOutTxParam().OutboundTxTssNonce, toChain, outTxHash) signer.reportToOutTxTracker(zetaBridge, toChain.ChainId, tx.Nonce(), outTxHash, logger) break // successful broadcast; no need to retry } From 64ed9b116dc555f745a44b0b3b5a750c305ad97f Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:00:25 -0600 Subject: [PATCH 3/3] fix: outtx tracker report timeout (#1616) * fix outtx tracker report timeout * make generate * remove err when print Info logs * update changelog --------- Co-authored-by: Lucas Bertrand --- changelog.md | 3 ++ zetaclient/evm_signer.go | 83 +++++++++++++++++++++++++-------- zetaclient/interfaces.go | 1 + zetaclient/tx.go | 10 ++++ zetaclient/zetacore_observer.go | 3 +- 5 files changed, 80 insertions(+), 20 deletions(-) diff --git a/changelog.md b/changelog.md index bdd9866e84..16d486fb0c 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,9 @@ ## Unreleased +### Fixes +* [1610](https://github.com/zeta-chain/node/issues/1610) - add pending outtx hash to tracker after monitoring for 10 minutes + ## Version: v12.1.0 ### Tests diff --git a/zetaclient/evm_signer.go b/zetaclient/evm_signer.go index 204ff75cc7..05a476a64b 100644 --- a/zetaclient/evm_signer.go +++ b/zetaclient/evm_signer.go @@ -21,12 +21,15 @@ import ( "github.com/rs/zerolog/log" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/zetacore/common" + crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) const ( + OutTxInclusionTimeout = 10 * time.Minute OutTxTrackerReportTimeout = 10 * time.Minute + ZetaBlockTime = 6500 * time.Millisecond ) type EVMSigner struct { @@ -592,7 +595,7 @@ func (signer *EVMSigner) TryProcessOutTx( } } -// reportToOutTxTracker reports outTxHash to tracker only when tx receipt is available +// reportToOutTxTracker reports outTxHash to tracker only when tx is included in a block func (signer *EVMSigner) reportToOutTxTracker(zetaBridge ZetaCoreBridger, chainID int64, nonce uint64, outTxHash string, logger zerolog.Logger) { // skip if already being reported signer.mu.Lock() @@ -611,36 +614,78 @@ func (signer *EVMSigner) reportToOutTxTracker(zetaBridge ZetaCoreBridger, chainI signer.mu.Unlock() }() - // try fetching tx receipt for 10 minutes + // try monitoring tx inclusion status for 10 minutes + var err error + report := false + isPending := false + blockNumber := uint64(0) tStart := time.Now() for { - if time.Since(tStart) > OutTxTrackerReportTimeout { // give up after 10 minutes - logger.Info().Msgf("reportToOutTxTracker: outTxHash report timeout for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) - return + // give up after 10 minutes of monitoring + time.Sleep(10 * time.Second) + if time.Since(tStart) > OutTxInclusionTimeout { + // if tx is still pending after timeout, report to outTxTracker anyway as we cannot monitor forever + if isPending { + report = true // probably will be included later + } + logger.Info().Msgf("reportToOutTxTracker: timeout waiting tx inclusion for chain %d nonce %d outTxHash %s report %v", chainID, nonce, outTxHash, report) + break } - receipt, err := signer.client.TransactionReceipt(context.TODO(), ethcommon.HexToHash(outTxHash)) + // try getting the tx + _, isPending, err = signer.client.TransactionByHash(context.TODO(), ethcommon.HexToHash(outTxHash)) if err != nil { - logger.Info().Err(err).Msgf("reportToOutTxTracker: receipt not available for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) - time.Sleep(10 * time.Second) + logger.Info().Err(err).Msgf("reportToOutTxTracker: error getting tx for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) continue } - if receipt != nil { - _, isPending, err := signer.client.TransactionByHash(context.TODO(), ethcommon.HexToHash(outTxHash)) - if err != nil || isPending { - logger.Info().Err(err).Msgf("reportToOutTxTracker: error getting tx or tx is pending for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) - time.Sleep(10 * time.Second) - continue + // if tx is include in a block, try getting receipt + if !isPending { + report = true // included + receipt, err := signer.client.TransactionReceipt(context.TODO(), ethcommon.HexToHash(outTxHash)) + if err != nil { + logger.Info().Err(err).Msgf("reportToOutTxTracker: error getting receipt for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) + } + if receipt != nil { + blockNumber = receipt.BlockNumber.Uint64() } break } + // keep monitoring pending tx + logger.Info().Msgf("reportToOutTxTracker: tx has not been included yet for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) } - // report to outTxTracker - zetaHash, err := zetaBridge.AddTxHashToOutTxTracker(chainID, nonce, outTxHash, nil, "", -1) - if err != nil { - logger.Err(err).Msgf("reportToOutTxTracker: unable to add to tracker on ZetaCore for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) + // try adding to outTx tracker for 10 minutes + if report { + tStart := time.Now() + for { + // give up after 10 minutes of retrying + if time.Since(tStart) > OutTxTrackerReportTimeout { + logger.Info().Msgf("reportToOutTxTracker: timeout adding outtx tracker for chain %d nonce %d outTxHash %s, please add manually", chainID, nonce, outTxHash) + break + } + // stop if the cctx is already finalized + cctx, err := zetaBridge.GetCctxByNonce(chainID, nonce) + if err != nil { + logger.Err(err).Msgf("reportToOutTxTracker: error getting cctx for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) + } else if !crosschainkeeper.IsPending(*cctx) { + logger.Info().Msgf("reportToOutTxTracker: cctx already finalized for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) + break + } + // report to outTx tracker + zetaHash, err := zetaBridge.AddTxHashToOutTxTracker(chainID, nonce, outTxHash, nil, "", -1) + if err != nil { + logger.Err(err).Msgf("reportToOutTxTracker: error adding to outtx tracker for chain %d nonce %d outTxHash %s", chainID, nonce, outTxHash) + } else if zetaHash != "" { + logger.Info().Msgf("reportToOutTxTracker: added outTxHash to core successful %s, chain %d nonce %d outTxHash %s block %d", + zetaHash, chainID, nonce, outTxHash, blockNumber) + } else { + // stop if the tracker contains the outTxHash + logger.Info().Msgf("reportToOutTxTracker: outtx tracker contains outTxHash %s for chain %d nonce %d", outTxHash, chainID, nonce) + break + } + // retry otherwise + time.Sleep(ZetaBlockTime * 3) + } } - logger.Info().Msgf("reportToOutTxTracker: reported outTxHash to core successful %s, chain %d nonce %d outTxHash %s", zetaHash, chainID, nonce, outTxHash) }() } diff --git a/zetaclient/interfaces.go b/zetaclient/interfaces.go index 6ca41f59fd..6ccf4faef8 100644 --- a/zetaclient/interfaces.go +++ b/zetaclient/interfaces.go @@ -82,6 +82,7 @@ type ZetaCoreBridger interface { ListPendingCctx(chainID int64) ([]*crosschaintypes.CrossChainTx, uint64, error) GetPendingNoncesByChain(chainID int64) (observertypes.PendingNonces, error) GetCctxByNonce(chainID int64, nonce uint64) (*crosschaintypes.CrossChainTx, error) + GetOutTxTracker(chain common.Chain, nonce uint64) (*crosschaintypes.OutTxTracker, error) GetAllOutTxTrackerByChain(chainID int64, order Order) ([]crosschaintypes.OutTxTracker, error) GetCrosschainFlags() (observertypes.CrosschainFlags, error) GetObserverList() ([]string, error) diff --git a/zetaclient/tx.go b/zetaclient/tx.go index 15847c3f80..491b76d133 100644 --- a/zetaclient/tx.go +++ b/zetaclient/tx.go @@ -2,6 +2,7 @@ package zetaclient import ( "fmt" + "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -78,6 +79,15 @@ func (b *ZetaCoreBridge) AddTxHashToOutTxTracker( blockHash string, txIndex int64, ) (string, error) { + // don't report if the tracker already contains the txHash + tracker, err := b.GetOutTxTracker(common.Chain{ChainId: chainID}, nonce) + if err == nil { + for _, hash := range tracker.HashList { + if strings.EqualFold(hash.TxHash, txHash) { + return "", nil + } + } + } signerAddress := b.keys.GetOperatorAddress().String() msg := types.NewMsgAddToOutTxTracker(signerAddress, chainID, nonce, txHash, proof, blockHash, txIndex) diff --git a/zetaclient/zetacore_observer.go b/zetaclient/zetacore_observer.go index 8d8d426a39..333c6ce262 100644 --- a/zetaclient/zetacore_observer.go +++ b/zetaclient/zetacore_observer.go @@ -249,7 +249,8 @@ func (co *CoreObserver) scheduleCctxEVM( continue } if params.OutboundTxTssNonce > cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce+MaxLookaheadNonce { - co.logger.ZetaChainWatcher.Error().Msgf("scheduleCctxEVM: nonce too high: signing %d, earliest pending %d", params.OutboundTxTssNonce, cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce) + co.logger.ZetaChainWatcher.Error().Msgf("scheduleCctxEVM: nonce too high: signing %d, earliest pending %d, chain %d", + params.OutboundTxTssNonce, cctxList[0].GetCurrentOutTxParam().OutboundTxTssNonce, chainID) break }