From 14520d8c2856bb5b63160af293a52bba789a7507 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Thu, 30 Jan 2025 21:36:57 -0500 Subject: [PATCH 01/14] reorganise code to packages, according to usage --- cmd/zetatool/ballot/ballot.go | 53 +++++++ cmd/zetatool/ballot/ballot_test.go | 64 ++++++++ cmd/zetatool/{inbound => ballot}/bitcoin.go | 100 +++++++----- cmd/zetatool/{inbound => ballot}/solana.go | 50 +++--- cmd/zetatool/cctx/cctx_details.go | 53 +++++++ cmd/zetatool/{inbound => chains}/evm.go | 165 +++++++++++++------- cmd/zetatool/cli/cctx_tracker.go | 45 ++++++ cmd/zetatool/cli/inbound.go | 52 ++++++ cmd/zetatool/config/config.go | 2 +- cmd/zetatool/context/context.go | 75 +++++++++ cmd/zetatool/inbound/inbound.go | 125 --------------- cmd/zetatool/main.go | 5 +- cmd/zetatool/tracker/track_cctx.go | 65 ++++++++ pkg/rpc/clients_crosschain.go | 5 +- pkg/rpc/clients_test.go | 2 +- zetaclient/zetacore/tx.go | 2 +- 16 files changed, 611 insertions(+), 252 deletions(-) create mode 100644 cmd/zetatool/ballot/ballot.go create mode 100644 cmd/zetatool/ballot/ballot_test.go rename cmd/zetatool/{inbound => ballot}/bitcoin.go (67%) rename cmd/zetatool/{inbound => ballot}/solana.go (59%) create mode 100644 cmd/zetatool/cctx/cctx_details.go rename cmd/zetatool/{inbound => chains}/evm.go (68%) create mode 100644 cmd/zetatool/cli/cctx_tracker.go create mode 100644 cmd/zetatool/cli/inbound.go create mode 100644 cmd/zetatool/context/context.go delete mode 100644 cmd/zetatool/inbound/inbound.go create mode 100644 cmd/zetatool/tracker/track_cctx.go diff --git a/cmd/zetatool/ballot/ballot.go b/cmd/zetatool/ballot/ballot.go new file mode 100644 index 0000000000..b4dcced6e1 --- /dev/null +++ b/cmd/zetatool/ballot/ballot.go @@ -0,0 +1,53 @@ +package ballot + +import ( + "fmt" + + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/chains" + + "github.com/zeta-chain/node/cmd/zetatool/context" +) + +func GetBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { + var ( + ballotIdentifierMessage = cctx.NewCCTXDetails() + inboundChain = ctx.GetInboundChain() + ) + + if ctx.GetInboundChain().IsEVMChain() { + ballotIdentifierMessage, err := chains.EvmInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for evm chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + + if inboundChain.IsBitcoinChain() { + ballotIdentifierMessage, err := btcInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for bitcoin chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + + if inboundChain.IsSolanaChain() { + ballotIdentifierMessage, err := solanaInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for solana chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + + return ballotIdentifierMessage, nil + +} diff --git a/cmd/zetatool/ballot/ballot_test.go b/cmd/zetatool/ballot/ballot_test.go new file mode 100644 index 0000000000..00293b3044 --- /dev/null +++ b/cmd/zetatool/ballot/ballot_test.go @@ -0,0 +1,64 @@ +package ballot_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/node/cmd/zetatool/ballot" + zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" + "github.com/zeta-chain/node/pkg/chains" +) + +func Test_GetBallotIdentifier(t *testing.T) { + tt := []struct { + name string + inboundHash string + inboundChainID int64 + expectedBallotIdentifier string + }{ + { + name: chains.Ethereum.Name, + inboundHash: "0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f56cb9a", + inboundChainID: chains.Ethereum.ChainId, + expectedBallotIdentifier: "0xae189ab5cd884af784835297ac43eb55deb8a7800023534c580f44ee2b3eb5ed", + }, + { + name: chains.BaseMainnet.Name, + inboundHash: "0x88ee0943863fd8649546eb3affaf725f8caf09f44ebc5aa64de592b2edf378c8", + inboundChainID: chains.BaseMainnet.ChainId, + expectedBallotIdentifier: "0xe2b4c3f5dbef8fb7feb14bdf0a3f63ca7018678ecb6ae99ff697ccd962932ca2", + }, + { + name: chains.BscMainnet.Name, + inboundHash: "0xfa18cbcdbf70e987600647ee77a1a28f5ca707acf9b72462fada02fff2a94d2f", + inboundChainID: chains.BscMainnet.ChainId, + expectedBallotIdentifier: "0xc7b289172db825b3c0490f263f35c8596b6f1fab8ec4c44db46de3020fe9e6e6", + }, + { + name: chains.Polygon.Name, + inboundHash: "0x70b9b3ba89ff647257ab0085d90d60dc99b693c66931c4535e117b66a25236ce", + inboundChainID: chains.Polygon.ChainId, + expectedBallotIdentifier: "0xf8ed419d9798aed83070763355628e2638ae9a4a47aa9c93ffc32f4b72c9fef4", + }, + { + name: chains.SolanaMainnet.Name, + inboundHash: "5oj38HmTH4k2NSsqHK9oRrLjpPNBkm17dNXHFsaT6cTuJQRPWTCGqsPpRumPEbpL2B6Wuv51M69WoJwM24864PjB", + inboundChainID: chains.SolanaMainnet.ChainId, + expectedBallotIdentifier: "0xd7823bbbae1e3c893ac34d1053834c9591336eb6b3925b3cc1d0fa60f4eeaa4b", + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + ctx, err := zetatoolcontext.NewContext(context.Background(), tc.inboundChainID, tc.inboundHash, "") + require.NoError(t, err) + ballotIdentifierMessage, err := ballot.GetBallotIdentifier(ctx) + require.NoError(t, err) + if ballotIdentifierMessage.CCCTXIdentifier != tc.expectedBallotIdentifier { + t.Errorf("expected %s, got %s", tc.expectedBallotIdentifier, ballotIdentifierMessage.CCCTXIdentifier) + } + }) + } + +} diff --git a/cmd/zetatool/inbound/bitcoin.go b/cmd/zetatool/ballot/bitcoin.go similarity index 67% rename from cmd/zetatool/inbound/bitcoin.go rename to cmd/zetatool/ballot/bitcoin.go index 9cf652b110..6e60176548 100644 --- a/cmd/zetatool/inbound/bitcoin.go +++ b/cmd/zetatool/ballot/bitcoin.go @@ -1,7 +1,6 @@ -package inbound +package ballot import ( - "context" "encoding/hex" "fmt" "math/big" @@ -10,11 +9,12 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/rs/zerolog" + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/context" - "github.com/zeta-chain/node/cmd/zetatool/config" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" - "github.com/zeta-chain/node/pkg/rpc" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/x/observer/types" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client" @@ -23,17 +23,21 @@ import ( zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) -func btcInboundBallotIdentifier( - ctx context.Context, - cfg config.Config, - zetacoreClient rpc.Clients, - inboundHash string, - inboundChain chains.Chain, - zetaChainID int64, - logger zerolog.Logger) (string, error) { +func btcInboundBallotIdentifier(ctx context.Context) (cctx.CCTXDetails, error) { + var ( + inboundHash = ctx.GetInboundHash() + cctxDetails = cctx.NewCCTXDetails() + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + cfg = ctx.GetConfig() + logger = ctx.GetLogger() + goCtx = ctx.GetContext() + ) + params, err := chains.BitcoinNetParamsFromChainID(inboundChain.ChainId) if err != nil { - return "", fmt.Errorf("unable to get bitcoin net params from chain id: %w", err) + return cctxDetails, fmt.Errorf("unable to get bitcoin net params from chain id: %w", err) } connCfg := zetaclientConfig.BTCConfig{ @@ -45,25 +49,26 @@ func btcInboundBallotIdentifier( rpcClient, err := client.New(connCfg, inboundChain.ChainId, logger) if err != nil { - return "", fmt.Errorf("unable to create rpc client: %w", err) + return cctxDetails, fmt.Errorf("unable to create rpc client: %w", err) } - err = rpcClient.Ping(ctx) + err = rpcClient.Ping(goCtx) if err != nil { - return "", fmt.Errorf("error ping the bitcoin server: %w", err) + return cctxDetails, fmt.Errorf("error ping the bitcoin server: %w", err) } - res, err := zetacoreClient.Observer.GetTssAddress(context.Background(), &types.QueryGetTssAddressRequest{}) + + res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) if err != nil { - return "", fmt.Errorf("failed to get tss address: %w", err) + return cctxDetails, fmt.Errorf("failed to get tss address: %w", err) } tssBtcAddress := res.GetBtc() - chainParams, err := zetacoreClient.GetChainParamsForChainID(context.Background(), inboundChain.ChainId) + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) if err != nil { - return "", fmt.Errorf("failed to get chain params: %w", err) + return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) } - return bitcoinBallotIdentifier( + err = bitcoinBallotIdentifier( ctx, rpcClient, params, @@ -72,7 +77,12 @@ func btcInboundBallotIdentifier( inboundChain.ChainId, zetaChainID, chainParams.ConfirmationCount, + &cctxDetails, ) + if err != nil { + return cctxDetails, fmt.Errorf("failed to get bitcoin ballot identifier: %w", err) + } + return cctxDetails, nil } func bitcoinBallotIdentifier( @@ -83,32 +93,39 @@ func bitcoinBallotIdentifier( txHash string, senderChainID int64, zetacoreChainID int64, - confirmationCount uint64) (string, error) { + confirmationCount uint64, + cctxDetails *cctx.CCTXDetails, +) error { + var ( + goCtx = ctx.GetContext() + ) + hash, err := chainhash.NewHashFromStr(txHash) if err != nil { - return "", err + return err } - confirmationMessage := "" - tx, err := btcClient.GetRawTransactionVerbose(ctx, hash) + tx, err := btcClient.GetRawTransactionVerbose(goCtx, hash) if err != nil { - return "", err + return err } if tx.Confirmations < confirmationCount { - confirmationMessage = fmt.Sprintf("tx might not be confirmed on chain: %d", senderChainID) + cctxDetails.Status = cctx.PendingInboundConfirmation + } else { + cctxDetails.Status = cctx.PendingInboundVoting } blockHash, err := chainhash.NewHashFromStr(tx.BlockHash) if err != nil { - return "", err + return err } - blockVb, err := btcClient.GetBlockVerbose(ctx, blockHash) + blockVb, err := btcClient.GetBlockVerbose(goCtx, blockHash) if err != nil { - return "", err + return err } event, err := zetaclientObserver.GetBtcEvent( - ctx, + goCtx, btcClient, *tx, tss, @@ -118,28 +135,28 @@ func bitcoinBallotIdentifier( common.CalcDepositorFee, ) if err != nil { - return "", fmt.Errorf("error getting btc event: %w", err) + return fmt.Errorf("error getting btc event: %w", err) } if event == nil { - return "", fmt.Errorf("no event built for btc sent to TSS") + return fmt.Errorf("no event built for btc sent to TSS") } - return identifierFromBtcEvent(event, senderChainID, zetacoreChainID, confirmationMessage) + return identifierFromBtcEvent(event, senderChainID, zetacoreChainID, cctxDetails) } func identifierFromBtcEvent(event *zetaclientObserver.BTCInboundEvent, senderChainID int64, - zetacoreChainID int64, confirmationMessage string) (string, error) { + zetacoreChainID int64, cctxDetails *cctx.CCTXDetails) error { // decode event memo bytes err := event.DecodeMemoBytes(senderChainID) if err != nil { - return "", fmt.Errorf("error decoding memo bytes: %w", err) + return fmt.Errorf("error decoding memo bytes: %w", err) } // convert the amount to integer (satoshis) amountSats, err := common.GetSatoshis(event.Value) if err != nil { - return "", fmt.Errorf("error converting amount to satoshis: %w", err) + return fmt.Errorf("error converting amount to satoshis: %w", err) } amountInt := big.NewInt(amountSats) @@ -155,14 +172,11 @@ func identifierFromBtcEvent(event *zetaclientObserver.BTCInboundEvent, } } if msg == nil { - return "", fmt.Errorf("failed to create vote message") + return fmt.Errorf("failed to create vote message") } - index := msg.Digest() - if confirmationMessage != "" { - return fmt.Sprintf("ballot identifier: %s warning: %s", index, confirmationMessage), nil - } - return fmt.Sprintf("ballot identifier: %s", msg.Digest()), nil + cctxDetails.CCCTXIdentifier = msg.Digest() + return nil } // NewInboundVoteFromLegacyMemo creates a MsgVoteInbound message for inbound that uses legacy memo diff --git a/cmd/zetatool/inbound/solana.go b/cmd/zetatool/ballot/solana.go similarity index 59% rename from cmd/zetatool/inbound/solana.go rename to cmd/zetatool/ballot/solana.go index 014e51845f..4c80ea7b02 100644 --- a/cmd/zetatool/inbound/solana.go +++ b/cmd/zetatool/ballot/solana.go @@ -1,52 +1,55 @@ -package inbound +package ballot import ( - "context" "encoding/hex" "fmt" cosmosmath "cosmossdk.io/math" "github.com/gagliardetto/solana-go" solrpc "github.com/gagliardetto/solana-go/rpc" - "github.com/rs/zerolog" + "github.com/zeta-chain/node/cmd/zetatool/context" + + "github.com/zeta-chain/node/cmd/zetatool/cctx" - "github.com/zeta-chain/node/cmd/zetatool/config" - "github.com/zeta-chain/node/pkg/chains" solanacontracts "github.com/zeta-chain/node/pkg/contracts/solana" - "github.com/zeta-chain/node/pkg/rpc" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/solana/observer" solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" clienttypes "github.com/zeta-chain/node/zetaclient/types" ) -func solanaInboundBallotIdentifier(ctx context.Context, - cfg config.Config, - zetacoreClient rpc.Clients, - inboundHash string, - inboundChain chains.Chain, - zetaChainID int64, - logger zerolog.Logger) (string, error) { +func solanaInboundBallotIdentifier(ctx context.Context) (cctx.CCTXDetails, error) { + var ( + inboundHash = ctx.GetInboundHash() + cctxDetails = cctx.NewCCTXDetails() + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + cfg = ctx.GetConfig() + logger = ctx.GetLogger() + goCtx = ctx.GetContext() + ) solClient := solrpc.New(cfg.SolanaRPC) if solClient == nil { - return "", fmt.Errorf("error creating rpc client") + return cctxDetails, fmt.Errorf("error creating rpc client") } signature := solana.MustSignatureFromBase58(inboundHash) - txResult, err := solanarpc.GetTransaction(ctx, solClient, signature) + txResult, err := solanarpc.GetTransaction(goCtx, solClient, signature) if err != nil { - return "", fmt.Errorf("error getting transaction: %w", err) + return cctxDetails, fmt.Errorf("error getting transaction: %w", err) } - chainParams, err := zetacoreClient.GetChainParamsForChainID(context.Background(), inboundChain.ChainId) + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) if err != nil { - return "", fmt.Errorf("failed to get chain params: %w", err) + return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) } gatewayID, _, err := solanacontracts.ParseGatewayWithPDA(chainParams.GatewayAddress) if err != nil { - return "", fmt.Errorf("cannot parse gateway address: %s, err: %w", chainParams.GatewayAddress, err) + return cctxDetails, fmt.Errorf("cannot parse gateway address: %s, err: %w", chainParams.GatewayAddress, err) } events, err := observer.FilterInboundEvents(txResult, @@ -56,7 +59,7 @@ func solanaInboundBallotIdentifier(ctx context.Context, ) if err != nil { - return "", fmt.Errorf("failed to filter solana inbound events: %w", err) + return cctxDetails, fmt.Errorf("failed to filter solana inbound events: %w", err) } msg := &crosschaintypes.MsgVoteInbound{} @@ -65,11 +68,14 @@ func solanaInboundBallotIdentifier(ctx context.Context, for _, event := range events { msg, err = voteMsgFromSolEvent(event, zetaChainID) if err != nil { - return "", fmt.Errorf("failed to create vote message: %w", err) + return cctxDetails, fmt.Errorf("failed to create vote message: %w", err) } } - return fmt.Sprintf("ballot identifier: %s", msg.Digest()), nil + cctxDetails.CCCTXIdentifier = msg.Digest() + cctxDetails.Status = cctx.PendingInboundVoting + + return cctxDetails, nil } // voteMsgFromSolEvent builds a MsgVoteInbound from an inbound event diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go new file mode 100644 index 0000000000..895121c381 --- /dev/null +++ b/cmd/zetatool/cctx/cctx_details.go @@ -0,0 +1,53 @@ +package cctx + +import crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + +type Status int + +const ( + Unknown Status = iota + PendingInboundConfirmation Status = 1 + PendingInboundVoting Status = 2 + PendingOutbound Status = 3 + OutboundMined Status = 4 + PendingRevert Status = 5 + Reverted Status = 6 + PendingOutboundConfirmation Status = 7 + PendingRevertConfirmation Status = 8 + Aborted Status = 9 +) + +type CCTXDetails struct { + CCCTXIdentifier string `json:"cctx_identifier"` + Status Status `json:"status"` + OutboundChainID int64 `json:"outbound_chain_id"` + OutboundTrackerHashList []string `json:"outbound_tracker_hash_list"` +} + +func NewCCTXDetails() CCTXDetails { + return CCTXDetails{ + CCCTXIdentifier: "", + Status: Unknown, + } +} + +func (c *CCTXDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxStatus) { + switch status { + case crosschaintypes.CctxStatus_PendingOutbound: + c.Status = PendingOutbound + case crosschaintypes.CctxStatus_OutboundMined: + c.Status = OutboundMined + case crosschaintypes.CctxStatus_Reverted: + c.Status = Reverted + case crosschaintypes.CctxStatus_PendingRevert: + c.Status = PendingRevert + case crosschaintypes.CctxStatus_Aborted: + c.Status = Aborted + default: + c.Status = Unknown + } +} + +func (c *CCTXDetails) IsPendingConfirmation() bool { + return c.Status == PendingOutbound || c.Status == PendingRevert +} diff --git a/cmd/zetatool/inbound/evm.go b/cmd/zetatool/chains/evm.go similarity index 68% rename from cmd/zetatool/inbound/evm.go rename to cmd/zetatool/chains/evm.go index 97da3a39e1..083fd03862 100644 --- a/cmd/zetatool/inbound/evm.go +++ b/cmd/zetatool/chains/evm.go @@ -1,8 +1,7 @@ -package inbound +package chains import ( "bytes" - "context" "encoding/base64" "encoding/hex" "fmt" @@ -12,6 +11,8 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" @@ -21,15 +22,14 @@ import ( "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/pkg/crypto" - "github.com/zeta-chain/node/pkg/rpc" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/x/observer/types" - evmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" + zetaevmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" clienttypes "github.com/zeta-chain/node/zetaclient/types" "github.com/zeta-chain/node/zetaclient/zetacore" ) -func resolveRPC(chain chains.Chain, cfg config.Config) string { +func resolveRPC(chain chains.Chain, cfg *config.Config) string { return map[chains.Network]string{ chains.Network_eth: cfg.EthereumRPC, chains.Network_base: cfg.BaseRPC, @@ -38,53 +38,50 @@ func resolveRPC(chain chains.Chain, cfg config.Config) string { }[chain.Network] } -func evmInboundBallotIdentifier(ctx context.Context, - cfg config.Config, - zetacoreClient rpc.Clients, - inboundHash string, - inboundChain chains.Chain, - zetaChainID int64) (string, error) { - evmRRC := resolveRPC(inboundChain, cfg) - if evmRRC == "" { - return "", fmt.Errorf("rpc not found for chain %d network %s", inboundChain.ChainId, inboundChain.Network) - } - rpcClient, err := ethrpc.DialHTTP(evmRRC) +func EvmInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { + var ( + inboundHash = ctx.GetInboundHash() + cctxDetails = cctx.NewCCTXDetails() + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + goCtx = ctx.GetContext() + ) + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) if err != nil { - return "", fmt.Errorf("failed to connect to eth rpc: %w", err) + return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) } - evmClient := ethclient.NewClient(rpcClient) + evmClient, err := getEvmClient(ctx) + if err != nil { + return cctxDetails, fmt.Errorf("failed to create evm client: %w", err) + } // create evm client for the observation chain tx, receipt, err := getEvmTx(ctx, evmClient, inboundHash, inboundChain) if err != nil { - return "", fmt.Errorf("failed to get tx: %w", err) + return cctxDetails, fmt.Errorf("failed to get tx: %w", err) } - - chainParams, err := zetacoreClient.GetChainParamsForChainID(context.Background(), inboundChain.ChainId) + // Signer is unused + c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) + confirmed, err := c.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) if err != nil { - return "", fmt.Errorf("failed to get chain params: %w", err) + return cctxDetails, fmt.Errorf("unable to confirm tx: %w", err) + } + if !confirmed { + cctxDetails.Status = cctx.PendingInboundConfirmation + } else { + cctxDetails.Status = cctx.PendingInboundVoting } - res, err := zetacoreClient.Observer.GetTssAddress(context.Background(), &types.QueryGetTssAddressRequest{}) + res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) if err != nil { - return "", fmt.Errorf("failed to get tss address: %w", err) + return cctxDetails, fmt.Errorf("failed to get tss address: %w", err) } tssEthAddress := res.GetEth() if tx.To() == nil { - return "", fmt.Errorf("invalid transaction,to field is empty %s", inboundHash) - } - - confirmationMessage := "" - - // Signer is unused - c := evmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) - confirmed, err := c.IsTxConfirmed(ctx, inboundHash, chainParams.ConfirmationCount) - if err != nil { - return "", fmt.Errorf("unable to confirm tx: %w", err) - } - if !confirmed { - confirmationMessage = fmt.Sprintf("tx might not be confirmed on chain %d", inboundChain.ChainId) + return cctxDetails, fmt.Errorf("invalid transaction,to field is empty %s", inboundHash) } msg := &crosschaintypes.MsgVoteInbound{} @@ -96,7 +93,7 @@ func evmInboundBallotIdentifier(ctx context.Context, addrConnector := ethcommon.HexToAddress(chainParams.ConnectorContractAddress) connector, err := zetaconnector.NewZetaConnectorNonEth(addrConnector, evmClient) if err != nil { - return "", fmt.Errorf("failed to get connector contract: %w", err) + return cctxDetails, fmt.Errorf("failed to get connector contract: %w", err) } for _, log := range receipt.Logs { event, err := connector.ParseZetaSent(*log) @@ -110,11 +107,11 @@ func evmInboundBallotIdentifier(ctx context.Context, addrCustody := ethcommon.HexToAddress(chainParams.Erc20CustodyContractAddress) custody, err := erc20custody.NewERC20Custody(addrCustody, evmClient) if err != nil { - return "", fmt.Errorf("failed to get custody contract: %w", err) + return cctxDetails, fmt.Errorf("failed to get custody contract: %w", err) } - sender, err := evmClient.TransactionSender(ctx, tx, receipt.BlockHash, receipt.TransactionIndex) + sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) if err != nil { - return "", fmt.Errorf("failed to get tx sender: %w", err) + return cctxDetails, fmt.Errorf("failed to get tx sender: %w", err) } for _, log := range receipt.Logs { zetaDeposited, err := custody.ParseDeposited(*log) @@ -126,11 +123,11 @@ func evmInboundBallotIdentifier(ctx context.Context, case tssEthAddress: { if receipt.Status != ethtypes.ReceiptStatusSuccessful { - return "", fmt.Errorf("tx failed on chain %d", inboundChain.ChainId) + return cctxDetails, fmt.Errorf("tx failed on chain %d", inboundChain.ChainId) } - sender, err := evmClient.TransactionSender(ctx, tx, receipt.BlockHash, receipt.TransactionIndex) + sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) if err != nil { - return "", fmt.Errorf("failed to get tx sender: %w", err) + return cctxDetails, fmt.Errorf("failed to get tx sender: %w", err) } msg = gasVoteV1(tx, sender, receipt.BlockNumber.Uint64(), inboundChain.ChainId, zetaChainID) } @@ -139,7 +136,7 @@ func evmInboundBallotIdentifier(ctx context.Context, gatewayAddr := ethcommon.HexToAddress(chainParams.GatewayAddress) gateway, err := gatewayevm.NewGatewayEVM(gatewayAddr, evmClient) if err != nil { - return "", fmt.Errorf("failed to get gateway contract: %w", err) + return cctxDetails, fmt.Errorf("failed to get gateway contract: %w", err) } for _, log := range receipt.Logs { if log == nil || log.Address != gatewayAddr { @@ -148,45 +145,105 @@ func evmInboundBallotIdentifier(ctx context.Context, eventDeposit, err := gateway.ParseDeposited(*log) if err == nil { msg = depositInboundVoteV2(eventDeposit, inboundChain.ChainId, zetaChainID) - return msg.Digest(), nil + break } eventDepositAndCall, err := gateway.ParseDepositedAndCalled(*log) if err == nil { msg = depositAndCallInboundVoteV2(eventDepositAndCall, inboundChain.ChainId, zetaChainID) - return msg.Digest(), nil + break } eventCall, err := gateway.ParseCalled(*log) if err == nil { msg = callInboundVoteV2(eventCall, inboundChain.ChainId, zetaChainID) + break } } } default: - return "", fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) + return cctxDetails, fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) } - if confirmationMessage != "" { - return fmt.Sprintf("ballot identifier: %s warning: %s", msg.Digest(), confirmationMessage), nil + cctxDetails.CCCTXIdentifier = msg.Digest() + return cctxDetails, nil +} + +func CheckOutboundTx(ctx *context.Context, cctxDetails *cctx.CCTXDetails) error { + var ( + inboundHash = ctx.GetInboundHash() + outboundChainID = cctxDetails.OutboundChainID + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + goCtx = ctx.GetContext() + ) + + outboundChain, found := chains.GetChainFromChainID(outboundChainID, []chains.Chain{}) + if !found { + return fmt.Errorf("chain not supported,chain id: %d", outboundChainID) } - return fmt.Sprintf("ballot identifier: %s", msg.Digest()), nil + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChainID) + if err != nil { + return fmt.Errorf("failed to get chain params: %v", err) + } + + evmClient, err := getEvmClient(ctx) + if err != nil { + return fmt.Errorf("failed to create evm client: %v", err) + } + + // create evm client for the observation chain + tx, receipt, err := getEvmTx(ctx, evmClient, inboundHash, outboundChain) + if err != nil { + return fmt.Errorf("failed to get tx: %v", err) + } + // Signer is unused + c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) + confirmed, err := c.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) + if err != nil { + return fmt.Errorf("unable to confirm tx: %w", err) + } + if !confirmed { + cctxDetails.Status = cctx.PendingInboundConfirmation + } else { + cctxDetails.Status = cctx.PendingInboundVoting + } + +} + +func getEvmClient(ctx *context.Context) (*ethclient.Client, error) { + var ( + inboundChain = ctx.GetInboundChain() + ) + + evmRRC := resolveRPC(ctx.GetInboundChain(), ctx.GetConfig()) + if evmRRC == "" { + return nil, fmt.Errorf("rpc not found for chain %d network %s", inboundChain.ChainId, inboundChain.Network) + } + rpcClient, err := ethrpc.DialHTTP(evmRRC) + if err != nil { + return nil, fmt.Errorf("failed to connect to eth rpc: %w", err) + } + return ethclient.NewClient(rpcClient), nil } func getEvmTx( - ctx context.Context, + ctx *context.Context, evmClient *ethclient.Client, inboundHash string, inboundChain chains.Chain, ) (*ethtypes.Transaction, *ethtypes.Receipt, error) { + + goCtx := ctx.GetContext() // Fetch transaction from the inbound hash := ethcommon.HexToHash(inboundHash) - tx, isPending, err := evmClient.TransactionByHash(ctx, hash) + tx, isPending, err := evmClient.TransactionByHash(goCtx, hash) if err != nil { return nil, nil, fmt.Errorf("tx not found on chain: %w,chainID: %d", err, inboundChain.ChainId) } if isPending { return nil, nil, fmt.Errorf("tx is still pending on chain: %d", inboundChain.ChainId) } - receipt, err := evmClient.TransactionReceipt(ctx, hash) + receipt, err := evmClient.TransactionReceipt(goCtx, hash) if err != nil { return nil, nil, fmt.Errorf("failed to get receipt: %w, tx hash: %s", err, inboundHash) } diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go new file mode 100644 index 0000000000..08bd4275bf --- /dev/null +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -0,0 +1,45 @@ +package cli + +import ( + "context" + "fmt" + "strconv" + + "github.com/spf13/cobra" + "github.com/zeta-chain/node/cmd/zetatool/ballot" + "github.com/zeta-chain/node/cmd/zetatool/config" + zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" +) + +func NewTrackCCTXCMD() *cobra.Command { + return &cobra.Command{ + Use: "track-cctx [inboundHash] [chainID]", + Short: "track a cross chain transaction", + RunE: TrackCCTX, + Args: cobra.ExactArgs(2), + } +} + +func TrackCCTX(cmd *cobra.Command, args []string) error { + inboundHash := args[0] + inboundChainID, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("failed to parse chain id") + } + configFile, err := cmd.Flags().GetString(config.FlagConfig) + if err != nil { + return fmt.Errorf("failed to read value for flag %s , err %w", config.FlagConfig, err) + } + + ctx, err := zetatoolcontext.NewContext(context.Background(), inboundChainID, inboundHash, configFile) + if err != nil { + return fmt.Errorf("failed to create context: %w", err) + } + + cctx, err := ballot.GetBallotIdentifier(ctx) + if err != nil { + return fmt.Errorf("failed to get ballot identifier: %w", err) + } + + return nil +} diff --git a/cmd/zetatool/cli/inbound.go b/cmd/zetatool/cli/inbound.go new file mode 100644 index 0000000000..86b6e5ed38 --- /dev/null +++ b/cmd/zetatool/cli/inbound.go @@ -0,0 +1,52 @@ +package cli + +import ( + "context" + "fmt" + "strconv" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/zeta-chain/node/cmd/zetatool/ballot" + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/config" + zetacontext "github.com/zeta-chain/node/cmd/zetatool/context" +) + +func NewGetInboundBallotCMD() *cobra.Command { + return &cobra.Command{ + Use: "get-ballot [inboundHash] [chainID]", + Short: "fetch ballot identifier from the inbound hash", + RunE: GetInboundBallot, + Args: cobra.ExactArgs(2), + } +} + +func GetInboundBallot(cmd *cobra.Command, args []string) error { + inboundHash := args[0] + inboundChainID, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return fmt.Errorf("failed to parse chain id") + } + configFile, err := cmd.Flags().GetString(config.FlagConfig) + if err != nil { + return fmt.Errorf("failed to read value for flag %s , err %w", config.FlagConfig, err) + } + + ctx, err := zetacontext.NewContext(context.Background(), inboundChainID, inboundHash, configFile) + if err != nil { + return fmt.Errorf("failed to create context: %w", err) + } + + ballotIdentifierMessage, err := ballot.GetBallotIdentifier(ctx) + if err != nil { + return fmt.Errorf("failed to get ballot identifier: %w", err) + } + + msg := fmt.Sprintf("ballot identifier: %s", ballotIdentifierMessage.CCCTXIdentifier) + if ballotIdentifierMessage.Status == cctx.PendingInboundConfirmation { + msg = fmt.Sprintf("%s, warning : inbound hash might not be confirmed yet ", msg) + } + log.Info().Msgf("%s", msg) + return nil +} diff --git a/cmd/zetatool/config/config.go b/cmd/zetatool/config/config.go index 14d74ae24d..9a40ef3573 100644 --- a/cmd/zetatool/config/config.go +++ b/cmd/zetatool/config/config.go @@ -57,7 +57,7 @@ func MainnetConfig() *Config { BtcPassword: "", BtcHost: "", BtcParams: "", - SolanaRPC: "", + SolanaRPC: "https://api.mainnet-beta.solana.com", BaseRPC: "https://base-mainnet.public.blastapi.io", BscRPC: "https://bsc-mainnet.public.blastapi.io", PolygonRPC: "https://polygon-bor-rpc.publicnode.com", diff --git a/cmd/zetatool/context/context.go b/cmd/zetatool/context/context.go new file mode 100644 index 0000000000..e63caf9d5e --- /dev/null +++ b/cmd/zetatool/context/context.go @@ -0,0 +1,75 @@ +package context + +import ( + "context" + "fmt" + "time" + + "github.com/rs/zerolog" + "github.com/zeta-chain/node/cmd/zetatool/config" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/pkg/rpc" + zetacorerpc "github.com/zeta-chain/node/pkg/rpc" +) + +type Context struct { + ctx context.Context + config *config.Config + zetaCoreClient rpc.Clients + inboundHash string + inboundChain chains.Chain + logger zerolog.Logger +} + +func NewContext(ctx context.Context, inboundChainID int64, inboundHash string, configFile string) (*Context, error) { + observationChain, found := chains.GetChainFromChainID(inboundChainID, []chains.Chain{}) + if !found { + return nil, fmt.Errorf("chain not supported,chain id: %d", inboundChainID) + } + cfg, err := config.GetConfig(observationChain, configFile) + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) + } + zetacoreClient, err := zetacorerpc.NewCometBFTClients(cfg.ZetaChainRPC) + if err != nil { + return nil, fmt.Errorf("failed to create zetacore client: %w", err) + } + // logger is used when calling internal zetaclient functions which need a logger. + // we do not need to log those messages for this tool + logger := zerolog.New(zerolog.ConsoleWriter{ + Out: zerolog.Nop(), + TimeFormat: time.RFC3339, + }).With().Timestamp().Logger() + return &Context{ + ctx: ctx, + config: cfg, + zetaCoreClient: zetacoreClient, + inboundChain: observationChain, + inboundHash: inboundHash, + logger: logger, + }, nil +} + +func (c *Context) GetContext() context.Context { + return c.ctx +} + +func (c *Context) GetConfig() *config.Config { + return c.config +} + +func (c *Context) GetZetaCoreClient() rpc.Clients { + return c.zetaCoreClient +} + +func (c *Context) GetInboundHash() string { + return c.inboundHash +} + +func (c *Context) GetInboundChain() chains.Chain { + return c.inboundChain +} + +func (c *Context) GetLogger() zerolog.Logger { + return c.logger +} diff --git a/cmd/zetatool/inbound/inbound.go b/cmd/zetatool/inbound/inbound.go deleted file mode 100644 index bf31a84618..0000000000 --- a/cmd/zetatool/inbound/inbound.go +++ /dev/null @@ -1,125 +0,0 @@ -package inbound - -import ( - "context" - "fmt" - "strconv" - "time" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "github.com/spf13/cobra" - - "github.com/zeta-chain/node/cmd/zetatool/config" - "github.com/zeta-chain/node/pkg/chains" - zetacorerpc "github.com/zeta-chain/node/pkg/rpc" -) - -func NewGetInboundBallotCMD() *cobra.Command { - return &cobra.Command{ - Use: "get-ballot [inboundHash] [chainID]", - Short: "fetch ballot identifier from the inbound hash", - RunE: GetInboundBallot, - Args: cobra.ExactArgs(2), - } -} - -func GetInboundBallot(cmd *cobra.Command, args []string) error { - inboundHash := args[0] - inboundChainID, err := strconv.ParseInt(args[1], 10, 64) - if err != nil { - return fmt.Errorf("failed to parse chain id") - } - configFile, err := cmd.Flags().GetString(config.FlagConfig) - if err != nil { - return fmt.Errorf("failed to read value for flag %s , err %w", config.FlagConfig, err) - } - - return GetBallotIdentifier(inboundHash, inboundChainID, configFile) -} - -func GetBallotIdentifier(inboundHash string, inboundChainID int64, configFile string) error { - observationChain, found := chains.GetChainFromChainID(inboundChainID, []chains.Chain{}) - if !found { - return fmt.Errorf("chain not supported,chain id: %d", inboundChainID) - } - - cfg, err := config.GetConfig(observationChain, configFile) - if err != nil { - return fmt.Errorf("failed to get config: %w", err) - } - - zetacoreClient, err := zetacorerpc.NewCometBFTClients(cfg.ZetaChainRPC) - if err != nil { - return fmt.Errorf("failed to create zetacore client: %w", err) - } - - ctx := context.Background() - ballotIdentifierMessage := "" - - // logger is used when calling internal zetaclient functions which need a logger. - // we do not need to log those messages for this tool - logger := zerolog.New(zerolog.ConsoleWriter{ - Out: zerolog.Nop(), - TimeFormat: time.RFC3339, - }).With().Timestamp().Logger() - - if observationChain.IsEVMChain() { - ballotIdentifierMessage, err = evmInboundBallotIdentifier( - ctx, - *cfg, - zetacoreClient, - inboundHash, - observationChain, - cfg.ZetaChainID, - ) - if err != nil { - return fmt.Errorf( - "failed to get inbound ballot for evm chain %d, %w", - observationChain.ChainId, - err, - ) - } - } - - if observationChain.IsBitcoinChain() { - ballotIdentifierMessage, err = btcInboundBallotIdentifier( - ctx, - *cfg, - zetacoreClient, - inboundHash, - observationChain, - cfg.ZetaChainID, - logger, - ) - if err != nil { - return fmt.Errorf( - "failed to get inbound ballot for bitcoin chain %d, %w", - observationChain.ChainId, - err, - ) - } - } - - if observationChain.IsSolanaChain() { - ballotIdentifierMessage, err = solanaInboundBallotIdentifier( - ctx, - *cfg, - zetacoreClient, - inboundHash, - observationChain, - cfg.ZetaChainID, - logger, - ) - if err != nil { - return fmt.Errorf( - "failed to get inbound ballot for solana chain %d, %w", - observationChain.ChainId, - err, - ) - } - } - - log.Info().Msgf("%s", ballotIdentifierMessage) - return nil -} diff --git a/cmd/zetatool/main.go b/cmd/zetatool/main.go index c79451d413..0161b348c4 100644 --- a/cmd/zetatool/main.go +++ b/cmd/zetatool/main.go @@ -5,9 +5,9 @@ import ( "os" "github.com/spf13/cobra" + "github.com/zeta-chain/node/cmd/zetatool/cli" "github.com/zeta-chain/node/cmd/zetatool/config" - "github.com/zeta-chain/node/cmd/zetatool/inbound" ) var rootCmd = &cobra.Command{ @@ -16,7 +16,8 @@ var rootCmd = &cobra.Command{ } func init() { - rootCmd.AddCommand(inbound.NewGetInboundBallotCMD()) + rootCmd.AddCommand(cli.NewGetInboundBallotCMD()) + rootCmd.AddCommand(cli.NewTrackCCTXCMD()) rootCmd.PersistentFlags().String(config.FlagConfig, "", "custom config file: --config filename.json") } diff --git a/cmd/zetatool/tracker/track_cctx.go b/cmd/zetatool/tracker/track_cctx.go new file mode 100644 index 0000000000..08ae8ef3d5 --- /dev/null +++ b/cmd/zetatool/tracker/track_cctx.go @@ -0,0 +1,65 @@ +package tracker + +import ( + "fmt" + + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/context" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func UpdateCCTXStatus(ctx context.Context, cctxDetails *cctx.CCTXDetails) error { + var ( + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + switch cctxDetails.Status { + case cctx.PendingInboundConfirmation: + return nil + default: + cctx, err := zetacoreClient.GetCctxByHash(goCtx, cctxDetails.CCCTXIdentifier) + if err != nil { + return fmt.Errorf("failed to get cctx: %w", err) + } + + cctxDetails.UpdateStatusFromZetacoreCCTX(cctx.CctxStatus.Status) + } + return nil +} + +func UpdateHashListForPendingCCTX(ctx context.Context, cctxDetails *cctx.CCTXDetails) error { + var ( + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + + if !cctxDetails.IsPendingConfirmation() { + return nil + } + + CCTX, err := zetacoreClient.GetCctxByHash(goCtx, cctxDetails.CCCTXIdentifier) + if err != nil { + return fmt.Errorf("failed to get cctx: %w", err) + } + + outboundParams := CCTX.GetCurrentOutboundParam() + + tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundParams.ReceiverChainId, outboundParams.TssNonce) + if err != nil { + switch { + case err.Error() == "rpc error: code = NotFound desc = not found": + return err + case CCTX.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound: + cctxDetails.Status = cctx.PendingOutboundConfirmation + case CCTX.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert: + cctxDetails.Status = cctx.PendingRevertConfirmation + } + } + hashList := []string{} + for _, hash := range tracker.HashList { + hashList = append(hashList, hash.TxHash) + } + + cctxDetails.OutboundTrackerHashList = hashList + return nil +} diff --git a/pkg/rpc/clients_crosschain.go b/pkg/rpc/clients_crosschain.go index f1ef8f5e34..8a95c518ff 100644 --- a/pkg/rpc/clients_crosschain.go +++ b/pkg/rpc/clients_crosschain.go @@ -6,7 +6,6 @@ import ( "cosmossdk.io/errors" "google.golang.org/grpc" - "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/x/crosschain/types" ) @@ -130,10 +129,10 @@ func (c *Clients) ListPendingCCTX(ctx context.Context, chainID int64) ([]*types. // GetOutboundTracker returns the outbound tracker for a chain and nonce func (c *Clients) GetOutboundTracker( ctx context.Context, - chain chains.Chain, + chainID int64, nonce uint64, ) (*types.OutboundTracker, error) { - in := &types.QueryGetOutboundTrackerRequest{ChainID: chain.ChainId, Nonce: nonce} + in := &types.QueryGetOutboundTrackerRequest{ChainID: chainID, Nonce: nonce} resp, err := c.Crosschain.OutboundTracker(ctx, in) if err != nil { diff --git a/pkg/rpc/clients_test.go b/pkg/rpc/clients_test.go index 3a15bedafd..4e00de8d9d 100644 --- a/pkg/rpc/clients_test.go +++ b/pkg/rpc/clients_test.go @@ -683,7 +683,7 @@ func TestZetacore_GetOutboundTracker(t *testing.T) { client := setupZetacoreClients(t) ctx := context.Background() - resp, err := client.GetOutboundTracker(ctx, chain, 456) + resp, err := client.GetOutboundTracker(ctx, chain.ChainId, 456) require.NoError(t, err) require.Equal(t, expectedOutput.OutboundTracker, *resp) } diff --git a/zetaclient/zetacore/tx.go b/zetaclient/zetacore/tx.go index 3d06feb202..78e7670f48 100644 --- a/zetaclient/zetacore/tx.go +++ b/zetaclient/zetacore/tx.go @@ -90,7 +90,7 @@ func WrapMessageWithAuthz(msg sdk.Msg) (sdk.Msg, clientauthz.Signer, error) { // PostOutboundTracker adds an outbound tracker func (c *Client) PostOutboundTracker(ctx context.Context, chainID int64, nonce uint64, txHash string) (string, error) { // don't report if the tracker already contains the txHash - tracker, err := c.GetOutboundTracker(ctx, chains.Chain{ChainId: chainID}, nonce) + tracker, err := c.GetOutboundTracker(ctx, chainID, nonce) if err == nil { for _, hash := range tracker.HashList { if strings.EqualFold(hash.TxHash, txHash) { From aa39beb4379fe0d7800cf9e432fba3b27e1ee53f Mon Sep 17 00:00:00 2001 From: Tanmay Date: Sat, 1 Feb 2025 11:43:48 -0500 Subject: [PATCH 02/14] add withdraw tx checks --- cmd/zetatool/ballot/ballot.go | 75 ++++++---- cmd/zetatool/ballot/bitcoin.go | 4 +- cmd/zetatool/ballot/solana.go | 2 +- cmd/zetatool/cctx/cctx_details.go | 137 +++++++++++++++--- cmd/zetatool/cctx/cctx_status.go | 53 +++++++ cmd/zetatool/chains/evm.go | 64 ++++---- cmd/zetatool/chains/zetachain.go | 37 +++++ cmd/zetatool/cli/cctx_tracker.go | 60 +++++++- .../cli/{inbound.go => inbound_ballot.go} | 7 +- cmd/zetatool/config/config.go | 5 +- cmd/zetatool/tracker/track_cctx.go | 65 --------- pkg/rpc/clients_crosschain.go | 5 +- zetaclient/zetacore/tx.go | 4 +- 13 files changed, 360 insertions(+), 158 deletions(-) create mode 100644 cmd/zetatool/cctx/cctx_status.go create mode 100644 cmd/zetatool/chains/zetachain.go rename cmd/zetatool/cli/{inbound.go => inbound_ballot.go} (86%) delete mode 100644 cmd/zetatool/tracker/track_cctx.go diff --git a/cmd/zetatool/ballot/ballot.go b/cmd/zetatool/ballot/ballot.go index b4dcced6e1..fe513346ec 100644 --- a/cmd/zetatool/ballot/ballot.go +++ b/cmd/zetatool/ballot/ballot.go @@ -11,43 +11,60 @@ import ( func GetBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { var ( - ballotIdentifierMessage = cctx.NewCCTXDetails() + ballotIdentifierMessage = cctx.CCTXDetails{} inboundChain = ctx.GetInboundChain() + err error ) - if ctx.GetInboundChain().IsEVMChain() { - ballotIdentifierMessage, err := chains.EvmInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for evm chain %d, %w", - inboundChain.ChainId, - err, - ) + switch { + case inboundChain.IsZetaChain(): + { + ballotIdentifierMessage, err = chains.CheckInBoundTx(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for zeta chain %d, %w", + inboundChain.ChainId, + err, + ) + } } - } - if inboundChain.IsBitcoinChain() { - ballotIdentifierMessage, err := btcInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for bitcoin chain %d, %w", - inboundChain.ChainId, - err, - ) + case inboundChain.IsEVMChain(): + { + ballotIdentifierMessage, err = chains.EvmInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for evm chain %d, %w", + inboundChain.ChainId, + err, + ) + } } - } - - if inboundChain.IsSolanaChain() { - ballotIdentifierMessage, err := solanaInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for solana chain %d, %w", - inboundChain.ChainId, - err, - ) + case inboundChain.IsBitcoinChain(): + { + ballotIdentifierMessage, err = btcInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for bitcoin chain %d, %w", + inboundChain.ChainId, + err, + ) + } } + case inboundChain.IsSolanaChain(): + { + ballotIdentifierMessage, err = solanaInboundBallotIdentifier(ctx) + if err != nil { + return ballotIdentifierMessage, fmt.Errorf( + "failed to get inbound ballot for solana chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + default: + ballotIdentifierMessage.Message = "Chain not supported" } return ballotIdentifierMessage, nil - } diff --git a/cmd/zetatool/ballot/bitcoin.go b/cmd/zetatool/ballot/bitcoin.go index 6e60176548..d917ead34d 100644 --- a/cmd/zetatool/ballot/bitcoin.go +++ b/cmd/zetatool/ballot/bitcoin.go @@ -23,7 +23,7 @@ import ( zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) -func btcInboundBallotIdentifier(ctx context.Context) (cctx.CCTXDetails, error) { +func btcInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { var ( inboundHash = ctx.GetInboundHash() cctxDetails = cctx.NewCCTXDetails() @@ -86,7 +86,7 @@ func btcInboundBallotIdentifier(ctx context.Context) (cctx.CCTXDetails, error) { } func bitcoinBallotIdentifier( - ctx context.Context, + ctx *context.Context, btcClient *client.Client, params *chaincfg.Params, tss string, diff --git a/cmd/zetatool/ballot/solana.go b/cmd/zetatool/ballot/solana.go index 4c80ea7b02..7ede9cabee 100644 --- a/cmd/zetatool/ballot/solana.go +++ b/cmd/zetatool/ballot/solana.go @@ -19,7 +19,7 @@ import ( clienttypes "github.com/zeta-chain/node/zetaclient/types" ) -func solanaInboundBallotIdentifier(ctx context.Context) (cctx.CCTXDetails, error) { +func solanaInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { var ( inboundHash = ctx.GetInboundHash() cctxDetails = cctx.NewCCTXDetails() diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 895121c381..00aba56512 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -1,27 +1,20 @@ package cctx -import crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" - -type Status int - -const ( - Unknown Status = iota - PendingInboundConfirmation Status = 1 - PendingInboundVoting Status = 2 - PendingOutbound Status = 3 - OutboundMined Status = 4 - PendingRevert Status = 5 - Reverted Status = 6 - PendingOutboundConfirmation Status = 7 - PendingRevertConfirmation Status = 8 - Aborted Status = 9 +import ( + "fmt" + + "github.com/zeta-chain/node/cmd/zetatool/context" + "github.com/zeta-chain/node/pkg/chains" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) type CCTXDetails struct { - CCCTXIdentifier string `json:"cctx_identifier"` - Status Status `json:"status"` - OutboundChainID int64 `json:"outbound_chain_id"` - OutboundTrackerHashList []string `json:"outbound_tracker_hash_list"` + CCCTXIdentifier string `json:"cctx_identifier"` + Status Status `json:"status"` + OutboundChain chains.Chain `json:"outbound_chain_id"` + OutboundTssNonce uint64 `json:"outbound_tss_nonce"` + OutboundTrackerHashList []string `json:"outbound_tracker_hash_list"` + Message string `json:"message"` } func NewCCTXDetails() CCTXDetails { @@ -48,6 +41,110 @@ func (c *CCTXDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxSt } } -func (c *CCTXDetails) IsPendingConfirmation() bool { +func (c *CCTXDetails) IsPending() bool { return c.Status == PendingOutbound || c.Status == PendingRevert } + +func (c *CCTXDetails) IsPendingConfirmation() bool { + return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation +} + +func (c *CCTXDetails) Print() string { + return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCCTXIdentifier, c.Status.String(), c.Message) +} + +func (c *CCTXDetails) UpdateCCTXStatus(ctx *context.Context) { + var ( + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + + CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCCTXIdentifier) + if err != nil { + c.Message = fmt.Sprintf("failed to get cctx: %v", err) + return + } + + c.UpdateStatusFromZetacoreCCTX(CCTX.CctxStatus.Status) + + return +} + +func (c *CCTXDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { + var ( + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCCTXIdentifier) + if err != nil { + c.Message = fmt.Sprintf("failed to get cctx: %v", err) + } + chainId := CCTX.GetCurrentOutboundParam().ReceiverChainId + + // This is almost impossible to happen as the cctx would not have been created if the chain was not supported + chain, found := chains.GetChainFromChainID(chainId, []chains.Chain{}) + if !found { + c.Message = fmt.Sprintf("receiver chain not supported,chain id: %d", chainId) + } + c.OutboundChain = chain + c.OutboundTssNonce = CCTX.GetCurrentOutboundParam().TssNonce + return +} + +func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { + var ( + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + outboundChain = c.OutboundChain + outboundNonce = c.OutboundTssNonce + ) + + if !c.IsPending() { + return + } + + tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundChain, outboundNonce) + // tracker is found that means the outbound has broadcasted but we are waiting for confirmations + if err == nil && tracker != nil { + c.updateToPendingConfirmation() + var hashList []string + for _, hash := range tracker.HashList { + hashList = append(hashList, hash.TxHash) + } + c.OutboundTrackerHashList = hashList + return + } + // the cctx is in pending state by the outbound signing has not been done + c.updateToPendingSigning() + return +} + +// 1 - Signing +func (c *CCTXDetails) updateToPendingSigning() { + switch { + case c.Status == PendingOutbound: + c.Status = PendingOutboundSigning + case c.Status == PendingRevert: + c.Status = PendingRevertSigning + } +} + +// 2 - Confirmation +func (c *CCTXDetails) updateToPendingConfirmation() { + switch { + case c.Status == PendingOutbound: + c.Status = PendingOutboundConfirmation + case c.Status == PendingRevert: + c.Status = PendingRevertConfirmation + } +} + +// UpdateToPendingVoting 3 - Voting +func (c *CCTXDetails) UpdateToPendingVoting() { + switch { + case c.Status == PendingOutboundConfirmation: + c.Status = PendingOutboundVoting + case c.Status == PendingRevertConfirmation: + c.Status = PendingRevertVoting + } +} diff --git a/cmd/zetatool/cctx/cctx_status.go b/cmd/zetatool/cctx/cctx_status.go new file mode 100644 index 0000000000..02c1efc05d --- /dev/null +++ b/cmd/zetatool/cctx/cctx_status.go @@ -0,0 +1,53 @@ +package cctx + +type Status int + +const ( + Unknown Status = iota + PendingInboundConfirmation Status = 1 + PendingInboundVoting Status = 2 + PendingOutbound Status = 3 + OutboundMined Status = 4 + PendingRevert Status = 5 + Reverted Status = 6 + PendingOutboundConfirmation Status = 7 + PendingRevertConfirmation Status = 8 + PendingRevertVoting Status = 10 + Aborted Status = 11 + PendingOutboundSigning Status = 12 + PendingRevertSigning Status = 13 + PendingOutboundVoting Status = 14 +) + +func (s Status) String() string { + switch s { + case PendingInboundConfirmation: + return "PendingInboundConfirmation" + case PendingInboundVoting: + return "PendingInboundVoting" + case PendingOutbound: + return "PendingOutbound" + case OutboundMined: + return "OutboundMined" + case PendingRevert: + return "PendingRevert" + case Reverted: + return "Reverted" + case PendingOutboundConfirmation: + return "PendingOutboundConfirmation" + case PendingRevertConfirmation: + return "PendingRevertConfirmation" + case PendingRevertVoting: + return "PendingRevertVoting" + case Aborted: + return "Aborted" + case PendingOutboundSigning: + return "PendingOutboundSigning" + case PendingRevertSigning: + return "PendingRevertSigning" + case PendingOutboundVoting: + return "PEndingOutboundVoting" + default: + return "Unknown" + } +} diff --git a/cmd/zetatool/chains/evm.go b/cmd/zetatool/chains/evm.go index 083fd03862..72718cbc12 100644 --- a/cmd/zetatool/chains/evm.go +++ b/cmd/zetatool/chains/evm.go @@ -167,47 +167,53 @@ func EvmInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) return cctxDetails, nil } +// CheckOutboundTx checks if the outbound transaction is confirmed on the outbound chain. +// If it's confirmed, we update the status to PendingOutboundVoting or PendingRevertVoting. Which means that the confirmation is done and we are not waiting for observers to vote +// Transition Status PendingConfirmation -> Status PendingVoting func CheckOutboundTx(ctx *context.Context, cctxDetails *cctx.CCTXDetails) error { var ( - inboundHash = ctx.GetInboundHash() - outboundChainID = cctxDetails.OutboundChainID - zetacoreClient = ctx.GetZetaCoreClient() - zetaChainID = ctx.GetConfig().ZetaChainID - goCtx = ctx.GetContext() + txHashList = cctxDetails.OutboundTrackerHashList + outboundChain = cctxDetails.OutboundChain + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() ) - outboundChain, found := chains.GetChainFromChainID(outboundChainID, []chains.Chain{}) - if !found { - return fmt.Errorf("chain not supported,chain id: %d", outboundChainID) - } - - chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChainID) + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) if err != nil { return fmt.Errorf("failed to get chain params: %v", err) } + // create evm client for the observation chain evmClient, err := getEvmClient(ctx) if err != nil { return fmt.Errorf("failed to create evm client: %v", err) } - // create evm client for the observation chain - tx, receipt, err := getEvmTx(ctx, evmClient, inboundHash, outboundChain) - if err != nil { - return fmt.Errorf("failed to get tx: %v", err) - } - // Signer is unused - c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) - confirmed, err := c.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) - if err != nil { - return fmt.Errorf("unable to confirm tx: %w", err) + foundConfirmedTx := false + + // If one of the hash is confirmed, we update the status to pending voting + // There might be a condition where we have multiple txs and the wrong tx is confirmed. + //To verify that we need, check CCTX data + for _, hash := range txHashList { + tx, _, err := getEvmTx(ctx, evmClient, hash, outboundChain) + if err != nil { + continue + } + // Signer is unused + c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) + confirmed, err := c.IsTxConfirmed(goCtx, hash, chainParams.ConfirmationCount) + if err != nil { + continue + } + if confirmed { + foundConfirmedTx = true + break + } } - if !confirmed { - cctxDetails.Status = cctx.PendingInboundConfirmation - } else { - cctxDetails.Status = cctx.PendingInboundVoting + if foundConfirmedTx { + cctxDetails.UpdateToPendingVoting() } - + return nil } func getEvmClient(ctx *context.Context) (*ethclient.Client, error) { @@ -230,7 +236,7 @@ func getEvmTx( ctx *context.Context, evmClient *ethclient.Client, inboundHash string, - inboundChain chains.Chain, + chain chains.Chain, ) (*ethtypes.Transaction, *ethtypes.Receipt, error) { goCtx := ctx.GetContext() @@ -238,10 +244,10 @@ func getEvmTx( hash := ethcommon.HexToHash(inboundHash) tx, isPending, err := evmClient.TransactionByHash(goCtx, hash) if err != nil { - return nil, nil, fmt.Errorf("tx not found on chain: %w,chainID: %d", err, inboundChain.ChainId) + return nil, nil, fmt.Errorf("tx not found on chain: %w,chainID: %d", err, chain.ChainId) } if isPending { - return nil, nil, fmt.Errorf("tx is still pending on chain: %d", inboundChain.ChainId) + return nil, nil, fmt.Errorf("tx is still pending on chain: %d", chain.ChainId) } receipt, err := evmClient.TransactionReceipt(goCtx, hash) if err != nil { diff --git a/cmd/zetatool/chains/zetachain.go b/cmd/zetatool/chains/zetachain.go new file mode 100644 index 0000000000..8c77950da9 --- /dev/null +++ b/cmd/zetatool/chains/zetachain.go @@ -0,0 +1,37 @@ +package chains + +import ( + "fmt" + + "github.com/zeta-chain/node/cmd/zetatool/cctx" + "github.com/zeta-chain/node/cmd/zetatool/context" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" +) + +func CheckInBoundTx(ctx *context.Context) (cctx.CCTXDetails, error) { + var ( + inboundHash = ctx.GetInboundHash() + cctxDetails = cctx.NewCCTXDetails() + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + + inboundHashToCCTX, err := zetacoreClient.Crosschain.InboundHashToCctx( + goCtx, &crosschaintypes.QueryGetInboundHashToCctxRequest{ + InboundHash: inboundHash, + }) + if err != nil { + return cctxDetails, fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) + } + if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) == 0 { + return cctxDetails, fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) + } + + if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) > 1 { + return cctxDetails, fmt.Errorf("inbound hash more than one cctx %s", inboundHash) + } + + cctxDetails.CCCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] + cctxDetails.Status = cctx.PendingOutbound + return cctxDetails, nil +} diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index 08bd4275bf..a8295a61a0 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -5,8 +5,11 @@ import ( "fmt" "strconv" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/zeta-chain/node/cmd/zetatool/ballot" + "github.com/zeta-chain/node/cmd/zetatool/cctx" + zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" "github.com/zeta-chain/node/cmd/zetatool/config" zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" ) @@ -36,10 +39,61 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create context: %w", err) } - cctx, err := ballot.GetBallotIdentifier(ctx) + cctxDetails, err := trackCCTX(ctx) if err != nil { - return fmt.Errorf("failed to get ballot identifier: %w", err) + return fmt.Errorf("failed to track cctx: %w", err) } - + log.Info().Msg(cctxDetails.Print()) return nil } + +func trackCCTX(ctx *zetatoolcontext.Context) (cctx.CCTXDetails, error) { + + var ( + cctxDetails = cctx.CCTXDetails{} + err error + ) + // Get the ballot identifier for the inbound transaction and confirm that cctx status in atleast either PendingInboundConfirmation or PendingInboundVoting + cctxDetails, err = ballot.GetBallotIdentifier(ctx) + if err != nil { + return cctxDetails, fmt.Errorf("failed to get ballot identifier: %v", err) + } + // Reject unknown status , as it is not valid + if cctxDetails.Status == cctx.Unknown || cctxDetails.CCCTXIdentifier == "" { + return cctxDetails, fmt.Errorf("unknown status") + } + + // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address.We can show some information to the user. + // Add any error messages to the message field of the response instead of throwing an error + // Update cctx status from zetacore , if cctx is not found, it will continue to be in the status retuned by GetBallotIdentifier function PendingInboundVoting or PendingInboundConfirmation + cctxDetails.UpdateCCTXStatus(ctx) + + // The cctx details now have status from zetacore, we have not tried to a get more granular status from the outbound chain yet. + // If it's not pending, we can just return here + if !cctxDetails.IsPending() { + return cctxDetails, nil + } + + // update outbound details, this does not translation any status, it just updates the details + cctxDetails.UpdateCCTXOutboundDetails(ctx) + + // Update tx hash list from outbound tracker + // If the tracker is found it means the outbound is broadcasted but we are waiting for the confirmations + // If the tracker is not found it means the outbound is not broadcasted yet + cctxDetails.UpdateHashListAndPendingStatus(ctx) + + // If its not pending confirmation we can return here, it means the outbound is not broadcasted yet its pending tss signing + if !cctxDetails.IsPendingConfirmation() { + return cctxDetails, nil + } + + // Check outbound tx , we are waiting for the outbound tx to be confirmed + switch { + case cctxDetails.OutboundChain.IsEVMChain(): + err = zetatoolchains.CheckOutboundTx(ctx, &cctxDetails) + if err != nil { + return cctxDetails, err + } + } + return cctxDetails, nil +} diff --git a/cmd/zetatool/cli/inbound.go b/cmd/zetatool/cli/inbound_ballot.go similarity index 86% rename from cmd/zetatool/cli/inbound.go rename to cmd/zetatool/cli/inbound_ballot.go index 86b6e5ed38..77681e5d6a 100644 --- a/cmd/zetatool/cli/inbound.go +++ b/cmd/zetatool/cli/inbound_ballot.go @@ -42,11 +42,10 @@ func GetInboundBallot(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to get ballot identifier: %w", err) } - - msg := fmt.Sprintf("ballot identifier: %s", ballotIdentifierMessage.CCCTXIdentifier) if ballotIdentifierMessage.Status == cctx.PendingInboundConfirmation { - msg = fmt.Sprintf("%s, warning : inbound hash might not be confirmed yet ", msg) + log.Print("Ballot Identifier: , warning the inbound hash might not be confirmed yet", ballotIdentifierMessage.CCCTXIdentifier) + return nil } - log.Info().Msgf("%s", msg) + log.Print("Ballot Identifier: ", ballotIdentifierMessage.CCCTXIdentifier) return nil } diff --git a/cmd/zetatool/config/config.go b/cmd/zetatool/config/config.go index 9a40ef3573..05e5267bb2 100644 --- a/cmd/zetatool/config/config.go +++ b/cmd/zetatool/config/config.go @@ -18,9 +18,9 @@ const ( func TestnetConfig() *Config { return &Config{ - ZetaChainRPC: "https://zetachain-testnet-grpc.itrocket.net:443", + ZetaChainRPC: "https://zetachain-athens.g.allthatnode.com/archive/tendermint", EthereumRPC: "https://ethereum-sepolia-rpc.publicnode.com", - ZetaChainID: 101, + ZetaChainID: 7001, BtcUser: "", BtcPassword: "", BtcHost: "", @@ -64,6 +64,7 @@ func MainnetConfig() *Config { } } +// PrivateNetConfig returns a config for a private network, used for localnet testing func PrivateNetConfig() *Config { return &Config{ ZetaChainRPC: "http://127.0.0.1:26657", diff --git a/cmd/zetatool/tracker/track_cctx.go b/cmd/zetatool/tracker/track_cctx.go deleted file mode 100644 index 08ae8ef3d5..0000000000 --- a/cmd/zetatool/tracker/track_cctx.go +++ /dev/null @@ -1,65 +0,0 @@ -package tracker - -import ( - "fmt" - - "github.com/zeta-chain/node/cmd/zetatool/cctx" - "github.com/zeta-chain/node/cmd/zetatool/context" - crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" -) - -func UpdateCCTXStatus(ctx context.Context, cctxDetails *cctx.CCTXDetails) error { - var ( - zetacoreClient = ctx.GetZetaCoreClient() - goCtx = ctx.GetContext() - ) - switch cctxDetails.Status { - case cctx.PendingInboundConfirmation: - return nil - default: - cctx, err := zetacoreClient.GetCctxByHash(goCtx, cctxDetails.CCCTXIdentifier) - if err != nil { - return fmt.Errorf("failed to get cctx: %w", err) - } - - cctxDetails.UpdateStatusFromZetacoreCCTX(cctx.CctxStatus.Status) - } - return nil -} - -func UpdateHashListForPendingCCTX(ctx context.Context, cctxDetails *cctx.CCTXDetails) error { - var ( - zetacoreClient = ctx.GetZetaCoreClient() - goCtx = ctx.GetContext() - ) - - if !cctxDetails.IsPendingConfirmation() { - return nil - } - - CCTX, err := zetacoreClient.GetCctxByHash(goCtx, cctxDetails.CCCTXIdentifier) - if err != nil { - return fmt.Errorf("failed to get cctx: %w", err) - } - - outboundParams := CCTX.GetCurrentOutboundParam() - - tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundParams.ReceiverChainId, outboundParams.TssNonce) - if err != nil { - switch { - case err.Error() == "rpc error: code = NotFound desc = not found": - return err - case CCTX.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound: - cctxDetails.Status = cctx.PendingOutboundConfirmation - case CCTX.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert: - cctxDetails.Status = cctx.PendingRevertConfirmation - } - } - hashList := []string{} - for _, hash := range tracker.HashList { - hashList = append(hashList, hash.TxHash) - } - - cctxDetails.OutboundTrackerHashList = hashList - return nil -} diff --git a/pkg/rpc/clients_crosschain.go b/pkg/rpc/clients_crosschain.go index 8a95c518ff..78140a70cb 100644 --- a/pkg/rpc/clients_crosschain.go +++ b/pkg/rpc/clients_crosschain.go @@ -4,6 +4,7 @@ import ( "context" "cosmossdk.io/errors" + "github.com/zeta-chain/node/pkg/chains" "google.golang.org/grpc" "github.com/zeta-chain/node/x/crosschain/types" @@ -129,10 +130,10 @@ func (c *Clients) ListPendingCCTX(ctx context.Context, chainID int64) ([]*types. // GetOutboundTracker returns the outbound tracker for a chain and nonce func (c *Clients) GetOutboundTracker( ctx context.Context, - chainID int64, + chain chains.Chain, nonce uint64, ) (*types.OutboundTracker, error) { - in := &types.QueryGetOutboundTrackerRequest{ChainID: chainID, Nonce: nonce} + in := &types.QueryGetOutboundTrackerRequest{ChainID: chain.ChainId, Nonce: nonce} resp, err := c.Crosschain.OutboundTracker(ctx, in) if err != nil { diff --git a/zetaclient/zetacore/tx.go b/zetaclient/zetacore/tx.go index 78e7670f48..8b24a59fbd 100644 --- a/zetaclient/zetacore/tx.go +++ b/zetaclient/zetacore/tx.go @@ -90,7 +90,9 @@ func WrapMessageWithAuthz(msg sdk.Msg) (sdk.Msg, clientauthz.Signer, error) { // PostOutboundTracker adds an outbound tracker func (c *Client) PostOutboundTracker(ctx context.Context, chainID int64, nonce uint64, txHash string) (string, error) { // don't report if the tracker already contains the txHash - tracker, err := c.GetOutboundTracker(ctx, chainID, nonce) + tracker, err := c.GetOutboundTracker(ctx, chains.Chain{ + ChainId: chainID, + }, nonce) if err == nil { for _, hash := range tracker.HashList { if strings.EqualFold(hash.TxHash, txHash) { From 5e0605d5a16c4f8813287c51394adfa57311438b Mon Sep 17 00:00:00 2001 From: Tanmay Date: Sun, 2 Feb 2025 13:53:14 -0500 Subject: [PATCH 03/14] add chains package --- cmd/zetae2e/local/bitcoin.go | 54 +++ cmd/zetae2e/local/evm.go | 124 +++---- cmd/zetae2e/local/local.go | 52 +-- cmd/zetatool/ballot/ballot.go | 70 ---- cmd/zetatool/ballot/solana.go | 110 ------- cmd/zetatool/{ballot => cctx}/ballot_test.go | 19 +- cmd/zetatool/cctx/cctx_details.go | 27 +- cmd/zetatool/cctx/cctx_status.go | 2 +- cmd/zetatool/cctx/inbound.go | 327 +++++++++++++++++++ cmd/zetatool/cctx/outbound.go | 75 +++++ cmd/zetatool/{chains => cctx}/zetachain.go | 16 +- cmd/zetatool/{ballot => chains}/bitcoin.go | 115 ++----- cmd/zetatool/chains/evm.go | 213 +----------- cmd/zetatool/chains/solana.go | 41 +++ cmd/zetatool/cli/cctx_tracker.go | 23 +- cmd/zetatool/cli/inbound_ballot.go | 11 +- cmd/zetatool/config/config.go | 1 + cmd/zetatool/main.go | 1 + e2e/e2etests/test_eth_deposit.go | 2 + e2e/e2etests/test_eth_withdraw.go | 2 + 20 files changed, 664 insertions(+), 621 deletions(-) delete mode 100644 cmd/zetatool/ballot/ballot.go delete mode 100644 cmd/zetatool/ballot/solana.go rename cmd/zetatool/{ballot => cctx}/ballot_test.go (82%) create mode 100644 cmd/zetatool/cctx/inbound.go create mode 100644 cmd/zetatool/cctx/outbound.go rename cmd/zetatool/{chains => cctx}/zetachain.go (55%) rename cmd/zetatool/{ballot => chains}/bitcoin.go (56%) create mode 100644 cmd/zetatool/chains/solana.go diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 443ab26c0c..19db09c2b2 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -6,6 +6,7 @@ import ( "github.com/fatih/color" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/e2etests" @@ -13,6 +14,59 @@ import ( "github.com/zeta-chain/node/testutil" ) +func startBitcoinTests(eg *errgroup.Group, conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, light, skipBitcoinSetup bool) { + { + // start the bitcoin tests + // btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs + bitcoinDepositTests := []string{ + e2etests.TestBitcoinDonationName, + e2etests.TestBitcoinDepositName, + e2etests.TestBitcoinDepositAndCallName, + e2etests.TestBitcoinDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoDepositName, + e2etests.TestBitcoinStdMemoDepositAndCallName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, + e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName, + e2etests.TestCrosschainSwapName, + } + bitcoinDepositTestsAdvanced := []string{ + e2etests.TestBitcoinDepositAndCallRevertWithDustName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, + e2etests.TestBitcoinDepositAndWithdrawWithDustName, + } + bitcoinWithdrawTests := []string{ + e2etests.TestBitcoinWithdrawSegWitName, + e2etests.TestBitcoinWithdrawInvalidAddressName, + e2etests.TestLegacyZetaWithdrawBTCRevertName, + } + bitcoinWithdrawTestsAdvanced := []string{ + e2etests.TestBitcoinWithdrawTaprootName, + e2etests.TestBitcoinWithdrawLegacyName, + e2etests.TestBitcoinWithdrawP2SHName, + e2etests.TestBitcoinWithdrawP2WSHName, + e2etests.TestBitcoinWithdrawMultipleName, + e2etests.TestBitcoinWithdrawRestrictedName, + } + + if !light { + // if light is enabled, only the most basic tests are run and advanced are skipped + bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...) + bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...) + } + bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines( + conf, + deployerRunner, + verbose, + !skipBitcoinSetup, + bitcoinDepositTests, + bitcoinWithdrawTests, + ) + eg.Go(bitcoinDepositTestRoutine) + eg.Go(bitcoinWithdrawTestRoutine) + } +} + // bitcoinTestRoutines returns test routines for deposit and withdraw tests func bitcoinTestRoutines( conf config.Config, diff --git a/cmd/zetae2e/local/evm.go b/cmd/zetae2e/local/evm.go index bcb88041e8..07257dc628 100644 --- a/cmd/zetae2e/local/evm.go +++ b/cmd/zetae2e/local/evm.go @@ -19,70 +19,70 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne e2etests.TestETHDepositName, e2etests.TestETHDepositAndCallName, e2etests.TestETHWithdrawName, - e2etests.TestETHWithdrawAndArbitraryCallName, - e2etests.TestETHWithdrawAndCallName, - e2etests.TestETHWithdrawAndCallThroughContractName, - e2etests.TestZEVMToEVMArbitraryCallName, - e2etests.TestZEVMToEVMCallName, - e2etests.TestZEVMToEVMCallThroughContractName, - e2etests.TestEVMToZEVMCallName, - e2etests.TestETHDepositAndCallNoMessageName, - e2etests.TestETHWithdrawAndCallNoMessageName, - e2etests.TestEtherWithdrawRestrictedName, + //e2etests.TestETHWithdrawAndArbitraryCallName, + //e2etests.TestETHWithdrawAndCallName, + //e2etests.TestETHWithdrawAndCallThroughContractName, + //e2etests.TestZEVMToEVMArbitraryCallName, + //e2etests.TestZEVMToEVMCallName, + //e2etests.TestZEVMToEVMCallThroughContractName, + //e2etests.TestEVMToZEVMCallName, + //e2etests.TestETHDepositAndCallNoMessageName, + //e2etests.TestETHWithdrawAndCallNoMessageName, + //e2etests.TestEtherWithdrawRestrictedName, )) - // Test happy paths for erc20 token workflow - eg.Go(evmTestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, - e2etests.TestETHDepositName, // necessary to pay fees on ZEVM - e2etests.TestERC20DepositName, - e2etests.TestERC20DepositAndCallName, - e2etests.TestERC20WithdrawName, - e2etests.TestERC20WithdrawAndArbitraryCallName, - e2etests.TestERC20WithdrawAndCallName, - e2etests.TestERC20DepositAndCallNoMessageName, - e2etests.TestERC20WithdrawAndCallNoMessageName, - e2etests.TestDepositAndCallSwapName, - e2etests.TestERC20DepositRestrictedName, - )) - - // Test revert cases for gas token workflow - eg.Go( - evmTestRoutine( - conf, - "eth-revert", - conf.AdditionalAccounts.UserEtherRevert, - color.FgHiYellow, - deployerRunner, - verbose, - e2etests.TestETHDepositName, // necessary to pay fees on ZEVM and withdraw - e2etests.TestETHDepositAndCallRevertName, - e2etests.TestETHDepositAndCallRevertWithCallName, - e2etests.TestETHWithdrawAndCallRevertName, - e2etests.TestETHWithdrawAndCallRevertWithCallName, - e2etests.TestETHWithdrawAndCallRevertWithWithdrawName, - e2etests.TestDepositAndCallOutOfGasName, - ), - ) - - // Test revert cases for erc20 token workflow - eg.Go( - evmTestRoutine( - conf, - "erc20-revert", - conf.AdditionalAccounts.UserERC20Revert, - color.FgHiRed, - deployerRunner, - verbose, - e2etests.TestETHDepositName, // necessary to pay fees on ZEVM - e2etests.TestERC20DepositName, // necessary to have assets to withdraw - e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts - e2etests.TestOperationAddLiquidityERC20Name, - e2etests.TestERC20DepositAndCallRevertName, - e2etests.TestERC20DepositAndCallRevertWithCallName, - e2etests.TestERC20WithdrawAndCallRevertName, - e2etests.TestERC20WithdrawAndCallRevertWithCallName, - ), - ) + //// Test happy paths for erc20 token workflow + //eg.Go(evmTestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, + // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM + // e2etests.TestERC20DepositName, + // e2etests.TestERC20DepositAndCallName, + // e2etests.TestERC20WithdrawName, + // e2etests.TestERC20WithdrawAndArbitraryCallName, + // e2etests.TestERC20WithdrawAndCallName, + // e2etests.TestERC20DepositAndCallNoMessageName, + // e2etests.TestERC20WithdrawAndCallNoMessageName, + // e2etests.TestDepositAndCallSwapName, + // e2etests.TestERC20DepositRestrictedName, + //)) + // + //// Test revert cases for gas token workflow + //eg.Go( + // evmTestRoutine( + // conf, + // "eth-revert", + // conf.AdditionalAccounts.UserEtherRevert, + // color.FgHiYellow, + // deployerRunner, + // verbose, + // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM and withdraw + // e2etests.TestETHDepositAndCallRevertName, + // e2etests.TestETHDepositAndCallRevertWithCallName, + // e2etests.TestETHWithdrawAndCallRevertName, + // e2etests.TestETHWithdrawAndCallRevertWithCallName, + // e2etests.TestETHWithdrawAndCallRevertWithWithdrawName, + // e2etests.TestDepositAndCallOutOfGasName, + // ), + //) + // + //// Test revert cases for erc20 token workflow + //eg.Go( + // evmTestRoutine( + // conf, + // "erc20-revert", + // conf.AdditionalAccounts.UserERC20Revert, + // color.FgHiRed, + // deployerRunner, + // verbose, + // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM + // e2etests.TestERC20DepositName, // necessary to have assets to withdraw + // e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts + // e2etests.TestOperationAddLiquidityERC20Name, + // e2etests.TestERC20DepositAndCallRevertName, + // e2etests.TestERC20DepositAndCallRevertWithCallName, + // e2etests.TestERC20WithdrawAndCallRevertName, + // e2etests.TestERC20WithdrawAndCallRevertWithCallName, + // ), + //) } // evmTestRoutine runs EVM chain related e2e tests diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index ad589a58a6..11ececf5ad 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -11,6 +11,7 @@ import ( "time" "github.com/fatih/color" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -291,55 +292,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipRegular { // start the EVM tests startEVMTests(&eg, conf, deployerRunner, verbose) - - // start the bitcoin tests - // btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs - bitcoinDepositTests := []string{ - e2etests.TestBitcoinDonationName, - e2etests.TestBitcoinDepositName, - e2etests.TestBitcoinDepositAndCallName, - e2etests.TestBitcoinDepositAndCallRevertName, - e2etests.TestBitcoinStdMemoDepositName, - e2etests.TestBitcoinStdMemoDepositAndCallName, - e2etests.TestBitcoinStdMemoDepositAndCallRevertName, - e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, - e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName, - e2etests.TestCrosschainSwapName, - } - bitcoinDepositTestsAdvanced := []string{ - e2etests.TestBitcoinDepositAndCallRevertWithDustName, - e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, - e2etests.TestBitcoinDepositAndWithdrawWithDustName, - } - bitcoinWithdrawTests := []string{ - e2etests.TestBitcoinWithdrawSegWitName, - e2etests.TestBitcoinWithdrawInvalidAddressName, - e2etests.TestLegacyZetaWithdrawBTCRevertName, - } - bitcoinWithdrawTestsAdvanced := []string{ - e2etests.TestBitcoinWithdrawTaprootName, - e2etests.TestBitcoinWithdrawLegacyName, - e2etests.TestBitcoinWithdrawP2SHName, - e2etests.TestBitcoinWithdrawP2WSHName, - e2etests.TestBitcoinWithdrawMultipleName, - e2etests.TestBitcoinWithdrawRestrictedName, - } - - if !light { - // if light is enabled, only the most basic tests are run and advanced are skipped - bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...) - bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...) - } - bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines( - conf, - deployerRunner, - verbose, - !skipBitcoinSetup, - bitcoinDepositTests, - bitcoinWithdrawTests, - ) - eg.Go(bitcoinDepositTestRoutine) - eg.Go(bitcoinWithdrawTestRoutine) + log.Info().Msgf("EVM tests started %v %v", light, skipBitcoinSetup) + //startBitcoinTests(&eg, conf, deployerRunner, verbose, light, skipBitcoinSetup) } if !skipPrecompiles { diff --git a/cmd/zetatool/ballot/ballot.go b/cmd/zetatool/ballot/ballot.go deleted file mode 100644 index fe513346ec..0000000000 --- a/cmd/zetatool/ballot/ballot.go +++ /dev/null @@ -1,70 +0,0 @@ -package ballot - -import ( - "fmt" - - "github.com/zeta-chain/node/cmd/zetatool/cctx" - "github.com/zeta-chain/node/cmd/zetatool/chains" - - "github.com/zeta-chain/node/cmd/zetatool/context" -) - -func GetBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { - var ( - ballotIdentifierMessage = cctx.CCTXDetails{} - inboundChain = ctx.GetInboundChain() - err error - ) - - switch { - case inboundChain.IsZetaChain(): - { - ballotIdentifierMessage, err = chains.CheckInBoundTx(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for zeta chain %d, %w", - inboundChain.ChainId, - err, - ) - } - } - - case inboundChain.IsEVMChain(): - { - ballotIdentifierMessage, err = chains.EvmInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for evm chain %d, %w", - inboundChain.ChainId, - err, - ) - } - } - case inboundChain.IsBitcoinChain(): - { - ballotIdentifierMessage, err = btcInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for bitcoin chain %d, %w", - inboundChain.ChainId, - err, - ) - } - } - case inboundChain.IsSolanaChain(): - { - ballotIdentifierMessage, err = solanaInboundBallotIdentifier(ctx) - if err != nil { - return ballotIdentifierMessage, fmt.Errorf( - "failed to get inbound ballot for solana chain %d, %w", - inboundChain.ChainId, - err, - ) - } - } - default: - ballotIdentifierMessage.Message = "Chain not supported" - } - - return ballotIdentifierMessage, nil -} diff --git a/cmd/zetatool/ballot/solana.go b/cmd/zetatool/ballot/solana.go deleted file mode 100644 index 7ede9cabee..0000000000 --- a/cmd/zetatool/ballot/solana.go +++ /dev/null @@ -1,110 +0,0 @@ -package ballot - -import ( - "encoding/hex" - "fmt" - - cosmosmath "cosmossdk.io/math" - "github.com/gagliardetto/solana-go" - solrpc "github.com/gagliardetto/solana-go/rpc" - "github.com/zeta-chain/node/cmd/zetatool/context" - - "github.com/zeta-chain/node/cmd/zetatool/cctx" - - solanacontracts "github.com/zeta-chain/node/pkg/contracts/solana" - - crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" - "github.com/zeta-chain/node/zetaclient/chains/solana/observer" - solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" - clienttypes "github.com/zeta-chain/node/zetaclient/types" -) - -func solanaInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { - var ( - inboundHash = ctx.GetInboundHash() - cctxDetails = cctx.NewCCTXDetails() - inboundChain = ctx.GetInboundChain() - zetacoreClient = ctx.GetZetaCoreClient() - zetaChainID = ctx.GetConfig().ZetaChainID - cfg = ctx.GetConfig() - logger = ctx.GetLogger() - goCtx = ctx.GetContext() - ) - solClient := solrpc.New(cfg.SolanaRPC) - if solClient == nil { - return cctxDetails, fmt.Errorf("error creating rpc client") - } - - signature := solana.MustSignatureFromBase58(inboundHash) - - txResult, err := solanarpc.GetTransaction(goCtx, solClient, signature) - if err != nil { - return cctxDetails, fmt.Errorf("error getting transaction: %w", err) - } - - chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) - } - - gatewayID, _, err := solanacontracts.ParseGatewayWithPDA(chainParams.GatewayAddress) - if err != nil { - return cctxDetails, fmt.Errorf("cannot parse gateway address: %s, err: %w", chainParams.GatewayAddress, err) - } - - events, err := observer.FilterInboundEvents(txResult, - gatewayID, - inboundChain.ChainId, - logger, - ) - - if err != nil { - return cctxDetails, fmt.Errorf("failed to filter solana inbound events: %w", err) - } - - msg := &crosschaintypes.MsgVoteInbound{} - - // build inbound vote message from events and post to zetacore - for _, event := range events { - msg, err = voteMsgFromSolEvent(event, zetaChainID) - if err != nil { - return cctxDetails, fmt.Errorf("failed to create vote message: %w", err) - } - } - - cctxDetails.CCCTXIdentifier = msg.Digest() - cctxDetails.Status = cctx.PendingInboundVoting - - return cctxDetails, nil -} - -// voteMsgFromSolEvent builds a MsgVoteInbound from an inbound event -func voteMsgFromSolEvent(event *clienttypes.InboundEvent, - zetaChainID int64) (*crosschaintypes.MsgVoteInbound, error) { - // decode event memo bytes to get the receiver - err := event.DecodeMemo() - if err != nil { - return nil, fmt.Errorf("failed to decode memo: %w", err) - } - - // create inbound vote message - return crosschaintypes.NewMsgVoteInbound( - "", - event.Sender, - event.SenderChainID, - event.Sender, - event.Receiver, - zetaChainID, - cosmosmath.NewUint(event.Amount), - hex.EncodeToString(event.Memo), - event.TxHash, - event.BlockNumber, - 0, - event.CoinType, - event.Asset, - 0, // not a smart contract call - crosschaintypes.ProtocolContractVersion_V1, - false, // not relevant for v1 - crosschaintypes.InboundStatus_SUCCESS, - ), nil -} diff --git a/cmd/zetatool/ballot/ballot_test.go b/cmd/zetatool/cctx/ballot_test.go similarity index 82% rename from cmd/zetatool/ballot/ballot_test.go rename to cmd/zetatool/cctx/ballot_test.go index 00293b3044..19d902b3c9 100644 --- a/cmd/zetatool/ballot/ballot_test.go +++ b/cmd/zetatool/cctx/ballot_test.go @@ -1,51 +1,57 @@ -package ballot_test +package cctx_test import ( "context" "testing" "github.com/stretchr/testify/require" - "github.com/zeta-chain/node/cmd/zetatool/ballot" + "github.com/zeta-chain/node/cmd/zetatool/cctx" zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/chains" ) -func Test_GetBallotIdentifier(t *testing.T) { +func Test_InboundBallotIdentifier(t *testing.T) { tt := []struct { name string inboundHash string inboundChainID int64 expectedBallotIdentifier string + expectError bool }{ { name: chains.Ethereum.Name, inboundHash: "0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f56cb9a", inboundChainID: chains.Ethereum.ChainId, expectedBallotIdentifier: "0xae189ab5cd884af784835297ac43eb55deb8a7800023534c580f44ee2b3eb5ed", + expectError: false, }, { name: chains.BaseMainnet.Name, inboundHash: "0x88ee0943863fd8649546eb3affaf725f8caf09f44ebc5aa64de592b2edf378c8", inboundChainID: chains.BaseMainnet.ChainId, expectedBallotIdentifier: "0xe2b4c3f5dbef8fb7feb14bdf0a3f63ca7018678ecb6ae99ff697ccd962932ca2", + expectError: false, }, { name: chains.BscMainnet.Name, inboundHash: "0xfa18cbcdbf70e987600647ee77a1a28f5ca707acf9b72462fada02fff2a94d2f", inboundChainID: chains.BscMainnet.ChainId, expectedBallotIdentifier: "0xc7b289172db825b3c0490f263f35c8596b6f1fab8ec4c44db46de3020fe9e6e6", + expectError: false, }, { name: chains.Polygon.Name, inboundHash: "0x70b9b3ba89ff647257ab0085d90d60dc99b693c66931c4535e117b66a25236ce", inboundChainID: chains.Polygon.ChainId, expectedBallotIdentifier: "0xf8ed419d9798aed83070763355628e2638ae9a4a47aa9c93ffc32f4b72c9fef4", + expectError: false, }, { name: chains.SolanaMainnet.Name, inboundHash: "5oj38HmTH4k2NSsqHK9oRrLjpPNBkm17dNXHFsaT6cTuJQRPWTCGqsPpRumPEbpL2B6Wuv51M69WoJwM24864PjB", inboundChainID: chains.SolanaMainnet.ChainId, expectedBallotIdentifier: "0xd7823bbbae1e3c893ac34d1053834c9591336eb6b3925b3cc1d0fa60f4eeaa4b", + expectError: false, }, } @@ -53,10 +59,11 @@ func Test_GetBallotIdentifier(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx, err := zetatoolcontext.NewContext(context.Background(), tc.inboundChainID, tc.inboundHash, "") require.NoError(t, err) - ballotIdentifierMessage, err := ballot.GetBallotIdentifier(ctx) + c := cctx.NewCCTXDetails() + err = c.CheckInbound(ctx) require.NoError(t, err) - if ballotIdentifierMessage.CCCTXIdentifier != tc.expectedBallotIdentifier { - t.Errorf("expected %s, got %s", tc.expectedBallotIdentifier, ballotIdentifierMessage.CCCTXIdentifier) + if !tc.expectError && c.CCCTXIdentifier != tc.expectedBallotIdentifier { + t.Errorf("expected %s, got %s", tc.expectedBallotIdentifier, c.CCCTXIdentifier) } }) } diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 00aba56512..bed834e8ea 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -17,8 +17,8 @@ type CCTXDetails struct { Message string `json:"message"` } -func NewCCTXDetails() CCTXDetails { - return CCTXDetails{ +func NewCCTXDetails() *CCTXDetails { + return &CCTXDetails{ CCCTXIdentifier: "", Status: Unknown, } @@ -50,6 +50,10 @@ func (c *CCTXDetails) IsPendingConfirmation() bool { } func (c *CCTXDetails) Print() string { + return fmt.Sprintf("CCTX: %s Status: %s", c.CCCTXIdentifier, c.Status.String()) +} + +func (c *CCTXDetails) DebugPrint() string { return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCCTXIdentifier, c.Status.String(), c.Message) } @@ -106,7 +110,7 @@ func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundChain, outboundNonce) // tracker is found that means the outbound has broadcasted but we are waiting for confirmations if err == nil && tracker != nil { - c.updateToPendingConfirmation() + c.updateOutboundConfirmation() var hashList []string for _, hash := range tracker.HashList { hashList = append(hashList, hash.TxHash) @@ -115,12 +119,19 @@ func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { return } // the cctx is in pending state by the outbound signing has not been done - c.updateToPendingSigning() + c.updateOutboundSigning() return } +func (c *CCTXDetails) updateInboundConfirmation(isConfirmed bool) { + c.Status = PendingInboundConfirmation + if isConfirmed { + c.Status = PendingInboundVoting + } +} + // 1 - Signing -func (c *CCTXDetails) updateToPendingSigning() { +func (c *CCTXDetails) updateOutboundSigning() { switch { case c.Status == PendingOutbound: c.Status = PendingOutboundSigning @@ -130,7 +141,7 @@ func (c *CCTXDetails) updateToPendingSigning() { } // 2 - Confirmation -func (c *CCTXDetails) updateToPendingConfirmation() { +func (c *CCTXDetails) updateOutboundConfirmation() { switch { case c.Status == PendingOutbound: c.Status = PendingOutboundConfirmation @@ -139,8 +150,8 @@ func (c *CCTXDetails) updateToPendingConfirmation() { } } -// UpdateToPendingVoting 3 - Voting -func (c *CCTXDetails) UpdateToPendingVoting() { +// UpdateOutboundVoting 3 - Voting +func (c *CCTXDetails) UpdateOutboundVoting() { switch { case c.Status == PendingOutboundConfirmation: c.Status = PendingOutboundVoting diff --git a/cmd/zetatool/cctx/cctx_status.go b/cmd/zetatool/cctx/cctx_status.go index 02c1efc05d..0da1e49327 100644 --- a/cmd/zetatool/cctx/cctx_status.go +++ b/cmd/zetatool/cctx/cctx_status.go @@ -46,7 +46,7 @@ func (s Status) String() string { case PendingRevertSigning: return "PendingRevertSigning" case PendingOutboundVoting: - return "PEndingOutboundVoting" + return "PendingOutboundVoting" default: return "Unknown" } diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go new file mode 100644 index 0000000000..dac89614a5 --- /dev/null +++ b/cmd/zetatool/cctx/inbound.go @@ -0,0 +1,327 @@ +package cctx + +import ( + "fmt" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/gagliardetto/solana-go" + solrpc "github.com/gagliardetto/solana-go/rpc" + zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" + "github.com/zeta-chain/node/cmd/zetatool/context" + "github.com/zeta-chain/node/pkg/chains" + solanacontracts "github.com/zeta-chain/node/pkg/contracts/solana" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + "github.com/zeta-chain/node/x/observer/types" + "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client" + zetaevmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" + "github.com/zeta-chain/node/zetaclient/chains/solana/observer" + solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" + zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" + "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" +) + +func (c *CCTXDetails) CheckInbound(ctx *context.Context) error { + var ( + inboundChain = ctx.GetInboundChain() + err error + ) + + switch { + case inboundChain.IsZetaChain(): + { + err = CheckInBoundTx(ctx, c) + if err != nil { + return fmt.Errorf( + "failed to get inbound ballot for zeta chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + + case inboundChain.IsEVMChain(): + { + err = c.evmInboundBallotIdentifier(ctx) + if err != nil { + return fmt.Errorf( + "failed to get inbound ballot for evm chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + case inboundChain.IsBitcoinChain(): + { + err = c.btcInboundBallotIdentifier(ctx) + if err != nil { + return fmt.Errorf( + "failed to get inbound ballot for bitcoin chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + case inboundChain.IsSolanaChain(): + { + err = c.solanaInboundBallotIdentifier(ctx) + if err != nil { + return fmt.Errorf( + "failed to get inbound ballot for solana chain %d, %w", + inboundChain.ChainId, + err, + ) + } + } + default: + c.Message = "Chain not supported" + } + + return nil +} + +func (c *CCTXDetails) btcInboundBallotIdentifier(ctx *context.Context) error { + var ( + inboundHash = ctx.GetInboundHash() + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + cfg = ctx.GetConfig() + logger = ctx.GetLogger() + goCtx = ctx.GetContext() + ) + + params, err := chains.BitcoinNetParamsFromChainID(inboundChain.ChainId) + if err != nil { + return fmt.Errorf("unable to get bitcoin net params from chain id: %w", err) + } + + connCfg := zetaclientConfig.BTCConfig{ + RPCUsername: cfg.BtcUser, + RPCPassword: cfg.BtcPassword, + RPCHost: cfg.BtcHost, + RPCParams: params.Name, + } + + rpcClient, err := client.New(connCfg, inboundChain.ChainId, logger) + if err != nil { + return fmt.Errorf("unable to create rpc client: %w", err) + } + + err = rpcClient.Ping(goCtx) + if err != nil { + return fmt.Errorf("error ping the bitcoin server: %w", err) + } + + res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) + if err != nil { + return fmt.Errorf("failed to get tss address: %w", err) + } + tssBtcAddress := res.GetBtc() + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) + if err != nil { + return fmt.Errorf("failed to get chain params: %w", err) + } + + cctxIdentifier, isConfirmed, err := zetatoolchains.BitcoinBallotIdentifier( + ctx, + rpcClient, + params, + tssBtcAddress, + inboundHash, + inboundChain.ChainId, + zetaChainID, + chainParams.ConfirmationCount, + ) + if err != nil { + return fmt.Errorf("failed to get bitcoin ballot identifier: %w", err) + } + c.CCCTXIdentifier = cctxIdentifier + c.updateInboundConfirmation(isConfirmed) + return nil +} + +func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { + var ( + inboundHash = ctx.GetInboundHash() + isConfirmed = false + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + goCtx = ctx.GetContext() + ) + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) + if err != nil { + return fmt.Errorf("failed to get chain params: %w", err) + } + + evmClient, err := zetatoolchains.GetEvmClient(ctx, inboundChain) + if err != nil { + return fmt.Errorf("failed to create evm client: %w", err) + } + // create evm client for the observation chain + tx, receipt, err := zetatoolchains.GetEvmTx(ctx, evmClient, inboundHash, inboundChain) + if err != nil { + return fmt.Errorf("failed to get tx: %w", err) + } + // Signer is unused + zetaEvmClient := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) + isConfirmed, err = zetaEvmClient.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) + if err != nil { + return fmt.Errorf("unable to confirm tx: %w", err) + } + res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) + if err != nil { + return fmt.Errorf("failed to get tss address: %w", err) + } + tssEthAddress := res.GetEth() + + if tx.To() == nil { + return fmt.Errorf("invalid transaction,to field is empty %s", inboundHash) + } + + msg := &crosschaintypes.MsgVoteInbound{} + // Create inbound vote message based on the cointype and protocol version + switch tx.To().Hex() { + case chainParams.ConnectorContractAddress: + { + // build inbound vote message and post vote + addrConnector := ethcommon.HexToAddress(chainParams.ConnectorContractAddress) + connector, err := zetaconnector.NewZetaConnectorNonEth(addrConnector, evmClient) + if err != nil { + return fmt.Errorf("failed to get connector contract: %w", err) + } + for _, log := range receipt.Logs { + event, err := connector.ParseZetaSent(*log) + if err == nil && event != nil { + msg = zetatoolchains.ZetaTokenVoteV1(event, inboundChain.ChainId) + } + } + } + case chainParams.Erc20CustodyContractAddress: + { + addrCustody := ethcommon.HexToAddress(chainParams.Erc20CustodyContractAddress) + custody, err := erc20custody.NewERC20Custody(addrCustody, evmClient) + if err != nil { + return fmt.Errorf("failed to get custody contract: %w", err) + } + sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) + if err != nil { + return fmt.Errorf("failed to get tx sender: %w", err) + } + for _, log := range receipt.Logs { + zetaDeposited, err := custody.ParseDeposited(*log) + if err == nil && zetaDeposited != nil { + msg = zetatoolchains.Erc20VoteV1(zetaDeposited, sender, inboundChain.ChainId, zetaChainID) + } + } + } + case tssEthAddress: + { + if receipt.Status != ethtypes.ReceiptStatusSuccessful { + return fmt.Errorf("tx failed on chain %d", inboundChain.ChainId) + } + sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) + if err != nil { + return fmt.Errorf("failed to get tx sender: %w", err) + } + msg = zetatoolchains.GasVoteV1(tx, sender, receipt.BlockNumber.Uint64(), inboundChain.ChainId, zetaChainID) + } + case chainParams.GatewayAddress: + { + gatewayAddr := ethcommon.HexToAddress(chainParams.GatewayAddress) + gateway, err := gatewayevm.NewGatewayEVM(gatewayAddr, evmClient) + if err != nil { + return fmt.Errorf("failed to get gateway contract: %w", err) + } + for _, log := range receipt.Logs { + if log == nil || log.Address != gatewayAddr { + continue + } + eventDeposit, err := gateway.ParseDeposited(*log) + if err == nil { + msg = zetatoolchains.DepositInboundVoteV2(eventDeposit, inboundChain.ChainId, zetaChainID) + break + } + eventDepositAndCall, err := gateway.ParseDepositedAndCalled(*log) + if err == nil { + msg = zetatoolchains.DepositAndCallInboundVoteV2(eventDepositAndCall, inboundChain.ChainId, zetaChainID) + break + } + eventCall, err := gateway.ParseCalled(*log) + if err == nil { + msg = zetatoolchains.CallInboundVoteV2(eventCall, inboundChain.ChainId, zetaChainID) + break + } + } + } + default: + return fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) + } + + c.CCCTXIdentifier = msg.Digest() + c.updateInboundConfirmation(isConfirmed) + return nil +} + +func (c *CCTXDetails) solanaInboundBallotIdentifier(ctx *context.Context) error { + var ( + inboundHash = ctx.GetInboundHash() + inboundChain = ctx.GetInboundChain() + zetacoreClient = ctx.GetZetaCoreClient() + zetaChainID = ctx.GetConfig().ZetaChainID + cfg = ctx.GetConfig() + logger = ctx.GetLogger() + goCtx = ctx.GetContext() + ) + solClient := solrpc.New(cfg.SolanaRPC) + if solClient == nil { + return fmt.Errorf("error creating rpc client") + } + + signature := solana.MustSignatureFromBase58(inboundHash) + + txResult, err := solanarpc.GetTransaction(goCtx, solClient, signature) + if err != nil { + return fmt.Errorf("error getting transaction: %w", err) + } + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) + if err != nil { + return fmt.Errorf("failed to get chain params: %w", err) + } + + gatewayID, _, err := solanacontracts.ParseGatewayWithPDA(chainParams.GatewayAddress) + if err != nil { + return fmt.Errorf("cannot parse gateway address: %s, err: %w", chainParams.GatewayAddress, err) + } + + events, err := observer.FilterInboundEvents(txResult, + gatewayID, + inboundChain.ChainId, + logger, + ) + + if err != nil { + return fmt.Errorf("failed to filter solana inbound events: %w", err) + } + + msg := &crosschaintypes.MsgVoteInbound{} + + // build inbound vote message from events and post to zetacore + for _, event := range events { + msg, err = zetatoolchains.VoteMsgFromSolEvent(event, zetaChainID) + if err != nil { + return fmt.Errorf("failed to create vote message: %w", err) + } + } + + c.CCCTXIdentifier = msg.Digest() + c.Status = PendingInboundVoting + + return nil +} diff --git a/cmd/zetatool/cctx/outbound.go b/cmd/zetatool/cctx/outbound.go new file mode 100644 index 0000000000..412947b0b3 --- /dev/null +++ b/cmd/zetatool/cctx/outbound.go @@ -0,0 +1,75 @@ +package cctx + +import ( + "fmt" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" + "github.com/zeta-chain/node/cmd/zetatool/context" + zetaevmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" +) + +func (c *CCTXDetails) CheckOutbound(ctx *context.Context) error { + var ( + outboundChain = ctx.GetInboundChain() + err error + ) + + switch { + case outboundChain.IsEVMChain(): + err = c.checkOutboundTx(ctx) + if err != nil { + return err + } + } + return nil +} + +// checkOutboundTx checks if the outbound transaction is confirmed on the outbound chain. +// If it's confirmed, we update the status to PendingOutboundVoting or PendingRevertVoting. Which means that the confirmation is done and we are not waiting for observers to vote +// Transition Status PendingConfirmation -> Status PendingVoting +func (c *CCTXDetails) checkOutboundTx(ctx *context.Context) error { + var ( + txHashList = c.OutboundTrackerHashList + outboundChain = c.OutboundChain + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) + if err != nil { + return fmt.Errorf("failed to get chain params: %v", err) + } + + // create evm client for the observation chain + evmClient, err := zetatoolchains.GetEvmClient(ctx, outboundChain) + if err != nil { + return fmt.Errorf("failed to create evm client: %v", err) + } + + foundConfirmedTx := false + + // If one of the hash is confirmed, we update the status to pending voting + // There might be a condition where we have multiple txs and the wrong tx is confirmed. + //To verify that we need, check CCTX data + for _, hash := range txHashList { + tx, _, err := zetatoolchains.GetEvmTx(ctx, evmClient, hash, outboundChain) + if err != nil { + continue + } + // Signer is unused + c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) + confirmed, err := c.IsTxConfirmed(goCtx, hash, chainParams.ConfirmationCount) + if err != nil { + continue + } + if confirmed { + foundConfirmedTx = true + break + } + } + if foundConfirmedTx { + c.UpdateOutboundVoting() + } + return nil +} diff --git a/cmd/zetatool/chains/zetachain.go b/cmd/zetatool/cctx/zetachain.go similarity index 55% rename from cmd/zetatool/chains/zetachain.go rename to cmd/zetatool/cctx/zetachain.go index 8c77950da9..e2159853cc 100644 --- a/cmd/zetatool/chains/zetachain.go +++ b/cmd/zetatool/cctx/zetachain.go @@ -1,17 +1,15 @@ -package chains +package cctx import ( "fmt" - "github.com/zeta-chain/node/cmd/zetatool/cctx" "github.com/zeta-chain/node/cmd/zetatool/context" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -func CheckInBoundTx(ctx *context.Context) (cctx.CCTXDetails, error) { +func CheckInBoundTx(ctx *context.Context, cctxDetails *CCTXDetails) error { var ( inboundHash = ctx.GetInboundHash() - cctxDetails = cctx.NewCCTXDetails() zetacoreClient = ctx.GetZetaCoreClient() goCtx = ctx.GetContext() ) @@ -21,17 +19,17 @@ func CheckInBoundTx(ctx *context.Context) (cctx.CCTXDetails, error) { InboundHash: inboundHash, }) if err != nil { - return cctxDetails, fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) + return fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) } if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) == 0 { - return cctxDetails, fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) + return fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) } if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) > 1 { - return cctxDetails, fmt.Errorf("inbound hash more than one cctx %s", inboundHash) + return fmt.Errorf("inbound hash more than one cctx %s", inboundHash) } cctxDetails.CCCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] - cctxDetails.Status = cctx.PendingOutbound - return cctxDetails, nil + cctxDetails.Status = PendingOutbound + return nil } diff --git a/cmd/zetatool/ballot/bitcoin.go b/cmd/zetatool/chains/bitcoin.go similarity index 56% rename from cmd/zetatool/ballot/bitcoin.go rename to cmd/zetatool/chains/bitcoin.go index d917ead34d..ce55caae5c 100644 --- a/cmd/zetatool/ballot/bitcoin.go +++ b/cmd/zetatool/chains/bitcoin.go @@ -1,4 +1,4 @@ -package ballot +package chains import ( "encoding/hex" @@ -9,83 +9,15 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/rs/zerolog" - "github.com/zeta-chain/node/cmd/zetatool/cctx" "github.com/zeta-chain/node/cmd/zetatool/context" - - "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" - crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" - "github.com/zeta-chain/node/x/observer/types" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/common" zetaclientObserver "github.com/zeta-chain/node/zetaclient/chains/bitcoin/observer" - zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) -func btcInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { - var ( - inboundHash = ctx.GetInboundHash() - cctxDetails = cctx.NewCCTXDetails() - inboundChain = ctx.GetInboundChain() - zetacoreClient = ctx.GetZetaCoreClient() - zetaChainID = ctx.GetConfig().ZetaChainID - cfg = ctx.GetConfig() - logger = ctx.GetLogger() - goCtx = ctx.GetContext() - ) - - params, err := chains.BitcoinNetParamsFromChainID(inboundChain.ChainId) - if err != nil { - return cctxDetails, fmt.Errorf("unable to get bitcoin net params from chain id: %w", err) - } - - connCfg := zetaclientConfig.BTCConfig{ - RPCUsername: cfg.BtcUser, - RPCPassword: cfg.BtcPassword, - RPCHost: cfg.BtcHost, - RPCParams: params.Name, - } - - rpcClient, err := client.New(connCfg, inboundChain.ChainId, logger) - if err != nil { - return cctxDetails, fmt.Errorf("unable to create rpc client: %w", err) - } - - err = rpcClient.Ping(goCtx) - if err != nil { - return cctxDetails, fmt.Errorf("error ping the bitcoin server: %w", err) - } - - res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get tss address: %w", err) - } - tssBtcAddress := res.GetBtc() - - chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) - } - - err = bitcoinBallotIdentifier( - ctx, - rpcClient, - params, - tssBtcAddress, - inboundHash, - inboundChain.ChainId, - zetaChainID, - chainParams.ConfirmationCount, - &cctxDetails, - ) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get bitcoin ballot identifier: %w", err) - } - return cctxDetails, nil -} - -func bitcoinBallotIdentifier( +func BitcoinBallotIdentifier( ctx *context.Context, btcClient *client.Client, params *chaincfg.Params, @@ -94,34 +26,32 @@ func bitcoinBallotIdentifier( senderChainID int64, zetacoreChainID int64, confirmationCount uint64, - cctxDetails *cctx.CCTXDetails, -) error { +) (cctxIdentifier string, isConfirmed bool, err error) { var ( goCtx = ctx.GetContext() ) hash, err := chainhash.NewHashFromStr(txHash) if err != nil { - return err + return } tx, err := btcClient.GetRawTransactionVerbose(goCtx, hash) if err != nil { - return err + return } - if tx.Confirmations < confirmationCount { - cctxDetails.Status = cctx.PendingInboundConfirmation - } else { - cctxDetails.Status = cctx.PendingInboundVoting + + if tx.Confirmations >= confirmationCount { + isConfirmed = true } blockHash, err := chainhash.NewHashFromStr(tx.BlockHash) if err != nil { - return err + return } blockVb, err := btcClient.GetBlockVerbose(goCtx, blockHash) if err != nil { - return err + return } event, err := zetaclientObserver.GetBtcEvent( @@ -135,28 +65,33 @@ func bitcoinBallotIdentifier( common.CalcDepositorFee, ) if err != nil { - return fmt.Errorf("error getting btc event: %w", err) + return } if event == nil { - return fmt.Errorf("no event built for btc sent to TSS") + err = fmt.Errorf("no event built for btc sent to TSS") + return } - return identifierFromBtcEvent(event, senderChainID, zetacoreChainID, cctxDetails) + cctxIdentifier, err = identifierFromBtcEvent(event, senderChainID, zetacoreChainID) + if err != nil { + return + } + return } func identifierFromBtcEvent(event *zetaclientObserver.BTCInboundEvent, senderChainID int64, - zetacoreChainID int64, cctxDetails *cctx.CCTXDetails) error { + zetacoreChainID int64) (cctxIdentifier string, err error) { // decode event memo bytes - err := event.DecodeMemoBytes(senderChainID) + err = event.DecodeMemoBytes(senderChainID) if err != nil { - return fmt.Errorf("error decoding memo bytes: %w", err) + return } // convert the amount to integer (satoshis) amountSats, err := common.GetSatoshis(event.Value) if err != nil { - return fmt.Errorf("error converting amount to satoshis: %w", err) + return } amountInt := big.NewInt(amountSats) @@ -172,11 +107,11 @@ func identifierFromBtcEvent(event *zetaclientObserver.BTCInboundEvent, } } if msg == nil { - return fmt.Errorf("failed to create vote message") + return } - cctxDetails.CCCTXIdentifier = msg.Digest() - return nil + cctxIdentifier = msg.Digest() + return } // NewInboundVoteFromLegacyMemo creates a MsgVoteInbound message for inbound that uses legacy memo diff --git a/cmd/zetatool/chains/evm.go b/cmd/zetatool/chains/evm.go index 72718cbc12..1d6dd0d5d7 100644 --- a/cmd/zetatool/chains/evm.go +++ b/cmd/zetatool/chains/evm.go @@ -11,22 +11,18 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" - "github.com/zeta-chain/node/cmd/zetatool/cctx" - "github.com/zeta-chain/node/cmd/zetatool/context" - "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" - "github.com/zeta-chain/node/cmd/zetatool/config" + "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/constant" "github.com/zeta-chain/node/pkg/crypto" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" - "github.com/zeta-chain/node/x/observer/types" - zetaevmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" clienttypes "github.com/zeta-chain/node/zetaclient/types" "github.com/zeta-chain/node/zetaclient/zetacore" + "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" ) func resolveRPC(chain chains.Chain, cfg *config.Config) string { @@ -38,192 +34,11 @@ func resolveRPC(chain chains.Chain, cfg *config.Config) string { }[chain.Network] } -func EvmInboundBallotIdentifier(ctx *context.Context) (cctx.CCTXDetails, error) { - var ( - inboundHash = ctx.GetInboundHash() - cctxDetails = cctx.NewCCTXDetails() - inboundChain = ctx.GetInboundChain() - zetacoreClient = ctx.GetZetaCoreClient() - zetaChainID = ctx.GetConfig().ZetaChainID - goCtx = ctx.GetContext() - ) - - chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, inboundChain.ChainId) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get chain params: %w", err) - } - - evmClient, err := getEvmClient(ctx) - if err != nil { - return cctxDetails, fmt.Errorf("failed to create evm client: %w", err) - } - // create evm client for the observation chain - tx, receipt, err := getEvmTx(ctx, evmClient, inboundHash, inboundChain) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get tx: %w", err) - } - // Signer is unused - c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) - confirmed, err := c.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) - if err != nil { - return cctxDetails, fmt.Errorf("unable to confirm tx: %w", err) - } - if !confirmed { - cctxDetails.Status = cctx.PendingInboundConfirmation - } else { - cctxDetails.Status = cctx.PendingInboundVoting - } - - res, err := zetacoreClient.Observer.GetTssAddress(goCtx, &types.QueryGetTssAddressRequest{}) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get tss address: %w", err) - } - tssEthAddress := res.GetEth() - - if tx.To() == nil { - return cctxDetails, fmt.Errorf("invalid transaction,to field is empty %s", inboundHash) - } - - msg := &crosschaintypes.MsgVoteInbound{} - // Create inbound vote message based on the cointype and protocol version - switch tx.To().Hex() { - case chainParams.ConnectorContractAddress: - { - // build inbound vote message and post vote - addrConnector := ethcommon.HexToAddress(chainParams.ConnectorContractAddress) - connector, err := zetaconnector.NewZetaConnectorNonEth(addrConnector, evmClient) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get connector contract: %w", err) - } - for _, log := range receipt.Logs { - event, err := connector.ParseZetaSent(*log) - if err == nil && event != nil { - msg = zetaTokenVoteV1(event, inboundChain.ChainId) - } - } - } - case chainParams.Erc20CustodyContractAddress: - { - addrCustody := ethcommon.HexToAddress(chainParams.Erc20CustodyContractAddress) - custody, err := erc20custody.NewERC20Custody(addrCustody, evmClient) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get custody contract: %w", err) - } - sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get tx sender: %w", err) - } - for _, log := range receipt.Logs { - zetaDeposited, err := custody.ParseDeposited(*log) - if err == nil && zetaDeposited != nil { - msg = erc20VoteV1(zetaDeposited, sender, inboundChain.ChainId, zetaChainID) - } - } - } - case tssEthAddress: - { - if receipt.Status != ethtypes.ReceiptStatusSuccessful { - return cctxDetails, fmt.Errorf("tx failed on chain %d", inboundChain.ChainId) - } - sender, err := evmClient.TransactionSender(goCtx, tx, receipt.BlockHash, receipt.TransactionIndex) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get tx sender: %w", err) - } - msg = gasVoteV1(tx, sender, receipt.BlockNumber.Uint64(), inboundChain.ChainId, zetaChainID) - } - case chainParams.GatewayAddress: - { - gatewayAddr := ethcommon.HexToAddress(chainParams.GatewayAddress) - gateway, err := gatewayevm.NewGatewayEVM(gatewayAddr, evmClient) - if err != nil { - return cctxDetails, fmt.Errorf("failed to get gateway contract: %w", err) - } - for _, log := range receipt.Logs { - if log == nil || log.Address != gatewayAddr { - continue - } - eventDeposit, err := gateway.ParseDeposited(*log) - if err == nil { - msg = depositInboundVoteV2(eventDeposit, inboundChain.ChainId, zetaChainID) - break - } - eventDepositAndCall, err := gateway.ParseDepositedAndCalled(*log) - if err == nil { - msg = depositAndCallInboundVoteV2(eventDepositAndCall, inboundChain.ChainId, zetaChainID) - break - } - eventCall, err := gateway.ParseCalled(*log) - if err == nil { - msg = callInboundVoteV2(eventCall, inboundChain.ChainId, zetaChainID) - break - } - } - } - default: - return cctxDetails, fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) - } - - cctxDetails.CCCTXIdentifier = msg.Digest() - return cctxDetails, nil -} - -// CheckOutboundTx checks if the outbound transaction is confirmed on the outbound chain. -// If it's confirmed, we update the status to PendingOutboundVoting or PendingRevertVoting. Which means that the confirmation is done and we are not waiting for observers to vote -// Transition Status PendingConfirmation -> Status PendingVoting -func CheckOutboundTx(ctx *context.Context, cctxDetails *cctx.CCTXDetails) error { - var ( - txHashList = cctxDetails.OutboundTrackerHashList - outboundChain = cctxDetails.OutboundChain - zetacoreClient = ctx.GetZetaCoreClient() - goCtx = ctx.GetContext() - ) - - chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) - if err != nil { - return fmt.Errorf("failed to get chain params: %v", err) - } - - // create evm client for the observation chain - evmClient, err := getEvmClient(ctx) - if err != nil { - return fmt.Errorf("failed to create evm client: %v", err) - } - - foundConfirmedTx := false - - // If one of the hash is confirmed, we update the status to pending voting - // There might be a condition where we have multiple txs and the wrong tx is confirmed. - //To verify that we need, check CCTX data - for _, hash := range txHashList { - tx, _, err := getEvmTx(ctx, evmClient, hash, outboundChain) - if err != nil { - continue - } - // Signer is unused - c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) - confirmed, err := c.IsTxConfirmed(goCtx, hash, chainParams.ConfirmationCount) - if err != nil { - continue - } - if confirmed { - foundConfirmedTx = true - break - } - } - if foundConfirmedTx { - cctxDetails.UpdateToPendingVoting() - } - return nil -} - -func getEvmClient(ctx *context.Context) (*ethclient.Client, error) { - var ( - inboundChain = ctx.GetInboundChain() - ) +func GetEvmClient(ctx *context.Context, chain chains.Chain) (*ethclient.Client, error) { - evmRRC := resolveRPC(ctx.GetInboundChain(), ctx.GetConfig()) + evmRRC := resolveRPC(chain, ctx.GetConfig()) if evmRRC == "" { - return nil, fmt.Errorf("rpc not found for chain %d network %s", inboundChain.ChainId, inboundChain.Network) + return nil, fmt.Errorf("rpc not found for chain %d network %s", chain.ChainId, chain.Network) } rpcClient, err := ethrpc.DialHTTP(evmRRC) if err != nil { @@ -232,7 +47,7 @@ func getEvmClient(ctx *context.Context) (*ethclient.Client, error) { return ethclient.NewClient(rpcClient), nil } -func getEvmTx( +func GetEvmTx( ctx *context.Context, evmClient *ethclient.Client, inboundHash string, @@ -256,7 +71,7 @@ func getEvmTx( return tx, receipt, nil } -func zetaTokenVoteV1( +func ZetaTokenVoteV1( event *zetaconnector.ZetaConnectorNonEthZetaSent, observationChain int64, ) *crosschaintypes.MsgVoteInbound { @@ -289,7 +104,7 @@ func zetaTokenVoteV1( ) } -func erc20VoteV1( +func Erc20VoteV1( event *erc20custody.ERC20CustodyDeposited, sender ethcommon.Address, observationChain int64, @@ -319,7 +134,7 @@ func erc20VoteV1( ) } -func gasVoteV1( +func GasVoteV1( tx *ethtypes.Transaction, sender ethcommon.Address, blockNumber uint64, @@ -351,7 +166,7 @@ func gasVoteV1( ) } -func depositInboundVoteV2(event *gatewayevm.GatewayEVMDeposited, +func DepositInboundVoteV2(event *gatewayevm.GatewayEVMDeposited, senderChainID int64, zetacoreChainID int64) *crosschaintypes.MsgVoteInbound { // if event.Asset is zero, it's a native token @@ -389,7 +204,7 @@ func depositInboundVoteV2(event *gatewayevm.GatewayEVMDeposited, ) } -func depositAndCallInboundVoteV2(event *gatewayevm.GatewayEVMDepositedAndCalled, +func DepositAndCallInboundVoteV2(event *gatewayevm.GatewayEVMDepositedAndCalled, senderChainID int64, zetacoreChainID int64) *crosschaintypes.MsgVoteInbound { // if event.Asset is zero, it's a native token @@ -421,7 +236,7 @@ func depositAndCallInboundVoteV2(event *gatewayevm.GatewayEVMDepositedAndCalled, ) } -func callInboundVoteV2(event *gatewayevm.GatewayEVMCalled, +func CallInboundVoteV2(event *gatewayevm.GatewayEVMCalled, senderChainID int64, zetacoreChainID int64) *crosschaintypes.MsgVoteInbound { return crosschaintypes.NewMsgVoteInbound( diff --git a/cmd/zetatool/chains/solana.go b/cmd/zetatool/chains/solana.go new file mode 100644 index 0000000000..81f3731f99 --- /dev/null +++ b/cmd/zetatool/chains/solana.go @@ -0,0 +1,41 @@ +package chains + +import ( + "encoding/hex" + "fmt" + + cosmosmath "cosmossdk.io/math" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" + clienttypes "github.com/zeta-chain/node/zetaclient/types" +) + +// voteMsgFromSolEvent builds a MsgVoteInbound from an inbound event +func VoteMsgFromSolEvent(event *clienttypes.InboundEvent, + zetaChainID int64) (*crosschaintypes.MsgVoteInbound, error) { + // decode event memo bytes to get the receiver + err := event.DecodeMemo() + if err != nil { + return nil, fmt.Errorf("failed to decode memo: %w", err) + } + + // create inbound vote message + return crosschaintypes.NewMsgVoteInbound( + "", + event.Sender, + event.SenderChainID, + event.Sender, + event.Receiver, + zetaChainID, + cosmosmath.NewUint(event.Amount), + hex.EncodeToString(event.Memo), + event.TxHash, + event.BlockNumber, + 0, + event.CoinType, + event.Asset, + 0, // not a smart contract call + crosschaintypes.ProtocolContractVersion_V1, + false, // not relevant for v1 + crosschaintypes.InboundStatus_SUCCESS, + ), nil +} diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index a8295a61a0..d73a28df71 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -7,9 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/zeta-chain/node/cmd/zetatool/ballot" "github.com/zeta-chain/node/cmd/zetatool/cctx" - zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" "github.com/zeta-chain/node/cmd/zetatool/config" zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" ) @@ -43,18 +41,22 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to track cctx: %w", err) } + if cmd.Flag(config.FlagDebug).Changed { + log.Info().Msg(cctxDetails.DebugPrint()) + return nil + } log.Info().Msg(cctxDetails.Print()) return nil } -func trackCCTX(ctx *zetatoolcontext.Context) (cctx.CCTXDetails, error) { +func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.CCTXDetails, error) { var ( - cctxDetails = cctx.CCTXDetails{} + cctxDetails = cctx.NewCCTXDetails() err error ) // Get the ballot identifier for the inbound transaction and confirm that cctx status in atleast either PendingInboundConfirmation or PendingInboundVoting - cctxDetails, err = ballot.GetBallotIdentifier(ctx) + err = cctxDetails.CheckInbound(ctx) if err != nil { return cctxDetails, fmt.Errorf("failed to get ballot identifier: %v", err) } @@ -65,7 +67,7 @@ func trackCCTX(ctx *zetatoolcontext.Context) (cctx.CCTXDetails, error) { // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address.We can show some information to the user. // Add any error messages to the message field of the response instead of throwing an error - // Update cctx status from zetacore , if cctx is not found, it will continue to be in the status retuned by GetBallotIdentifier function PendingInboundVoting or PendingInboundConfirmation + // Update cctx status from zetacore , if cctx is not found, it will continue to be in the status retuned by CheckInbound function PendingInboundVoting or PendingInboundConfirmation cctxDetails.UpdateCCTXStatus(ctx) // The cctx details now have status from zetacore, we have not tried to a get more granular status from the outbound chain yet. @@ -88,12 +90,9 @@ func trackCCTX(ctx *zetatoolcontext.Context) (cctx.CCTXDetails, error) { } // Check outbound tx , we are waiting for the outbound tx to be confirmed - switch { - case cctxDetails.OutboundChain.IsEVMChain(): - err = zetatoolchains.CheckOutboundTx(ctx, &cctxDetails) - if err != nil { - return cctxDetails, err - } + err = cctxDetails.CheckOutbound(ctx) + if err != nil { + return cctxDetails, err } return cctxDetails, nil } diff --git a/cmd/zetatool/cli/inbound_ballot.go b/cmd/zetatool/cli/inbound_ballot.go index 77681e5d6a..bb409de58f 100644 --- a/cmd/zetatool/cli/inbound_ballot.go +++ b/cmd/zetatool/cli/inbound_ballot.go @@ -7,7 +7,6 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/zeta-chain/node/cmd/zetatool/ballot" "github.com/zeta-chain/node/cmd/zetatool/cctx" "github.com/zeta-chain/node/cmd/zetatool/config" zetacontext "github.com/zeta-chain/node/cmd/zetatool/context" @@ -38,14 +37,16 @@ func GetInboundBallot(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create context: %w", err) } - ballotIdentifierMessage, err := ballot.GetBallotIdentifier(ctx) + cctxDetails := cctx.NewCCTXDetails() + + err = cctxDetails.CheckInbound(ctx) if err != nil { return fmt.Errorf("failed to get ballot identifier: %w", err) } - if ballotIdentifierMessage.Status == cctx.PendingInboundConfirmation { - log.Print("Ballot Identifier: , warning the inbound hash might not be confirmed yet", ballotIdentifierMessage.CCCTXIdentifier) + if cctxDetails.Status == cctx.PendingInboundConfirmation { + log.Print("Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", cctxDetails.CCCTXIdentifier) return nil } - log.Print("Ballot Identifier: ", ballotIdentifierMessage.CCCTXIdentifier) + log.Print("Ballot Identifier: ", cctxDetails.CCCTXIdentifier) return nil } diff --git a/cmd/zetatool/config/config.go b/cmd/zetatool/config/config.go index 05e5267bb2..31c3291846 100644 --- a/cmd/zetatool/config/config.go +++ b/cmd/zetatool/config/config.go @@ -14,6 +14,7 @@ var AppFs = afero.NewOsFs() const ( FlagConfig = "config" defaultCfgFileName = "zetatool_config.json" + FlagDebug = "debug" ) func TestnetConfig() *Config { diff --git a/cmd/zetatool/main.go b/cmd/zetatool/main.go index 0161b348c4..e2e212e629 100644 --- a/cmd/zetatool/main.go +++ b/cmd/zetatool/main.go @@ -19,6 +19,7 @@ func init() { rootCmd.AddCommand(cli.NewGetInboundBallotCMD()) rootCmd.AddCommand(cli.NewTrackCCTXCMD()) rootCmd.PersistentFlags().String(config.FlagConfig, "", "custom config file: --config filename.json") + rootCmd.PersistentFlags().Bool(config.FlagDebug, false, "enable debug mode, to show more details on why the command might be failing") } func main() { diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index 59c0c42dba..1575370793 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -1,6 +1,7 @@ package e2etests import ( + "fmt" "math/big" "github.com/stretchr/testify/require" @@ -21,6 +22,7 @@ func TestETHDeposit(r *runner.E2ERunner, args []string) { // perform the deposit tx := r.ETHDeposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + r.Logger.Print(fmt.Sprintf("deposit tx: %s", tx.Hash().Hex())) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "deposit") diff --git a/e2e/e2etests/test_eth_withdraw.go b/e2e/e2etests/test_eth_withdraw.go index 09986e5917..529e19d7ae 100644 --- a/e2e/e2etests/test_eth_withdraw.go +++ b/e2e/e2etests/test_eth_withdraw.go @@ -1,6 +1,7 @@ package e2etests import ( + "fmt" "math/big" "github.com/stretchr/testify/require" @@ -24,6 +25,7 @@ func TestETHWithdraw(r *runner.E2ERunner, args []string) { // perform the withdraw tx := r.ETHWithdraw(r.EVMAddress(), amount, gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + fmt.Println("withdraw tx", tx.Hash().Hex()) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "withdraw") From b94f55a10560cd7c03146aee8c859ca5583d2744 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Sun, 2 Feb 2025 13:57:49 -0500 Subject: [PATCH 04/14] add unit test for mainnet ballots --- cmd/zetatool/cctx/cctx_details.go | 15 ++++++++------- cmd/zetatool/cctx/cctx_status.go | 1 + cmd/zetatool/cctx/inbound.go | 6 +++--- .../cctx/{ballot_test.go => inbound_test.go} | 4 ++-- cmd/zetatool/cctx/zetachain.go | 2 +- cmd/zetatool/cli/cctx_tracker.go | 2 +- cmd/zetatool/cli/inbound_ballot.go | 4 ++-- 7 files changed, 18 insertions(+), 16 deletions(-) rename cmd/zetatool/cctx/{ballot_test.go => inbound_test.go} (96%) diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index bed834e8ea..22199e0ae8 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -8,8 +8,9 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) +// CCTXDetails represents the status of a CCTX transaction type CCTXDetails struct { - CCCTXIdentifier string `json:"cctx_identifier"` + CCTXIdentifier string `json:"cctx_identifier"` Status Status `json:"status"` OutboundChain chains.Chain `json:"outbound_chain_id"` OutboundTssNonce uint64 `json:"outbound_tss_nonce"` @@ -19,8 +20,8 @@ type CCTXDetails struct { func NewCCTXDetails() *CCTXDetails { return &CCTXDetails{ - CCCTXIdentifier: "", - Status: Unknown, + CCTXIdentifier: "", + Status: Unknown, } } @@ -50,11 +51,11 @@ func (c *CCTXDetails) IsPendingConfirmation() bool { } func (c *CCTXDetails) Print() string { - return fmt.Sprintf("CCTX: %s Status: %s", c.CCCTXIdentifier, c.Status.String()) + return fmt.Sprintf("CCTX: %s Status: %s", c.CCTXIdentifier, c.Status.String()) } func (c *CCTXDetails) DebugPrint() string { - return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCCTXIdentifier, c.Status.String(), c.Message) + return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCTXIdentifier, c.Status.String(), c.Message) } func (c *CCTXDetails) UpdateCCTXStatus(ctx *context.Context) { @@ -63,7 +64,7 @@ func (c *CCTXDetails) UpdateCCTXStatus(ctx *context.Context) { goCtx = ctx.GetContext() ) - CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCCTXIdentifier) + CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCTXIdentifier) if err != nil { c.Message = fmt.Sprintf("failed to get cctx: %v", err) return @@ -79,7 +80,7 @@ func (c *CCTXDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { zetacoreClient = ctx.GetZetaCoreClient() goCtx = ctx.GetContext() ) - CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCCTXIdentifier) + CCTX, err := zetacoreClient.GetCctxByHash(goCtx, c.CCTXIdentifier) if err != nil { c.Message = fmt.Sprintf("failed to get cctx: %v", err) } diff --git a/cmd/zetatool/cctx/cctx_status.go b/cmd/zetatool/cctx/cctx_status.go index 0da1e49327..e2a9c2ad0d 100644 --- a/cmd/zetatool/cctx/cctx_status.go +++ b/cmd/zetatool/cctx/cctx_status.go @@ -1,5 +1,6 @@ package cctx +// Status represents the status of a CCTX transaction, it is more granular than the status present on zetacore type Status int const ( diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index dac89614a5..43984b7d48 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -139,7 +139,7 @@ func (c *CCTXDetails) btcInboundBallotIdentifier(ctx *context.Context) error { if err != nil { return fmt.Errorf("failed to get bitcoin ballot identifier: %w", err) } - c.CCCTXIdentifier = cctxIdentifier + c.CCTXIdentifier = cctxIdentifier c.updateInboundConfirmation(isConfirmed) return nil } @@ -263,7 +263,7 @@ func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { return fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) } - c.CCCTXIdentifier = msg.Digest() + c.CCTXIdentifier = msg.Digest() c.updateInboundConfirmation(isConfirmed) return nil } @@ -320,7 +320,7 @@ func (c *CCTXDetails) solanaInboundBallotIdentifier(ctx *context.Context) error } } - c.CCCTXIdentifier = msg.Digest() + c.CCTXIdentifier = msg.Digest() c.Status = PendingInboundVoting return nil diff --git a/cmd/zetatool/cctx/ballot_test.go b/cmd/zetatool/cctx/inbound_test.go similarity index 96% rename from cmd/zetatool/cctx/ballot_test.go rename to cmd/zetatool/cctx/inbound_test.go index 19d902b3c9..c55f78d1cb 100644 --- a/cmd/zetatool/cctx/ballot_test.go +++ b/cmd/zetatool/cctx/inbound_test.go @@ -62,8 +62,8 @@ func Test_InboundBallotIdentifier(t *testing.T) { c := cctx.NewCCTXDetails() err = c.CheckInbound(ctx) require.NoError(t, err) - if !tc.expectError && c.CCCTXIdentifier != tc.expectedBallotIdentifier { - t.Errorf("expected %s, got %s", tc.expectedBallotIdentifier, c.CCCTXIdentifier) + if !tc.expectError && c.CCTXIdentifier != tc.expectedBallotIdentifier { + t.Errorf("expected %s, got %s", tc.expectedBallotIdentifier, c.CCTXIdentifier) } }) } diff --git a/cmd/zetatool/cctx/zetachain.go b/cmd/zetatool/cctx/zetachain.go index e2159853cc..389848eff4 100644 --- a/cmd/zetatool/cctx/zetachain.go +++ b/cmd/zetatool/cctx/zetachain.go @@ -29,7 +29,7 @@ func CheckInBoundTx(ctx *context.Context, cctxDetails *CCTXDetails) error { return fmt.Errorf("inbound hash more than one cctx %s", inboundHash) } - cctxDetails.CCCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] + cctxDetails.CCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] cctxDetails.Status = PendingOutbound return nil } diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index d73a28df71..770615e0ca 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -61,7 +61,7 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.CCTXDetails, error) { return cctxDetails, fmt.Errorf("failed to get ballot identifier: %v", err) } // Reject unknown status , as it is not valid - if cctxDetails.Status == cctx.Unknown || cctxDetails.CCCTXIdentifier == "" { + if cctxDetails.Status == cctx.Unknown || cctxDetails.CCTXIdentifier == "" { return cctxDetails, fmt.Errorf("unknown status") } diff --git a/cmd/zetatool/cli/inbound_ballot.go b/cmd/zetatool/cli/inbound_ballot.go index bb409de58f..39c064acb2 100644 --- a/cmd/zetatool/cli/inbound_ballot.go +++ b/cmd/zetatool/cli/inbound_ballot.go @@ -44,9 +44,9 @@ func GetInboundBallot(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to get ballot identifier: %w", err) } if cctxDetails.Status == cctx.PendingInboundConfirmation { - log.Print("Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", cctxDetails.CCCTXIdentifier) + log.Print("Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", cctxDetails.CCTXIdentifier) return nil } - log.Print("Ballot Identifier: ", cctxDetails.CCCTXIdentifier) + log.Print("Ballot Identifier: ", cctxDetails.CCTXIdentifier) return nil } From 8dd8857e0f151ac5ed42397282addd6ccd513035 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 3 Feb 2025 11:46:19 -0500 Subject: [PATCH 05/14] add outbound support for bitcoin and solana --- Makefile | 4 +- cmd/zetae2e/local/bitcoin.go | 8 +- cmd/zetae2e/local/evm.go | 122 ++++++++++++++-------------- cmd/zetae2e/local/local.go | 4 +- cmd/zetatool/cctx/cctx_details.go | 50 ++++++------ cmd/zetatool/cctx/inbound.go | 53 +++++++++--- cmd/zetatool/cctx/outbound.go | 120 ++++++++++++++++++++++++--- cmd/zetatool/cctx/zetachain.go | 35 -------- cmd/zetatool/chains/bitcoin.go | 1 + cmd/zetatool/chains/evm.go | 9 +- cmd/zetatool/chains/solana.go | 1 + cmd/zetatool/cli/cctx_tracker.go | 25 +++--- cmd/zetatool/cli/inbound_ballot.go | 6 +- cmd/zetatool/context/context.go | 6 +- cmd/zetatool/main.go | 5 +- e2e/e2etests/test_eth_deposit.go | 2 - pkg/rpc/clients_crosschain.go | 2 +- x/observer/migrations/v9/migrate.go | 3 + 18 files changed, 281 insertions(+), 175 deletions(-) delete mode 100644 cmd/zetatool/cctx/zetachain.go diff --git a/Makefile b/Makefile index 7cac08567b..21a13d2b94 100644 --- a/Makefile +++ b/Makefile @@ -399,7 +399,7 @@ $(BINDIR)/runsim: define run-sim-test @echo "Running $(1)" @go test -mod=readonly $(SIMAPP) -run $(2) -Enabled=true \ - -NumBlocks=$(3) -BlockSize=$(4) -Commit=true -Period=0 -v -timeout $(5) + -NumBlocks=$(3) -BlockSize=$(4) -Commit=true -Period=0 -v -timeout $(5) -Seed 42 endef test-sim-nondeterminism: @@ -409,7 +409,7 @@ test-sim-fullappsimulation: $(call run-sim-test,"TestFullAppSimulation",TestFullAppSimulation,100,200,30m) test-sim-import-export: - $(call run-sim-test,"test-import-export",TestAppImportExport,100,200,30m) + $(call run-sim-test,"test-import-export",TestAppImportExport,300,200,30m) test-sim-after-import: $(call run-sim-test,"test-sim-after-import",TestAppSimulationAfterImport,100,200,30m) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 19db09c2b2..f689b7d20e 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -14,7 +14,13 @@ import ( "github.com/zeta-chain/node/testutil" ) -func startBitcoinTests(eg *errgroup.Group, conf config.Config, deployerRunner *runner.E2ERunner, verbose bool, light, skipBitcoinSetup bool) { +func startBitcoinTests( + eg *errgroup.Group, + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, + light, skipBitcoinSetup bool, +) { { // start the bitcoin tests // btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs diff --git a/cmd/zetae2e/local/evm.go b/cmd/zetae2e/local/evm.go index 07257dc628..f3b7581838 100644 --- a/cmd/zetae2e/local/evm.go +++ b/cmd/zetae2e/local/evm.go @@ -19,70 +19,70 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne e2etests.TestETHDepositName, e2etests.TestETHDepositAndCallName, e2etests.TestETHWithdrawName, - //e2etests.TestETHWithdrawAndArbitraryCallName, - //e2etests.TestETHWithdrawAndCallName, - //e2etests.TestETHWithdrawAndCallThroughContractName, - //e2etests.TestZEVMToEVMArbitraryCallName, - //e2etests.TestZEVMToEVMCallName, - //e2etests.TestZEVMToEVMCallThroughContractName, - //e2etests.TestEVMToZEVMCallName, - //e2etests.TestETHDepositAndCallNoMessageName, - //e2etests.TestETHWithdrawAndCallNoMessageName, - //e2etests.TestEtherWithdrawRestrictedName, + e2etests.TestETHWithdrawAndArbitraryCallName, + e2etests.TestETHWithdrawAndCallName, + e2etests.TestETHWithdrawAndCallThroughContractName, + e2etests.TestZEVMToEVMArbitraryCallName, + e2etests.TestZEVMToEVMCallName, + e2etests.TestZEVMToEVMCallThroughContractName, + e2etests.TestEVMToZEVMCallName, + e2etests.TestETHDepositAndCallNoMessageName, + e2etests.TestETHWithdrawAndCallNoMessageName, + e2etests.TestEtherWithdrawRestrictedName, )) //// Test happy paths for erc20 token workflow - //eg.Go(evmTestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, - // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM - // e2etests.TestERC20DepositName, - // e2etests.TestERC20DepositAndCallName, - // e2etests.TestERC20WithdrawName, - // e2etests.TestERC20WithdrawAndArbitraryCallName, - // e2etests.TestERC20WithdrawAndCallName, - // e2etests.TestERC20DepositAndCallNoMessageName, - // e2etests.TestERC20WithdrawAndCallNoMessageName, - // e2etests.TestDepositAndCallSwapName, - // e2etests.TestERC20DepositRestrictedName, - //)) - // - //// Test revert cases for gas token workflow - //eg.Go( - // evmTestRoutine( - // conf, - // "eth-revert", - // conf.AdditionalAccounts.UserEtherRevert, - // color.FgHiYellow, - // deployerRunner, - // verbose, - // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM and withdraw - // e2etests.TestETHDepositAndCallRevertName, - // e2etests.TestETHDepositAndCallRevertWithCallName, - // e2etests.TestETHWithdrawAndCallRevertName, - // e2etests.TestETHWithdrawAndCallRevertWithCallName, - // e2etests.TestETHWithdrawAndCallRevertWithWithdrawName, - // e2etests.TestDepositAndCallOutOfGasName, - // ), - //) - // - //// Test revert cases for erc20 token workflow - //eg.Go( - // evmTestRoutine( - // conf, - // "erc20-revert", - // conf.AdditionalAccounts.UserERC20Revert, - // color.FgHiRed, - // deployerRunner, - // verbose, - // e2etests.TestETHDepositName, // necessary to pay fees on ZEVM - // e2etests.TestERC20DepositName, // necessary to have assets to withdraw - // e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts - // e2etests.TestOperationAddLiquidityERC20Name, - // e2etests.TestERC20DepositAndCallRevertName, - // e2etests.TestERC20DepositAndCallRevertWithCallName, - // e2etests.TestERC20WithdrawAndCallRevertName, - // e2etests.TestERC20WithdrawAndCallRevertWithCallName, - // ), - //) + eg.Go(evmTestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, + e2etests.TestETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestERC20DepositName, + e2etests.TestERC20DepositAndCallName, + e2etests.TestERC20WithdrawName, + e2etests.TestERC20WithdrawAndArbitraryCallName, + e2etests.TestERC20WithdrawAndCallName, + e2etests.TestERC20DepositAndCallNoMessageName, + e2etests.TestERC20WithdrawAndCallNoMessageName, + e2etests.TestDepositAndCallSwapName, + e2etests.TestERC20DepositRestrictedName, + )) + + // Test revert cases for gas token workflow + eg.Go( + evmTestRoutine( + conf, + "eth-revert", + conf.AdditionalAccounts.UserEtherRevert, + color.FgHiYellow, + deployerRunner, + verbose, + e2etests.TestETHDepositName, // necessary to pay fees on ZEVM and withdraw + e2etests.TestETHDepositAndCallRevertName, + e2etests.TestETHDepositAndCallRevertWithCallName, + e2etests.TestETHWithdrawAndCallRevertName, + e2etests.TestETHWithdrawAndCallRevertWithCallName, + e2etests.TestETHWithdrawAndCallRevertWithWithdrawName, + e2etests.TestDepositAndCallOutOfGasName, + ), + ) + + // Test revert cases for erc20 token workflow + eg.Go( + evmTestRoutine( + conf, + "erc20-revert", + conf.AdditionalAccounts.UserERC20Revert, + color.FgHiRed, + deployerRunner, + verbose, + e2etests.TestETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestERC20DepositName, // necessary to have assets to withdraw + e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts + e2etests.TestOperationAddLiquidityERC20Name, + e2etests.TestERC20DepositAndCallRevertName, + e2etests.TestERC20DepositAndCallRevertWithCallName, + e2etests.TestERC20WithdrawAndCallRevertName, + e2etests.TestERC20WithdrawAndCallRevertWithCallName, + ), + ) } // evmTestRoutine runs EVM chain related e2e tests diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 11ececf5ad..716acc81ad 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -11,7 +11,6 @@ import ( "time" "github.com/fatih/color" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -292,8 +291,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if !skipRegular { // start the EVM tests startEVMTests(&eg, conf, deployerRunner, verbose) - log.Info().Msgf("EVM tests started %v %v", light, skipBitcoinSetup) - //startBitcoinTests(&eg, conf, deployerRunner, verbose, light, skipBitcoinSetup) + startBitcoinTests(&eg, conf, deployerRunner, verbose, light, skipBitcoinSetup) } if !skipPrecompiles { diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 22199e0ae8..444db59869 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -8,8 +8,8 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -// CCTXDetails represents the status of a CCTX transaction -type CCTXDetails struct { +// TrackingDetails represents the status of a CCTX transaction +type TrackingDetails struct { CCTXIdentifier string `json:"cctx_identifier"` Status Status `json:"status"` OutboundChain chains.Chain `json:"outbound_chain_id"` @@ -18,14 +18,14 @@ type CCTXDetails struct { Message string `json:"message"` } -func NewCCTXDetails() *CCTXDetails { - return &CCTXDetails{ +func NewCCTXDetails() *TrackingDetails { + return &TrackingDetails{ CCTXIdentifier: "", Status: Unknown, } } -func (c *CCTXDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxStatus) { +func (c *TrackingDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxStatus) { switch status { case crosschaintypes.CctxStatus_PendingOutbound: c.Status = PendingOutbound @@ -42,23 +42,24 @@ func (c *CCTXDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxSt } } -func (c *CCTXDetails) IsPending() bool { +func (c *TrackingDetails) IsPendingOutbound() bool { return c.Status == PendingOutbound || c.Status == PendingRevert } -func (c *CCTXDetails) IsPendingConfirmation() bool { +func (c *TrackingDetails) IsPendingConfirmation() bool { return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation } -func (c *CCTXDetails) Print() string { +func (c *TrackingDetails) Print() string { return fmt.Sprintf("CCTX: %s Status: %s", c.CCTXIdentifier, c.Status.String()) } -func (c *CCTXDetails) DebugPrint() string { +func (c *TrackingDetails) DebugPrint() string { return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCTXIdentifier, c.Status.String(), c.Message) } -func (c *CCTXDetails) UpdateCCTXStatus(ctx *context.Context) { +// UpdateCCTXStatus updates the TrackingDetails with status from zetacore +func (c *TrackingDetails) UpdateCCTXStatus(ctx *context.Context) { var ( zetacoreClient = ctx.GetZetaCoreClient() goCtx = ctx.GetContext() @@ -75,7 +76,7 @@ func (c *CCTXDetails) UpdateCCTXStatus(ctx *context.Context) { return } -func (c *CCTXDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { +func (c *TrackingDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { var ( zetacoreClient = ctx.GetZetaCoreClient() goCtx = ctx.GetContext() @@ -84,19 +85,19 @@ func (c *CCTXDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { if err != nil { c.Message = fmt.Sprintf("failed to get cctx: %v", err) } - chainId := CCTX.GetCurrentOutboundParam().ReceiverChainId + chainID := CCTX.GetCurrentOutboundParam().ReceiverChainId // This is almost impossible to happen as the cctx would not have been created if the chain was not supported - chain, found := chains.GetChainFromChainID(chainId, []chains.Chain{}) + chain, found := chains.GetChainFromChainID(chainID, []chains.Chain{}) if !found { - c.Message = fmt.Sprintf("receiver chain not supported,chain id: %d", chainId) + c.Message = fmt.Sprintf("receiver chain not supported,chain id: %d", chainID) } c.OutboundChain = chain c.OutboundTssNonce = CCTX.GetCurrentOutboundParam().TssNonce return } -func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { +func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { var ( zetacoreClient = ctx.GetZetaCoreClient() goCtx = ctx.GetContext() @@ -104,12 +105,12 @@ func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { outboundNonce = c.OutboundTssNonce ) - if !c.IsPending() { + if !c.IsPendingOutbound() { return } tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundChain, outboundNonce) - // tracker is found that means the outbound has broadcasted but we are waiting for confirmations + // tracker is found that means the outbound has been broadcast, but we are waiting for confirmations if err == nil && tracker != nil { c.updateOutboundConfirmation() var hashList []string @@ -124,15 +125,16 @@ func (c *CCTXDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { return } -func (c *CCTXDetails) updateInboundConfirmation(isConfirmed bool) { +// 0 - Inbound Confirmation +func (c *TrackingDetails) updateInboundConfirmation(isConfirmed bool) { c.Status = PendingInboundConfirmation if isConfirmed { c.Status = PendingInboundVoting } } -// 1 - Signing -func (c *CCTXDetails) updateOutboundSigning() { +// 1 - Outbound Signing +func (c *TrackingDetails) updateOutboundSigning() { switch { case c.Status == PendingOutbound: c.Status = PendingOutboundSigning @@ -141,8 +143,8 @@ func (c *CCTXDetails) updateOutboundSigning() { } } -// 2 - Confirmation -func (c *CCTXDetails) updateOutboundConfirmation() { +// 2 - Outbound Confirmation +func (c *TrackingDetails) updateOutboundConfirmation() { switch { case c.Status == PendingOutbound: c.Status = PendingOutboundConfirmation @@ -151,8 +153,8 @@ func (c *CCTXDetails) updateOutboundConfirmation() { } } -// UpdateOutboundVoting 3 - Voting -func (c *CCTXDetails) UpdateOutboundVoting() { +// 3 - Outbound Voting +func (c *TrackingDetails) updateOutboundVoting() { switch { case c.Status == PendingOutboundConfirmation: c.Status = PendingOutboundVoting diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index 43984b7d48..abf98c0e06 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -7,6 +7,10 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/gagliardetto/solana-go" solrpc "github.com/gagliardetto/solana-go/rpc" + "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" + zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/chains" @@ -18,12 +22,9 @@ import ( "github.com/zeta-chain/node/zetaclient/chains/solana/observer" solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" - "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" ) -func (c *CCTXDetails) CheckInbound(ctx *context.Context) error { +func (c *TrackingDetails) CheckInbound(ctx *context.Context) error { var ( inboundChain = ctx.GetInboundChain() err error @@ -32,7 +33,7 @@ func (c *CCTXDetails) CheckInbound(ctx *context.Context) error { switch { case inboundChain.IsZetaChain(): { - err = CheckInBoundTx(ctx, c) + err = c.zevmInboundBallotIdentifier(ctx) if err != nil { return fmt.Errorf( "failed to get inbound ballot for zeta chain %d, %w", @@ -82,7 +83,7 @@ func (c *CCTXDetails) CheckInbound(ctx *context.Context) error { return nil } -func (c *CCTXDetails) btcInboundBallotIdentifier(ctx *context.Context) error { +func (c *TrackingDetails) btcInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() inboundChain = ctx.GetInboundChain() @@ -144,10 +145,9 @@ func (c *CCTXDetails) btcInboundBallotIdentifier(ctx *context.Context) error { return nil } -func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { +func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() - isConfirmed = false inboundChain = ctx.GetInboundChain() zetacoreClient = ctx.GetZetaCoreClient() zetaChainID = ctx.GetConfig().ZetaChainID @@ -170,7 +170,7 @@ func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { } // Signer is unused zetaEvmClient := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId())) - isConfirmed, err = zetaEvmClient.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) + isConfirmed, err := zetaEvmClient.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount) if err != nil { return fmt.Errorf("unable to confirm tx: %w", err) } @@ -249,7 +249,11 @@ func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { } eventDepositAndCall, err := gateway.ParseDepositedAndCalled(*log) if err == nil { - msg = zetatoolchains.DepositAndCallInboundVoteV2(eventDepositAndCall, inboundChain.ChainId, zetaChainID) + msg = zetatoolchains.DepositAndCallInboundVoteV2( + eventDepositAndCall, + inboundChain.ChainId, + zetaChainID, + ) break } eventCall, err := gateway.ParseCalled(*log) @@ -268,7 +272,7 @@ func (c *CCTXDetails) evmInboundBallotIdentifier(ctx *context.Context) error { return nil } -func (c *CCTXDetails) solanaInboundBallotIdentifier(ctx *context.Context) error { +func (c *TrackingDetails) solanaInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() inboundChain = ctx.GetInboundChain() @@ -325,3 +329,30 @@ func (c *CCTXDetails) solanaInboundBallotIdentifier(ctx *context.Context) error return nil } + +func (c *TrackingDetails) zevmInboundBallotIdentifier(ctx *context.Context) error { + var ( + inboundHash = ctx.GetInboundHash() + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + ) + + inboundHashToCCTX, err := zetacoreClient.Crosschain.InboundHashToCctx( + goCtx, &crosschaintypes.QueryGetInboundHashToCctxRequest{ + InboundHash: inboundHash, + }) + if err != nil { + return fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) + } + if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) == 0 { + return fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) + } + + if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) > 1 { + return fmt.Errorf("inbound hash more than one cctx %s", inboundHash) + } + + c.CCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] + c.Status = PendingOutbound + return nil +} diff --git a/cmd/zetatool/cctx/outbound.go b/cmd/zetatool/cctx/outbound.go index 412947b0b3..41f4688913 100644 --- a/cmd/zetatool/cctx/outbound.go +++ b/cmd/zetatool/cctx/outbound.go @@ -3,32 +3,42 @@ package cctx import ( "fmt" + "github.com/btcsuite/btcd/chaincfg/chainhash" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/gagliardetto/solana-go" + solrpc "github.com/gagliardetto/solana-go/rpc" + zetatoolchains "github.com/zeta-chain/node/cmd/zetatool/chains" "github.com/zeta-chain/node/cmd/zetatool/context" + "github.com/zeta-chain/node/pkg/chains" + "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client" zetaevmclient "github.com/zeta-chain/node/zetaclient/chains/evm/client" + solanarpc "github.com/zeta-chain/node/zetaclient/chains/solana/rpc" + zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) -func (c *CCTXDetails) CheckOutbound(ctx *context.Context) error { +func (c *TrackingDetails) CheckOutbound(ctx *context.Context) error { var ( outboundChain = ctx.GetInboundChain() - err error ) + // We do not need to handle the case for zeta chain as the outbound is confirmed in the same block. switch { case outboundChain.IsEVMChain(): - err = c.checkOutboundTx(ctx) - if err != nil { - return err - } + return c.checkEvmOutboundTx(ctx) + case outboundChain.IsBitcoinChain(): + return c.checkBitcoinOutboundTx(ctx) + case outboundChain.IsSolanaChain(): + return c.checkSolanaOutboundTx(ctx) + default: + return fmt.Errorf("unsupported outbound chain") } - return nil } -// checkOutboundTx checks if the outbound transaction is confirmed on the outbound chain. +// checkEvmOutboundTx checks if the outbound transaction is confirmed on the outbound chain. // If it's confirmed, we update the status to PendingOutboundVoting or PendingRevertVoting. Which means that the confirmation is done and we are not waiting for observers to vote // Transition Status PendingConfirmation -> Status PendingVoting -func (c *CCTXDetails) checkOutboundTx(ctx *context.Context) error { +func (c *TrackingDetails) checkEvmOutboundTx(ctx *context.Context) error { var ( txHashList = c.OutboundTrackerHashList outboundChain = c.OutboundChain @@ -51,7 +61,7 @@ func (c *CCTXDetails) checkOutboundTx(ctx *context.Context) error { // If one of the hash is confirmed, we update the status to pending voting // There might be a condition where we have multiple txs and the wrong tx is confirmed. - //To verify that we need, check CCTX data + // To verify that we need, check CCTX data for _, hash := range txHashList { tx, _, err := zetatoolchains.GetEvmTx(ctx, evmClient, hash, outboundChain) if err != nil { @@ -69,7 +79,95 @@ func (c *CCTXDetails) checkOutboundTx(ctx *context.Context) error { } } if foundConfirmedTx { - c.UpdateOutboundVoting() + c.updateOutboundVoting() + } + return nil +} + +func (c *TrackingDetails) checkSolanaOutboundTx(ctx *context.Context) error { + var ( + txHashList = c.OutboundTrackerHashList + goCtx = ctx.GetContext() + cfg = ctx.GetConfig() + ) + + foundConfirmedTx := false + solClient := solrpc.New(cfg.SolanaRPC) + if solClient == nil { + return fmt.Errorf("error creating rpc client") + } + for _, hash := range txHashList { + signature := solana.MustSignatureFromBase58(hash) + _, err := solanarpc.GetTransaction(goCtx, solClient, signature) + if err != nil { + continue + } + foundConfirmedTx = true + } + + if foundConfirmedTx { + c.updateOutboundVoting() + } + return nil +} + +func (c *TrackingDetails) checkBitcoinOutboundTx(ctx *context.Context) error { + var ( + txHashList = c.OutboundTrackerHashList + outboundChain = c.OutboundChain + zetacoreClient = ctx.GetZetaCoreClient() + goCtx = ctx.GetContext() + cfg = ctx.GetConfig() + logger = ctx.GetLogger() + ) + + chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) + if err != nil { + return fmt.Errorf("failed to get chain params: %v", err) + } + confirmationCount := chainParams.ConfirmationCount + + params, err := chains.BitcoinNetParamsFromChainID(outboundChain.ChainId) + if err != nil { + return fmt.Errorf("unable to get bitcoin net params from chain id: %w", err) + } + + connCfg := zetaclientConfig.BTCConfig{ + RPCUsername: cfg.BtcUser, + RPCPassword: cfg.BtcPassword, + RPCHost: cfg.BtcHost, + RPCParams: params.Name, + } + + btcClient, err := client.New(connCfg, outboundChain.ChainId, logger) + if err != nil { + return fmt.Errorf("unable to create rpc client: %w", err) + } + + err = btcClient.Ping(goCtx) + if err != nil { + return fmt.Errorf("error ping the bitcoin server: %w", err) + } + + foundConfirmedTx := false + + for _, hash := range txHashList { + txHash, err := chainhash.NewHashFromStr(hash) + if err != nil { + continue + } + tx, err := btcClient.GetRawTransactionVerbose(goCtx, txHash) + if err != nil { + continue + } + + if tx.Confirmations >= confirmationCount { + foundConfirmedTx = true + } + } + + if foundConfirmedTx { + c.updateOutboundVoting() } return nil } diff --git a/cmd/zetatool/cctx/zetachain.go b/cmd/zetatool/cctx/zetachain.go deleted file mode 100644 index 389848eff4..0000000000 --- a/cmd/zetatool/cctx/zetachain.go +++ /dev/null @@ -1,35 +0,0 @@ -package cctx - -import ( - "fmt" - - "github.com/zeta-chain/node/cmd/zetatool/context" - crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" -) - -func CheckInBoundTx(ctx *context.Context, cctxDetails *CCTXDetails) error { - var ( - inboundHash = ctx.GetInboundHash() - zetacoreClient = ctx.GetZetaCoreClient() - goCtx = ctx.GetContext() - ) - - inboundHashToCCTX, err := zetacoreClient.Crosschain.InboundHashToCctx( - goCtx, &crosschaintypes.QueryGetInboundHashToCctxRequest{ - InboundHash: inboundHash, - }) - if err != nil { - return fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) - } - if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) == 0 { - return fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) - } - - if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) > 1 { - return fmt.Errorf("inbound hash more than one cctx %s", inboundHash) - } - - cctxDetails.CCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] - cctxDetails.Status = PendingOutbound - return nil -} diff --git a/cmd/zetatool/chains/bitcoin.go b/cmd/zetatool/chains/bitcoin.go index ce55caae5c..25677d0130 100644 --- a/cmd/zetatool/chains/bitcoin.go +++ b/cmd/zetatool/chains/bitcoin.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/rs/zerolog" + "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/coin" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" diff --git a/cmd/zetatool/chains/evm.go b/cmd/zetatool/chains/evm.go index 1d6dd0d5d7..8cdb1f023c 100644 --- a/cmd/zetatool/chains/evm.go +++ b/cmd/zetatool/chains/evm.go @@ -11,6 +11,10 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" + "github.com/zeta-chain/node/cmd/zetatool/config" "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/chains" @@ -20,9 +24,6 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" clienttypes "github.com/zeta-chain/node/zetaclient/types" "github.com/zeta-chain/node/zetaclient/zetacore" - "github.com/zeta-chain/protocol-contracts/pkg/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/gatewayevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/zetaconnector.non-eth.sol" ) func resolveRPC(chain chains.Chain, cfg *config.Config) string { @@ -35,7 +36,6 @@ func resolveRPC(chain chains.Chain, cfg *config.Config) string { } func GetEvmClient(ctx *context.Context, chain chains.Chain) (*ethclient.Client, error) { - evmRRC := resolveRPC(chain, ctx.GetConfig()) if evmRRC == "" { return nil, fmt.Errorf("rpc not found for chain %d network %s", chain.ChainId, chain.Network) @@ -53,7 +53,6 @@ func GetEvmTx( inboundHash string, chain chains.Chain, ) (*ethtypes.Transaction, *ethtypes.Receipt, error) { - goCtx := ctx.GetContext() // Fetch transaction from the inbound hash := ethcommon.HexToHash(inboundHash) diff --git a/cmd/zetatool/chains/solana.go b/cmd/zetatool/chains/solana.go index 81f3731f99..45420633ba 100644 --- a/cmd/zetatool/chains/solana.go +++ b/cmd/zetatool/chains/solana.go @@ -5,6 +5,7 @@ import ( "fmt" cosmosmath "cosmossdk.io/math" + crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" clienttypes "github.com/zeta-chain/node/zetaclient/types" ) diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index 770615e0ca..98929f7b7f 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -7,6 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/zeta-chain/node/cmd/zetatool/cctx" "github.com/zeta-chain/node/cmd/zetatool/config" zetatoolcontext "github.com/zeta-chain/node/cmd/zetatool/context" @@ -49,8 +50,7 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { return nil } -func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.CCTXDetails, error) { - +func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { var ( cctxDetails = cctx.NewCCTXDetails() err error @@ -60,36 +60,35 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.CCTXDetails, error) { if err != nil { return cctxDetails, fmt.Errorf("failed to get ballot identifier: %v", err) } - // Reject unknown status , as it is not valid + // Reject unknown status, as it is not valid if cctxDetails.Status == cctx.Unknown || cctxDetails.CCTXIdentifier == "" { return cctxDetails, fmt.Errorf("unknown status") } - // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address.We can show some information to the user. - // Add any error messages to the message field of the response instead of throwing an error - // Update cctx status from zetacore , if cctx is not found, it will continue to be in the status retuned by CheckInbound function PendingInboundVoting or PendingInboundConfirmation + // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address. + // Update cctx status from zetacore.This copies the status from zetacore to the cctx details.The cctx status can only be `PendingInboundVoting` or `PendingInboundConfirmation` at this point cctxDetails.UpdateCCTXStatus(ctx) // The cctx details now have status from zetacore, we have not tried to a get more granular status from the outbound chain yet. - // If it's not pending, we can just return here - if !cctxDetails.IsPending() { + // If it's not pending, we can just return here. + if !cctxDetails.IsPendingOutbound() { return cctxDetails, nil } - // update outbound details, this does not translation any status, it just updates the details + // update outbound details, this does not transition any status. cctxDetails.UpdateCCTXOutboundDetails(ctx) // Update tx hash list from outbound tracker - // If the tracker is found it means the outbound is broadcasted but we are waiting for the confirmations - // If the tracker is not found it means the outbound is not broadcasted yet + // If the tracker is found, it means the outbound is broadcast, but we are waiting for the confirmations + // If the tracker is not found, it means the outbound is not broadcast yet, we are wwaiting for the tss to sign the outbound cctxDetails.UpdateHashListAndPendingStatus(ctx) - // If its not pending confirmation we can return here, it means the outbound is not broadcasted yet its pending tss signing + // If its not pending confirmation, we can return here, it means the outbound is not broadcast yet its pending tss signing if !cctxDetails.IsPendingConfirmation() { return cctxDetails, nil } - // Check outbound tx , we are waiting for the outbound tx to be confirmed + // Check outbound tx, we are waiting for the outbound tx to be confirmed err = cctxDetails.CheckOutbound(ctx) if err != nil { return cctxDetails, err diff --git a/cmd/zetatool/cli/inbound_ballot.go b/cmd/zetatool/cli/inbound_ballot.go index 39c064acb2..e602c08487 100644 --- a/cmd/zetatool/cli/inbound_ballot.go +++ b/cmd/zetatool/cli/inbound_ballot.go @@ -7,6 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "github.com/zeta-chain/node/cmd/zetatool/cctx" "github.com/zeta-chain/node/cmd/zetatool/config" zetacontext "github.com/zeta-chain/node/cmd/zetatool/context" @@ -44,7 +45,10 @@ func GetInboundBallot(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to get ballot identifier: %w", err) } if cctxDetails.Status == cctx.PendingInboundConfirmation { - log.Print("Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", cctxDetails.CCTXIdentifier) + log.Printf( + "Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", + cctxDetails.CCTXIdentifier, + ) return nil } log.Print("Ballot Identifier: ", cctxDetails.CCTXIdentifier) diff --git a/cmd/zetatool/context/context.go b/cmd/zetatool/context/context.go index e63caf9d5e..1a61be38c6 100644 --- a/cmd/zetatool/context/context.go +++ b/cmd/zetatool/context/context.go @@ -6,16 +6,16 @@ import ( "time" "github.com/rs/zerolog" + "github.com/zeta-chain/node/cmd/zetatool/config" "github.com/zeta-chain/node/pkg/chains" - "github.com/zeta-chain/node/pkg/rpc" zetacorerpc "github.com/zeta-chain/node/pkg/rpc" ) type Context struct { ctx context.Context config *config.Config - zetaCoreClient rpc.Clients + zetaCoreClient zetacorerpc.Clients inboundHash string inboundChain chains.Chain logger zerolog.Logger @@ -58,7 +58,7 @@ func (c *Context) GetConfig() *config.Config { return c.config } -func (c *Context) GetZetaCoreClient() rpc.Clients { +func (c *Context) GetZetaCoreClient() zetacorerpc.Clients { return c.zetaCoreClient } diff --git a/cmd/zetatool/main.go b/cmd/zetatool/main.go index e2e212e629..e26806fee6 100644 --- a/cmd/zetatool/main.go +++ b/cmd/zetatool/main.go @@ -5,8 +5,8 @@ import ( "os" "github.com/spf13/cobra" - "github.com/zeta-chain/node/cmd/zetatool/cli" + "github.com/zeta-chain/node/cmd/zetatool/cli" "github.com/zeta-chain/node/cmd/zetatool/config" ) @@ -19,7 +19,8 @@ func init() { rootCmd.AddCommand(cli.NewGetInboundBallotCMD()) rootCmd.AddCommand(cli.NewTrackCCTXCMD()) rootCmd.PersistentFlags().String(config.FlagConfig, "", "custom config file: --config filename.json") - rootCmd.PersistentFlags().Bool(config.FlagDebug, false, "enable debug mode, to show more details on why the command might be failing") + rootCmd.PersistentFlags(). + Bool(config.FlagDebug, false, "enable debug mode, to show more details on why the command might be failing") } func main() { diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index 1575370793..59c0c42dba 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -1,7 +1,6 @@ package e2etests import ( - "fmt" "math/big" "github.com/stretchr/testify/require" @@ -22,7 +21,6 @@ func TestETHDeposit(r *runner.E2ERunner, args []string) { // perform the deposit tx := r.ETHDeposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) - r.Logger.Print(fmt.Sprintf("deposit tx: %s", tx.Hash().Hex())) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "deposit") diff --git a/pkg/rpc/clients_crosschain.go b/pkg/rpc/clients_crosschain.go index 78140a70cb..f1ef8f5e34 100644 --- a/pkg/rpc/clients_crosschain.go +++ b/pkg/rpc/clients_crosschain.go @@ -4,9 +4,9 @@ import ( "context" "cosmossdk.io/errors" - "github.com/zeta-chain/node/pkg/chains" "google.golang.org/grpc" + "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/x/crosschain/types" ) diff --git a/x/observer/migrations/v9/migrate.go b/x/observer/migrations/v9/migrate.go index c936c7feee..b0d60e6538 100644 --- a/x/observer/migrations/v9/migrate.go +++ b/x/observer/migrations/v9/migrate.go @@ -1,6 +1,8 @@ package v9 import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/node/x/observer/types" @@ -17,6 +19,7 @@ const MaturityBlocks = int64(100) // MigrateStore migrates the x/observer module state from the consensus version 8 to version 9. // The migration deletes all the ballots and ballot lists that are older than MaturityBlocks. func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { + fmt.Println("Migrating x/observer store") currentHeight := ctx.BlockHeight() // Maturity blocks is a parameter in the emissions module if currentHeight < MaturityBlocks { From 4acbd68c7f7cd986ce6771fb5cbded52b87c9b22 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 3 Feb 2025 13:30:40 -0500 Subject: [PATCH 06/14] add changelog entry --- changelog.md | 6 +++++ cmd/zetae2e/local/evm.go | 2 +- cmd/zetatool/cctx/cctx_details.go | 36 ++++++++++++++++-------------- cmd/zetatool/cctx/inbound.go | 1 + cmd/zetatool/cctx/inbound_test.go | 7 ------ cmd/zetatool/cctx/outbound.go | 6 ++--- cmd/zetatool/config/config_test.go | 2 +- docs/cli/zetatool/get_ballot.md | 23 +++++++++++++++++++ docs/cli/zetatool/readme.md | 28 +++-------------------- docs/cli/zetatool/track_cctx.md | 26 +++++++++++++++++++++ pkg/rpc/clients_test.go | 2 +- 11 files changed, 84 insertions(+), 55 deletions(-) create mode 100644 docs/cli/zetatool/get_ballot.md create mode 100644 docs/cli/zetatool/track_cctx.md diff --git a/changelog.md b/changelog.md index 5db074482a..d648d384dd 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # CHANGELOG +## Unreleased + +### Features + +* [3455](https://github.com/zeta-chain/node/pull/3455) - add `track-cctx` command to zetatools + ## v27.0.0 ### Breaking Changes diff --git a/cmd/zetae2e/local/evm.go b/cmd/zetae2e/local/evm.go index f3b7581838..bcb88041e8 100644 --- a/cmd/zetae2e/local/evm.go +++ b/cmd/zetae2e/local/evm.go @@ -31,7 +31,7 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne e2etests.TestEtherWithdrawRestrictedName, )) - //// Test happy paths for erc20 token workflow + // Test happy paths for erc20 token workflow eg.Go(evmTestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, e2etests.TestETHDepositName, // necessary to pay fees on ZEVM e2etests.TestERC20DepositName, diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 444db59869..060b03e927 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -8,7 +8,7 @@ import ( crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" ) -// TrackingDetails represents the status of a CCTX transaction +// TrackingDetails tracks the status of a CCTX transaction type TrackingDetails struct { CCTXIdentifier string `json:"cctx_identifier"` Status Status `json:"status"` @@ -25,6 +25,7 @@ func NewCCTXDetails() *TrackingDetails { } } +// UpdateStatusFromZetacoreCCTX updates the status of the TrackingDetails from the zetacore CCTX status func (c *TrackingDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.CctxStatus) { switch status { case crosschaintypes.CctxStatus_PendingOutbound: @@ -42,20 +43,12 @@ func (c *TrackingDetails) UpdateStatusFromZetacoreCCTX(status crosschaintypes.Cc } } -func (c *TrackingDetails) IsPendingOutbound() bool { - return c.Status == PendingOutbound || c.Status == PendingRevert -} - -func (c *TrackingDetails) IsPendingConfirmation() bool { - return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation -} - func (c *TrackingDetails) Print() string { - return fmt.Sprintf("CCTX: %s Status: %s", c.CCTXIdentifier, c.Status.String()) + return fmt.Sprintf("CCTX Identifier: %s Status: %s", c.CCTXIdentifier, c.Status.String()) } func (c *TrackingDetails) DebugPrint() string { - return fmt.Sprintf("CCTX: %s Status: %s Message: %s", c.CCTXIdentifier, c.Status.String(), c.Message) + return fmt.Sprintf("CCTX Identifier: %s Status: %s Message: %s", c.CCTXIdentifier, c.Status.String(), c.Message) } // UpdateCCTXStatus updates the TrackingDetails with status from zetacore @@ -76,6 +69,7 @@ func (c *TrackingDetails) UpdateCCTXStatus(ctx *context.Context) { return } +// UpdateCCTXOutboundDetails updates the TrackingDetails with the outbound chain and nonce func (c *TrackingDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { var ( zetacoreClient = ctx.GetZetaCoreClient() @@ -97,6 +91,9 @@ func (c *TrackingDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { return } +// UpdateHashListAndPendingStatus updates the TrackingDetails with the hash list and updates pending status +// If the tracker is found, it means the outbound is broadcast, but we are waiting for the confirmations +// If the tracker is not found, it means the outbound is not broadcast yet; we are waiting for the tss to sign the outbound func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { var ( zetacoreClient = ctx.GetZetaCoreClient() @@ -105,12 +102,8 @@ func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { outboundNonce = c.OutboundTssNonce ) - if !c.IsPendingOutbound() { - return - } - tracker, err := zetacoreClient.GetOutboundTracker(goCtx, outboundChain, outboundNonce) - // tracker is found that means the outbound has been broadcast, but we are waiting for confirmations + // the tracker is found that means the outbound has been broadcast, but we are waiting for confirmations if err == nil && tracker != nil { c.updateOutboundConfirmation() var hashList []string @@ -120,11 +113,20 @@ func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { c.OutboundTrackerHashList = hashList return } - // the cctx is in pending state by the outbound signing has not been done + // the cctx is in pending state, but the outbound signing has not been done c.updateOutboundSigning() return } +func (c *TrackingDetails) IsPendingOutbound() bool { + return c.Status == PendingOutbound || c.Status == PendingRevert +} + +func (c *TrackingDetails) IsPendingConfirmation() bool { + return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation +} + +// State transitions for TrackingDetails // 0 - Inbound Confirmation func (c *TrackingDetails) updateInboundConfirmation(isConfirmed bool) { c.Status = PendingInboundConfirmation diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index abf98c0e06..7b6aa34b51 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -24,6 +24,7 @@ import ( zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) +// CheckInbound checks the inbound chain and gets the inbound ballot identifier and updates the TrackingDetails func (c *TrackingDetails) CheckInbound(ctx *context.Context) error { var ( inboundChain = ctx.GetInboundChain() diff --git a/cmd/zetatool/cctx/inbound_test.go b/cmd/zetatool/cctx/inbound_test.go index c55f78d1cb..54f1313791 100644 --- a/cmd/zetatool/cctx/inbound_test.go +++ b/cmd/zetatool/cctx/inbound_test.go @@ -46,13 +46,6 @@ func Test_InboundBallotIdentifier(t *testing.T) { expectedBallotIdentifier: "0xf8ed419d9798aed83070763355628e2638ae9a4a47aa9c93ffc32f4b72c9fef4", expectError: false, }, - { - name: chains.SolanaMainnet.Name, - inboundHash: "5oj38HmTH4k2NSsqHK9oRrLjpPNBkm17dNXHFsaT6cTuJQRPWTCGqsPpRumPEbpL2B6Wuv51M69WoJwM24864PjB", - inboundChainID: chains.SolanaMainnet.ChainId, - expectedBallotIdentifier: "0xd7823bbbae1e3c893ac34d1053834c9591336eb6b3925b3cc1d0fa60f4eeaa4b", - expectError: false, - }, } for _, tc := range tt { diff --git a/cmd/zetatool/cctx/outbound.go b/cmd/zetatool/cctx/outbound.go index 41f4688913..fb4f18992d 100644 --- a/cmd/zetatool/cctx/outbound.go +++ b/cmd/zetatool/cctx/outbound.go @@ -48,13 +48,13 @@ func (c *TrackingDetails) checkEvmOutboundTx(ctx *context.Context) error { chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) if err != nil { - return fmt.Errorf("failed to get chain params: %v", err) + return fmt.Errorf("failed to get chain params: %w", err) } // create evm client for the observation chain evmClient, err := zetatoolchains.GetEvmClient(ctx, outboundChain) if err != nil { - return fmt.Errorf("failed to create evm client: %v", err) + return fmt.Errorf("failed to create evm client: %w", err) } foundConfirmedTx := false @@ -123,7 +123,7 @@ func (c *TrackingDetails) checkBitcoinOutboundTx(ctx *context.Context) error { chainParams, err := zetacoreClient.GetChainParamsForChainID(goCtx, outboundChain.ChainId) if err != nil { - return fmt.Errorf("failed to get chain params: %v", err) + return fmt.Errorf("failed to get chain params: %w", err) } confirmationCount := chainParams.ConfirmationCount diff --git a/cmd/zetatool/config/config_test.go b/cmd/zetatool/config/config_test.go index 8640fa7939..3c51bdbf34 100644 --- a/cmd/zetatool/config/config_test.go +++ b/cmd/zetatool/config/config_test.go @@ -36,7 +36,7 @@ func TestGetConfig(t *testing.T) { cfg, err = config.GetConfig(chains.Sepolia, "") require.NoError(t, err) - require.Equal(t, "https://zetachain-testnet-grpc.itrocket.net:443", cfg.ZetaChainRPC) + require.Equal(t, "https://zetachain-athens.g.allthatnode.com/archive/tendermint", cfg.ZetaChainRPC) cfg, err = config.GetConfig(chains.GoerliLocalnet, "") require.NoError(t, err) diff --git a/docs/cli/zetatool/get_ballot.md b/docs/cli/zetatool/get_ballot.md new file mode 100644 index 0000000000..0754192371 --- /dev/null +++ b/docs/cli/zetatool/get_ballot.md @@ -0,0 +1,23 @@ +## Usage + +### Fetching the Ballot Identifier + +### Command +```shell +zetatool get-ballot [inboundHash] [chainID] --config +``` +### Example +```shell +zetatool get-ballot 0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f56cb9a 1 +{"level":"info","time":"2025-01-20T11:30:47-05:00","message":"ballot identifier: 0xae189ab5cd884af784835297ac43eb55deb8a7800023534c580f44ee2b3eb5ed"} +``` + +- `inboundHash`: The inbound hash of the transaction for which the ballot identifier is to be fetched +- `chainID`: The chain ID of the chain to which the transaction belongs +- `config`: [Optional] The path to the configuration file. When not provided, the configuration in the file is user. A sample config is provided at `cmd/zetatool/config/sample_config.json` + +The Config contains the rpcs needed for the tool to function, +if not provided the tool automatically uses the default rpcs.It is able to fetch the rpc needed using the chain ID + +The command returns a ballot identifier for the given inbound hash. + diff --git a/docs/cli/zetatool/readme.md b/docs/cli/zetatool/readme.md index 8d522755ab..0b91a14a67 100644 --- a/docs/cli/zetatool/readme.md +++ b/docs/cli/zetatool/readme.md @@ -1,30 +1,8 @@ # ZetaTool -ZetaTool is a utility CLI for Zetachain.It currently provides a command to fetch the ballot/cctx identifier from the inbound hash +ZetaTool is a utility CLI for Zetachain.It currently provides the following functionalities: +- `get-ballot` : Fetch the (inbound ballot/cctx) identifier from the inbound hash and chain id +- `track-cctx` : Track the status of a cctx using from the inbound hash and chain id ## Installation Use the target : `make install-zetatool` - -## Usage - -### Fetching the Ballot Identifier - -### Command -```shell -zetatool get-ballot [inboundHash] [chainID] --config -``` -### Example -```shell -zetatool get-ballot 0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f56cb9a 1 -{"level":"info","time":"2025-01-20T11:30:47-05:00","message":"ballot identifier: 0xae189ab5cd884af784835297ac43eb55deb8a7800023534c580f44ee2b3eb5ed"} -``` - -- `inboundHash`: The inbound hash of the transaction for which the ballot identifier is to be fetched -- `chainID`: The chain ID of the chain to which the transaction belongs -- `config`: [Optional] The path to the configuration file. When not provided, the configuration in the file is user. A sample config is provided at `cmd/zetatool/config/sample_config.json` - -The Config contains the rpcs needed for the tool to function, -if not provided the tool automatically uses the default rpcs.It is able to fetch the rpc needed using the chain ID - -The command returns a ballot identifier for the given inbound hash. - diff --git a/docs/cli/zetatool/track_cctx.md b/docs/cli/zetatool/track_cctx.md new file mode 100644 index 0000000000..dbaed6c3bd --- /dev/null +++ b/docs/cli/zetatool/track_cctx.md @@ -0,0 +1,26 @@ +## Usage + +### TRack the status of a CCTX + +### Command +```shell +zetatool track-cctx [inboundHash] [chainID] --config +``` +### Example +```shell +zetatool track-cctx 0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f56cb9a 1 +{"level":"info","time":"2025-02-03T12:59:33-05:00","message":"CCTX Identifier: 0xae189ab5cd884af784835297ac43eb55deb8a7800023534c580f44ee2b3eb5ed Status: OutboundMined"} +``` + +- `inboundHash`: The inbound hash of the transaction for which the ballot identifier is to be fetched +- `chainID`: The chain ID of the chain to which the transaction belongs +- `config`: [Optional] The path to the configuration file. When not provided, the configuration in the file is user. A sample config is provided at `cmd/zetatool/config/sample_config.json` +- `debug`: [Optional] The debug flag is used to print additional debug information when set to true + +The Config contains the rpcs needed for the tool to function, +if not provided the tool automatically uses the default rpcs.It is able to fetch the rpc needed using the chain ID + +The command returns +- The CCTX identifier +- The status of the CCTX + diff --git a/pkg/rpc/clients_test.go b/pkg/rpc/clients_test.go index 4e00de8d9d..3a15bedafd 100644 --- a/pkg/rpc/clients_test.go +++ b/pkg/rpc/clients_test.go @@ -683,7 +683,7 @@ func TestZetacore_GetOutboundTracker(t *testing.T) { client := setupZetacoreClients(t) ctx := context.Background() - resp, err := client.GetOutboundTracker(ctx, chain.ChainId, 456) + resp, err := client.GetOutboundTracker(ctx, chain, 456) require.NoError(t, err) require.Equal(t, expectedOutput.OutboundTracker, *resp) } From 837c288e35a933370fd83b563915de4eff7d3835 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 3 Feb 2025 14:02:58 -0500 Subject: [PATCH 07/14] add comments on status --- cmd/zetae2e/local/bitcoin.go | 1 + cmd/zetatool/cctx/cctx_status.go | 41 +++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index f689b7d20e..2e5af15f6c 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -14,6 +14,7 @@ import ( "github.com/zeta-chain/node/testutil" ) +// startBitcoinTests starts Bitcoin related tests func startBitcoinTests( eg *errgroup.Group, conf config.Config, diff --git a/cmd/zetatool/cctx/cctx_status.go b/cmd/zetatool/cctx/cctx_status.go index e2a9c2ad0d..14088dda43 100644 --- a/cmd/zetatool/cctx/cctx_status.go +++ b/cmd/zetatool/cctx/cctx_status.go @@ -4,20 +4,33 @@ package cctx type Status int const ( - Unknown Status = iota - PendingInboundConfirmation Status = 1 - PendingInboundVoting Status = 2 - PendingOutbound Status = 3 - OutboundMined Status = 4 - PendingRevert Status = 5 - Reverted Status = 6 - PendingOutboundConfirmation Status = 7 - PendingRevertConfirmation Status = 8 - PendingRevertVoting Status = 10 - Aborted Status = 11 - PendingOutboundSigning Status = 12 - PendingRevertSigning Status = 13 - PendingOutboundVoting Status = 14 + Unknown Status = iota + // Zetacore statuses + PendingOutbound Status = 1 + OutboundMined Status = 2 + PendingRevert Status = 3 + Reverted Status = 4 + Aborted Status = 5 + // Zetatool only statuses + // PendingInboundConfirmation the inbound transaction is pending confirmation on the inbound chain + PendingInboundConfirmation Status = 6 + // PendingInboundVoting the inbound transaction is confirmed on the inbound chain, and we are waiting for observers to vote + PendingInboundVoting Status = 7 + // PendingOutboundSigning the outbound transaction is pending signing by the tss + PendingOutboundSigning Status = 8 + // PendingRevertSigning the revert transaction is pending signing by the tss + PendingRevertSigning Status = 9 + // PendingOutboundConfirmation the outbound transaction + // broadcast by the tss is pending confirmation on the outbound chain + PendingOutboundConfirmation Status = 10 + // PendingRevertConfirmation the revert transaction broadcast by the tss is pending confirmation on the outbound chain + PendingRevertConfirmation Status = 11 + // PendingOutboundVoting the outbound transaction is confirmed on the outbound chain, + //and we are waiting for observers to vote + PendingOutboundVoting Status = 12 + // PendingRevertVoting the revert transaction is confirmed on the outbound chain, + //and we are waiting for observers to vote + PendingRevertVoting Status = 13 ) func (s Status) String() string { From 43b006a14e0a7e109e390d40734f538b262aedc5 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Mon, 3 Feb 2025 15:08:11 -0500 Subject: [PATCH 08/14] remove debug prints --- cmd/zetatool/cctx/inbound.go | 8 ++++++-- docs/cli/zetatool/get_ballot.md | 2 +- docs/cli/zetatool/track_cctx.md | 2 +- e2e/e2etests/test_eth_withdraw.go | 2 -- x/observer/migrations/v9/migrate.go | 3 --- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index 7b6aa34b51..ed82290b6c 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -24,7 +24,7 @@ import ( zetaclientConfig "github.com/zeta-chain/node/zetaclient/config" ) -// CheckInbound checks the inbound chain and gets the inbound ballot identifier and updates the TrackingDetails +// CheckInbound checks the inbound chain,gets the inbound ballot identifier and updates the TrackingDetails func (c *TrackingDetails) CheckInbound(ctx *context.Context) error { var ( inboundChain = ctx.GetInboundChain() @@ -79,11 +79,12 @@ func (c *TrackingDetails) CheckInbound(ctx *context.Context) error { } default: c.Message = "Chain not supported" + return nil } - return nil } +// btcInboundBallotIdentifier gets the inbound ballot identifier for the inbound hash from bitcoin chain func (c *TrackingDetails) btcInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() @@ -146,6 +147,7 @@ func (c *TrackingDetails) btcInboundBallotIdentifier(ctx *context.Context) error return nil } +// evmInboundBallotIdentifier gets the inbound ballot identifier for the inbound hash from evm chain func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() @@ -273,6 +275,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error return nil } +// solanaInboundBallotIdentifier gets the inbound ballot identifier for the inbound hash from solana chain func (c *TrackingDetails) solanaInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() @@ -331,6 +334,7 @@ func (c *TrackingDetails) solanaInboundBallotIdentifier(ctx *context.Context) er return nil } +// zevmInboundBallotIdentifier gets the inbound ballot identifier for the inbound hash from zetachain func (c *TrackingDetails) zevmInboundBallotIdentifier(ctx *context.Context) error { var ( inboundHash = ctx.GetInboundHash() diff --git a/docs/cli/zetatool/get_ballot.md b/docs/cli/zetatool/get_ballot.md index 0754192371..052f94cebe 100644 --- a/docs/cli/zetatool/get_ballot.md +++ b/docs/cli/zetatool/get_ballot.md @@ -1,6 +1,6 @@ ## Usage -### Fetching the Ballot Identifier +### Fetching the Inbound Ballot Identifier ### Command ```shell diff --git a/docs/cli/zetatool/track_cctx.md b/docs/cli/zetatool/track_cctx.md index dbaed6c3bd..7881332c55 100644 --- a/docs/cli/zetatool/track_cctx.md +++ b/docs/cli/zetatool/track_cctx.md @@ -1,6 +1,6 @@ ## Usage -### TRack the status of a CCTX +### Track the status of a CCTX ### Command ```shell diff --git a/e2e/e2etests/test_eth_withdraw.go b/e2e/e2etests/test_eth_withdraw.go index 529e19d7ae..09986e5917 100644 --- a/e2e/e2etests/test_eth_withdraw.go +++ b/e2e/e2etests/test_eth_withdraw.go @@ -1,7 +1,6 @@ package e2etests import ( - "fmt" "math/big" "github.com/stretchr/testify/require" @@ -25,7 +24,6 @@ func TestETHWithdraw(r *runner.E2ERunner, args []string) { // perform the withdraw tx := r.ETHWithdraw(r.EVMAddress(), amount, gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) - fmt.Println("withdraw tx", tx.Hash().Hex()) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) r.Logger.CCTX(*cctx, "withdraw") diff --git a/x/observer/migrations/v9/migrate.go b/x/observer/migrations/v9/migrate.go index b0d60e6538..c936c7feee 100644 --- a/x/observer/migrations/v9/migrate.go +++ b/x/observer/migrations/v9/migrate.go @@ -1,8 +1,6 @@ package v9 import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/node/x/observer/types" @@ -19,7 +17,6 @@ const MaturityBlocks = int64(100) // MigrateStore migrates the x/observer module state from the consensus version 8 to version 9. // The migration deletes all the ballots and ballot lists that are older than MaturityBlocks. func MigrateStore(ctx sdk.Context, observerKeeper observerKeeper) error { - fmt.Println("Migrating x/observer store") currentHeight := ctx.BlockHeight() // Maturity blocks is a parameter in the emissions module if currentHeight < MaturityBlocks { From 799185ce2bcc0187c70756b9601f526867f1f65b Mon Sep 17 00:00:00 2001 From: Tanmay Date: Tue, 4 Feb 2025 13:04:26 -0500 Subject: [PATCH 09/14] rename cctxDEtails to cctxTrackingDetails --- cmd/zetatool/cctx/cctx_details.go | 2 +- cmd/zetatool/cctx/inbound_test.go | 2 +- cmd/zetatool/cli/cctx_tracker.go | 38 +++++++++++++++--------------- cmd/zetatool/cli/inbound_ballot.go | 10 ++++---- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 060b03e927..54a8db7d4a 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -18,7 +18,7 @@ type TrackingDetails struct { Message string `json:"message"` } -func NewCCTXDetails() *TrackingDetails { +func NewTrackingDetails() *TrackingDetails { return &TrackingDetails{ CCTXIdentifier: "", Status: Unknown, diff --git a/cmd/zetatool/cctx/inbound_test.go b/cmd/zetatool/cctx/inbound_test.go index 54f1313791..4ee382ca6a 100644 --- a/cmd/zetatool/cctx/inbound_test.go +++ b/cmd/zetatool/cctx/inbound_test.go @@ -52,7 +52,7 @@ func Test_InboundBallotIdentifier(t *testing.T) { t.Run(tc.name, func(t *testing.T) { ctx, err := zetatoolcontext.NewContext(context.Background(), tc.inboundChainID, tc.inboundHash, "") require.NoError(t, err) - c := cctx.NewCCTXDetails() + c := cctx.NewTrackingDetails() err = c.CheckInbound(ctx) require.NoError(t, err) if !tc.expectError && c.CCTXIdentifier != tc.expectedBallotIdentifier { diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index 98929f7b7f..0d12961b4e 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -38,60 +38,60 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create context: %w", err) } - cctxDetails, err := trackCCTX(ctx) + cctxTrackingDetails, err := trackCCTX(ctx) if err != nil { return fmt.Errorf("failed to track cctx: %w", err) } if cmd.Flag(config.FlagDebug).Changed { - log.Info().Msg(cctxDetails.DebugPrint()) + log.Info().Msg(cctxTrackingDetails.DebugPrint()) return nil } - log.Info().Msg(cctxDetails.Print()) + log.Info().Msg(cctxTrackingDetails.Print()) return nil } func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { var ( - cctxDetails = cctx.NewCCTXDetails() - err error + cctxTrackingDetails = cctx.NewTrackingDetails() + err error ) // Get the ballot identifier for the inbound transaction and confirm that cctx status in atleast either PendingInboundConfirmation or PendingInboundVoting - err = cctxDetails.CheckInbound(ctx) + err = cctxTrackingDetails.CheckInbound(ctx) if err != nil { - return cctxDetails, fmt.Errorf("failed to get ballot identifier: %v", err) + return cctxTrackingDetails, fmt.Errorf("failed to get ballot identifier: %v", err) } // Reject unknown status, as it is not valid - if cctxDetails.Status == cctx.Unknown || cctxDetails.CCTXIdentifier == "" { - return cctxDetails, fmt.Errorf("unknown status") + if cctxTrackingDetails.Status == cctx.Unknown || cctxTrackingDetails.CCTXIdentifier == "" { + return cctxTrackingDetails, fmt.Errorf("unknown status") } // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address. // Update cctx status from zetacore.This copies the status from zetacore to the cctx details.The cctx status can only be `PendingInboundVoting` or `PendingInboundConfirmation` at this point - cctxDetails.UpdateCCTXStatus(ctx) + cctxTrackingDetails.UpdateCCTXStatus(ctx) // The cctx details now have status from zetacore, we have not tried to a get more granular status from the outbound chain yet. // If it's not pending, we can just return here. - if !cctxDetails.IsPendingOutbound() { - return cctxDetails, nil + if !cctxTrackingDetails.IsPendingOutbound() { + return cctxTrackingDetails, nil } // update outbound details, this does not transition any status. - cctxDetails.UpdateCCTXOutboundDetails(ctx) + cctxTrackingDetails.UpdateCCTXOutboundDetails(ctx) // Update tx hash list from outbound tracker // If the tracker is found, it means the outbound is broadcast, but we are waiting for the confirmations // If the tracker is not found, it means the outbound is not broadcast yet, we are wwaiting for the tss to sign the outbound - cctxDetails.UpdateHashListAndPendingStatus(ctx) + cctxTrackingDetails.UpdateHashListAndPendingStatus(ctx) // If its not pending confirmation, we can return here, it means the outbound is not broadcast yet its pending tss signing - if !cctxDetails.IsPendingConfirmation() { - return cctxDetails, nil + if !cctxTrackingDetails.IsPendingConfirmation() { + return cctxTrackingDetails, nil } // Check outbound tx, we are waiting for the outbound tx to be confirmed - err = cctxDetails.CheckOutbound(ctx) + err = cctxTrackingDetails.CheckOutbound(ctx) if err != nil { - return cctxDetails, err + return cctxTrackingDetails, err } - return cctxDetails, nil + return cctxTrackingDetails, nil } diff --git a/cmd/zetatool/cli/inbound_ballot.go b/cmd/zetatool/cli/inbound_ballot.go index e602c08487..2cf5482b4e 100644 --- a/cmd/zetatool/cli/inbound_ballot.go +++ b/cmd/zetatool/cli/inbound_ballot.go @@ -38,19 +38,19 @@ func GetInboundBallot(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to create context: %w", err) } - cctxDetails := cctx.NewCCTXDetails() + cctxTrackingDetails := cctx.NewTrackingDetails() - err = cctxDetails.CheckInbound(ctx) + err = cctxTrackingDetails.CheckInbound(ctx) if err != nil { return fmt.Errorf("failed to get ballot identifier: %w", err) } - if cctxDetails.Status == cctx.PendingInboundConfirmation { + if cctxTrackingDetails.Status == cctx.PendingInboundConfirmation { log.Printf( "Ballot Identifier: %s, warning the inbound hash might not be confirmed yet", - cctxDetails.CCTXIdentifier, + cctxTrackingDetails.CCTXIdentifier, ) return nil } - log.Print("Ballot Identifier: ", cctxDetails.CCTXIdentifier) + log.Print("Ballot Identifier: ", cctxTrackingDetails.CCTXIdentifier) return nil } From 27e05c62f1b69768a832407dcd5627967c08a05d Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 5 Feb 2025 15:02:27 -0500 Subject: [PATCH 10/14] improve string comparison for addresses --- cmd/zetatool/cctx/inbound.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index ed82290b6c..214b6c5363 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -2,6 +2,7 @@ package cctx import ( "fmt" + "strings" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -189,8 +190,9 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error msg := &crosschaintypes.MsgVoteInbound{} // Create inbound vote message based on the cointype and protocol version - switch tx.To().Hex() { - case chainParams.ConnectorContractAddress: + + switch { + case compareAddress(tx.To().Hex(), chainParams.ConnectorContractAddress): { // build inbound vote message and post vote addrConnector := ethcommon.HexToAddress(chainParams.ConnectorContractAddress) @@ -205,7 +207,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error } } } - case chainParams.Erc20CustodyContractAddress: + case compareAddress(tx.To().Hex(), chainParams.Erc20CustodyContractAddress): { addrCustody := ethcommon.HexToAddress(chainParams.Erc20CustodyContractAddress) custody, err := erc20custody.NewERC20Custody(addrCustody, evmClient) @@ -223,7 +225,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error } } } - case tssEthAddress: + case compareAddress(tx.To().Hex(), tssEthAddress): { if receipt.Status != ethtypes.ReceiptStatusSuccessful { return fmt.Errorf("tx failed on chain %d", inboundChain.ChainId) @@ -234,7 +236,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error } msg = zetatoolchains.GasVoteV1(tx, sender, receipt.BlockNumber.Uint64(), inboundChain.ChainId, zetaChainID) } - case chainParams.GatewayAddress: + case compareAddress(tx.To().Hex(), chainParams.GatewayAddress): { gatewayAddr := ethcommon.HexToAddress(chainParams.GatewayAddress) gateway, err := gatewayevm.NewGatewayEVM(gatewayAddr, evmClient) @@ -361,3 +363,9 @@ func (c *TrackingDetails) zevmInboundBallotIdentifier(ctx *context.Context) erro c.Status = PendingOutbound return nil } + +func compareAddress(a string, b string) bool { + lowerA := strings.ToLower(a) + lowerB := strings.ToLower(b) + return strings.EqualFold(lowerA, lowerB) +} From fc4b3f04304786ed6997e6e37dd4d61e56ff9600 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 5 Feb 2025 20:29:36 -0500 Subject: [PATCH 11/14] add cctx tracking for deposit cctx that creates a withdraw --- cmd/zetae2e/local/bitcoin.go | 95 +++++++++++++++---------------- cmd/zetae2e/local/evm.go | 34 +++++------ cmd/zetatool/cctx/cctx_details.go | 10 ++++ cmd/zetatool/cctx/inbound.go | 8 +-- cmd/zetatool/chains/bitcoin.go | 19 +++---- cmd/zetatool/cli/cctx_tracker.go | 74 ++++++++++++++++++------ 6 files changed, 143 insertions(+), 97 deletions(-) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 2e5af15f6c..02b49610bc 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -22,56 +22,55 @@ func startBitcoinTests( verbose bool, light, skipBitcoinSetup bool, ) { - { - // start the bitcoin tests - // btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs - bitcoinDepositTests := []string{ - e2etests.TestBitcoinDonationName, - e2etests.TestBitcoinDepositName, - e2etests.TestBitcoinDepositAndCallName, - e2etests.TestBitcoinDepositAndCallRevertName, - e2etests.TestBitcoinStdMemoDepositName, - e2etests.TestBitcoinStdMemoDepositAndCallName, - e2etests.TestBitcoinStdMemoDepositAndCallRevertName, - e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, - e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName, - e2etests.TestCrosschainSwapName, - } - bitcoinDepositTestsAdvanced := []string{ - e2etests.TestBitcoinDepositAndCallRevertWithDustName, - e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, - e2etests.TestBitcoinDepositAndWithdrawWithDustName, - } - bitcoinWithdrawTests := []string{ - e2etests.TestBitcoinWithdrawSegWitName, - e2etests.TestBitcoinWithdrawInvalidAddressName, - e2etests.TestLegacyZetaWithdrawBTCRevertName, - } - bitcoinWithdrawTestsAdvanced := []string{ - e2etests.TestBitcoinWithdrawTaprootName, - e2etests.TestBitcoinWithdrawLegacyName, - e2etests.TestBitcoinWithdrawP2SHName, - e2etests.TestBitcoinWithdrawP2WSHName, - e2etests.TestBitcoinWithdrawMultipleName, - e2etests.TestBitcoinWithdrawRestrictedName, - } + // start the bitcoin tests + // btc withdraw tests are those that need a Bitcoin node wallet to send UTXOs + bitcoinDepositTests := []string{ + e2etests.TestBitcoinDonationName, + e2etests.TestBitcoinDepositName, + e2etests.TestBitcoinDepositAndCallName, + e2etests.TestBitcoinDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoDepositName, + e2etests.TestBitcoinStdMemoDepositAndCallName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertName, + e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, + e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName, + e2etests.TestCrosschainSwapName, + } + bitcoinDepositTestsAdvanced := []string{ + e2etests.TestBitcoinDepositAndCallRevertWithDustName, + e2etests.TestBitcoinStdMemoDepositAndCallRevertOtherAddressName, + e2etests.TestBitcoinDepositAndWithdrawWithDustName, + } + bitcoinWithdrawTests := []string{ + e2etests.TestBitcoinWithdrawSegWitName, + e2etests.TestBitcoinWithdrawInvalidAddressName, + e2etests.TestLegacyZetaWithdrawBTCRevertName, + } + bitcoinWithdrawTestsAdvanced := []string{ + e2etests.TestBitcoinWithdrawTaprootName, + e2etests.TestBitcoinWithdrawLegacyName, + e2etests.TestBitcoinWithdrawP2SHName, + e2etests.TestBitcoinWithdrawP2WSHName, + e2etests.TestBitcoinWithdrawMultipleName, + e2etests.TestBitcoinWithdrawRestrictedName, + } - if !light { - // if light is enabled, only the most basic tests are run and advanced are skipped - bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...) - bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...) - } - bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines( - conf, - deployerRunner, - verbose, - !skipBitcoinSetup, - bitcoinDepositTests, - bitcoinWithdrawTests, - ) - eg.Go(bitcoinDepositTestRoutine) - eg.Go(bitcoinWithdrawTestRoutine) + if !light { + // if light is enabled, only the most basic tests are run and advanced are skipped + bitcoinDepositTests = append(bitcoinDepositTests, bitcoinDepositTestsAdvanced...) + bitcoinWithdrawTests = append(bitcoinWithdrawTests, bitcoinWithdrawTestsAdvanced...) } + bitcoinDepositTestRoutine, bitcoinWithdrawTestRoutine := bitcoinTestRoutines( + conf, + deployerRunner, + verbose, + !skipBitcoinSetup, + bitcoinDepositTests, + bitcoinWithdrawTests, + ) + eg.Go(bitcoinDepositTestRoutine) + eg.Go(bitcoinWithdrawTestRoutine) + } // bitcoinTestRoutines returns test routines for deposit and withdraw tests diff --git a/cmd/zetae2e/local/evm.go b/cmd/zetae2e/local/evm.go index bcb88041e8..7c5dfc3928 100644 --- a/cmd/zetae2e/local/evm.go +++ b/cmd/zetae2e/local/evm.go @@ -18,17 +18,17 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne eg.Go(evmTestRoutine(conf, "eth", conf.AdditionalAccounts.UserEther, color.FgHiGreen, deployerRunner, verbose, e2etests.TestETHDepositName, e2etests.TestETHDepositAndCallName, - e2etests.TestETHWithdrawName, - e2etests.TestETHWithdrawAndArbitraryCallName, - e2etests.TestETHWithdrawAndCallName, - e2etests.TestETHWithdrawAndCallThroughContractName, - e2etests.TestZEVMToEVMArbitraryCallName, - e2etests.TestZEVMToEVMCallName, - e2etests.TestZEVMToEVMCallThroughContractName, - e2etests.TestEVMToZEVMCallName, - e2etests.TestETHDepositAndCallNoMessageName, - e2etests.TestETHWithdrawAndCallNoMessageName, - e2etests.TestEtherWithdrawRestrictedName, + //e2etests.TestETHWithdrawName, + //e2etests.TestETHWithdrawAndArbitraryCallName, + //e2etests.TestETHWithdrawAndCallName, + //e2etests.TestETHWithdrawAndCallThroughContractName, + //e2etests.TestZEVMToEVMArbitraryCallName, + //e2etests.TestZEVMToEVMCallName, + //e2etests.TestZEVMToEVMCallThroughContractName, + //e2etests.TestEVMToZEVMCallName, + //e2etests.TestETHDepositAndCallNoMessageName, + //e2etests.TestETHWithdrawAndCallNoMessageName, + //e2etests.TestEtherWithdrawRestrictedName, )) // Test happy paths for erc20 token workflow @@ -36,13 +36,13 @@ func startEVMTests(eg *errgroup.Group, conf config.Config, deployerRunner *runne e2etests.TestETHDepositName, // necessary to pay fees on ZEVM e2etests.TestERC20DepositName, e2etests.TestERC20DepositAndCallName, - e2etests.TestERC20WithdrawName, - e2etests.TestERC20WithdrawAndArbitraryCallName, - e2etests.TestERC20WithdrawAndCallName, - e2etests.TestERC20DepositAndCallNoMessageName, - e2etests.TestERC20WithdrawAndCallNoMessageName, + //e2etests.TestERC20WithdrawName, + //e2etests.TestERC20WithdrawAndArbitraryCallName, + //e2etests.TestERC20WithdrawAndCallName, + //e2etests.TestERC20DepositAndCallNoMessageName, + //e2etests.TestERC20WithdrawAndCallNoMessageName, e2etests.TestDepositAndCallSwapName, - e2etests.TestERC20DepositRestrictedName, + //e2etests.TestERC20DepositRestrictedName, )) // Test revert cases for gas token workflow diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 54a8db7d4a..311c803fdb 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -79,6 +79,11 @@ func (c *TrackingDetails) UpdateCCTXOutboundDetails(ctx *context.Context) { if err != nil { c.Message = fmt.Sprintf("failed to get cctx: %v", err) } + outboundParams := CCTX.GetCurrentOutboundParam() + if outboundParams == nil { + c.Message = "outbound params not found" + return + } chainID := CCTX.GetCurrentOutboundParam().ReceiverChainId // This is almost impossible to happen as the cctx would not have been created if the chain was not supported @@ -118,6 +123,11 @@ func (c *TrackingDetails) UpdateHashListAndPendingStatus(ctx *context.Context) { return } +// IsInboundFinalized checks if the inbound voting has been finalized +func (c *TrackingDetails) IsInboundFinalized() bool { + return !(c.Status == PendingInboundConfirmation || c.Status == PendingInboundVoting) +} + func (c *TrackingDetails) IsPendingOutbound() bool { return c.Status == PendingOutbound || c.Status == PendingRevert } diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index 214b6c5363..7e28c105e3 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -269,7 +269,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error } } default: - return fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s", inboundHash) + return fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s to address %s", inboundHash, tx.To()) } c.CCTXIdentifier = msg.Digest() @@ -351,14 +351,10 @@ func (c *TrackingDetails) zevmInboundBallotIdentifier(ctx *context.Context) erro if err != nil { return fmt.Errorf("inbound chain is zetachain , cctx should be available in the same block: %w", err) } - if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) == 0 { + if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) < 1 { return fmt.Errorf("inbound hash does not have any cctx linked %s", inboundHash) } - if len(inboundHashToCCTX.InboundHashToCctx.CctxIndex) > 1 { - return fmt.Errorf("inbound hash more than one cctx %s", inboundHash) - } - c.CCTXIdentifier = inboundHashToCCTX.InboundHashToCctx.CctxIndex[0] c.Status = PendingOutbound return nil diff --git a/cmd/zetatool/chains/bitcoin.go b/cmd/zetatool/chains/bitcoin.go index 25677d0130..b8f9836e6b 100644 --- a/cmd/zetatool/chains/bitcoin.go +++ b/cmd/zetatool/chains/bitcoin.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/rs/zerolog" + "github.com/zeta-chain/node/pkg/memo" "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/coin" @@ -74,9 +75,6 @@ func BitcoinBallotIdentifier( } cctxIdentifier, err = identifierFromBtcEvent(event, senderChainID, zetacoreChainID) - if err != nil { - return - } return } @@ -139,9 +137,10 @@ func voteFromLegacyMemo( coin.CoinType_Gas, "", 0, - crosschaintypes.ProtocolContractVersion_V1, + crosschaintypes.ProtocolContractVersion_V2, false, // not relevant for v1 crosschaintypes.InboundStatus_SUCCESS, + crosschaintypes.WithCrossChainCall(len(event.MemoBytes) > 0), ) } @@ -156,9 +155,8 @@ func voteFromStdMemo( RevertAddress: event.MemoStd.RevertOptions.RevertAddress, } - // make a legacy message so that zetacore can process it as V1 - msgBytes := append(event.MemoStd.Receiver.Bytes(), event.MemoStd.Payload...) - message := hex.EncodeToString(msgBytes) + // check if the memo is a cross-chain call, or simple token deposit + isCrosschainCall := event.MemoStd.OpCode == memo.OpCodeCall || event.MemoStd.OpCode == memo.OpCodeDepositAndCall return crosschaintypes.NewMsgVoteInbound( "", @@ -168,16 +166,17 @@ func voteFromStdMemo( event.ToAddress, zetacoreChainID, cosmosmath.NewUintFromBigInt(amountSats), - message, + hex.EncodeToString(event.MemoStd.Payload), event.TxHash, event.BlockNumber, 0, coin.CoinType_Gas, "", 0, - crosschaintypes.ProtocolContractVersion_V1, + crosschaintypes.ProtocolContractVersion_V2, false, // not relevant for v1 - crosschaintypes.InboundStatus_SUCCESS, + event.Status, crosschaintypes.WithRevertOptions(revertOptions), + crosschaintypes.WithCrossChainCall(isCrosschainCall), ) } diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index 0d12961b4e..3e78425f47 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -23,6 +23,13 @@ func NewTrackCCTXCMD() *cobra.Command { } func TrackCCTX(cmd *cobra.Command, args []string) error { + var ( + ctx = &zetatoolcontext.Context{} + trackingDetailsList []cctx.TrackingDetails + err error + maxCCTXChainLength = 5 // Maximum number of cctx chains to track + ) + inboundHash := args[0] inboundChainID, err := strconv.ParseInt(args[1], 10, 64) if err != nil { @@ -32,21 +39,53 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to read value for flag %s , err %w", config.FlagConfig, err) } + for i := 0; i < maxCCTXChainLength; i++ { + chainID := inboundChainID + chainHash := inboundHash - ctx, err := zetatoolcontext.NewContext(context.Background(), inboundChainID, inboundHash, configFile) - if err != nil { - return fmt.Errorf("failed to create context: %w", err) - } + // if len of tx is greated than 0, we have already tracked the cctx and we are trying to continue tracking the chain + // In thin case we should use the cctx identifier from the last cctx as the inbound hash and outbound chain id as theinbound chain id + if len(trackingDetailsList) > 0 { + lastTrackingDetails := trackingDetailsList[len(trackingDetailsList)-1] + // The last inbound was not finalized, this means that next cctx has not been created, yet we can return. + // There is no need to log the error here as we are just trying to track the cctx + if !lastTrackingDetails.IsInboundFinalized() { + return nil + } + // Update the chain id and hash to the last cctx details + chainID = lastTrackingDetails.OutboundChain.ChainId + chainHash = lastTrackingDetails.CCTXIdentifier + } - cctxTrackingDetails, err := trackCCTX(ctx) - if err != nil { - return fmt.Errorf("failed to track cctx: %w", err) - } - if cmd.Flag(config.FlagDebug).Changed { - log.Info().Msg(cctxTrackingDetails.DebugPrint()) - return nil + // Create a new context based on the chain id and hash + ctx, err = zetatoolcontext.NewContext(context.Background(), chainID, chainHash, configFile) + if err != nil { + return fmt.Errorf("failed to create context: %w", err) + } + + // fetch the cctx details and status + cctxTrackingDetails, err := trackCCTX(ctx) + if err != nil { + // The error can be caused by two reasons + // 1. We have reached the end of the cctx error chain, we can return + // 2. There was an error in tracking the cctx. + // If debug flag is set, we log everything + if cmd.Flag(config.FlagDebug).Changed { + log.Error().Msgf("failed to track cctx: %v", err) + if cctxTrackingDetails != nil { + log.Info().Msg(cctxTrackingDetails.DebugPrint()) + } + } + // If the length of the tracking details is 0, it means that we have not tracked any cctx yet, we log the error + if len(trackingDetailsList) == 0 { + log.Error().Msgf("failed to track cctx: %v", err) + } + return nil + } + + log.Info().Msg(cctxTrackingDetails.Print()) + trackingDetailsList = append(trackingDetailsList, *cctxTrackingDetails) } - log.Info().Msg(cctxTrackingDetails.Print()) return nil } @@ -58,7 +97,7 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { // Get the ballot identifier for the inbound transaction and confirm that cctx status in atleast either PendingInboundConfirmation or PendingInboundVoting err = cctxTrackingDetails.CheckInbound(ctx) if err != nil { - return cctxTrackingDetails, fmt.Errorf("failed to get ballot identifier: %v", err) + return cctxTrackingDetails, fmt.Errorf("failed to get ballot identifier: %w", err) } // Reject unknown status, as it is not valid if cctxTrackingDetails.Status == cctx.Unknown || cctxTrackingDetails.CCTXIdentifier == "" { @@ -69,15 +108,18 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { // Update cctx status from zetacore.This copies the status from zetacore to the cctx details.The cctx status can only be `PendingInboundVoting` or `PendingInboundConfirmation` at this point cctxTrackingDetails.UpdateCCTXStatus(ctx) + // UpdateCCTXStatus can return without updating the cctx details if the cctx is not found.That is fine which just means that the cctx is not yet created + // If the inbound is finalized, we can update the outbound details as they would now be available + if cctxTrackingDetails.IsInboundFinalized() { + cctxTrackingDetails.UpdateCCTXOutboundDetails(ctx) + } + // The cctx details now have status from zetacore, we have not tried to a get more granular status from the outbound chain yet. // If it's not pending, we can just return here. if !cctxTrackingDetails.IsPendingOutbound() { return cctxTrackingDetails, nil } - // update outbound details, this does not transition any status. - cctxTrackingDetails.UpdateCCTXOutboundDetails(ctx) - // Update tx hash list from outbound tracker // If the tracker is found, it means the outbound is broadcast, but we are waiting for the confirmations // If the tracker is not found, it means the outbound is not broadcast yet, we are wwaiting for the tss to sign the outbound From 186aedd871edba2638b99f42208fbaba1de874ef Mon Sep 17 00:00:00 2001 From: Tanmay Date: Wed, 5 Feb 2025 21:33:43 -0500 Subject: [PATCH 12/14] generate files --- cmd/zetae2e/local/bitcoin.go | 1 - cmd/zetatool/cctx/cctx_details.go | 2 ++ cmd/zetatool/cctx/inbound.go | 11 +++++++++-- cmd/zetatool/cctx/inbound_test.go | 7 +++++++ cmd/zetatool/chains/bitcoin.go | 2 +- cmd/zetatool/chains/solana.go | 12 +++--------- cmd/zetatool/cli/cctx_tracker.go | 12 ++++++------ cmd/zetatool/config/config.go | 10 +++++----- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index 02b49610bc..483f489bea 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -70,7 +70,6 @@ func startBitcoinTests( ) eg.Go(bitcoinDepositTestRoutine) eg.Go(bitcoinWithdrawTestRoutine) - } // bitcoinTestRoutines returns test routines for deposit and withdraw tests diff --git a/cmd/zetatool/cctx/cctx_details.go b/cmd/zetatool/cctx/cctx_details.go index 311c803fdb..f1162a0306 100644 --- a/cmd/zetatool/cctx/cctx_details.go +++ b/cmd/zetatool/cctx/cctx_details.go @@ -128,10 +128,12 @@ func (c *TrackingDetails) IsInboundFinalized() bool { return !(c.Status == PendingInboundConfirmation || c.Status == PendingInboundVoting) } +// IsPendingOutbound checks if the cctx is pending processing the outbound transaction (outbound or revert) func (c *TrackingDetails) IsPendingOutbound() bool { return c.Status == PendingOutbound || c.Status == PendingRevert } +// IsPendingConfirmation checks if the cctx is pending outbound confirmation (outbound or revert func (c *TrackingDetails) IsPendingConfirmation() bool { return c.Status == PendingOutboundConfirmation || c.Status == PendingRevertConfirmation } diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index 7e28c105e3..b3a584acf6 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -269,7 +269,11 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error } } default: - return fmt.Errorf("irrelevant transaction , not sent to any known address txHash: %s to address %s", inboundHash, tx.To()) + return fmt.Errorf( + "irrelevant transaction , not sent to any known address txHash: %s to address %s", + inboundHash, + tx.To(), + ) } c.CCTXIdentifier = msg.Digest() @@ -293,7 +297,10 @@ func (c *TrackingDetails) solanaInboundBallotIdentifier(ctx *context.Context) er return fmt.Errorf("error creating rpc client") } - signature := solana.MustSignatureFromBase58(inboundHash) + signature, err := solana.SignatureFromBase58(inboundHash) + if err != nil { + return fmt.Errorf("error parsing signature: %w", err) + } txResult, err := solanarpc.GetTransaction(goCtx, solClient, signature) if err != nil { diff --git a/cmd/zetatool/cctx/inbound_test.go b/cmd/zetatool/cctx/inbound_test.go index 4ee382ca6a..33ae9c8ed1 100644 --- a/cmd/zetatool/cctx/inbound_test.go +++ b/cmd/zetatool/cctx/inbound_test.go @@ -46,6 +46,13 @@ func Test_InboundBallotIdentifier(t *testing.T) { expectedBallotIdentifier: "0xf8ed419d9798aed83070763355628e2638ae9a4a47aa9c93ffc32f4b72c9fef4", expectError: false, }, + { + name: chains.SolanaMainnet.Name, + inboundHash: "5oj38HmTH4k2NSsqHK9oRrLjpPNBkm17dNXHFsaT6cTuJQRPWTCGqsPpRumPEbpL2B6Wuv51M69WoJwM24864PjB", + inboundChainID: chains.SolanaMainnet.ChainId, + expectedBallotIdentifier: "0xfb5f2adc2a23c301d3231613284d937f6f45cc7b1139011abbc8486de7fcbd5f", + expectError: false, + }, } for _, tc := range tt { diff --git a/cmd/zetatool/chains/bitcoin.go b/cmd/zetatool/chains/bitcoin.go index b8f9836e6b..9962cb857c 100644 --- a/cmd/zetatool/chains/bitcoin.go +++ b/cmd/zetatool/chains/bitcoin.go @@ -9,10 +9,10 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/rs/zerolog" - "github.com/zeta-chain/node/pkg/memo" "github.com/zeta-chain/node/cmd/zetatool/context" "github.com/zeta-chain/node/pkg/coin" + "github.com/zeta-chain/node/pkg/memo" crosschaintypes "github.com/zeta-chain/node/x/crosschain/types" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/client" "github.com/zeta-chain/node/zetaclient/chains/bitcoin/common" diff --git a/cmd/zetatool/chains/solana.go b/cmd/zetatool/chains/solana.go index 45420633ba..03ecabb13e 100644 --- a/cmd/zetatool/chains/solana.go +++ b/cmd/zetatool/chains/solana.go @@ -2,7 +2,6 @@ package chains import ( "encoding/hex" - "fmt" cosmosmath "cosmossdk.io/math" @@ -13,12 +12,6 @@ import ( // voteMsgFromSolEvent builds a MsgVoteInbound from an inbound event func VoteMsgFromSolEvent(event *clienttypes.InboundEvent, zetaChainID int64) (*crosschaintypes.MsgVoteInbound, error) { - // decode event memo bytes to get the receiver - err := event.DecodeMemo() - if err != nil { - return nil, fmt.Errorf("failed to decode memo: %w", err) - } - // create inbound vote message return crosschaintypes.NewMsgVoteInbound( "", @@ -35,8 +28,9 @@ func VoteMsgFromSolEvent(event *clienttypes.InboundEvent, event.CoinType, event.Asset, 0, // not a smart contract call - crosschaintypes.ProtocolContractVersion_V1, - false, // not relevant for v1 + crosschaintypes.ProtocolContractVersion_V2, + false, crosschaintypes.InboundStatus_SUCCESS, + crosschaintypes.WithCrossChainCall(event.IsCrossChainCall), ), nil } diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index 3e78425f47..d031aa13f3 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -69,17 +69,16 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { // The error can be caused by two reasons // 1. We have reached the end of the cctx error chain, we can return // 2. There was an error in tracking the cctx. + // If debug flag is set, we log everything - if cmd.Flag(config.FlagDebug).Changed { - log.Error().Msgf("failed to track cctx: %v", err) + // If the length of the tracking details is 0, it means that we have not tracked any cctx yet, we log the error + if cmd.Flag(config.FlagDebug).Changed || len(trackingDetailsList) == 0 { + log.Error().Msgf("failed to track cctx : %v", err) if cctxTrackingDetails != nil { log.Info().Msg(cctxTrackingDetails.DebugPrint()) } } - // If the length of the tracking details is 0, it means that we have not tracked any cctx yet, we log the error - if len(trackingDetailsList) == 0 { - log.Error().Msgf("failed to track cctx: %v", err) - } + return nil } @@ -105,6 +104,7 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { } // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address. + // After this we attach error messages to the message field as wealready have some details about the cctx which can be printed // Update cctx status from zetacore.This copies the status from zetacore to the cctx details.The cctx status can only be `PendingInboundVoting` or `PendingInboundConfirmation` at this point cctxTrackingDetails.UpdateCCTXStatus(ctx) diff --git a/cmd/zetatool/config/config.go b/cmd/zetatool/config/config.go index 31c3291846..11a7c1664d 100644 --- a/cmd/zetatool/config/config.go +++ b/cmd/zetatool/config/config.go @@ -21,12 +21,12 @@ func TestnetConfig() *Config { return &Config{ ZetaChainRPC: "https://zetachain-athens.g.allthatnode.com/archive/tendermint", EthereumRPC: "https://ethereum-sepolia-rpc.publicnode.com", - ZetaChainID: 7001, + ZetaChainID: chains.ZetaChainTestnet.ChainId, BtcUser: "", BtcPassword: "", BtcHost: "", BtcParams: "", - SolanaRPC: "", + SolanaRPC: "https://api.testnet.solana.com", BscRPC: "https://bsc-testnet-rpc.publicnode.com", PolygonRPC: "https://polygon-amoy.gateway.tenderly.com", BaseRPC: "https://base-sepolia-rpc.publicnode.com", @@ -37,7 +37,7 @@ func DevnetConfig() *Config { return &Config{ ZetaChainRPC: "", EthereumRPC: "", - ZetaChainID: 101, + ZetaChainID: chains.ZetaChainDevnet.ChainId, BtcUser: "", BtcPassword: "", BtcHost: "", @@ -53,7 +53,7 @@ func MainnetConfig() *Config { return &Config{ ZetaChainRPC: "https://zetachain-mainnet.g.allthatnode.com:443/archive/tendermint", EthereumRPC: "https://eth-mainnet.public.blastapi.io", - ZetaChainID: 7000, + ZetaChainID: chains.ZetaChainMainnet.ChainId, BtcUser: "", BtcPassword: "", BtcHost: "", @@ -70,7 +70,7 @@ func PrivateNetConfig() *Config { return &Config{ ZetaChainRPC: "http://127.0.0.1:26657", EthereumRPC: "http://127.0.0.1:8545", - ZetaChainID: 101, + ZetaChainID: chains.ZetaChainPrivnet.ChainId, BtcUser: "smoketest", BtcPassword: "123", BtcHost: "127.0.0.1:18443", From b6205598fb5fcfd7b1e8b649745eb752b4842951 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Thu, 6 Feb 2025 11:22:30 -0500 Subject: [PATCH 13/14] update docs --- changelog.md | 2 +- cmd/zetatool/cctx/inbound.go | 3 +-- cmd/zetatool/cli/cctx_tracker.go | 5 +++-- docs/cli/zetatool/track_cctx.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 876fa3273a..81315414a9 100644 --- a/changelog.md +++ b/changelog.md @@ -6,7 +6,7 @@ * [3461](https://github.com/zeta-chain/node/pull/3461) - add new 'ConfirmationParams' field to chain params to enable multiple confirmation count values, deprecating `confirmation_count` * [3455](https://github.com/zeta-chain/node/pull/3455) - add `track-cctx` command to zetatools -* + ### Tests * [3430](https://github.com/zeta-chain/node/pull/3430) - add simulation test for MsgWithDrawEmission diff --git a/cmd/zetatool/cctx/inbound.go b/cmd/zetatool/cctx/inbound.go index b3a584acf6..fdd591589b 100644 --- a/cmd/zetatool/cctx/inbound.go +++ b/cmd/zetatool/cctx/inbound.go @@ -79,8 +79,7 @@ func (c *TrackingDetails) CheckInbound(ctx *context.Context) error { } } default: - c.Message = "Chain not supported" - return nil + return fmt.Errorf("unsupported chain type %d", inboundChain.ChainId) } return nil } diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index d031aa13f3..cb8ae3e19d 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -26,6 +26,7 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { var ( ctx = &zetatoolcontext.Context{} trackingDetailsList []cctx.TrackingDetails + cctxTrackingDetails *cctx.TrackingDetails err error maxCCTXChainLength = 5 // Maximum number of cctx chains to track ) @@ -64,7 +65,7 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { } // fetch the cctx details and status - cctxTrackingDetails, err := trackCCTX(ctx) + cctxTrackingDetails, err = trackCCTX(ctx) if err != nil { // The error can be caused by two reasons // 1. We have reached the end of the cctx error chain, we can return @@ -104,7 +105,7 @@ func trackCCTX(ctx *zetatoolcontext.Context) (*cctx.TrackingDetails, error) { } // At this point, we have confirmed the inbound hash is valid, and it was sent to valid address. - // After this we attach error messages to the message field as wealready have some details about the cctx which can be printed + // After this we attach error messages to the message field as we already have some details about the cctx which can be printed // Update cctx status from zetacore.This copies the status from zetacore to the cctx details.The cctx status can only be `PendingInboundVoting` or `PendingInboundConfirmation` at this point cctxTrackingDetails.UpdateCCTXStatus(ctx) diff --git a/docs/cli/zetatool/track_cctx.md b/docs/cli/zetatool/track_cctx.md index 7881332c55..62953cdf4c 100644 --- a/docs/cli/zetatool/track_cctx.md +++ b/docs/cli/zetatool/track_cctx.md @@ -18,7 +18,7 @@ zetatool track-cctx 0x61008d7f79b2955a15e3cb95154a80e19c7385993fd0e083ff0cbe0b0f - `debug`: [Optional] The debug flag is used to print additional debug information when set to true The Config contains the rpcs needed for the tool to function, -if not provided the tool automatically uses the default rpcs.It is able to fetch the rpc needed using the chain ID +if not provided the tool automatically uses the default rpc .It is able to fetch the rpc needed using the chain ID The command returns - The CCTX identifier From 7b572e76fc294355db493cd164cde2de3f69877f Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 7 Feb 2025 09:31:03 -0500 Subject: [PATCH 14/14] solve lint error --- cmd/zetatool/cli/cctx_tracker.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/zetatool/cli/cctx_tracker.go b/cmd/zetatool/cli/cctx_tracker.go index cb8ae3e19d..5b98ee62c6 100644 --- a/cmd/zetatool/cli/cctx_tracker.go +++ b/cmd/zetatool/cli/cctx_tracker.go @@ -24,10 +24,8 @@ func NewTrackCCTXCMD() *cobra.Command { func TrackCCTX(cmd *cobra.Command, args []string) error { var ( - ctx = &zetatoolcontext.Context{} trackingDetailsList []cctx.TrackingDetails cctxTrackingDetails *cctx.TrackingDetails - err error maxCCTXChainLength = 5 // Maximum number of cctx chains to track ) @@ -59,7 +57,7 @@ func TrackCCTX(cmd *cobra.Command, args []string) error { } // Create a new context based on the chain id and hash - ctx, err = zetatoolcontext.NewContext(context.Background(), chainID, chainHash, configFile) + ctx, err := zetatoolcontext.NewContext(context.Background(), chainID, chainHash, configFile) if err != nil { return fmt.Errorf("failed to create context: %w", err) }