From 9aacfb7acdc55b413884c393f5c789879520f527 Mon Sep 17 00:00:00 2001 From: Charlie Chen <34498985+ws4charlie@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:09:53 -0600 Subject: [PATCH] feat: use JSON RPC for inbound tx observation (including blob tx in Decun upgrade) (#1836) * support observation of blob tx by using JSON RPC * add more unit tests for utils * renamed archived blobtx file * added entry to changelog * disable gosec error * improved names of funcs and file, added func comments * renamed funcs and removed vote boolean parameter * renamed unit test package as evm_test --- changelog.md | 1 + cmd/zetaclientd/debug.go | 27 +- cmd/zetaclientd/start.go | 4 + cmd/zetaclientd/utils.go | 11 + common/constant.go | 4 + e2e/e2etests/test_donation.go | 4 +- e2e/runner/bitcoin.go | 2 +- e2e/runner/setup_evm.go | 4 +- zetaclient/bitcoin/bitcoin_client.go | 3 +- zetaclient/bitcoin/bitcoin_client_test.go | 45 +- zetaclient/bitcoin/utils_test.go | 18 +- zetaclient/evm/constant.go | 34 + zetaclient/evm/evm_client.go | 369 +-- zetaclient/evm/evm_client_test.go | 209 +- zetaclient/evm/evm_signer.go | 6 - zetaclient/evm/inbounds.go | 291 +- zetaclient/evm/inbounds_test.go | 351 ++- zetaclient/evm/validation.go | 84 + zetaclient/evm/validation_test.go | 413 +++ ...38c1978482e50dd123287280a98910c1e318e.json | 71 + ...hain_8332_outtx_raw_result_nonce_148.json} | 0 zetaclient/testdata/cctx/cctx_1_7260.json | 34 + ...650a5f0e8fca6f5b76044a6cf106d21f27098.json | 35 + ...b990e076e2a4bffeb616035b239b7d33843da.json | 36 + ...1cbdf59154fd793ea7a22c297f7c3a722c532.json | 35 + ...ffc40ca898e134525c42c2ae3cbc5725f9d76.json | 33 + ...chain_1_block_ethrpc_trimmed_19363323.json | 2764 +++++++++++++++++ ...b990e076e2a4bffeb616035b239b7d33843da.json | 13 + ...1cbdf59154fd793ea7a22c297f7c3a722c532.json | 13 + ...ffc40ca898e134525c42c2ae3cbc5725f9d76.json | 13 + ...7d47bc9632b976e69b9d2cdeb527c2ba32155.json | 13 + ...b990e076e2a4bffeb616035b239b7d33843da.json | 44 + ...1cbdf59154fd793ea7a22c297f7c3a722c532.json | 14 + ...fc40ca898e134525c42c2ae3cbc5725f9d76.json} | 0 ...7d47bc9632b976e69b9d2cdeb527c2ba32155.json | 13 + ...b2c8af1339025c0e6bc6183b8bef2ebbed0d3.json | 15 + ...b2c8af1339025c0e6bc6183b8bef2ebbed0d3.json | 13 + ...3d71fb0a293eb8794be27a07240d69ee201da.json | 13 + ...3d71fb0a293eb8794be27a07240d69ee201da.json | 14 + zetaclient/testutils/constant.go | 28 + zetaclient/testutils/evm.go | 29 + .../{mempool-client.go => mempool_client.go} | 0 zetaclient/testutils/stub/chain_params.go | 35 + zetaclient/testutils/testdata.go | 266 ++ zetaclient/testutils/testdata_naming.go | 64 + zetaclient/testutils/utils.go | 63 - 46 files changed, 4839 insertions(+), 712 deletions(-) create mode 100644 zetaclient/evm/constant.go create mode 100644 zetaclient/evm/validation.go create mode 100644 zetaclient/evm/validation_test.go create mode 100644 zetaclient/testdata/btc/chain_8332_intx_raw_result_donation_6d1dde76b9aedaf2717a062602e38c1978482e50dd123287280a98910c1e318e.json rename zetaclient/testdata/btc/{outtx_8332_148_raw_result.json => chain_8332_outtx_raw_result_nonce_148.json} (100%) create mode 100644 zetaclient/testdata/cctx/cctx_1_7260.json create mode 100644 zetaclient/testdata/cctx/cctx_1_intx_Gas_0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098.json create mode 100644 zetaclient/testdata/cctx/cctx_intx_1_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json create mode 100644 zetaclient/testdata/cctx/cctx_intx_1_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json create mode 100644 zetaclient/testdata/cctx/cctx_intx_1_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json create mode 100644 zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19363323.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_ethrpc_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_ethrpc_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_ethrpc_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_ethrpc_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_receipt_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json create mode 100644 zetaclient/testdata/evm/chain_1_intx_receipt_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json rename zetaclient/testdata/evm/{chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json => chain_1_intx_receipt_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json} (100%) create mode 100644 zetaclient/testdata/evm/chain_1_intx_receipt_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json create mode 100644 zetaclient/testdata/evm/chain_1_outtx_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json create mode 100644 zetaclient/testdata/evm/chain_1_outtx_receipt_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json create mode 100644 zetaclient/testdata/evm/chain_5_blobtx_ethrpc_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json create mode 100644 zetaclient/testdata/evm/chain_5_blobtx_receipt_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json create mode 100644 zetaclient/testutils/evm.go rename zetaclient/testutils/{mempool-client.go => mempool_client.go} (100%) create mode 100644 zetaclient/testutils/stub/chain_params.go create mode 100644 zetaclient/testutils/testdata.go create mode 100644 zetaclient/testutils/testdata_naming.go delete mode 100644 zetaclient/testutils/utils.go diff --git a/changelog.md b/changelog.md index c109544cfe..5ebc2da24d 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ ### Features * [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses +* [1755](https://github.com/zeta-chain/node/issues/1755) - use evm JSON RPC for inbound tx (including blob tx) observation. * [1815](https://github.com/zeta-chain/node/pull/1815) - add authority module for authorized actions ### Tests diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index 8dcb6e666a..93d9717d9f 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -8,6 +8,8 @@ import ( "strings" "sync" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/onrik/ethrpc" "github.com/zeta-chain/zetacore/zetaclient/bitcoin" corecontext "github.com/zeta-chain/zetacore/zetaclient/core_context" "github.com/zeta-chain/zetacore/zetaclient/evm" @@ -18,7 +20,6 @@ import ( "github.com/btcsuite/btcd/rpcclient" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" "github.com/rs/zerolog" "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/common" @@ -101,26 +102,33 @@ func DebugCmd() *cobra.Command { } ob.WithZetaClient(bridge) ob.WithLogger(chainLogger) - client := ðclient.Client{} + var ethRPC *ethrpc.EthRPC + var client *ethclient.Client coinType := common.CoinType_Cmd for chain, evmConfig := range cfg.GetAllEVMConfigs() { if chainID == chain { + ethRPC = ethrpc.NewEthRPC(evmConfig.Endpoint) client, err = ethclient.Dial(evmConfig.Endpoint) if err != nil { return err } ob.WithEvmClient(client) + ob.WithEvmJSONRPC(ethRPC) ob.WithChain(*common.GetChainFromChainID(chainID)) } } hash := ethcommon.HexToHash(txHash) - tx, isPending, err := client.TransactionByHash(context.Background(), hash) + tx, isPending, err := ob.TransactionByHash(txHash) if err != nil { return fmt.Errorf("tx not found on chain %s , %d", err.Error(), chain.ChainId) } if isPending { return fmt.Errorf("tx is still pending") } + receipt, err := client.TransactionReceipt(context.Background(), hash) + if err != nil { + return fmt.Errorf("tx receipt not found on chain %s, %d", err.Error(), chain.ChainId) + } for _, chainParams := range chainParams { if chainParams.ChainId == chainID { @@ -135,32 +143,31 @@ func DebugCmd() *cobra.Command { return fmt.Errorf("missing chain params for chain %d", chainID) } evmChainParams.ZetaTokenContractAddress = chainParams.ZetaTokenContractAddress - if strings.EqualFold(tx.To().Hex(), chainParams.ConnectorContractAddress) { + if strings.EqualFold(tx.To, chainParams.ConnectorContractAddress) { coinType = common.CoinType_Zeta - } else if strings.EqualFold(tx.To().Hex(), chainParams.Erc20CustodyContractAddress) { + } else if strings.EqualFold(tx.To, chainParams.Erc20CustodyContractAddress) { coinType = common.CoinType_ERC20 - } else if strings.EqualFold(tx.To().Hex(), tssEthAddress) { + } else if strings.EqualFold(tx.To, tssEthAddress) { coinType = common.CoinType_Gas } - } } switch coinType { case common.CoinType_Zeta: - ballotIdentifier, err = ob.CheckReceiptForCoinTypeZeta(txHash, false) + ballotIdentifier, err = ob.CheckAndVoteInboundTokenZeta(tx, receipt, false) if err != nil { return err } case common.CoinType_ERC20: - ballotIdentifier, err = ob.CheckReceiptForCoinTypeERC20(txHash, false) + ballotIdentifier, err = ob.CheckAndVoteInboundTokenERC20(tx, receipt, false) if err != nil { return err } case common.CoinType_Gas: - ballotIdentifier, err = ob.CheckReceiptForCoinTypeGas(txHash, false) + ballotIdentifier, err = ob.CheckAndVoteInboundTokenGas(tx, receipt, false) if err != nil { return err } diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 8458acb802..b0b181a105 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -12,6 +12,7 @@ import ( "syscall" "time" + ethcommon "github.com/ethereum/go-ethereum/common" "github.com/libp2p/go-libp2p/core" maddr "github.com/multiformats/go-multiaddr" "github.com/pkg/errors" @@ -208,6 +209,9 @@ func start(_ *cobra.Command, _ []string) error { // Defensive check: Make sure the tss address is set to the current TSS address and not the newly generated one tss.CurrentPubkey = currentTss.TssPubkey + if tss.EVMAddress() == (ethcommon.Address{}) || tss.BTCAddress() == "" { + startLogger.Error().Msg("TSS address is not set in zetacore") + } startLogger.Info().Msgf("Current TSS address \n ETH : %s \n BTC : %s \n PubKey : %s ", tss.EVMAddress(), tss.BTCAddress(), tss.CurrentPubkey) if len(appContext.ZetaCoreContext().GetEnabledChains()) == 0 { startLogger.Error().Msgf("No chains enabled in updated config %s ", cfg.String()) diff --git a/cmd/zetaclientd/utils.go b/cmd/zetaclientd/utils.go index 82a16dcb7f..6dafd72312 100644 --- a/cmd/zetaclientd/utils.go +++ b/cmd/zetaclientd/utils.go @@ -67,6 +67,9 @@ func CreateSignerMap( loggers.Std.Error().Msgf("ChainParam not found for chain %s", evmConfig.Chain.String()) continue } + if !evmChainParams.IsSupported { + continue + } mpiAddress := ethcommon.HexToAddress(evmChainParams.ConnectorContractAddress) erc20CustodyAddress := ethcommon.HexToAddress(evmChainParams.Erc20CustodyContractAddress) signer, err := evm.NewEVMSigner(evmConfig.Chain, evmConfig.Endpoint, tss, config.GetConnectorABI(), config.GetERC20CustodyABI(), mpiAddress, erc20CustodyAddress, loggers, ts) @@ -104,6 +107,14 @@ func CreateChainClientMap( if evmConfig.Chain.IsZetaChain() { continue } + evmChainParams, found := appContext.ZetaCoreContext().GetEVMChainParams(evmConfig.Chain.ChainId) + if !found { + loggers.Std.Error().Msgf("ChainParam not found for chain %s", evmConfig.Chain.String()) + continue + } + if !evmChainParams.IsSupported { + continue + } co, err := evm.NewEVMChainClient(appContext, bridge, tss, dbpath, loggers, *evmConfig, ts) if err != nil { loggers.Std.Error().Err(err).Msgf("NewEVMChainClient error for chain %s", evmConfig.Chain.String()) diff --git a/common/constant.go b/common/constant.go index 7ea982d2fe..2e10d21b87 100644 --- a/common/constant.go +++ b/common/constant.go @@ -3,4 +3,8 @@ package common const ( // DefaultGasPriceMultiplier is the default gas price multiplier for outbond txs DefaultGasPriceMultiplier = 2 + + // DonationMessage is the message for donation transactions + // Transaction sent to the TSS or ERC20 Custody address containing this message are considered as a donation + DonationMessage = "I am rich!" ) diff --git a/e2e/e2etests/test_donation.go b/e2e/e2etests/test_donation.go index 028109da28..0428f6229d 100644 --- a/e2e/e2etests/test_donation.go +++ b/e2e/e2etests/test_donation.go @@ -3,9 +3,9 @@ package e2etests import ( "math/big" + "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" - "github.com/zeta-chain/zetacore/zetaclient/evm" ) // TestDonationEther tests donation of ether to the tss address @@ -19,7 +19,7 @@ func TestDonationEther(r *runner.E2ERunner, args []string) { panic("Invalid amount specified for TestDonationEther.") } - txDonation, err := r.SendEther(r.TSSAddress, amount, []byte(evm.DonationMessage)) + txDonation, err := r.SendEther(r.TSSAddress, amount, []byte(common.DonationMessage)) if err != nil { panic(err) } diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 05e4a9befe..1aa9c52ea8 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -117,7 +117,7 @@ func (runner *E2ERunner) DepositBTC(testHeader bool) { 0.11, utxos[4:5], btc, - []byte(zetabitcoin.DonationMessage), + []byte(common.DonationMessage), runner.BTCDeployerAddress, ) if err != nil { diff --git a/e2e/runner/setup_evm.go b/e2e/runner/setup_evm.go index 27674a6e5d..9ffb68056e 100644 --- a/e2e/runner/setup_evm.go +++ b/e2e/runner/setup_evm.go @@ -8,11 +8,11 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol" zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/contracts/erc20" "github.com/zeta-chain/zetacore/e2e/contracts/testdapp" "github.com/zeta-chain/zetacore/e2e/utils" - "github.com/zeta-chain/zetacore/zetaclient/evm" ) const ( @@ -62,7 +62,7 @@ func (runner *E2ERunner) SetupEVM(contractsDeployed bool) { // donate to the TSS address to avoid account errors because deploying gas token ZRC20 will automatically mint // gas token on ZetaChain to initialize the pool - txDonation, err := runner.SendEther(runner.TSSAddress, big.NewInt(101000000000000000), []byte(evm.DonationMessage)) + txDonation, err := runner.SendEther(runner.TSSAddress, big.NewInt(101000000000000000), []byte(common.DonationMessage)) if err != nil { panic(err) } diff --git a/zetaclient/bitcoin/bitcoin_client.go b/zetaclient/bitcoin/bitcoin_client.go index 1614efefc0..bb47a6eaa4 100644 --- a/zetaclient/bitcoin/bitcoin_client.go +++ b/zetaclient/bitcoin/bitcoin_client.go @@ -89,7 +89,6 @@ const ( btcBlocksPerDay = 144 // for LRU block cache size bigValueSats = 200000000 // 2 BTC bigValueConfirmationCount = 6 // 6 confirmations for value >= 2 BTC - DonationMessage = "I am rich!" ) func (ob *BTCChainClient) WithZetaClient(bridge *zetabridge.ZetaCoreBridge) { @@ -769,7 +768,7 @@ func GetBtcEvent( logger.Warn().Err(err).Msgf("error hex decoding memo") return nil, fmt.Errorf("error hex decoding memo: %s", err) } - if bytes.Equal(memoBytes, []byte(DonationMessage)) { + if bytes.Equal(memoBytes, []byte(common.DonationMessage)) { logger.Info().Msgf("donation tx: %s; value %f", tx.Txid, value) return nil, fmt.Errorf("donation tx: %s; value %f", tx.Txid, value) } diff --git a/zetaclient/bitcoin/bitcoin_client_test.go b/zetaclient/bitcoin/bitcoin_client_test.go index 63aec7f099..f92f3a01fd 100644 --- a/zetaclient/bitcoin/bitcoin_client_test.go +++ b/zetaclient/bitcoin/bitcoin_client_test.go @@ -16,7 +16,6 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/common" - crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/testutils" "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" @@ -30,18 +29,6 @@ func MockBTCClientMainnet() *BTCChainClient { } } -// LoadTxRawResultNCctx loads archived outtx raw result and corresponding cctx -func LoadTxRawResultNCctx(t *testing.T, fileTxResult string, fileCctx string) (btcjson.TxRawResult, *crosschaintypes.CrossChainTx) { - var rawResult btcjson.TxRawResult - err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) - require.NoError(t, err) - - var cctx crosschaintypes.CrossChainTx - err = testutils.LoadObjectFromJSONFile(&cctx, path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json")) - require.NoError(t, err) - return rawResult, &cctx -} - func TestConfirmationThreshold(t *testing.T) { client := &BTCChainClient{Mu: &sync.Mutex{}} t.Run("should return confirmations in chain param", func(t *testing.T) { @@ -194,20 +181,20 @@ func TestCalcDepositorFee828440(t *testing.T) { func TestCheckTSSVout(t *testing.T) { // the archived outtx raw result file and cctx file // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 - fileCctx := path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json") - fileTxResult := path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json") + chainID := int64(8332) + nonce := uint64(148) // create mainnet mock client btcClient := MockBTCClientMainnet() t.Run("valid TSS vout should pass", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() err := btcClient.checkTSSVout(params, rawResult.Vout) require.NoError(t, err) }) t.Run("should fail if vout length < 2 or > 3", func(t *testing.T) { - _, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + _, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() err := btcClient.checkTSSVout(params, []btcjson.Vout{{}}) @@ -217,7 +204,7 @@ func TestCheckTSSVout(t *testing.T) { require.ErrorContains(t, err, "invalid number of vouts") }) t.Run("should fail if vout 0 is not to the TSS address", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus @@ -226,7 +213,7 @@ func TestCheckTSSVout(t *testing.T) { require.ErrorContains(t, err, "not match TSS address") }) t.Run("should fail if vout 0 not match nonce mark", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() // not match nonce mark @@ -235,7 +222,7 @@ func TestCheckTSSVout(t *testing.T) { require.ErrorContains(t, err, "not match nonce-mark amount") }) t.Run("should fail if vout 1 is not to the receiver address", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() // not receiver address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus @@ -244,7 +231,7 @@ func TestCheckTSSVout(t *testing.T) { require.ErrorContains(t, err, "not match params receiver") }) t.Run("should fail if vout 1 not match payment amount", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() // not match payment amount @@ -253,7 +240,7 @@ func TestCheckTSSVout(t *testing.T) { require.ErrorContains(t, err, "not match params amount") }) t.Run("should fail if vout 2 is not to the TSS address", func(t *testing.T) { - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() // not TSS address, bc1qh297vdt8xq6df5xae9z8gzd4jsu9a392mp0dus @@ -266,15 +253,15 @@ func TestCheckTSSVout(t *testing.T) { func TestCheckTSSVoutCancelled(t *testing.T) { // the archived outtx raw result file and cctx file // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 - fileCctx := path.Join("../", testutils.TestDataPathCctx, "cctx_8332_148.json") - fileTxResult := path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json") + chainID := int64(8332) + nonce := uint64(148) // create mainnet mock client btcClient := MockBTCClientMainnet() t.Run("valid TSS vout should pass", func(t *testing.T) { // remove change vout to simulate cancelled tx - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) rawResult.Vout[1] = rawResult.Vout[2] rawResult.Vout = rawResult.Vout[:2] params := cctx.GetCurrentOutTxParam() @@ -283,7 +270,7 @@ func TestCheckTSSVoutCancelled(t *testing.T) { require.NoError(t, err) }) t.Run("should fail if vout length < 1 or > 2", func(t *testing.T) { - _, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + _, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) params := cctx.GetCurrentOutTxParam() err := btcClient.checkTSSVoutCancelled(params, []btcjson.Vout{}) @@ -294,7 +281,7 @@ func TestCheckTSSVoutCancelled(t *testing.T) { }) t.Run("should fail if vout 0 is not to the TSS address", func(t *testing.T) { // remove change vout to simulate cancelled tx - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) rawResult.Vout[1] = rawResult.Vout[2] rawResult.Vout = rawResult.Vout[:2] params := cctx.GetCurrentOutTxParam() @@ -306,7 +293,7 @@ func TestCheckTSSVoutCancelled(t *testing.T) { }) t.Run("should fail if vout 0 not match nonce mark", func(t *testing.T) { // remove change vout to simulate cancelled tx - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) rawResult.Vout[1] = rawResult.Vout[2] rawResult.Vout = rawResult.Vout[:2] params := cctx.GetCurrentOutTxParam() @@ -318,7 +305,7 @@ func TestCheckTSSVoutCancelled(t *testing.T) { }) t.Run("should fail if vout 1 is not to the TSS address", func(t *testing.T) { // remove change vout to simulate cancelled tx - rawResult, cctx := LoadTxRawResultNCctx(t, fileTxResult, fileCctx) + rawResult, cctx := testutils.LoadBTCTxRawResultNCctx(t, chainID, nonce) rawResult.Vout[1] = rawResult.Vout[2] rawResult.Vout = rawResult.Vout[:2] params := cctx.GetCurrentOutTxParam() diff --git a/zetaclient/bitcoin/utils_test.go b/zetaclient/bitcoin/utils_test.go index 56c7e0d967..6ae066f424 100644 --- a/zetaclient/bitcoin/utils_test.go +++ b/zetaclient/bitcoin/utils_test.go @@ -13,15 +13,15 @@ import ( func TestDecodeP2WPKHVout(t *testing.T) { // load archived outtx raw result // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + chain := common.BtcMainnetChain() + nonce := uint64(148) + nameTx := path.Join("../", testutils.TestDataPathBTC, testutils.FileNameBTCOuttx(chain.ChainId, nonce)) + var rawResult btcjson.TxRawResult - err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) + err := testutils.LoadObjectFromJSONFile(&rawResult, nameTx) require.NoError(t, err) require.Len(t, rawResult.Vout, 3) - // it's a mainnet outtx - chain := common.BtcMainnetChain() - nonce := uint64(148) - // decode vout 0, nonce mark 148 receiver, amount, err := DecodeP2WPKHVout(rawResult.Vout[0], chain) require.NoError(t, err) @@ -44,12 +44,14 @@ func TestDecodeP2WPKHVout(t *testing.T) { func TestDecodeP2WPKHVoutErrors(t *testing.T) { // load archived outtx raw result // https://blockstream.info/tx/030cd813443f7b70cc6d8a544d320c6d8465e4528fc0f3410b599dc0b26753a0 + chain := common.BtcMainnetChain() + nonce := uint64(148) + nameTx := path.Join("../", testutils.TestDataPathBTC, testutils.FileNameBTCOuttx(chain.ChainId, nonce)) + var rawResult btcjson.TxRawResult - err := testutils.LoadObjectFromJSONFile(&rawResult, path.Join("../", testutils.TestDataPathBTC, "outtx_8332_148_raw_result.json")) + err := testutils.LoadObjectFromJSONFile(&rawResult, nameTx) require.NoError(t, err) - chain := common.BtcMainnetChain() - t.Run("should return error on invalid amount", func(t *testing.T) { invalidVout := rawResult.Vout[0] invalidVout.Value = -0.5 // negative amount, should not happen diff --git a/zetaclient/evm/constant.go b/zetaclient/evm/constant.go new file mode 100644 index 0000000000..8dcd2309d6 --- /dev/null +++ b/zetaclient/evm/constant.go @@ -0,0 +1,34 @@ +package evm + +import "time" + +const ( + // ZetaBlockTime is the block time of the Zeta network + ZetaBlockTime = 6500 * time.Millisecond + + // OutTxInclusionTimeout is the timeout for waiting for an outtx to be included in a block + OutTxInclusionTimeout = 20 * time.Minute + + // OutTxTrackerReportTimeout is the timeout for waiting for an outtx tracker report + OutTxTrackerReportTimeout = 10 * time.Minute + + // [signature, zetaTxSenderAddress, destinationChainId] + // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L34 + TopicsZetaSent = 3 + + // [signature, sourceChainId, destinationAddress, internalSendHash] + // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L45 + TopicsZetaReceived = 4 + + // [signature, destinationChainId, internalSendHash] + // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L54 + TopicsZetaReverted = 3 + + // [signature, recipient, asset] + // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ERC20Custody.sol#L43 + TopicsWithdrawn = 3 + + // [signature, asset] + // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ERC20Custody.sol#L42 + TopicsDeposited = 2 +) diff --git a/zetaclient/evm/evm_client.go b/zetaclient/evm/evm_client.go index b0d85631ca..55e3850a44 100644 --- a/zetaclient/evm/evm_client.go +++ b/zetaclient/evm/evm_client.go @@ -1,7 +1,6 @@ package evm import ( - "bytes" "context" "fmt" "math" @@ -38,7 +37,7 @@ import ( "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/common" - "github.com/zeta-chain/zetacore/x/crosschain/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" @@ -61,19 +60,10 @@ type Log struct { ChainLogger zerolog.Logger // Parent logger ExternalChainWatcher zerolog.Logger // Observes external Chains for incoming trasnactions WatchGasPrice zerolog.Logger // Observes external Chains for Gas prices and posts to core - ObserveOutTx zerolog.Logger // Observes external Chains for Outgoing transactions + ObserveOutTx zerolog.Logger // Observes external Chains for outgoing transactions Compliance zerolog.Logger // Compliance logger } -const ( - DonationMessage = "I am rich!" - TopicsZetaSent = 3 // [signature, zetaTxSenderAddress, destinationChainId] https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L34 - TopicsZetaReceived = 4 // [signature, sourceChainId, destinationAddress, internalSendHash] https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L45 - TopicsZetaReverted = 3 // [signature, destinationChainId, internalSendHash] https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ZetaConnector.base.sol#L54 - TopicsWithdrawn = 3 // [signature, recipient, asset] https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ERC20Custody.sol#L43 - TopicsDeposited = 2 // [signature, asset] https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ERC20Custody.sol#L42 -) - // ChainClient represents the chain configuration for an EVM chain // Filled with above constants depending on chain type ChainClient struct { @@ -81,7 +71,7 @@ type ChainClient struct { evmClient interfaces.EVMRPCClient zetaClient interfaces.ZetaCoreBridger Tss interfaces.TSSSigner - evmClientAlternate *ethrpc.EthRPC // a fallback rpc client + evmJSONRPC *ethrpc.EthRPC lastBlockScanned uint64 lastBlock uint64 BlockTimeExternalChain uint64 // block time in seconds @@ -91,8 +81,6 @@ type ChainClient struct { outTxPendingTransactions map[string]*ethtypes.Transaction outTXConfirmedReceipts map[string]*ethtypes.Receipt outTXConfirmedTransactions map[string]*ethtypes.Transaction - MinNonce int64 - MaxNonce int64 OutTxChan chan OutTx // send to this channel if you want something back! stop chan struct{} logger Log @@ -100,9 +88,8 @@ type ChainClient struct { chainParams observertypes.ChainParams ts *metrics.TelemetryServer - blockCache *lru.Cache - blockCacheV3 *lru.Cache // blockCacheV3 caches blocks containing type-3 (BlobTxType) transactions - headerCache *lru.Cache + blockCache *lru.Cache + headerCache *lru.Cache } // NewEVMChainClient returns a new configuration based on supplied target chain @@ -150,7 +137,7 @@ func NewEVMChainClient( return nil, err } ob.evmClient = client - ob.evmClientAlternate = ethrpc.NewEthRPC(evmCfg.Endpoint) + ob.evmJSONRPC = ethrpc.NewEthRPC(evmCfg.Endpoint) // create block header and block caches ob.blockCache, err = lru.New(1000) @@ -158,11 +145,6 @@ func NewEVMChainClient( ob.logger.ChainLogger.Error().Err(err).Msg("failed to create block cache") return nil, err } - ob.blockCacheV3, err = lru.New(1000) - if err != nil { - ob.logger.ChainLogger.Error().Err(err).Msg("failed to create block cache v3") - return nil, err - } ob.headerCache, err = lru.New(1000) if err != nil { ob.logger.ChainLogger.Error().Err(err).Msg("failed to create header cache") @@ -200,12 +182,24 @@ func (ob *ChainClient) WithEvmClient(client *ethclient.Client) { ob.evmClient = client } -func (ob *ChainClient) WithZetaClient(bridge *zetabridge.ZetaCoreBridge) { +func (ob *ChainClient) WithEvmJSONRPC(client *ethrpc.EthRPC) { + ob.Mu.Lock() + defer ob.Mu.Unlock() + ob.evmJSONRPC = client +} + +func (ob *ChainClient) WithZetaClient(bridge interfaces.ZetaCoreBridger) { ob.Mu.Lock() defer ob.Mu.Unlock() ob.zetaClient = bridge } +func (ob *ChainClient) WithBlockCache(cache *lru.Cache) { + ob.Mu.Lock() + defer ob.Mu.Unlock() + ob.blockCache = cache +} + func (ob *ChainClient) SetChainParams(params observertypes.ChainParams) { ob.Mu.Lock() defer ob.Mu.Unlock() @@ -320,7 +314,7 @@ func (ob *ChainClient) Stop() { // returns: isIncluded, isConfirmed, Error // If isConfirmed, it also post to ZetaCore -func (ob *ChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zerolog.Logger) (bool, bool, error) { +func (ob *ChainClient) IsSendOutTxProcessed(cctx *crosschaintypes.CrossChainTx, logger zerolog.Logger) (bool, bool, error) { sendHash := cctx.Index cointype := cctx.GetCurrentOutTxParam().CoinType nonce := cctx.GetCurrentOutTxParam().OutboundTxTssNonce @@ -447,7 +441,7 @@ func (ob *ChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zer if confHeight <= ob.GetLastBlockHeight() { logger.Info().Msg("Confirmed! Sending PostConfirmation to zetabridge...") // sanity check tx event - err = ob.CheckEvmTxLog(vLog, connectorAddr, transaction.Hash().Hex(), TopicsZetaReceived) + err = ValidateEvmTxLog(vLog, connectorAddr, transaction.Hash().Hex(), TopicsZetaReceived) if err != nil { logger.Error().Err(err).Msgf("CheckEvmTxLog error on ZetaReceived event, chain %d nonce %d txhash %s", ob.chain.ChainId, nonce, transaction.Hash().Hex()) return false, false, err @@ -485,7 +479,7 @@ func (ob *ChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zer if confHeight <= ob.GetLastBlockHeight() { logger.Info().Msg("Confirmed! Sending PostConfirmation to zetabridge...") // sanity check tx event - err = ob.CheckEvmTxLog(vLog, connectorAddr, transaction.Hash().Hex(), TopicsZetaReverted) + err = ValidateEvmTxLog(vLog, connectorAddr, transaction.Hash().Hex(), TopicsZetaReverted) if err != nil { logger.Error().Err(err).Msgf("CheckEvmTxLog error on ZetaReverted event, chain %d nonce %d txhash %s", ob.chain.ChainId, nonce, transaction.Hash().Hex()) return false, false, err @@ -553,7 +547,7 @@ func (ob *ChainClient) IsSendOutTxProcessed(cctx *types.CrossChainTx, logger zer if err == nil { logger.Info().Msgf("Found (ERC20Custody.Withdrawn Event) sendHash %s on chain %s txhash %s", sendHash, ob.chain.String(), vLog.TxHash.Hex()) // sanity check tx event - err = ob.CheckEvmTxLog(vLog, addrCustody, transaction.Hash().Hex(), TopicsWithdrawn) + err = ValidateEvmTxLog(vLog, addrCustody, transaction.Hash().Hex(), TopicsWithdrawn) if err != nil { logger.Error().Err(err).Msgf("CheckEvmTxLog error on Withdrawn event, chain %d nonce %d txhash %s", ob.chain.ChainId, nonce, transaction.Hash().Hex()) return false, false, err @@ -727,7 +721,7 @@ func (ob *ChainClient) checkConfirmedTx(txHash string, nonce uint64) (*ethtypes. // query transaction transaction, isPending, err := ob.evmClient.TransactionByHash(ctxt, ethcommon.HexToHash(txHash)) if err != nil { - log.Error().Err(err).Msgf("confirmTxByHash: TransactionByHash error, txHash %s nonce %d", txHash, nonce) + log.Error().Err(err).Msgf("confirmTxByHash: error getting transaction for outtx %s chain %d", txHash, ob.chain.ChainId) return nil, nil, false } if transaction == nil { // should not happen @@ -739,16 +733,16 @@ func (ob *ChainClient) checkConfirmedTx(txHash string, nonce uint64) (*ethtypes. signer := ethtypes.NewLondonSigner(big.NewInt(ob.chain.ChainId)) from, err := signer.Sender(transaction) if err != nil { - log.Error().Err(err).Msgf("confirmTxByHash: local recovery of sender address failed for txHash %s chain %d", transaction.Hash().Hex(), ob.chain.ChainId) + log.Error().Err(err).Msgf("confirmTxByHash: local recovery of sender address failed for outtx %s chain %d", transaction.Hash().Hex(), ob.chain.ChainId) return nil, nil, false } if from != ob.Tss.EVMAddress() { // must be TSS address - log.Error().Msgf("confirmTxByHash: sender %s for txHash %s chain %d is not TSS address %s", + log.Error().Msgf("confirmTxByHash: sender %s for outtx %s chain %d is not TSS address %s", from.Hex(), transaction.Hash().Hex(), ob.chain.ChainId, ob.Tss.EVMAddress().Hex()) return nil, nil, false } if transaction.Nonce() != nonce { // must match cctx nonce - log.Error().Msgf("confirmTxByHash: txHash %s nonce mismatch: wanted %d, got tx nonce %d", txHash, nonce, transaction.Nonce()) + log.Error().Msgf("confirmTxByHash: outtx %s nonce mismatch: wanted %d, got tx nonce %d", txHash, nonce, transaction.Nonce()) return nil, nil, false } @@ -780,7 +774,7 @@ func (ob *ChainClient) checkConfirmedTx(txHash string, nonce uint64) (*ethtypes. // cross-check tx inclusion against the block // Note: a guard for false BlockNumber in receipt. The blob-carrying tx won't come here - err = ob.checkTxInclusion(transaction, receipt.BlockNumber.Uint64(), receipt.TransactionIndex) + err = ob.CheckTxInclusion(transaction, receipt) if err != nil { log.Error().Err(err).Msgf("confirmTxByHash: checkTxInclusion error for txHash %s nonce %d", txHash, nonce) return nil, nil, false @@ -789,36 +783,23 @@ func (ob *ChainClient) checkConfirmedTx(txHash string, nonce uint64) (*ethtypes. return receipt, transaction, true } -// checkTxInclusion returns nil only if tx is included in the block at blockNumber and txIndex -func (ob *ChainClient) checkTxInclusion(tx *ethtypes.Transaction, blockNumber uint64, txIndex uint) error { - block, blockRPC, fallBack, _, err := ob.GetBlockByNumberCached(blockNumber) +// CheckTxInclusion returns nil only if tx is included at the position indicated by the receipt ([block, index]) +func (ob *ChainClient) CheckTxInclusion(tx *ethtypes.Transaction, receipt *ethtypes.Receipt) error { + block, err := ob.GetBlockByNumberCached(receipt.BlockNumber.Uint64()) if err != nil { - return fmt.Errorf("GetBlockByNumberCached error for block %d txHash %s nonce %d: %w", blockNumber, tx.Hash(), tx.Nonce(), err) + return errors.Wrapf(err, "GetBlockByNumberCached error for block %d txHash %s nonce %d", + receipt.BlockNumber.Uint64(), tx.Hash(), tx.Nonce()) } - if !fallBack { - // #nosec G701 non negative value - if txIndex >= uint(len(block.Transactions())) { - return fmt.Errorf("transaction index %d out of range [0, %d), txHash %s nonce %d block %d", - txIndex, len(block.Transactions()), tx.Hash(), tx.Nonce(), blockNumber) - } - txAtIndex := block.Transactions()[txIndex] - if txAtIndex.Hash() != tx.Hash() { - ob.RemoveCachedBlock(blockNumber) // clean stale block from cache - return fmt.Errorf("transaction at index %d has different hash %s, txHash %s nonce %d block %d", - txIndex, txAtIndex.Hash().Hex(), tx.Hash(), tx.Nonce(), blockNumber) - } - } else { // fell back on ETH RPC as ethclient failed to parse the block - // #nosec G701 non negative value - if txIndex >= uint(len(blockRPC.Transactions)) { - return fmt.Errorf("transaction index %d out of range [0, %d), txHash %s nonce %d block %d", - txIndex, len(block.Transactions()), tx.Hash(), tx.Nonce(), blockNumber) - } - txAtIndex := blockRPC.Transactions[txIndex] - if ethcommon.HexToHash(txAtIndex.Hash) != tx.Hash() { - ob.RemoveCachedBlock(blockNumber) // clean stale block from cache - return fmt.Errorf("transaction at index %d has different hash %s, txHash %s nonce %d block %d", - txIndex, txAtIndex.Hash, tx.Hash(), tx.Nonce(), blockNumber) - } + // #nosec G701 non negative value + if receipt.TransactionIndex >= uint(len(block.Transactions)) { + return fmt.Errorf("transaction index %d out of range [0, %d), txHash %s nonce %d block %d", + receipt.TransactionIndex, len(block.Transactions), tx.Hash(), tx.Nonce(), receipt.BlockNumber.Uint64()) + } + txAtIndex := block.Transactions[receipt.TransactionIndex] + if !strings.EqualFold(txAtIndex.Hash, tx.Hash().Hex()) { + ob.RemoveCachedBlock(receipt.BlockNumber.Uint64()) // clean stale block from cache + return fmt.Errorf("transaction at index %d has different hash %s, txHash %s nonce %d block %d", + receipt.TransactionIndex, txAtIndex.Hash, tx.Hash(), tx.Nonce(), receipt.BlockNumber.Uint64()) } return nil } @@ -971,7 +952,7 @@ func (ob *ChainClient) observeInTX(sampledLogger zerolog.Logger) error { lastScannedDeposited := ob.observeERC20Deposited(startBlock, toBlock) // task 3: query the incoming tx to TSS address (read at most 100 blocks in one go) - lastScannedTssRecvd := ob.observeTssRecvd(startBlock, toBlock, flags) + lastScannedTssRecvd := ob.observerTSSReceive(startBlock, toBlock, flags) // note: using lowest height for all 3 events is not perfect, but it's simple and good enough lastScannedLowest := lastScannedZetaSent @@ -1018,7 +999,7 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { events := make([]*zetaconnector.ZetaConnectorNonEthZetaSent, 0) for iter.Next() { // sanity check tx event - err := ob.CheckEvmTxLog(&iter.Event.Raw, addrConnector, "", TopicsZetaSent) + err := ValidateEvmTxLog(&iter.Event.Raw, addrConnector, "", TopicsZetaSent) if err == nil { events = append(events, iter.Event) continue @@ -1041,25 +1022,25 @@ func (ob *ChainClient) observeZetaSent(startBlock, toBlock uint64) uint64 { // post to zetabridge beingScanned := uint64(0) + guard := make(map[string]bool) for _, event := range events { // remember which block we are scanning (there could be multiple events in the same block) if event.Raw.BlockNumber > beingScanned { beingScanned = event.Raw.BlockNumber } - msg := ob.GetInboundVoteMsgForZetaSentEvent(event) - if msg == nil { + // guard against multiple events in the same tx + if guard[event.Raw.TxHash.Hex()] { + ob.logger.ExternalChainWatcher.Warn().Msgf("observeZetaSent: multiple remote call events detected in tx %s", event.Raw.TxHash) continue } - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundMessagePassingExecutionGasLimit, msg) - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf( - "observeZetaSent: error posting event to zeta core for tx %s at height %d for chain %d", - event.Raw.TxHash.Hex(), event.Raw.BlockNumber, ob.chain.ChainId) - return beingScanned - 1 // we have to re-scan from this block next time - } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf( - "observeZetaSent: event detected in tx %s at height %d for chain %d, PostVoteInbound zeta tx: %s ballot %s", - event.Raw.TxHash.Hex(), event.Raw.BlockNumber, ob.chain.ChainId, zetaHash, ballot) + guard[event.Raw.TxHash.Hex()] = true + + msg := ob.BuildInboundVoteMsgForZetaSentEvent(event) + if msg != nil { + _, err = ob.PostVoteInbound(msg, common.CoinType_Zeta, zetabridge.PostVoteInboundMessagePassingExecutionGasLimit) + if err != nil { + return beingScanned - 1 // we have to re-scan from this block next time + } } } // successful processed all events in [startBlock, toBlock] @@ -1090,7 +1071,7 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 events := make([]*erc20custody.ERC20CustodyDeposited, 0) for iter.Next() { // sanity check tx event - err := ob.CheckEvmTxLog(&iter.Event.Raw, addrCustody, "", TopicsDeposited) + err := ValidateEvmTxLog(&iter.Event.Raw, addrCustody, "", TopicsDeposited) if err == nil { events = append(events, iter.Event) continue @@ -1112,62 +1093,44 @@ func (ob *ChainClient) observeERC20Deposited(startBlock, toBlock uint64) uint64 metrics.GetFilterLogsPerChain.WithLabelValues(ob.chain.ChainName.String()).Inc() // post to zetabridge - guard := make(map[string]bool) // guard against multiple events in the same tx + guard := make(map[string]bool) beingScanned := uint64(0) for _, event := range events { // remember which block we are scanning (there could be multiple events in the same block) if event.Raw.BlockNumber > beingScanned { beingScanned = event.Raw.BlockNumber } - tx, _, err := ob.evmClient.TransactionByHash(context.Background(), event.Raw.TxHash) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf( - "observeERC20Deposited: TransactionByHash error for tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) - return beingScanned - 1 // we have to re-scan from this block next time - } - sender, err := ob.GetTransactionSender(tx, event.Raw.BlockHash, event.Raw.TxIndex) + tx, _, err := ob.TransactionByHash(event.Raw.TxHash.Hex()) if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf( - "observeERC20Deposited: GetTransactionSender error for tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) + ob.logger.ExternalChainWatcher.Error().Err(err).Msgf( + "observeERC20Deposited: error getting transaction for intx %s chain %d", event.Raw.TxHash, ob.chain.ChainId) return beingScanned - 1 // we have to re-scan from this block next time } + sender := ethcommon.HexToAddress(tx.From) + + // guard against multiple events in the same tx if guard[event.Raw.TxHash.Hex()] { - ob.logger.ExternalChainWatcher.Warn().Msgf("more than one remote call event in a single tx %s; skip the rest", event.Raw.TxHash.Hex()) + ob.logger.ExternalChainWatcher.Warn().Msgf("observeERC20Deposited: multiple remote call events detected in tx %s", event.Raw.TxHash) continue } + guard[event.Raw.TxHash.Hex()] = true - msg := ob.GetInboundVoteMsgForDepositedEvent(event, sender) - if msg == nil { - continue - } - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf( - "observeERC20Deposited: error posting event to zeta core for tx %s at height %d for chain %d", - event.Raw.TxHash.Hex(), event.Raw.BlockNumber, ob.chain.ChainId) - return beingScanned - 1 // we have to re-scan from this block next time - } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf( - "observeERC20Deposited: event detected in tx %s at height %d for chain %d, PostVoteInbound zeta tx: %s ballot %s", - event.Raw.TxHash.Hex(), event.Raw.BlockNumber, ob.chain.ChainId, zetaHash, ballot) + msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender) + if msg != nil { + _, err = ob.PostVoteInbound(msg, common.CoinType_ERC20, zetabridge.PostVoteInboundExecutionGasLimit) + if err != nil { + return beingScanned - 1 // we have to re-scan from this block next time + } } - guard[event.Raw.TxHash.Hex()] = true } // successful processed all events in [startBlock, toBlock] return toBlock } -// observeTssRecvd queries the incoming gas asset to TSS address and posts to zetabridge +// observerTSSReceive queries the incoming gas asset to TSS address and posts to zetabridge // returns the last block successfully scanned -func (ob *ChainClient) observeTssRecvd(startBlock, toBlock uint64, flags observertypes.CrosschainFlags) uint64 { +func (ob *ChainClient) observerTSSReceive(startBlock, toBlock uint64, flags observertypes.CrosschainFlags) uint64 { if !ob.GetChainParams().IsSupported { - //ob.logger.ExternalChainWatcher.Warn().Msgf("observeTssRecvd: chain %d is not supported", ob.chain.ChainId) - return startBlock - 1 // lastScanned - } - // check TSS address (after keygen, ob.Tss.pubkey will be updated) - tssAddress := ob.Tss.EVMAddress() - if tssAddress == (ethcommon.Address{}) { - ob.logger.ExternalChainWatcher.Warn().Msgf("observeTssRecvd: TSS address not set") return startBlock - 1 // lastScanned } @@ -1185,39 +1148,23 @@ func (ob *ChainClient) observeTssRecvd(startBlock, toBlock uint64, flags observe } // TODO: we can track the total number of 'getBlockByNumber' RPC calls made - block, blockRPC, fallBack, skip, err := ob.GetBlockByNumberCached(bn) + block, err := ob.GetBlockByNumberCached(bn) if err != nil { - if skip { - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("observeTssRecvd: skip block %d for chain %d", bn, ob.chain.ChainId) - continue - } - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("observeTssRecvd: error getting block %d for chain %d", bn, ob.chain.ChainId) + ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("observerTSSReceive: error getting block %d for chain %d", bn, ob.chain.ChainId) return bn - 1 // we have to re-scan from this block next time } - if !fallBack { - for _, tx := range block.Transactions() { - if tx.To() != nil && *tx.To() == tssAddress { - if ok := ob.processIntxToTss(tx, bn, block.Hash()); !ok { - return bn - 1 // we have to re-scan this block next time - } + for i := range block.Transactions { + tx := block.Transactions[i] + if ethcommon.HexToAddress(tx.To) == ob.Tss.EVMAddress() { + receipt, err := ob.evmClient.TransactionReceipt(context.Background(), ethcommon.HexToHash(tx.Hash)) + if err != nil { + ob.logger.ExternalChainWatcher.Err(err).Msgf("observerTSSReceive: error getting receipt for intx %s chain %d", tx.Hash, ob.chain.ChainId) + return bn - 1 // we have to re-scan from this block next time } - } - } else { // fell back on ETH RPC as ethclient failed to parse the block - ob.logger.ExternalChainWatcher.Info().Msgf("observeTssRecvd: processing block %d using fallback for chain %d", bn, ob.chain.ChainId) - for _, txRPC := range blockRPC.Transactions { - if ethcommon.HexToAddress(txRPC.To) == tssAddress { - tx, _, err := ob.evmClient.TransactionByHash(context.Background(), ethcommon.HexToHash(txRPC.Hash)) - if err != nil { - if strings.Contains(err.Error(), "transaction type not supported") { - ob.logger.ExternalChainWatcher.Err(err).Msgf( - "observeTssRecvd: transaction type not supported for tx %s chain %d", txRPC.Hash, ob.chain.ChainId) - continue // skip blob-carrying tx to TSS address - } - return bn - 1 // we have to re-scan this block next time - } - if ok := ob.processIntxToTss(tx, bn, ethcommon.HexToHash(blockRPC.Hash)); !ok { - return bn - 1 // we have to re-scan this block next time - } + _, err = ob.CheckAndVoteInboundTokenGas(&tx, receipt, true) + if err != nil { + ob.logger.ExternalChainWatcher.Err(err).Msgf("observerTSSReceive: error checking and voting inbound gas asset for intx %s chain %d", tx.Hash, ob.chain.ChainId) + return bn - 1 // we have to re-scan this block next time } } } @@ -1226,48 +1173,6 @@ func (ob *ChainClient) observeTssRecvd(startBlock, toBlock uint64, flags observe return toBlock } -// processIntxToTss processes the incoming tx to TSS address and posts to zetacore -// returns true if the tx is successfully processed, false otherwise -func (ob *ChainClient) processIntxToTss(tx *ethtypes.Transaction, bn uint64, blockHash ethcommon.Hash) bool { - receipt, err := ob.evmClient.TransactionReceipt(context.Background(), tx.Hash()) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf( - "processIntxToTss: TransactionReceipt error for tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) - return false // we have to re-scan this block next time - } - if receipt.Status != 1 { // 1: successful, 0: failed - ob.logger.ExternalChainWatcher.Info().Msgf("processIntxToTss: tx %s chain %d failed; don't act", tx.Hash().Hex(), ob.chain.ChainId) - return true // skip failed tx - } - if bytes.Equal(tx.Data(), []byte(DonationMessage)) { - ob.logger.ExternalChainWatcher.Info().Msgf( - "processIntxToTss: thank you rich folk for your donation!: %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) - return true // skip donation tx - } - sender, err := ob.GetTransactionSender(tx, blockHash, receipt.TransactionIndex) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf( - "processIntxToTss: GetTransactionSender error for tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) - return false // we have to re-scan this block next time - } - - msg := ob.GetInboundVoteMsgForTokenSentToTSS(tx, sender, bn) - if msg == nil { - return true // should never happen, always non-nil - } - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msgf( - "processIntxToTss: error posting to zeta core for tx %s at height %d for chain %d", tx.Hash().Hex(), bn, ob.chain.ChainId) - return false // we have to re-scan this block next time - } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf( - "processIntxToTss: gas asset deposit detected in tx %s at height %d for chain %d, PostSend zeta tx: %s ballot %s", - tx.Hash().Hex(), bn, ob.chain.ChainId, zetaHash, ballot) - } - return true -} - func (ob *ChainClient) WatchGasPrice() { ob.logger.WatchGasPrice.Info().Msg("WatchGasPrice starting...") err := ob.PostGasPrice() @@ -1391,23 +1296,6 @@ func (ob *ChainClient) BuildReceiptsMap() error { return nil } -func (ob *ChainClient) BuildTransactionsMap() error { - logger := ob.logger - var transactions []clienttypes.TransactionSQLType - if err := ob.db.Find(&transactions).Error; err != nil { - logger.ChainLogger.Error().Err(err).Msg("error iterating over db") - return err - } - for _, transaction := range transactions { - trans, err := clienttypes.FromTransactionDBType(transaction.Transaction) - if err != nil { - return err - } - ob.outTXConfirmedTransactions[transaction.Identifier] = trans - } - return nil -} - // LoadDB open sql database and load data into EVMChainClient func (ob *ChainClient) LoadDB(dbPath string, chain common.Chain) error { if dbPath != "" { @@ -1440,34 +1328,37 @@ func (ob *ChainClient) LoadDB(dbPath string, chain common.Chain) error { return nil } -func (ob *ChainClient) SetMinAndMaxNonce(trackers []types.OutTxTracker) error { - minNonce, maxNonce := int64(-1), int64(0) - for _, tracker := range trackers { - conv := tracker.Nonce - // #nosec G701 always in range - intNonce := int64(conv) - if minNonce == -1 { - minNonce = intNonce - } - if intNonce < minNonce { - minNonce = intNonce - } - if intNonce > maxNonce { - maxNonce = intNonce - } - } - if minNonce != -1 { - atomic.StoreInt64(&ob.MinNonce, minNonce) +func (ob *ChainClient) GetTxID(nonce uint64) string { + tssAddr := ob.Tss.EVMAddress().String() + return fmt.Sprintf("%d-%s-%d", ob.chain.ChainId, tssAddr, nonce) +} + +// BlockByNumber query block by number via JSON-RPC +func (ob *ChainClient) BlockByNumber(blockNumber int) (*ethrpc.Block, error) { + block, err := ob.evmJSONRPC.EthGetBlockByNumber(blockNumber, true) + if err != nil { + return nil, err } - if maxNonce > 0 { - atomic.StoreInt64(&ob.MaxNonce, maxNonce) + for i := range block.Transactions { + err := ValidateEvmTransaction(&block.Transactions[i]) + if err != nil { + return nil, err + } } - return nil + return block, nil } -func (ob *ChainClient) GetTxID(nonce uint64) string { - tssAddr := ob.Tss.EVMAddress().String() - return fmt.Sprintf("%d-%s-%d", ob.chain.ChainId, tssAddr, nonce) +// TransactionByHash query transaction by hash via JSON-RPC +func (ob *ChainClient) TransactionByHash(txHash string) (*ethrpc.Transaction, bool, error) { + tx, err := ob.evmJSONRPC.EthGetTransactionByHash(txHash) + if err != nil { + return nil, false, err + } + err = ValidateEvmTransaction(tx) + if err != nil { + return nil, false, err + } + return tx, tx.BlockNumber == nil, nil } func (ob *ChainClient) GetBlockHeaderCached(blockNumber uint64) (*ethtypes.Header, error) { @@ -1484,37 +1375,23 @@ func (ob *ChainClient) GetBlockHeaderCached(blockNumber uint64) (*ethtypes.Heade // GetBlockByNumberCached get block by number from cache // returns block, ethrpc.Block, isFallback, isSkip, error -func (ob *ChainClient) GetBlockByNumberCached(blockNumber uint64) (*ethtypes.Block, *ethrpc.Block, bool, bool, error) { +func (ob *ChainClient) GetBlockByNumberCached(blockNumber uint64) (*ethrpc.Block, error) { if block, ok := ob.blockCache.Get(blockNumber); ok { - return block.(*ethtypes.Block), nil, false, false, nil + return block.(*ethrpc.Block), nil } - if block, ok := ob.blockCacheV3.Get(blockNumber); ok { - return nil, block.(*ethrpc.Block), true, false, nil + if blockNumber > math.MaxInt32 { + return nil, fmt.Errorf("block number %d is too large", blockNumber) } - block, err := ob.evmClient.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber)) + // #nosec G701 always in range, checked above + block, err := ob.BlockByNumber(int(blockNumber)) if err != nil { - if strings.Contains(err.Error(), "block header indicates no transactions") { - return nil, nil, false, true, err // it's ok skip empty block - } else if strings.Contains(err.Error(), "transaction type not supported") { - if blockNumber > math.MaxInt32 { - return nil, nil, true, false, fmt.Errorf("block number %d is too large", blockNumber) - } - // #nosec G701 always in range, checked above - rpcBlock, err := ob.evmClientAlternate.EthGetBlockByNumber(int(blockNumber), true) - if err != nil { - return nil, nil, true, false, err // fall back on ethRPC but still fail - } - ob.blockCacheV3.Add(blockNumber, rpcBlock) - return nil, rpcBlock, true, false, nil // fall back on ethRPC without error - } - return nil, nil, false, false, err + return nil, err } ob.blockCache.Add(blockNumber, block) - return block, nil, false, false, nil + return block, nil } // RemoveCachedBlock remove block from cache func (ob *ChainClient) RemoveCachedBlock(blockNumber uint64) { ob.blockCache.Remove(blockNumber) - ob.blockCacheV3.Remove(blockNumber) } diff --git a/zetaclient/evm/evm_client_test.go b/zetaclient/evm/evm_client_test.go index fdcee8c837..6f4cb677ed 100644 --- a/zetaclient/evm/evm_client_test.go +++ b/zetaclient/evm/evm_client_test.go @@ -1,131 +1,124 @@ -package evm +package evm_test import ( - "fmt" - "math/big" + "sync" "testing" - ethcommon "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" + "cosmossdk.io/math" lru "github.com/hashicorp/golang-lru" + "github.com/onrik/ethrpc" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/evm" + "github.com/zeta-chain/zetacore/zetaclient/testutils" ) -func TestEVMBlockCache(t *testing.T) { +func TestEVM_BlockCache(t *testing.T) { // create client blockCache, err := lru.New(1000) require.NoError(t, err) - blockCacheV3, err := lru.New(1000) - require.NoError(t, err) - ob := ChainClient{ - blockCache: blockCache, - blockCacheV3: blockCacheV3, - } + ob := &evm.ChainClient{Mu: &sync.Mutex{}} + ob.WithBlockCache(blockCache) // delete non-existing block should not panic - blockNumber := int64(10388180) - // #nosec G701 possible nummber - ob.RemoveCachedBlock(uint64(blockNumber)) + blockNumber := uint64(10388180) + ob.RemoveCachedBlock(blockNumber) // add a block - header := ðtypes.Header{ - Number: big.NewInt(blockNumber), + block := ðrpc.Block{ + // #nosec G701 always in range + Number: int(blockNumber), } - block := ethtypes.NewBlock(header, nil, nil, nil, nil) - ob.blockCache.Add(blockNumber, block) + blockCache.Add(blockNumber, block) + ob.WithBlockCache(blockCache) + + // block should be in cache + _, err = ob.GetBlockByNumberCached(blockNumber) + require.NoError(t, err) // delete the block should not panic - ob.RemoveCachedBlock(uint64(blockNumber)) + ob.RemoveCachedBlock(blockNumber) } -func TestCheckEvmTxLog(t *testing.T) { - // test data - connectorAddr := ethcommon.HexToAddress("0x00005e3125aba53c5652f9f0ce1a4cf91d8b15ea") - txHash := "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626" - topics := []ethcommon.Hash{ - // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog - ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), - ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), - ethcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000061"), - } +func TestEVM_CheckTxInclusion(t *testing.T) { + // load archived evm outtx Gas + // https://etherscan.io/tx/0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3 + chainID := int64(1) + coinType := common.CoinType_Gas + outtxHash := "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3" + tx, receipt := testutils.LoadEVMOuttxNReceipt(t, chainID, outtxHash, coinType) - tests := []struct { - name string - vLog *ethtypes.Log - fail bool - }{ - { - name: "chain reorganization", - vLog: ðtypes.Log{ - Removed: true, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: true, - }, - { - name: "emitter address mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: ethcommon.HexToAddress("0x184ba627DB853244c9f17f3Cb4378cB8B39bf147"), - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: true, - }, - { - name: "tx hash mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash("0x781c018d604af4dad0fe5e3cea4ad9fb949a996d8cd0cd04a92cadd7f08c05f2"), - Topics: topics, - }, - fail: true, - }, - { - name: "topics mismatch", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: []ethcommon.Hash{ - // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog - ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), - ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), - }, - }, - fail: true, - }, - { - name: "should pass", - vLog: ðtypes.Log{ - Removed: false, - Address: connectorAddr, - TxHash: ethcommon.HexToHash(txHash), - Topics: topics, - }, - fail: false, - }, - } + // load archived evm block + // https://etherscan.io/block/19363323 + blockNumber := receipt.BlockNumber.Uint64() + block := testutils.LoadEVMBlock(t, chainID, blockNumber, true) - evmClient := ChainClient{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - fmt.Printf("check test: %s\n", tt.name) - err := evmClient.CheckEvmTxLog( - tt.vLog, - connectorAddr, - "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626", - TopicsZetaSent, - ) - if tt.fail { - require.Error(t, err) - return - } else { - require.NoError(t, err) - } - }) - } + // create client + blockCache, err := lru.New(1000) + require.NoError(t, err) + ob := &evm.ChainClient{Mu: &sync.Mutex{}} + + // save block to cache + blockCache.Add(blockNumber, block) + ob.WithBlockCache(blockCache) + + t.Run("should pass for archived outtx", func(t *testing.T) { + err := ob.CheckTxInclusion(tx, receipt) + require.NoError(t, err) + }) + t.Run("should fail on tx index out of range", func(t *testing.T) { + // modify tx index to invalid number + copyReceipt := *receipt + // #nosec G701 non negative value + copyReceipt.TransactionIndex = uint(len(block.Transactions)) + err := ob.CheckTxInclusion(tx, ©Receipt) + require.ErrorContains(t, err, "out of range") + }) + t.Run("should fail on tx hash mismatch", func(t *testing.T) { + // change the tx at position 'receipt.TransactionIndex' to a different tx + priorTx := block.Transactions[receipt.TransactionIndex-1] + block.Transactions[receipt.TransactionIndex] = priorTx + blockCache.Add(blockNumber, block) + ob.WithBlockCache(blockCache) + + // check inclusion should fail + err := ob.CheckTxInclusion(tx, receipt) + require.ErrorContains(t, err, "has different hash") + + // wrong block should be removed from cache + _, ok := blockCache.Get(blockNumber) + require.False(t, ok) + }) +} + +func TestEVM_VoteOutboundBallot(t *testing.T) { + // load archived evm outtx Gas + // https://etherscan.io/tx/0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3 + chainID := int64(1) + coinType := common.CoinType_Gas + outtxHash := "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3" + tx, receipt := testutils.LoadEVMOuttxNReceipt(t, chainID, outtxHash, coinType) + + // load archived cctx + cctx := testutils.LoadCctxByNonce(t, chainID, tx.Nonce()) + + t.Run("outtx ballot should match cctx", func(t *testing.T) { + msg := types.NewMsgVoteOnObservedOutboundTx( + "anyCreator", + cctx.Index, + receipt.TxHash.Hex(), + receipt.BlockNumber.Uint64(), + receipt.GasUsed, + math.NewIntFromBigInt(tx.GasPrice()), + tx.Gas(), + math.NewUintFromBigInt(tx.Value()), + common.ReceiveStatus_Success, + chainID, + tx.Nonce(), + coinType, + ) + ballotExpected := cctx.GetCurrentOutTxParam().OutboundTxBallotIndex + require.Equal(t, ballotExpected, msg.Digest()) + }) } diff --git a/zetaclient/evm/evm_signer.go b/zetaclient/evm/evm_signer.go index c42e04b206..bdeef1b83a 100644 --- a/zetaclient/evm/evm_signer.go +++ b/zetaclient/evm/evm_signer.go @@ -32,12 +32,6 @@ import ( zbridge "github.com/zeta-chain/zetacore/zetaclient/zetabridge" ) -const ( - OutTxInclusionTimeout = 20 * time.Minute - OutTxTrackerReportTimeout = 10 * time.Minute - ZetaBlockTime = 6500 * time.Millisecond -) - type Signer struct { client interfaces.EVMRPCClient chain *common.Chain diff --git a/zetaclient/evm/inbounds.go b/zetaclient/evm/inbounds.go index ffb65fd5f4..5cc08f8126 100644 --- a/zetaclient/evm/inbounds.go +++ b/zetaclient/evm/inbounds.go @@ -4,13 +4,13 @@ import ( "bytes" "encoding/base64" "encoding/hex" - "errors" "fmt" - "math/big" "strings" sdkmath "cosmossdk.io/math" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onrik/ethrpc" + "github.com/pkg/errors" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" @@ -41,7 +41,7 @@ func (ob *ChainClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { for { select { case <-ticker.C(): - err := ob.ObserveTrackerSuggestions() + err := ob.ObserveIntxTrackers() if err != nil { ob.logger.ExternalChainWatcher.Err(err).Msg("ObserveTrackerSuggestions error") } @@ -53,227 +53,164 @@ func (ob *ChainClient) ExternalChainWatcherForNewInboundTrackerSuggestions() { } } -func (ob *ChainClient) ObserveTrackerSuggestions() error { +// ObserveIntxTrackers observes the inbound trackers for the chain +func (ob *ChainClient) ObserveIntxTrackers() error { trackers, err := ob.zetaClient.GetInboundTrackersForChain(ob.chain.ChainId) if err != nil { return err } for _, tracker := range trackers { - ob.logger.ExternalChainWatcher.Info().Msgf("checking tracker with hash :%s and coin-type :%s ", tracker.TxHash, tracker.CoinType) + // query tx and receipt + tx, _, err := ob.TransactionByHash(tracker.TxHash) + if err != nil { + return errors.Wrapf(err, "error getting transaction for intx %s chain %d", tracker.TxHash, ob.chain.ChainId) + } + receipt, err := ob.evmClient.TransactionReceipt(context.Background(), ethcommon.HexToHash(tracker.TxHash)) + if err != nil { + return errors.Wrapf(err, "error getting receipt for intx %s chain %d", tracker.TxHash, ob.chain.ChainId) + } + ob.logger.ExternalChainWatcher.Info().Msgf("checking tracker for intx %s chain %d", tracker.TxHash, ob.chain.ChainId) + + // check and vote on inbound tx switch tracker.CoinType { case common.CoinType_Zeta: - ballotIdentifier, err := ob.CheckReceiptForCoinTypeZeta(tracker.TxHash, true) - if err != nil { - return err - } - ob.logger.ExternalChainWatcher.Info().Msgf("Vote submitted for inbound Tracker,Chain : %s,Ballot Identifier : %s, coin-type %s", ob.chain.ChainName, ballotIdentifier, common.CoinType_Zeta.String()) + _, err = ob.CheckAndVoteInboundTokenZeta(tx, receipt, true) case common.CoinType_ERC20: - ballotIdentifier, err := ob.CheckReceiptForCoinTypeERC20(tracker.TxHash, true) - if err != nil { - return err - } - ob.logger.ExternalChainWatcher.Info().Msgf("Vote submitted for inbound Tracker,Chain : %s,Ballot Identifier : %s, coin-type %s", ob.chain.ChainName, ballotIdentifier, common.CoinType_ERC20.String()) + _, err = ob.CheckAndVoteInboundTokenERC20(tx, receipt, true) case common.CoinType_Gas: - ballotIdentifier, err := ob.CheckReceiptForCoinTypeGas(tracker.TxHash, true) - if err != nil { - return err - } - ob.logger.ExternalChainWatcher.Info().Msgf("Vote submitted for inbound Tracker,Chain : %s,Ballot Identifier : %s, coin-type %s", ob.chain.ChainName, ballotIdentifier, common.CoinType_Gas.String()) + _, err = ob.CheckAndVoteInboundTokenGas(tx, receipt, true) + default: + return fmt.Errorf("unknown coin type %s for intx %s chain %d", tracker.CoinType, tx.Hash, ob.chain.ChainId) + } + if err != nil { + return errors.Wrapf(err, "error checking and voting for intx %s chain %d", tx.Hash, ob.chain.ChainId) } } return nil } -func (ob *ChainClient) CheckReceiptForCoinTypeZeta(txHash string, vote bool) (string, error) { - addrConnector, connector, err := ob.GetConnectorContract() - if err != nil { - return "", err +// CheckAndVoteInboundTokenZeta checks and votes on the given inbound Zeta token +func (ob *ChainClient) CheckAndVoteInboundTokenZeta(tx *ethrpc.Transaction, receipt *ethtypes.Receipt, vote bool) (string, error) { + // check confirmations + if confirmed := ob.HasEnoughConfirmations(receipt, ob.GetLastBlockHeight()); !confirmed { + return "", fmt.Errorf("intx %s has not been confirmed yet: receipt block %d", tx.Hash, receipt.BlockNumber.Uint64()) } - hash := ethcommon.HexToHash(txHash) - receipt, err := ob.evmClient.TransactionReceipt(context.Background(), hash) + // get zeta connector contract + addrConnector, connector, err := ob.GetConnectorContract() if err != nil { return "", err } - // check if the tx is confirmed - lastHeight := ob.GetLastBlockHeight() - if !ob.HasEnoughConfirmations(receipt, lastHeight) { - return "", fmt.Errorf("txHash %s has not been confirmed yet: receipt block %d current block %d", txHash, receipt.BlockNumber, lastHeight) - } - + // build inbound vote message and post vote var msg *types.MsgVoteOnObservedInboundTx for _, log := range receipt.Logs { event, err := connector.ParseZetaSent(*log) if err == nil && event != nil { // sanity check tx event - err = ob.CheckEvmTxLog(&event.Raw, addrConnector, txHash, TopicsZetaSent) + err = ValidateEvmTxLog(&event.Raw, addrConnector, tx.Hash, TopicsZetaSent) if err == nil { - msg = ob.GetInboundVoteMsgForZetaSentEvent(event) - if msg != nil { - break - } + msg = ob.BuildInboundVoteMsgForZetaSentEvent(event) } else { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("CheckEvmTxLog error on ZetaSent event") + ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("CheckEvmTxLog error on intx %s chain %d", tx.Hash, ob.chain.ChainId) + return "", err } + break // only one event is allowed per tx } } if msg == nil { - return "", errors.New("no ZetaSent event found") + // no event, restricted tx, etc. + ob.logger.ExternalChainWatcher.Info().Msgf("no ZetaSent event found for intx %s chain %d", tx.Hash, ob.chain.ChainId) + return "", nil } - if !vote { - return msg.Digest(), nil + if vote { + return ob.PostVoteInbound(msg, common.CoinType_Zeta, zetabridge.PostVoteInboundMessagePassingExecutionGasLimit) } - - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundMessagePassingExecutionGasLimit, msg) - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("error posting to zeta core") - return "", err - } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf("ZetaSent event detected and reported: PostVoteInbound zeta tx: %s ballot %s", zetaHash, ballot) - } - return msg.Digest(), nil } -func (ob *ChainClient) CheckReceiptForCoinTypeERC20(txHash string, vote bool) (string, error) { - addrCustory, custody, err := ob.GetERC20CustodyContract() - if err != nil { - return "", err +// CheckAndVoteInboundTokenERC20 checks and votes on the given inbound ERC20 token +func (ob *ChainClient) CheckAndVoteInboundTokenERC20(tx *ethrpc.Transaction, receipt *ethtypes.Receipt, vote bool) (string, error) { + // check confirmations + if confirmed := ob.HasEnoughConfirmations(receipt, ob.GetLastBlockHeight()); !confirmed { + return "", fmt.Errorf("intx %s has not been confirmed yet: receipt block %d", tx.Hash, receipt.BlockNumber.Uint64()) } - // get transaction, receipt and sender - hash := ethcommon.HexToHash(txHash) - tx, _, err := ob.evmClient.TransactionByHash(context.Background(), hash) - if err != nil { - return "", err - } - receipt, err := ob.evmClient.TransactionReceipt(context.Background(), hash) - if err != nil { - return "", err - } - sender, err := ob.evmClient.TransactionSender(context.Background(), tx, receipt.BlockHash, receipt.TransactionIndex) + + // get erc20 custody contract + addrCustory, custody, err := ob.GetERC20CustodyContract() if err != nil { return "", err } + sender := ethcommon.HexToAddress(tx.From) - // check if the tx is confirmed - lastHeight := ob.GetLastBlockHeight() - if !ob.HasEnoughConfirmations(receipt, lastHeight) { - return "", fmt.Errorf("txHash %s has not been confirmed yet: receipt block %d current block %d", txHash, receipt.BlockNumber, lastHeight) - } - + // build inbound vote message and post vote var msg *types.MsgVoteOnObservedInboundTx for _, log := range receipt.Logs { zetaDeposited, err := custody.ParseDeposited(*log) if err == nil && zetaDeposited != nil { // sanity check tx event - err = ob.CheckEvmTxLog(&zetaDeposited.Raw, addrCustory, txHash, TopicsDeposited) + err = ValidateEvmTxLog(&zetaDeposited.Raw, addrCustory, tx.Hash, TopicsDeposited) if err == nil { - msg = ob.GetInboundVoteMsgForDepositedEvent(zetaDeposited, sender) - if err == nil { - break - } + msg = ob.BuildInboundVoteMsgForDepositedEvent(zetaDeposited, sender) } else { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("CheckEvmTxLog error on ERC20CustodyDeposited event") + ob.logger.ExternalChainWatcher.Error().Err(err).Msgf("CheckEvmTxLog error on intx %s chain %d", tx.Hash, ob.chain.ChainId) + return "", err } + break // only one event is allowed per tx } } if msg == nil { - return "", errors.New("no ERC20CustodyDeposited event found") + // no event, donation, restricted tx, etc. + ob.logger.ExternalChainWatcher.Info().Msgf("no Deposited event found for intx %s chain %d", tx.Hash, ob.chain.ChainId) + return "", nil } - if !vote { - return msg.Digest(), nil + if vote { + return ob.PostVoteInbound(msg, common.CoinType_ERC20, zetabridge.PostVoteInboundExecutionGasLimit) } - - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) - if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("error posting to zeta core") - return "", err - } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf("ERC20 Deposit event detected and reported: PostVoteInbound zeta tx: %s ballot %s", zetaHash, ballot) - } - return msg.Digest(), nil } -func (ob *ChainClient) CheckReceiptForCoinTypeGas(txHash string, vote bool) (string, error) { - // TSS address should be set - tssAddress := ob.Tss.EVMAddress() - if tssAddress == (ethcommon.Address{}) { - return "", errors.New("TSS address not set") - } - - // check transaction and receipt - hash := ethcommon.HexToHash(txHash) - tx, isPending, err := ob.evmClient.TransactionByHash(context.Background(), hash) - if err != nil { - return "", err +// CheckAndVoteInboundTokenGas checks and votes on the given inbound gas token +func (ob *ChainClient) CheckAndVoteInboundTokenGas(tx *ethrpc.Transaction, receipt *ethtypes.Receipt, vote bool) (string, error) { + // check confirmations + if confirmed := ob.HasEnoughConfirmations(receipt, ob.GetLastBlockHeight()); !confirmed { + return "", fmt.Errorf("intx %s has not been confirmed yet: receipt block %d", tx.Hash, receipt.BlockNumber.Uint64()) } - if isPending { - return "", errors.New("tx is still pending") + // checks receiver and tx status + if ethcommon.HexToAddress(tx.To) != ob.Tss.EVMAddress() { + return "", fmt.Errorf("tx.To %s is not TSS address", tx.To) } - if tx.To() == nil { - return "", errors.New("tx.To() is nil") - } - if *tx.To() != tssAddress { - return "", fmt.Errorf("tx.To() %s is not TSS address", tssAddress.Hex()) - } - - receipt, err := ob.evmClient.TransactionReceipt(context.Background(), hash) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msg("TransactionReceipt error") - return "", err - } - if receipt.Status != 1 { // 1: successful, 0: failed - ob.logger.ExternalChainWatcher.Info().Msgf("tx %s failed; don't act", tx.Hash().Hex()) - return "", errors.New("tx not successful yet") - } - sender, err := ob.evmClient.TransactionSender(context.Background(), tx, receipt.BlockHash, receipt.TransactionIndex) - if err != nil { - return "", err + if receipt.Status != ethtypes.ReceiptStatusSuccessful { + return "", errors.New("not a successful tx") } + sender := ethcommon.HexToAddress(tx.From) - // check if the tx is confirmed - lastHeight := ob.GetLastBlockHeight() - if !ob.HasEnoughConfirmations(receipt, lastHeight) { - return "", fmt.Errorf("txHash %s has not been confirmed yet: receipt block %d current block %d", txHash, receipt.BlockNumber, lastHeight) - } - msg := ob.GetInboundVoteMsgForTokenSentToTSS(tx, sender, receipt.BlockNumber.Uint64()) + // build inbound vote message and post vote + msg := ob.BuildInboundVoteMsgForTokenSentToTSS(tx, sender, receipt.BlockNumber.Uint64()) if msg == nil { - return "", errors.New("no message built for token sent to TSS") + // donation, restricted tx, etc. + ob.logger.ExternalChainWatcher.Info().Msgf("no vote message built for intx %s chain %d", tx.Hash, ob.chain.ChainId) + return "", nil } - if !vote { - return msg.Digest(), nil + if vote { + return ob.PostVoteInbound(msg, common.CoinType_Gas, zetabridge.PostVoteInboundExecutionGasLimit) } + return msg.Digest(), nil +} - zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg) +// PostVoteInbound posts a vote for the given vote message +func (ob *ChainClient) PostVoteInbound(msg *types.MsgVoteOnObservedInboundTx, coinType common.CoinType, retryGasLimit uint64) (string, error) { + txHash := msg.InTxHash + chainID := ob.chain.ChainId + zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, retryGasLimit, msg) if err != nil { - ob.logger.ExternalChainWatcher.Error().Err(err).Msg("error posting to zeta core") + ob.logger.ExternalChainWatcher.Err(err).Msgf("intx detected: error posting vote for chain %d token %s intx %s", chainID, coinType, txHash) return "", err } else if zetaHash != "" { - ob.logger.ExternalChainWatcher.Info().Msgf("Gas deposit detected and reported: PostVoteInbound zeta tx: %s ballot %s", zetaHash, ballot) + ob.logger.ExternalChainWatcher.Info().Msgf("intx detected: chain %d token %s intx %s vote %s ballot %s", chainID, coinType, txHash, zetaHash, ballot) + } else { + ob.logger.ExternalChainWatcher.Info().Msgf("intx detected: chain %d token %s intx %s already voted on ballot %s", chainID, coinType, txHash, ballot) } - - return msg.Digest(), nil -} - -// CheckEvmTxLog checks the basics of an EVM tx log -func (ob *ChainClient) CheckEvmTxLog(vLog *ethtypes.Log, wantAddress ethcommon.Address, wantHash string, wantTopics int) error { - if vLog.Removed { - return fmt.Errorf("log is removed, chain reorg?") - } - if vLog.Address != wantAddress { - return fmt.Errorf("log emitter address mismatch: want %s got %s", wantAddress.Hex(), vLog.Address.Hex()) - } - if vLog.TxHash.Hex() == "" { - return fmt.Errorf("log tx hash is empty: %d %s", vLog.BlockNumber, vLog.TxHash.Hex()) - } - if wantHash != "" && vLog.TxHash.Hex() != wantHash { - return fmt.Errorf("log tx hash mismatch: want %s got %s", wantHash, vLog.TxHash.Hex()) - } - if len(vLog.Topics) != wantTopics { - return fmt.Errorf("number of topics mismatch: want %d got %d", wantTopics, len(vLog.Topics)) - } - return nil + return ballot, err } // HasEnoughConfirmations checks if the given receipt has enough confirmations @@ -282,22 +219,8 @@ func (ob *ChainClient) HasEnoughConfirmations(receipt *ethtypes.Receipt, lastHei return lastHeight >= confHeight } -// GetTransactionSender returns the sender of the given transaction -func (ob *ChainClient) GetTransactionSender(tx *ethtypes.Transaction, blockHash ethcommon.Hash, txIndex uint) (ethcommon.Address, error) { - sender, err := ob.evmClient.TransactionSender(context.Background(), tx, blockHash, txIndex) - if err != nil { - // trying local recovery (assuming LondonSigner dynamic fee tx type) of sender address - signer := ethtypes.NewLondonSigner(big.NewInt(ob.chain.ChainId)) - sender, err = signer.Sender(tx) - if err != nil { - ob.logger.ExternalChainWatcher.Err(err).Msgf("can't recover the sender from tx hash %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) - return ethcommon.Address{}, err - } - } - return sender, nil -} - -func (ob *ChainClient) GetInboundVoteMsgForDepositedEvent(event *erc20custody.ERC20CustodyDeposited, sender ethcommon.Address) *types.MsgVoteOnObservedInboundTx { +// BuildInboundVoteMsgForDepositedEvent builds a inbound vote message for a Deposited event +func (ob *ChainClient) BuildInboundVoteMsgForDepositedEvent(event *erc20custody.ERC20CustodyDeposited, sender ethcommon.Address) *types.MsgVoteOnObservedInboundTx { // compliance check maybeReceiver := "" parsedAddress, _, err := common.ParseAddressAndData(hex.EncodeToString(event.Message)) @@ -311,7 +234,7 @@ func (ob *ChainClient) GetInboundVoteMsgForDepositedEvent(event *erc20custody.ER } // donation check - if bytes.Equal(event.Message, []byte(DonationMessage)) { + if bytes.Equal(event.Message, []byte(common.DonationMessage)) { ob.logger.ExternalChainWatcher.Info().Msgf("thank you rich folk for your donation! tx %s chain %d", event.Raw.TxHash.Hex(), ob.chain.ChainId) return nil } @@ -337,7 +260,8 @@ func (ob *ChainClient) GetInboundVoteMsgForDepositedEvent(event *erc20custody.ER ) } -func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.ZetaConnectorNonEthZetaSent) *types.MsgVoteOnObservedInboundTx { +// BuildInboundVoteMsgForZetaSentEvent builds a inbound vote message for a ZetaSent event +func (ob *ChainClient) BuildInboundVoteMsgForZetaSentEvent(event *zetaconnector.ZetaConnectorNonEthZetaSent) *types.MsgVoteOnObservedInboundTx { destChain := common.GetChainFromChainID(event.DestinationChainId.Int64()) if destChain == nil { ob.logger.ExternalChainWatcher.Warn().Msgf("chain id not supported %d", event.DestinationChainId.Int64()) @@ -387,8 +311,9 @@ func (ob *ChainClient) GetInboundVoteMsgForZetaSentEvent(event *zetaconnector.Ze ) } -func (ob *ChainClient) GetInboundVoteMsgForTokenSentToTSS(tx *ethtypes.Transaction, sender ethcommon.Address, blockNumber uint64) *types.MsgVoteOnObservedInboundTx { - message := hex.EncodeToString(tx.Data()) +// BuildInboundVoteMsgForTokenSentToTSS builds a inbound vote message for a token sent to TSS +func (ob *ChainClient) BuildInboundVoteMsgForTokenSentToTSS(tx *ethrpc.Transaction, sender ethcommon.Address, blockNumber uint64) *types.MsgVoteOnObservedInboundTx { + message := tx.Input // compliance check maybeReceiver := "" @@ -398,17 +323,19 @@ func (ob *ChainClient) GetInboundVoteMsgForTokenSentToTSS(tx *ethtypes.Transacti } if config.ContainRestrictedAddress(sender.Hex(), maybeReceiver) { clientcommon.PrintComplianceLog(ob.logger.ExternalChainWatcher, ob.logger.Compliance, - false, ob.chain.ChainId, tx.Hash().Hex(), sender.Hex(), sender.Hex(), "Gas") + false, ob.chain.ChainId, tx.Hash, sender.Hex(), sender.Hex(), "Gas") return nil } // donation check - if bytes.Equal(tx.Data(), []byte(DonationMessage)) { - ob.logger.ExternalChainWatcher.Info().Msgf("thank you rich folk for your donation! tx %s chain %d", tx.Hash().Hex(), ob.chain.ChainId) + // #nosec G703 err is already checked + data, _ := hex.DecodeString(message) + if bytes.Equal(data, []byte(common.DonationMessage)) { + ob.logger.ExternalChainWatcher.Info().Msgf("thank you rich folk for your donation! tx %s chain %d", tx.Hash, ob.chain.ChainId) return nil } ob.logger.ExternalChainWatcher.Info().Msgf("TSS inTx detected on chain %d tx %s block %d from %s value %s message %s", - ob.chain.ChainId, tx.Hash().Hex(), blockNumber, sender.Hex(), tx.Value().String(), message) + ob.chain.ChainId, tx.Hash, blockNumber, sender.Hex(), tx.Value.String(), message) return zetabridge.GetInBoundVoteMessage( sender.Hex(), @@ -416,9 +343,9 @@ func (ob *ChainClient) GetInboundVoteMsgForTokenSentToTSS(tx *ethtypes.Transacti sender.Hex(), sender.Hex(), ob.zetaClient.ZetaChain().ChainId, - sdkmath.NewUintFromBigInt(tx.Value()), + sdkmath.NewUintFromBigInt(&tx.Value), message, - tx.Hash().Hex(), + tx.Hash, blockNumber, 90_000, common.CoinType_Gas, diff --git a/zetaclient/evm/inbounds_test.go b/zetaclient/evm/inbounds_test.go index bd8556be63..15a3efbe76 100644 --- a/zetaclient/evm/inbounds_test.go +++ b/zetaclient/evm/inbounds_test.go @@ -1,90 +1,335 @@ -package evm +package evm_test import ( - "path" + "encoding/hex" + "sync" "testing" - "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" - ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" + "github.com/onrik/ethrpc" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/common" - "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/evm" + "github.com/zeta-chain/zetacore/zetaclient/interfaces" "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/stub" + clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" ) -func MockEVMClient(chain common.Chain) *ChainClient { - return &ChainClient{ - chain: chain, - zetaClient: stub.NewZetaCoreBridge(), +// MockEVMClient creates a mock ChainClient with custom chain, TSS, params etc +func MockEVMClient( + chain common.Chain, + tss interfaces.TSSSigner, + lastBlock uint64, + params observertypes.ChainParams) *evm.ChainClient { + client := &evm.ChainClient{ + Tss: tss, + Mu: &sync.Mutex{}, } + client.WithChain(chain) + client.WithZetaClient(stub.NewZetaCoreBridge()) + client.SetLastBlockHeight(lastBlock) + client.SetChainParams(params) + return client } -func MockConnectorNonEth() *zetaconnector.ZetaConnectorNonEth { - connector, err := zetaconnector.NewZetaConnectorNonEth(ethcommon.Address{}, ðclient.Client{}) - if err != nil { - panic(err) - } - return connector +func TestEVM_CheckAndVoteInboundTokenZeta(t *testing.T) { + // load archived ZetaSent intx, receipt and cctx + // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 + chainID := int64(1) + intxHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" + confirmation := uint64(10) + + t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { + tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, false) + require.NoError(t, err) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) + }) + t.Run("should fail on unconfirmed intx", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + _, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, false) + require.ErrorContains(t, err, "not been confirmed") + }) + t.Run("should not act if no ZetaSent event", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) + receipt.Logs = receipt.Logs[:2] // remove ZetaSent event + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, true) + require.NoError(t, err) + require.Equal(t, "", ballot) + }) + t.Run("should not act if emitter is not ZetaConnector", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Zeta) + chainID = 56 // use BSC chain connector + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + _, err := ob.CheckAndVoteInboundTokenZeta(tx, receipt, true) + require.ErrorContains(t, err, "emitter address mismatch") + }) } -func ParseReceiptZetaSent( - receipt *ethtypes.Receipt, - ob *ChainClient, - connector *zetaconnector.ZetaConnectorNonEth) *types.MsgVoteOnObservedInboundTx { - var msg *types.MsgVoteOnObservedInboundTx - for _, log := range receipt.Logs { - event, err := connector.ParseZetaSent(*log) - if err == nil && event != nil { - msg = ob.GetInboundVoteMsgForZetaSentEvent(event) - break // found - } - } - return msg +func TestEVM_CheckAndVoteInboundTokenERC20(t *testing.T) { + // load archived ERC20 intx, receipt and cctx + // https://etherscan.io/tx/0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da + chainID := int64(1) + intxHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" + confirmation := uint64(10) + + t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { + tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, false) + require.NoError(t, err) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) + }) + t.Run("should fail on unconfirmed intx", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + _, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, false) + require.ErrorContains(t, err, "not been confirmed") + }) + t.Run("should not act if no Deposit event", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) + receipt.Logs = receipt.Logs[:1] // remove Deposit event + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, true) + require.NoError(t, err) + require.Equal(t, "", ballot) + }) + t.Run("should not act if emitter is not ERC20 Custody", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_ERC20) + chainID = 56 // use BSC chain ERC20 custody + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + _, err := ob.CheckAndVoteInboundTokenERC20(tx, receipt, true) + require.ErrorContains(t, err, "emitter address mismatch") + }) } -func TestEthereum_GetInboundVoteMsgForZetaSentEvent(t *testing.T) { - // load archived ZetaSent receipt - // zeta-chain/crosschain/cctx/0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f - receipt := ethtypes.Receipt{} - name := "chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json" - err := testutils.LoadObjectFromJSONFile(&receipt, path.Join("../", testutils.TestDataPathEVM, name)) - require.NoError(t, err) +func TestEVM_CheckAndVoteInboundTokenGas(t *testing.T) { + // load archived Gas intx, receipt and cctx + // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 + chainID := int64(1) + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + confirmation := uint64(10) + + t.Run("should pass for archived intx, receipt and cctx", func(t *testing.T) { + tx, receipt, cctx := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) + require.NoError(t, err) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, ballot) + }) + t.Run("should fail on unconfirmed intx", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation - 1 + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + _, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) + require.ErrorContains(t, err, "not been confirmed") + }) + t.Run("should not act if receiver is not TSS", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) + tx.To = testutils.OtherAddress // use other address + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) + require.ErrorContains(t, err, "not TSS address") + require.Equal(t, "", ballot) + }) + t.Run("should not act if transaction failed", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) + receipt.Status = ethtypes.ReceiptStatusFailed + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) + require.ErrorContains(t, err, "not a successful tx") + require.Equal(t, "", ballot) + }) + t.Run("should not act on nil message", func(t *testing.T) { + tx, receipt, _ := testutils.LoadEVMIntxNReceiptNCctx(t, chainID, intxHash, common.CoinType_Gas) + tx.Input = hex.EncodeToString([]byte(common.DonationMessage)) // donation will result in nil message + require.NoError(t, evm.ValidateEvmTransaction(tx)) + lastBlock := receipt.BlockNumber.Uint64() + confirmation + + ob := MockEVMClient(common.EthChain(), stub.NewTSSMainnet(), lastBlock, stub.MockChainParams(chainID, confirmation)) + ballot, err := ob.CheckAndVoteInboundTokenGas(tx, receipt, false) + require.NoError(t, err) + require.Equal(t, "", ballot) + }) +} - // create mock client and connector - ob := MockEVMClient(common.EthChain()) - connector := MockConnectorNonEth() +func TestEVM_BuildInboundVoteMsgForZetaSentEvent(t *testing.T) { + // load archived ZetaSent receipt + // https://etherscan.io/tx/0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76 + chainID := int64(1) + intxHash := "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76" + receipt := testutils.LoadEVMIntxReceipt(t, chainID, intxHash, common.CoinType_Zeta) + cctx := testutils.LoadEVMIntxCctx(t, chainID, intxHash, common.CoinType_Zeta) // parse ZetaSent event - msg := ParseReceiptZetaSent(&receipt, ob, connector) - require.NotNil(t, msg) - require.Equal(t, "0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f", msg.Digest()) + ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + connector := stub.MockConnectorNonEth(chainID) + event := testutils.ParseReceiptZetaSent(receipt, connector) - // create config + // create test compliance config cfg := &config.Config{ ComplianceConfig: &config.ComplianceConfig{}, } + t.Run("should return vote msg for archived ZetaSent event", func(t *testing.T) { + msg := ob.BuildInboundVoteMsgForZetaSentEvent(event) + require.NotNil(t, msg) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, msg.Digest()) + }) t.Run("should return nil msg if sender is restricted", func(t *testing.T) { - cfg.ComplianceConfig.RestrictedAddresses = []string{msg.Sender} + sender := event.ZetaTxSenderAddress.Hex() + cfg.ComplianceConfig.RestrictedAddresses = []string{sender} config.LoadComplianceConfig(cfg) - msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) - require.Nil(t, msgRestricted) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(event) + require.Nil(t, msg) }) t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { - cfg.ComplianceConfig.RestrictedAddresses = []string{msg.Receiver} + receiver := clienttypes.BytesToEthHex(event.DestinationAddress) + cfg.ComplianceConfig.RestrictedAddresses = []string{receiver} config.LoadComplianceConfig(cfg) - msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) - require.Nil(t, msgRestricted) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(event) + require.Nil(t, msg) }) t.Run("should return nil msg if txOrigin is restricted", func(t *testing.T) { - cfg.ComplianceConfig.RestrictedAddresses = []string{msg.TxOrigin} + txOrigin := event.SourceTxOriginAddress.Hex() + cfg.ComplianceConfig.RestrictedAddresses = []string{txOrigin} + config.LoadComplianceConfig(cfg) + msg := ob.BuildInboundVoteMsgForZetaSentEvent(event) + require.Nil(t, msg) + }) +} + +func TestEVM_BuildInboundVoteMsgForDepositedEvent(t *testing.T) { + // load archived Deposited receipt + // https://etherscan.io/tx/0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da + chainID := int64(1) + intxHash := "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da" + tx, receipt := testutils.LoadEVMIntxNReceipt(t, chainID, intxHash, common.CoinType_ERC20) + cctx := testutils.LoadEVMIntxCctx(t, chainID, intxHash, common.CoinType_ERC20) + + // parse Deposited event + ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + custody := stub.MockERC20Custody(chainID) + event := testutils.ParseReceiptERC20Deposited(receipt, custody) + sender := ethcommon.HexToAddress(tx.From) + + // create test compliance config + cfg := &config.Config{ + ComplianceConfig: &config.ComplianceConfig{}, + } + + t.Run("should return vote msg for archived Deposited event", func(t *testing.T) { + msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender) + require.NotNil(t, msg) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, msg.Digest()) + }) + t.Run("should return nil msg if sender is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{sender.Hex()} + config.LoadComplianceConfig(cfg) + msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender) + require.Nil(t, msg) + }) + t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { + receiver := clienttypes.BytesToEthHex(event.Recipient) + cfg.ComplianceConfig.RestrictedAddresses = []string{receiver} config.LoadComplianceConfig(cfg) - msgRestricted := ParseReceiptZetaSent(&receipt, ob, connector) - require.Nil(t, msgRestricted) + msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender) + require.Nil(t, msg) + }) + t.Run("should return nil msg on donation transaction", func(t *testing.T) { + event.Message = []byte(common.DonationMessage) + msg := ob.BuildInboundVoteMsgForDepositedEvent(event, sender) + require.Nil(t, msg) + }) +} + +func TestEVM_BuildInboundVoteMsgForTokenSentToTSS(t *testing.T) { + // load archived gas token transfer to TSS + // https://etherscan.io/tx/0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532 + chainID := int64(1) + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + tx, receipt := testutils.LoadEVMIntxNReceipt(t, chainID, intxHash, common.CoinType_Gas) + require.NoError(t, evm.ValidateEvmTransaction(tx)) + cctx := testutils.LoadEVMIntxCctx(t, chainID, intxHash, common.CoinType_Gas) + + // load archived gas token donation to TSS + // https://etherscan.io/tx/0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155 + inTxHashDonation := "0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155" + txDonation, receiptDonation := testutils.LoadEVMIntxNReceiptDonation(t, chainID, inTxHashDonation, common.CoinType_Gas) + require.NoError(t, evm.ValidateEvmTransaction(txDonation)) + + // create test compliance config + ob := MockEVMClient(common.EthChain(), nil, 1, stub.MockChainParams(1, 1)) + cfg := &config.Config{ + ComplianceConfig: &config.ComplianceConfig{}, + } + + t.Run("should return vote msg for archived gas token transfer to TSS", func(t *testing.T) { + msg := ob.BuildInboundVoteMsgForTokenSentToTSS(tx, ethcommon.HexToAddress(tx.From), receipt.BlockNumber.Uint64()) + require.NotNil(t, msg) + require.Equal(t, cctx.InboundTxParams.InboundTxBallotIndex, msg.Digest()) + }) + t.Run("should return nil msg if sender is restricted", func(t *testing.T) { + cfg.ComplianceConfig.RestrictedAddresses = []string{tx.From} + config.LoadComplianceConfig(cfg) + msg := ob.BuildInboundVoteMsgForTokenSentToTSS(tx, ethcommon.HexToAddress(tx.From), receipt.BlockNumber.Uint64()) + require.Nil(t, msg) + }) + t.Run("should return nil msg if receiver is restricted", func(t *testing.T) { + txCopy := ðrpc.Transaction{} + *txCopy = *tx + message := hex.EncodeToString(ethcommon.HexToAddress(testutils.OtherAddress).Bytes()) + txCopy.Input = message // use other address as receiver + cfg.ComplianceConfig.RestrictedAddresses = []string{testutils.OtherAddress} + config.LoadComplianceConfig(cfg) + msg := ob.BuildInboundVoteMsgForTokenSentToTSS(txCopy, ethcommon.HexToAddress(txCopy.From), receipt.BlockNumber.Uint64()) + require.Nil(t, msg) + }) + t.Run("should return nil msg on donation transaction", func(t *testing.T) { + msg := ob.BuildInboundVoteMsgForTokenSentToTSS(txDonation, + ethcommon.HexToAddress(txDonation.From), receiptDonation.BlockNumber.Uint64()) + require.Nil(t, msg) }) } diff --git a/zetaclient/evm/validation.go b/zetaclient/evm/validation.go new file mode 100644 index 0000000000..8c7f9249c5 --- /dev/null +++ b/zetaclient/evm/validation.go @@ -0,0 +1,84 @@ +package evm + +import ( + "encoding/hex" + "fmt" + "strings" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onrik/ethrpc" + "github.com/pkg/errors" +) + +// ValidateEvmTxLog checks the basics of an EVM tx log +func ValidateEvmTxLog(vLog *ethtypes.Log, wantAddress ethcommon.Address, wantHash string, wantTopics int) error { + if vLog.Removed { + return fmt.Errorf("log is removed, chain reorg?") + } + if vLog.Address != wantAddress { + return fmt.Errorf("log emitter address mismatch: want %s got %s", wantAddress.Hex(), vLog.Address.Hex()) + } + if wantHash != "" && vLog.TxHash.Hex() != wantHash { + return fmt.Errorf("log tx hash mismatch: want %s got %s", wantHash, vLog.TxHash.Hex()) + } + if len(vLog.Topics) != wantTopics { + return fmt.Errorf("number of topics mismatch: want %d got %d", wantTopics, len(vLog.Topics)) + } + return nil +} + +// ValidateEvmTransaction checks the basics of an EVM transaction +// Note: these checks are to ensure the transaction is well-formed +// and can be safely used for further processing by zetaclient +func ValidateEvmTransaction(tx *ethrpc.Transaction) error { + if tx == nil { + return fmt.Errorf("transaction is nil") + } + if tx.Hash == "" { + return fmt.Errorf("transaction hash is empty") + } + if tx.Nonce < 0 { + return fmt.Errorf("transaction nonce %d is negative", tx.Nonce) + } + if !ethcommon.IsHexAddress(tx.From) { + return fmt.Errorf("transaction from %s is not a valid hex address", tx.From) + } + if tx.To != "" && !ethcommon.IsHexAddress(tx.To) { + // To address can be empty for contract creation + return fmt.Errorf("transaction to %s is not a valid hex address", tx.To) + } + if tx.Value.Sign() < 0 { + return fmt.Errorf("transaction value %s is negative", tx.Value.String()) + } + if tx.Gas < 0 { + return fmt.Errorf("transaction gas %d is negative", tx.Gas) + } + if tx.GasPrice.Sign() < 0 { + return fmt.Errorf("transaction gas price %s is negative", tx.GasPrice.String()) + } + // remove '0x' prefix from input data to be consistent with ethclient + tx.Input = strings.TrimPrefix(tx.Input, "0x") + + // tx input data should be hex encoded + if _, err := hex.DecodeString(tx.Input); err != nil { + return errors.Wrapf(err, "transaction input data is not hex encoded: %s", tx.Input) + } + + // inclusion checks + if tx.BlockNumber != nil { + if *tx.BlockNumber <= 0 { + return fmt.Errorf("transaction block number %d is not positive", *tx.BlockNumber) + } + if tx.BlockHash == "" { + return fmt.Errorf("transaction block hash is empty") + } + if tx.TransactionIndex == nil { + return fmt.Errorf("transaction index is nil") + } + if *tx.TransactionIndex < 0 { + return fmt.Errorf("transaction index %d is negative", *tx.TransactionIndex) + } + } + return nil +} diff --git a/zetaclient/evm/validation_test.go b/zetaclient/evm/validation_test.go new file mode 100644 index 0000000000..a1e2d3de9c --- /dev/null +++ b/zetaclient/evm/validation_test.go @@ -0,0 +1,413 @@ +package evm + +import ( + "fmt" + "math/big" + "testing" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onrik/ethrpc" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/zetaclient/testutils" +) + +func TestCheckEvmTxLog(t *testing.T) { + // test data + connectorAddr := ethcommon.HexToAddress("0x00005e3125aba53c5652f9f0ce1a4cf91d8b15ea") + txHash := "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626" + topics := []ethcommon.Hash{ + // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog + ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), + ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), + ethcommon.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000061"), + } + + tests := []struct { + name string + vLog *ethtypes.Log + fail bool + }{ + { + name: "chain reorganization", + vLog: ðtypes.Log{ + Removed: true, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: true, + }, + { + name: "emitter address mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: ethcommon.HexToAddress("0x184ba627DB853244c9f17f3Cb4378cB8B39bf147"), + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: true, + }, + { + name: "tx hash mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash("0x781c018d604af4dad0fe5e3cea4ad9fb949a996d8cd0cd04a92cadd7f08c05f2"), + Topics: topics, + }, + fail: true, + }, + { + name: "topics mismatch", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: []ethcommon.Hash{ + // https://goerli.etherscan.io/tx/0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626#eventlog + ethcommon.HexToHash("0x7ec1c94701e09b1652f3e1d307e60c4b9ebf99aff8c2079fd1d8c585e031c4e4"), + ethcommon.HexToHash("0x00000000000000000000000023856df5d563bd893fc7df864102d8bbfe7fc487"), + }, + }, + fail: true, + }, + { + name: "should pass", + vLog: ðtypes.Log{ + Removed: false, + Address: connectorAddr, + TxHash: ethcommon.HexToHash(txHash), + Topics: topics, + }, + fail: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Printf("check test: %s\n", tt.name) + err := ValidateEvmTxLog( + tt.vLog, + connectorAddr, + "0xb252c9e77feafdeeae25cc1f037a16c4b50fa03c494754b99a7339d816c79626", + TopicsZetaSent, + ) + if tt.fail { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestCheckEvmTransactionTable(t *testing.T) { + // use archived intx + chainID := int64(1) + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + + tests := []struct { + name string + tx *ethrpc.Transaction + fail bool + msg string + }{ + { + name: "should pass for valid transaction", + tx: testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas), + fail: false, + }, + { + name: "should fail for nil transaction", + tx: nil, + fail: true, + msg: "transaction is nil", + }, + { + name: "should fail for empty hash", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.Hash = "" + return tx + }(), + fail: true, + msg: "hash is empty", + }, + { + name: "should fail for negative nonce", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.Nonce = -1 + return tx + }(), + fail: true, + msg: "nonce -1 is negative", + }, + { + name: "should fail for empty from address", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.From = "" + return tx + }(), + fail: true, + msg: "not a valid hex address", + }, + { + name: "should fail for invalid from address", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.From = "0x" + return tx + }(), + fail: true, + msg: "from 0x is not a valid hex address", + }, + { + name: "should pass for empty to address", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.To = "" + return tx + }(), + fail: false, + }, + { + name: "should fail for invalid to address", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.To = "0xinvalid" + return tx + }(), + fail: true, + msg: "to 0xinvalid is not a valid hex address", + }, + { + name: "should fail for negative value", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.Value = *big.NewInt(-1) + return tx + }(), + fail: true, + msg: "value -1 is negative", + }, + { + name: "should fail for negative gas", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.Gas = -1 + return tx + }(), + fail: true, + msg: "gas -1 is negative", + }, + { + name: "should fail for negative gas price", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.GasPrice = *big.NewInt(-1) + return tx + }(), + fail: true, + msg: "gas price -1 is negative", + }, + { + name: "should remove '0x' prefix from input data", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + return tx + }(), + fail: false, + }, + { + name: "nil block number should pass", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.BlockNumber = nil + return tx + }(), + fail: false, + }, + { + name: "should fail for negative block number", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + negBlockNumber := -1 + tx.BlockNumber = &negBlockNumber + return tx + }(), + fail: true, + msg: "block number -1 is not positive", + }, + { + name: "should fail for empty block hash", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.BlockHash = "" + return tx + }(), + fail: true, + msg: "block hash is empty", + }, + { + name: "nil transaction index should fail", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.TransactionIndex = nil + return tx + }(), + fail: true, + msg: "index is nil", + }, + { + name: "should fail for negative transaction index", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + negTransactionIndex := -1 + tx.TransactionIndex = &negTransactionIndex + return tx + }(), + fail: true, + msg: "index -1 is negative", + }, + { + name: "should fail for invalid input data", + tx: func() *ethrpc.Transaction { + tx := testutils.LoadEVMIntx(t, chainID, intxHash, common.CoinType_Gas) + tx.Input = "03befinvalid" + return tx + }(), + fail: true, + msg: "input data is not hex encoded", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateEvmTransaction(tt.tx) + if tt.fail { + require.Error(t, err) + require.Contains(t, err.Error(), tt.msg) + return + } + require.NoError(t, err) + }) + } +} + +func TestCheckEvmTransaction(t *testing.T) { + // use archived intx + intxHash := "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532" + + t.Run("should pass for valid transaction", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + err := ValidateEvmTransaction(tx) + require.NoError(t, err) + }) + t.Run("should fail for nil transaction", func(t *testing.T) { + err := ValidateEvmTransaction(nil) + require.ErrorContains(t, err, "transaction is nil") + }) + t.Run("should fail for empty hash", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.Hash = "" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "hash is empty") + }) + t.Run("should fail for negative nonce", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.Nonce = -1 + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "nonce -1 is negative") + }) + t.Run("should fail for empty from address", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.From = "" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "not a valid hex address") + }) + t.Run("should fail for invalid from address", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.From = "0x" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "from 0x is not a valid hex address") + }) + t.Run("should pass for empty to address", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.To = "" + err := ValidateEvmTransaction(tx) + require.NoError(t, err) + }) + t.Run("should fail for invalid to address", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.To = "0xinvalid" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "to 0xinvalid is not a valid hex address") + }) + t.Run("should fail for negative value", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.Value = *big.NewInt(-1) + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "value -1 is negative") + }) + t.Run("should fail for negative gas", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.Gas = -1 + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "gas -1 is negative") + }) + t.Run("should fail for negative gas price", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.GasPrice = *big.NewInt(-1) + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "gas price -1 is negative") + }) + t.Run("should remove '0x' prefix from input data", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + err := ValidateEvmTransaction(tx) + require.NoError(t, err) + require.Equal(t, "", tx.Input) + }) + t.Run("nil block number should pass", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.BlockNumber = nil + err := ValidateEvmTransaction(tx) + require.NoError(t, err) + }) + t.Run("should fail for negative block number", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + negBlockNumber := -1 + tx.BlockNumber = &negBlockNumber + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "block number -1 is not positive") + }) + t.Run("should fail for empty block hash", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.BlockHash = "" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "block hash is empty") + }) + t.Run("nil transaction index should fail", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.TransactionIndex = nil + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "index is nil") + }) + t.Run("should fail for negative transaction index", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + negTransactionIndex := -1 + tx.TransactionIndex = &negTransactionIndex + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "index -1 is negative") + }) + t.Run("should fail for invalid input data", func(t *testing.T) { + tx := testutils.LoadEVMIntx(t, 1, intxHash, common.CoinType_Gas) + tx.Input = "03befinvalid" + err := ValidateEvmTransaction(tx) + require.ErrorContains(t, err, "input data is not hex encoded") + }) +} diff --git a/zetaclient/testdata/btc/chain_8332_intx_raw_result_donation_6d1dde76b9aedaf2717a062602e38c1978482e50dd123287280a98910c1e318e.json b/zetaclient/testdata/btc/chain_8332_intx_raw_result_donation_6d1dde76b9aedaf2717a062602e38c1978482e50dd123287280a98910c1e318e.json new file mode 100644 index 0000000000..b59da44f29 --- /dev/null +++ b/zetaclient/testdata/btc/chain_8332_intx_raw_result_donation_6d1dde76b9aedaf2717a062602e38c1978482e50dd123287280a98910c1e318e.json @@ -0,0 +1,71 @@ +{ + "hex": "02000000000103569ef6089afd16181b4c11c598adf075a25a46a02631e4bac94110a899949d010200000000ffffffff767d1098665eaebe4ddf4739c7d477ef8906f680bce8c68792a870927cb9c3310100000000ffffffff80f1176ab8a3c91b3493ad8fa4e33890917a5a0e455d4bb32f493789f799145b0000000000ffffffff03b050010000000000160014daaae0d3de9d8fdee31661e61aea828b59be786400000000000000000c6a0a4920616d2072696368218077060000000000160014264e3737a5ebac54f213e06c93aed0c020ba73bb0247304402202682b12bd1df24a039ec085b7e8197f1c0e2734ac828c88ef284dc70140134e00220787728d5d0d90f6b8a84621173f521e45f0353322249eb4b788f58a6ce826e20012103f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f2150248304502210081e5b9d681f41451d1ceca624e754f30d32156559333b2f6ca00843ef24349f3022024ddb4e825fb35e63e868572eb83d594a902b98f26282e5f0a9e2d809787add2012103f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f21502473044022036944c409c6db78b857630e562decb9fea6cb8d7eec4314cf1bb7c65f86f2d9e0220061eab418c32cc5e41508eb6e84c4407727674c983bec315db4e3434384fa3e8012103f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f21500000000", + "txid": "6d1dde76b9aedaf2717a062602e38c1978482e50dd123287280a98910c1e318e", + "hash": "d2637ca277ee3827bffb11399d5b76c96f46064127ba45cc01526d5e66d5901a", + "size": 540, + "vsize": 297, + "weight": 1188, + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "019d9499a81041c9bae43126a0465aa275f0ad98c5114c1b1816fd9a08f69e56", + "vout": 2, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "304402202682b12bd1df24a039ec085b7e8197f1c0e2734ac828c88ef284dc70140134e00220787728d5d0d90f6b8a84621173f521e45f0353322249eb4b788f58a6ce826e2001", + "03f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f215" + ], + "sequence": 4294967295 + }, + { + "txid": "31c3b97c9270a89287c6e8bc80f60689ef77d4c73947df4dbeae5e6698107d76", + "vout": 1, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "304502210081e5b9d681f41451d1ceca624e754f30d32156559333b2f6ca00843ef24349f3022024ddb4e825fb35e63e868572eb83d594a902b98f26282e5f0a9e2d809787add201", + "03f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f215" + ], + "sequence": 4294967295 + }, + { + "txid": "5b1499f78937492fb34b5d450e5a7a919038e3a48fad93341bc9a3b86a17f180", + "vout": 0, + "scriptSig": { "asm": "", "hex": "" }, + "txinwitness": [ + "3044022036944c409c6db78b857630e562decb9fea6cb8d7eec4314cf1bb7c65f86f2d9e0220061eab418c32cc5e41508eb6e84c4407727674c983bec315db4e3434384fa3e801", + "03f08a94307716b27e6522c98e0b40211dc90792f135bd311effc68818d136f215" + ], + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00086192, + "n": 0, + "scriptPubKey": { + "asm": "0 daaae0d3de9d8fdee31661e61aea828b59be7864", + "hex": "0014daaae0d3de9d8fdee31661e61aea828b59be7864", + "type": "witness_v0_keyhash" + } + }, + { + "value": 0, + "n": 1, + "scriptPubKey": { + "asm": "OP_RETURN 4920616d207269636821", + "hex": "6a0a4920616d207269636821", + "type": "nulldata" + } + }, + { + "value": 0.00423808, + "n": 2, + "scriptPubKey": { + "asm": "0 264e3737a5ebac54f213e06c93aed0c020ba73bb", + "hex": "0014264e3737a5ebac54f213e06c93aed0c020ba73bb", + "type": "witness_v0_keyhash" + } + } + ] +} diff --git a/zetaclient/testdata/btc/outtx_8332_148_raw_result.json b/zetaclient/testdata/btc/chain_8332_outtx_raw_result_nonce_148.json similarity index 100% rename from zetaclient/testdata/btc/outtx_8332_148_raw_result.json rename to zetaclient/testdata/btc/chain_8332_outtx_raw_result_nonce_148.json diff --git a/zetaclient/testdata/cctx/cctx_1_7260.json b/zetaclient/testdata/cctx/cctx_1_7260.json new file mode 100644 index 0000000000..46cb2e2a3a --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_1_7260.json @@ -0,0 +1,34 @@ +{ + "index": "0xbebecbf1d8c12016e38c09d095290df503fe29731722d939433fa47e3ed1f986", + "zeta_fees": "0", + "cctx_status": { "status": 3, "lastUpdate_timestamp": 1709574082 }, + "inbound_tx_params": { + "sender": "0xd91b507F2A3e2D4A32d0C86Ac19FEAD2D461008D", + "sender_chain_id": 7000, + "tx_origin": "0x8E62e3e6FbFF3E21F725395416A20EA4E2DeF015", + "coin_type": 1, + "amount": "42635427434588308", + "inbound_tx_observed_hash": "0x2720e3a98f18c288f4197d412bfce57e58f00dc4f8b31e335ffc0bf7208dd3c5", + "inbound_tx_observed_external_height": 2031411, + "inbound_tx_ballot_index": "0xbebecbf1d8c12016e38c09d095290df503fe29731722d939433fa47e3ed1f986" + }, + "outbound_tx_params": [ + { + "receiver": "0x8E62e3e6FbFF3E21F725395416A20EA4E2DeF015", + "receiver_chainId": 1, + "coin_type": 1, + "amount": "42635427434588308", + "outbound_tx_tss_nonce": 7260, + "outbound_tx_gas_limit": 21000, + "outbound_tx_gas_price": "236882693686", + "outbound_tx_hash": "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3", + "outbound_tx_ballot_index": "0x689d894606642a2a7964fa906ebf4998c22a00708544fa88e9c56b86c955066b", + "outbound_tx_observed_external_height": 19363323, + "outbound_tx_gas_used": 21000, + "outbound_tx_effective_gas_price": "236882693686", + "outbound_tx_effective_gas_limit": 21000, + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc", + "tx_finalization_status": 2 + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_1_intx_Gas_0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098.json b/zetaclient/testdata/cctx/cctx_1_intx_Gas_0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098.json new file mode 100644 index 0000000000..a167134b35 --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_1_intx_Gas_0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098.json @@ -0,0 +1,35 @@ +{ + "creator": "zeta1hjct6q7npsspsg3dgvzk3sdf89spmlpf7rqmnw", + "index": "0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098", + "zeta_fees": "0", + "cctx_status": { + "status": 3, + "status_message": "Remote omnichain contract call completed", + "lastUpdate_timestamp": 1709177431 + }, + "inbound_tx_params": { + "sender": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "sender_chain_id": 1, + "tx_origin": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "coin_type": 1, + "amount": "4000000000000000", + "inbound_tx_observed_hash": "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", + "inbound_tx_observed_external_height": 19330473, + "inbound_tx_ballot_index": "0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098", + "inbound_tx_finalized_zeta_height": 1965579, + "tx_finalization_status": 2 + }, + "outbound_tx_params": [ + { + "receiver": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "receiver_chainId": 7000, + "coin_type": 1, + "amount": "0", + "outbound_tx_gas_limit": 90000, + "outbound_tx_hash": "0x3b8c1dab5aa21ff90ddb569f2f962ff2d4aa8d914c9177900102e745955e6f35", + "outbound_tx_observed_external_height": 1965579, + "outbound_tx_effective_gas_price": "0", + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_intx_1_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json b/zetaclient/testdata/cctx/cctx_intx_1_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json new file mode 100644 index 0000000000..6df7bcfe3e --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_intx_1_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json @@ -0,0 +1,36 @@ +{ + "creator": "zeta1hjct6q7npsspsg3dgvzk3sdf89spmlpf7rqmnw", + "index": "0xd326700a1931f28853f44f8462f72588f94b1f248214d59a23c3e1b141ff5ede", + "zeta_fees": "0", + "cctx_status": { + "status": 3, + "status_message": "Remote omnichain contract call completed", + "lastUpdate_timestamp": 1709052990 + }, + "inbound_tx_params": { + "sender": "0x56BF8D4a6E7b59D2C0E40Cba2409a4a30ab4FbE2", + "sender_chain_id": 1, + "tx_origin": "0x56BF8D4a6E7b59D2C0E40Cba2409a4a30ab4FbE2", + "coin_type": 2, + "asset": "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "amount": "9992000000", + "inbound_tx_observed_hash": "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", + "inbound_tx_observed_external_height": 19320188, + "inbound_tx_ballot_index": "0xd326700a1931f28853f44f8462f72588f94b1f248214d59a23c3e1b141ff5ede", + "inbound_tx_finalized_zeta_height": 1944675, + "tx_finalization_status": 2 + }, + "outbound_tx_params": [ + { + "receiver": "0x56bf8d4a6e7b59d2c0e40cba2409a4a30ab4fbe2", + "receiver_chainId": 7000, + "coin_type": 2, + "amount": "0", + "outbound_tx_gas_limit": 1500000, + "outbound_tx_hash": "0xf63eaa3e01af477673aa9e86fb634df15d30a00734dab7450cb0fc28dbc9d11b", + "outbound_tx_observed_external_height": 1944675, + "outbound_tx_effective_gas_price": "0", + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_intx_1_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json b/zetaclient/testdata/cctx/cctx_intx_1_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json new file mode 100644 index 0000000000..a167134b35 --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_intx_1_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json @@ -0,0 +1,35 @@ +{ + "creator": "zeta1hjct6q7npsspsg3dgvzk3sdf89spmlpf7rqmnw", + "index": "0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098", + "zeta_fees": "0", + "cctx_status": { + "status": 3, + "status_message": "Remote omnichain contract call completed", + "lastUpdate_timestamp": 1709177431 + }, + "inbound_tx_params": { + "sender": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "sender_chain_id": 1, + "tx_origin": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "coin_type": 1, + "amount": "4000000000000000", + "inbound_tx_observed_hash": "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", + "inbound_tx_observed_external_height": 19330473, + "inbound_tx_ballot_index": "0x0210925c7dceeff563e6e240d6d650a5f0e8fca6f5b76044a6cf106d21f27098", + "inbound_tx_finalized_zeta_height": 1965579, + "tx_finalization_status": 2 + }, + "outbound_tx_params": [ + { + "receiver": "0xF829fa7069680b8C37A8086b37d4a24697E5003b", + "receiver_chainId": 7000, + "coin_type": 1, + "amount": "0", + "outbound_tx_gas_limit": 90000, + "outbound_tx_hash": "0x3b8c1dab5aa21ff90ddb569f2f962ff2d4aa8d914c9177900102e745955e6f35", + "outbound_tx_observed_external_height": 1965579, + "outbound_tx_effective_gas_price": "0", + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" + } + ] +} diff --git a/zetaclient/testdata/cctx/cctx_intx_1_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json b/zetaclient/testdata/cctx/cctx_intx_1_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json new file mode 100644 index 0000000000..b31c759c5f --- /dev/null +++ b/zetaclient/testdata/cctx/cctx_intx_1_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json @@ -0,0 +1,33 @@ +{ + "creator": "zeta1p0uwsq4naus5r4l7l744upy0k8ezzj84mn40nf", + "index": "0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f", + "zeta_fees": "0", + "cctx_status": { + "status": 3, + "status_message": "Remote omnichain contract call completed", + "lastUpdate_timestamp": 1708490549 + }, + "inbound_tx_params": { + "sender": "0x2f993766e8e1Ef9288B1F33F6aa244911A0A77a7", + "sender_chain_id": 1, + "tx_origin": "0x2f993766e8e1Ef9288B1F33F6aa244911A0A77a7", + "amount": "20000000000000000000", + "inbound_tx_observed_hash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "inbound_tx_observed_external_height": 19273702, + "inbound_tx_ballot_index": "0x477544c4b8c8be544b23328b21286125c89cd6bb5d1d6d388d91eea8ea1a6f1f", + "inbound_tx_finalized_zeta_height": 1851403, + "tx_finalization_status": 2 + }, + "outbound_tx_params": [ + { + "receiver": "0x2f993766e8e1ef9288b1f33f6aa244911a0a77a7", + "receiver_chainId": 7000, + "amount": "0", + "outbound_tx_gas_limit": 100000, + "outbound_tx_hash": "0x947434364da7c74d7e896a389aa8cb3122faf24bbcba64b141cb5acd7838209c", + "outbound_tx_observed_external_height": 1851403, + "outbound_tx_effective_gas_price": "0", + "tss_pubkey": "zetapub1addwnpepqtadxdyt037h86z60nl98t6zk56mw5zpnm79tsmvspln3hgt5phdc79kvfc" + } + ] +} diff --git a/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19363323.json b/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19363323.json new file mode 100644 index 0000000000..9807bcb679 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_block_ethrpc_trimmed_19363323.json @@ -0,0 +1,2764 @@ +{ + "Number": 19363323, + "Hash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "ParentHash": "0x68dd10b119b6924c63841b744feb5ef7d719eb32b09750ed3aa74f5799a25bf1", + "Nonce": "0x0000000000000000", + "Sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "LogsBloom": "0x192b4912a3aa915370b80d929ab41a817a2c0c0d4f06a6b00e0b1356270481a0be51d7a0c239866984797f0608bed3845235054edae2eda0aef236a09d2cb9086f30d0b917f49b7bc88f410f882dc07e44820095056e489f00924508b9363760388425cc023069829a8c51871c483f41623be4ec108b7f0c8b4296fb0e48dc7186901050060301ed38c8b262010239264ee8cda181886d9d2b29284f49f232295b252013158260b3aba793d4fed00c190f0fc708759058048ea91e63101a526de212b6e32c469c47c07b18830e7e281bc1eb2a0694690c140ed4a443ee743823417da9410b3a6371f0341ce6651e92d56eeeb2ec1b3f0772280b85803a3ddc4d", + "TransactionsRoot": "0xd16909bf10de17244c1b97dc968e3b31d010aef7c1909eb49361e5aeb6a1e8f3", + "StateRoot": "0x70cce15035a25ec6fb7b412e279e7d8a879d35acc78a8f656c8c84e0b42885c3", + "Miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "Difficulty": 0, + "TotalDifficulty": 58750003716598352816469, + "ExtraData": "0x6265617665726275696c642e6f7267", + "Size": 118560, + "GasLimit": 30000000, + "GasUsed": 15821825, + "Timestamp": 1709573687, + "Uncles": [], + "Transactions": [ + { + "Hash": "0x67e061aee2f48a1fc7960638dbf3127c5774efde2d4f2278f092d9e4186458a8", + "Nonce": 67815, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 0, + "From": "0xf9cafeb32467994e3affd61e30865e5ab32abe68", + "To": "0x429cf888dae41d589d57f6dc685707bec755fe63", + "Value": 19363323, + "Gas": 181495, + "GasPrice": 119455385794, + "Input": "0x" + }, + { + "Hash": "0x3e5f951de6003ce84aa82b1f82157f614ad281e029863ff12c4bf84c99ae19eb", + "Nonce": 595, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 1, + "From": "0xe53d00e1448b91f6e2f340ffc488d50165425a3a", + "To": "0x881d40237659c251811cec9c364ef91dc08d300c", + "Value": 0, + "Gas": 314341, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x4eb1305a89543a28589e8102086c4bee6799a279ce89fc472cedbff70a5e7d3a", + "Nonce": 67816, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 2, + "From": "0xf9cafeb32467994e3affd61e30865e5ab32abe68", + "To": "0x429cf888dae41d589d57f6dc685707bec755fe63", + "Value": 19363323, + "Gas": 172787, + "GasPrice": 429690960392, + "Input": "0x" + }, + { + "Hash": "0x6e0aff179919d2b96992893f22f72e5c72ba4ff843ac78aab6b8c941f142d98e", + "Nonce": 6980, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 3, + "From": "0x57f89070186768adf90ca59215daad1525395adf", + "To": "0xa52a64501e816016030511aea0445f1a97ee6694", + "Value": 62000000000000000, + "Gas": 280000, + "GasPrice": 125455385794, + "Input": "0x" + }, + { + "Hash": "0x5c9de26fb9578d2841369ddbb467d8f682fe4ca89482cdcb60331dbafdc8c077", + "Nonce": 15130, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 4, + "From": "0x3f890f67d2da8824b13709c3656505a696d9fa05", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 80000, + "GasPrice": 125455385794, + "Input": "0x" + }, + { + "Hash": "0xd90f6c1aaa6d80fb5fe22f1dc7c435589960cf44b37cba2a2431ac2958cb4408", + "Nonce": 15131, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 5, + "From": "0x3f890f67d2da8824b13709c3656505a696d9fa05", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 80000, + "GasPrice": 125455385794, + "Input": "0x" + }, + { + "Hash": "0xcadaca13a133ade56e755417a2635fdad60c9e7a1ac8d8ffc5ad6d4505dcaf15", + "Nonce": 11880, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 6, + "From": "0xa0b9faac81f34a849d57516a0193b37ec23e70b8", + "To": "0x767af52d988d1241a346851a1b39ccd11357376e", + "Value": 0, + "Gas": 409268, + "GasPrice": 134643084813, + "Input": "0x" + }, + { + "Hash": "0xb4e0296808cf1f55bfa2650184b91968be33edac0a1ecfc0b792436e091968f2", + "Nonce": 47, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 7, + "From": "0xd4d3082bf6bcb2d8fcb1817130033ca2f97cbe2d", + "To": "0x3c11f6265ddec22f4d049dde480615735f451646", + "Value": 0, + "Gas": 519546, + "GasPrice": 125568271179, + "Input": "0x" + }, + { + "Hash": "0xd10b3c3c3dbaab6af8512657112cda5eb94f6abcdbef827c23584fb2b4320c6b", + "Nonce": 25604, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 8, + "From": "0xdf1f6587aa741ff62cabb9bed8dad062713cf3ec", + "To": "0x1111111254eeb25477b68fb85ed929f73a960582", + "Value": 2000000000000000000, + "Gas": 281636, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0x35f719aeb1cf3f4425ae81a362ea63962136dbfe2f3a987dcc60a5ed428f73a8", + "Nonce": 987, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 9, + "From": "0x5b34448faa21e46fb95110bb48ec0415fb220000", + "To": "0x120d9eed00890000bd25c59de403234039000086", + "Value": 0, + "Gas": 500000, + "GasPrice": 141065424271, + "Input": "0x" + }, + { + "Hash": "0x606d4bce84591ef438b1f2c18f22cce864453b680df246c03f8546177b60f02c", + "Nonce": 198761, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 10, + "From": "0x6046945c5b5ef5933b8e73a98a6ad7bf3e031df7", + "To": "0xa69babef1ca67a37ffaf7a485dfff3382056e78c", + "Value": 5684480, + "Gas": 246202, + "GasPrice": 119455385794, + "Input": "0x" + }, + { + "Hash": "0x74c3bc6b078859d4424d89d31ff12aa4e49ece911771b6affab3d98cd8138e64", + "Nonce": 30616, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 11, + "From": "0xf5213a6a2f0890321712520b8048d9886c1a9900", + "To": "0xe592427a0aece92de3edee1f18e0157c05861564", + "Value": 0, + "Gas": 500000, + "GasPrice": 227264531024, + "Input": "0x" + }, + { + "Hash": "0x1550674c240b0fc7df3d8d3e9d66c8e800e50eae83e0957522796a51346dc35d", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 12, + "From": "0xfad73d581c05ec6cef97c8b4917eb2b18d0167ab", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 110275185378566445, + "Gas": 565338, + "GasPrice": 219455385794, + "Input": "0x" + }, + { + "Hash": "0x1cebdc0e6022aace4b1c096e291812227a746a43da34afe1808e64cc19c6f009", + "Nonce": 923, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 13, + "From": "0x2a5c3a2058eedd84d368ef54be29860d7837a156", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 100000000000000000, + "Gas": 565338, + "GasPrice": 171455385794, + "Input": "0x" + }, + { + "Hash": "0x35d29bad018f0ca8de2a26c2d5333dcb58c005734de38d26c3a40f47863a3cc6", + "Nonce": 816, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 14, + "From": "0xb0521f39826ad3ceae124c02cbed997cffc31d87", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 100000000000000000, + "Gas": 565338, + "GasPrice": 171455385794, + "Input": "0x" + }, + { + "Hash": "0x5fa3fd241defacff0198f5e5c5b3745bd6491127acfe5712842d828788cb7b2e", + "Nonce": 277, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 15, + "From": "0x2caaf5364d41e4438f3fc109c97a5c7cf7cebde3", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 100000000000000000, + "Gas": 565338, + "GasPrice": 171455385794, + "Input": "0x" + }, + { + "Hash": "0x31a3df14b92f37d0ee12a6fe8feb894d3fcc4efc6222fb11dd7f8b9aaf81388f", + "Nonce": 278, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 16, + "From": "0x78f4d43b82cdeb46e1dd9a380671f2fe69024d84", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 100000000000000000, + "Gas": 565338, + "GasPrice": 171455385794, + "Input": "0x" + }, + { + "Hash": "0x58645cd7fc4a97f93059a45e8550ab8e5e316188e90ec246c8e8d706cafcd663", + "Nonce": 195161, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 17, + "From": "0x43e4715ae093a4c86b5ecddb52216c4f879e9672", + "To": "0xa69babef1ca67a37ffaf7a485dfff3382056e78c", + "Value": 4230400, + "Gas": 382494, + "GasPrice": 119455385794, + "Input": "0x" + }, + { + "Hash": "0xfa2fef396d1769748a61699c3585047c6c87700c2aae337e8a9fd18677009a2a", + "Nonce": 55, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 18, + "From": "0x9219b4f233de79d94cd62e66b5baed2b05ea2f20", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 50000000000000000, + "Gas": 565338, + "GasPrice": 149455385794, + "Input": "0x" + }, + { + "Hash": "0x5acf158d3f4ab4b7e97f646a1b3071042204bab5db444b39a590180de344ff49", + "Nonce": 71, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 19, + "From": "0x88a6b29bfc8bead266f67fb44337b6645d509c8f", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 50000000000000000, + "Gas": 565338, + "GasPrice": 149455385794, + "Input": "0x" + }, + { + "Hash": "0xcfb09c6139d4eda747702500d719cbec57671ae3f6f4ac57883dd07eaef1da56", + "Nonce": 47, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 20, + "From": "0x3e8f6f0b4a7603d2ea1b83f54640a1d16eb691f4", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 50000000000000000, + "Gas": 565338, + "GasPrice": 149455385794, + "Input": "0x" + }, + { + "Hash": "0xda2ff2e2e98bc504a4ee054099929dfbc510122e8781c8c72dc2cacb00aa2d0f", + "Nonce": 73, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 21, + "From": "0xe7256f8dc546969b172fc972455a4d0a6cefeda8", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 50000000000000000, + "Gas": 565338, + "GasPrice": 149455385794, + "Input": "0x" + }, + { + "Hash": "0xc6679a1b9536f1339843613769837f0d87aeb2eb2124d85182b19aed58149e83", + "Nonce": 284, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 22, + "From": "0xbea553cce883c6a3e4d5f23e13b0f7b319a15138", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 42685579649016040, + "Gas": 311343, + "GasPrice": 139455385794, + "Input": "0x" + }, + { + "Hash": "0x56488d9367d5d74aa56369ea3719c5590a8d313d7fe9f65de4b6ca614a76c9ce", + "Nonce": 307, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 23, + "From": "0x3174341b1c0d228d055788a7e63e8c67a9d3ddf7", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 500000000000000000, + "Gas": 416091, + "GasPrice": 139455385794, + "Input": "0x" + }, + { + "Hash": "0xadfcf4d68e6cd51f4344afae4c8b0ed6d03f001816aa8c8a69c6a4ef6d730fcb", + "Nonce": 69410, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 24, + "From": "0x7692527f9f291e8bce0cbbe5732bf39654eef8e4", + "To": "0x73a8a6f5d9762ea5f1de193ec19cdf476c7e86b1", + "Value": 0, + "Gas": 500007, + "GasPrice": 135099230470, + "Input": "0x" + }, + { + "Hash": "0xbc1d1e9dfb1093d794b674ad8188eae8e97b38124dc8cb76d87e78ee809cf6cb", + "Nonce": 5815, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 25, + "From": "0x1eeef40082d80952c59c0e2e3c79c9798289e167", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 40000000000000000, + "Gas": 311160, + "GasPrice": 134455385794, + "Input": "0x" + }, + { + "Hash": "0x59b694d40386bdb2b18cb984b65b9dd0c18da5d40de925802b210d27e215b786", + "Nonce": 1017, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 26, + "From": "0x2e644c2a235bbc84ab0830dce11ef9ce5a41df76", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 40000000000000000, + "Gas": 311180, + "GasPrice": 134455385794, + "Input": "0x" + }, + { + "Hash": "0x3426b2cc0a36e7a86cc4c05fe465117f136454d7544c98b628b3a6a39f32ba8d", + "Nonce": 1534, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 27, + "From": "0x23c746dc6e096961e49062a5c5f9d11bb027c0e6", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 40000000000000000, + "Gas": 311180, + "GasPrice": 134455385794, + "Input": "0x" + }, + { + "Hash": "0x63c61c6c98af38b5f09ff9577dd357c58d5bf17355b42eb1d5c9f18de04b3792", + "Nonce": 2448, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 28, + "From": "0xebb8bce00cfa36e52019b069451f3f68afc84ebc", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 40000000000000000, + "Gas": 311180, + "GasPrice": 134455385794, + "Input": "0x" + }, + { + "Hash": "0xce7ea97fc9743c04c79d7c8b39d81c515becaa30786ada174c60a89823415f75", + "Nonce": 173401, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 29, + "From": "0x9aab3f81604c683a1a0d14019fbfe15bef7aa1ee", + "To": "0xa69babef1ca67a37ffaf7a485dfff3382056e78c", + "Value": 5263360, + "Gas": 249518, + "GasPrice": 119455385794, + "Input": "0x" + }, + { + "Hash": "0xb655fa324d90b1b85327d356c2759f5851b2946108f06c6cacbd0a96024e1364", + "Nonce": 1397, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 30, + "From": "0x1b0851136b34ec2d9025553cf07b95912e2fe5fe", + "To": "0x767c8bb1574bee5d4fe35e27e0003c89d43c5121", + "Value": 0, + "Gas": 132012, + "GasPrice": 129306721516, + "Input": "0x" + }, + { + "Hash": "0xe81eb9452b2fd4eb6d29816ecdafb251c5de77b0e32cbe4880b1a2182440ccd6", + "Nonce": 105, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 31, + "From": "0x11351f5083f9c7b66e3a0699ce588b1f207ded1f", + "To": "0x5c9321e92ba4eb43f2901c4952358e132163a85a", + "Value": 140000000000000016, + "Gas": 246788, + "GasPrice": 128367202712, + "Input": "0x" + }, + { + "Hash": "0x4d61d9ea15d8f53678d2d575b465c042e72e5349db533426c0138d3a8adac1cc", + "Nonce": 17, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 32, + "From": "0x11908972aef7ae0892579fb07aaa03c082bee810", + "To": "0x3328f7f4a1d1c57c35df56bbf0c9dcafca309c49", + "Value": 100000000000000000, + "Gas": 492642, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0x1e259f48a1f95ac2ba528ca7cfb52b04484b99523eb10287f7047bccc7dd4401", + "Nonce": 9951, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 33, + "From": "0x6f12b91bf6da752ada71b7a1a622c57d8a48818a", + "To": "0x77d5f03822f94b39ad6117f6a46761ec5879031b", + "Value": 0, + "Gas": 267658, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0xc49fc0a57a362aa6c64da4dcc8a0ac689edade3719f3cdd008de1fc9c89472bc", + "Nonce": 148, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 34, + "From": "0x117f74c1f8d3e7206e472482d6f9833cbbd75783", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 274502, + "GasPrice": 123451104962, + "Input": "0x" + }, + { + "Hash": "0x730327cbd0e04cf34f51c5e84e5c65f7e4954b7debcd2b373f6ea15447192544", + "Nonce": 32, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 35, + "From": "0x74c073907b1a3ab56d4aaca9132cc8fec96d2874", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 0, + "Gas": 305907, + "GasPrice": 122455385794, + "Input": "0x" + }, + { + "Hash": "0xeeb3ce450438f8cc43b8f5345550f3c2f9826012295b150dbd379cbb2fda9a7e", + "Nonce": 503, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 36, + "From": "0xd316237cbe08ccf2149bf1a21a05e78f05426ee3", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 0, + "Gas": 377536, + "GasPrice": 122455385794, + "Input": "0x" + }, + { + "Hash": "0x9ebe8f992268aa508ac04921b98517fedabda53df282d5ba37a18beeee2f0732", + "Nonce": 49, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 37, + "From": "0xb3c16538e242258ef1e43064c17152d591c99651", + "To": "0x80a64c6d7f12c47b7c66c5b4e20e72bc1fcd5d9e", + "Value": 50000000000000000, + "Gas": 284057, + "GasPrice": 122455385794, + "Input": "0x" + }, + { + "Hash": "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3", + "Nonce": 7260, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 38, + "From": "0x70e967acfcc17c3941e87562161406d41676fd83", + "To": "0x8e62e3e6fbff3e21f725395416a20ea4e2def015", + "Value": 42635427434588308, + "Gas": 21000, + "GasPrice": 236882693686, + "Input": "0x" + }, + { + "Hash": "0x03eb8b92e17d937fa4cfdc9167aab5447e96f402abe7559348099a6e0fed2a9a", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 39, + "From": "0xf8c241556a4c2e73b17f72b19b459ec33571061f", + "To": "0x42b693abd4de7efc1d9097a2a948163c9b9d8071", + "Value": 2693925390018536, + "Gas": 21000, + "GasPrice": 224242705798, + "Input": "0x" + }, + { + "Hash": "0x6e58619e0082f6fe9a4963a6678d2e5e73b372761b97d0f63392c26f3b89ec0b", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 40, + "From": "0x6eec31ecb3e010e915e5dc93551e018adeb71b4e", + "To": "0xa816b029743e9f3fa324b46de1423372e8d35b3e", + "Value": 787254449441387, + "Gas": 21000, + "GasPrice": 224242705798, + "Input": "0x" + }, + { + "Hash": "0xe095f24c9a2be56d35ba481c2f9aa10215a5031e8ef016e1127dcda7477d7c99", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 41, + "From": "0xf07d199894494bb742393ffcfbdbc21d19d87c36", + "To": "0x470148a80221ae6809430b56e9a14285b322edd3", + "Value": 23817659792826000, + "Gas": 21000, + "GasPrice": 222968581294, + "Input": "0x" + }, + { + "Hash": "0x42d133f83d1951cbe5b9bc92b08536cbe7d0e7b14f8ffcc88f9087bfd12676ef", + "Nonce": 10, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 42, + "From": "0x971d82179274a140573144fbc17262e5bfb3f6f7", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 96046, + "GasPrice": 198250000000, + "Input": "0x" + }, + { + "Hash": "0xc2594c9d8241754dab2a835c4c5eeee86990cb123eb30b93c45248f702ec9841", + "Nonce": 148, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 43, + "From": "0xd19cc69e35b526f57f75e49ca59e8d1cb89a10e0", + "To": "0xb798a3a62f3512830c0e2857c6f91f09d512ae07", + "Value": 17099668638374000, + "Gas": 21000, + "GasPrice": 168746731506, + "Input": "0x" + }, + { + "Hash": "0x5441529fcdf25e6ce9649bc38b4c1f19a462d1fbefe9c54b4c31434b874f499e", + "Nonce": 266, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 44, + "From": "0x7af8607f7f9cba85ea9ae15367e5b21cb9ac4a13", + "To": "0xd37bbe5744d730a1d98d8dc97c42f0ca46ad7146", + "Value": 108702430000000000, + "Gas": 44934, + "GasPrice": 160000000000, + "Input": "0x" + }, + { + "Hash": "0xfb0f12ff7d65a46f4f9a66b72a83d011b828590cc2bb1665ba1908ad9a94db9c", + "Nonce": 973486, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 45, + "From": "0x6081258689a75d253d87ce902a8de3887239fe80", + "To": "0x6081258689a75d253d87ce902a8de3887239fe80", + "Value": 0, + "Gas": 21000, + "GasPrice": 159582002857, + "Input": "0x" + }, + { + "Hash": "0x04740116c87d9de054c81565d9c8d5b94058f62f03d797b3e3a2e86494cc0cc1", + "Nonce": 46, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 46, + "From": "0x06dd528b63da9a3c4fcb09849b44cd1f10ddd047", + "To": "0xc944e90c64b2c07662a292be6244bdf05cda44a7", + "Value": 0, + "Gas": 62190, + "GasPrice": 158841991247, + "Input": "0x" + }, + { + "Hash": "0x50928ad3c935e0ea4c202a57ebd6848a09213551c1e6127e5d4892ede03a8ee4", + "Nonce": 109, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 47, + "From": "0x45a2db277389a895aed69ad75cb5efd378c06baa", + "To": "0xbf9513d8d83107acabccbd6a6171cd8f0d4e5a5b", + "Value": 62470544594981918, + "Gas": 21000, + "GasPrice": 156374221955, + "Input": "0x" + }, + { + "Hash": "0x21869bc77d3df135a9de27b43916145d92e4a208727b870be26343927a5b66b2", + "Nonce": 52, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 48, + "From": "0x98b9dcd902c9a613a53185a77f7a38d6f063fd54", + "To": "0xf69daf33465e786e033ee224b43729b0253f4ff8", + "Value": 1100000000000000089, + "Gas": 21000, + "GasPrice": 143000000000, + "Input": "0x" + }, + { + "Hash": "0x46cd4eaade64ba9b72b935e34a95debe63143e6a3666f943c41c03da88a7f17e", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 49, + "From": "0xd4949c771f1d5a0e328f10317dbabae37a8ccaf1", + "To": "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "Value": 0, + "Gas": 91117, + "GasPrice": 139122674482, + "Input": "0x" + }, + { + "Hash": "0x4aec1c1594fde939e3964cd8d1c21917719d2a52d56d5c10ff60d511569651c4", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 50, + "From": "0x939e61b03f67be6eff8ab265e5f6849feaa0c644", + "To": "0xd523794c879d9ec028960a231f866758e405be34", + "Value": 2941068354303240995, + "Gas": 280419, + "GasPrice": 136516626226, + "Input": "0x" + }, + { + "Hash": "0x36df8da8a58c035b727936c65c34e3d83d5d710c1a150ba8e6b9c3080cf14260", + "Nonce": 1783, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 51, + "From": "0x8599818b8396168ebe13c2b9faa7f7fb1dd6a120", + "To": "0x1111111254eeb25477b68fb85ed929f73a960582", + "Value": 0, + "Gas": 204663, + "GasPrice": 134000000000, + "Input": "0x" + }, + { + "Hash": "0x8fdd6d624b2993467147a1ea2ea7969bd55cae09621f9fe3a977afa3388c529d", + "Nonce": 65719, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 52, + "From": "0xfe0c760cbcb9da239b9ba805f0aeaed3be84f65a", + "To": "0x63756a3c3bf677baab9d9e06457402ed05be8570", + "Value": 0, + "Gas": 1000000, + "GasPrice": 130108276897, + "Input": "0x" + }, + { + "Hash": "0x37d221a1707bafda05975f5799e656b8c03a24a2049b2c60f7afd4cac3708b4a", + "Nonce": 18315, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 53, + "From": "0xafd99a1a7e2195a8e0fdb6e8bd45efdff15feadd", + "To": "0x7e26888a56beed933418daebb4edde0590e2a984", + "Value": 14204083224211346, + "Gas": 21000, + "GasPrice": 130108276897, + "Input": "0x" + }, + { + "Hash": "0x4869289c5ff37af5fb0905377a60f2a9cbaf17f4165d0bd9ca658c02b54fe675", + "Nonce": 596637, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 54, + "From": "0x1f8f16a29251fa399d89e1005e3f95427bf5b1de", + "To": "0x98a5c0759e888a9886662310b7384798605a8db3", + "Value": 5354171678023224, + "Gas": 21000, + "GasPrice": 130108276897, + "Input": "0x" + }, + { + "Hash": "0x56974172b37d43b968b5d4ac3b5a5accc20ccb90de79fec5c6db1b46b500bfa9", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 55, + "From": "0xfbdf8c36935e56009835587f238737f685aa9233", + "To": "0x13f4ea83d0bd40e75c8222255bc855a974568dd4", + "Value": 31222857500000000, + "Gas": 258886, + "GasPrice": 125568271179, + "Input": "0x" + }, + { + "Hash": "0x24d117645ea61f2717aea38626d22c011fb7a8ac9e1304a13099b2337f3b4a84", + "Nonce": 174641, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 56, + "From": "0xab782bc7d4a2b306825de5a7730034f8f63ee1bc", + "To": "0xaea46a60368a7bd060eec7df8cba43b7ef41ad85", + "Value": 0, + "Gas": 64744, + "GasPrice": 124459385794, + "Input": "0x" + }, + { + "Hash": "0xd1c275d614ef1ce175bd3fb2c88fcec8afb43bcd94b242eeb0d6e94fb2c3539e", + "Nonce": 1110, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 57, + "From": "0x29b9991db717094739b6e7a94e6ab1aa576460fa", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 111892, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0xbb5982d2ff9c846b552f60790528de448dcd909e3634932814f0cc77650132a5", + "Nonce": 1532, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 58, + "From": "0xbec48e672be281331f67f8a4b5f4e0b0186b10ac", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 111892, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0x5ce8e597da1ff33568857b13895c7fd192021fb827c343ef104eb427b69e8252", + "Nonce": 589, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 59, + "From": "0x0af0f1e03005786d4387ca1278ba7c4b12aba966", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 111892, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0x22a6fceef51e10b7afd31cea16c6a51ebe7da491cb0cba8f0d300dad984aa4b9", + "Nonce": 2673, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 60, + "From": "0x9f341aeb1ad195e5b4d962f2186020fd3ea98690", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 111892, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0xbe273055c720921458037810fd4a0002f018ec7ea9fd183018dabcd4dd78dcb2", + "Nonce": 452, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 61, + "From": "0xb24e3340ffddba2cecdb56c3873014474c7e2af9", + "To": "0x62aad9e99c604dd84c4fcb84d7660a66ec76e737", + "Value": 0, + "Gas": 111892, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0x4dbddeb66f6220908c724aedfe9f4651435f56f25ba42a7d62c00996500ea7f5", + "Nonce": 483, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 62, + "From": "0x6c72acd0517e451e6131163cf42a6404c8f826f5", + "To": "0xdccd2cde3c7c1e4ecc84e94f588d71fab4a5edf5", + "Value": 0, + "Gas": 120000, + "GasPrice": 124455385794, + "Input": "0x" + }, + { + "Hash": "0xc456f750a62a3ccac234fe2d9ef1a0a1cfbba7d04e39812196d2e41e737eee2d", + "Nonce": 882760, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 63, + "From": "0xcfc0f98f30742b6d880f90155d4ebb885e55ab33", + "To": "0xdf59d14798c7b8989f5cb86e45963e9028dcabba", + "Value": 24622000000000000, + "Gas": 21000, + "GasPrice": 124417385794, + "Input": "0x" + }, + { + "Hash": "0x5399a19c58c07375779f0e2a9852bd6eba68b5879c64097f36f3cc93cc2a8f0b", + "Nonce": 49, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 64, + "From": "0xdb7b232352694bb0cbecc6e77681a2150ded4ea4", + "To": "0x25702a1281f9582bb409acd00be066a7b89731ba", + "Value": 26832150000000000, + "Gas": 21000, + "GasPrice": 124000000000, + "Input": "0x" + }, + { + "Hash": "0xc769cc7499a661fc1d4167fa358c381fdf382f777ced9b94ea5acf4041fc94b0", + "Nonce": 20, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 65, + "From": "0x51371da312da435024f113b86c1181212c543612", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 76208, + "GasPrice": 123451104962, + "Input": "0x" + }, + { + "Hash": "0x8dcb8f940a9e921258cdb7621c4c7ebe444f7a816f7726b4d3a36864bb7e2252", + "Nonce": 172, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 66, + "From": "0x885a632ba5df94f5ba13c1065e71279379193c4f", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 140000000000000000, + "Gas": 187940, + "GasPrice": 123451104962, + "Input": "0x" + }, + { + "Hash": "0x58e1d56d138ce3abd4f16ee758498576afb8276e5b64af802b968efd07043803", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 67, + "From": "0x5ec8acbaa4893790f3e23fc14b39139ca8554bac", + "To": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "Value": 0, + "Gas": 200000, + "GasPrice": 123423564081, + "Input": "0x" + }, + { + "Hash": "0x4e1f748b02c6823ac7a6d1438db105eb9f3c8ce6a84169d9dcc664a93a7b38c1", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 68, + "From": "0x6153a95892df43b45f23db4533e057fe11977319", + "To": "0x0258f474786ddfd37abce6df6bbb1dd5dfc4434a", + "Value": 0, + "Gas": 100000, + "GasPrice": 123423564081, + "Input": "0x" + }, + { + "Hash": "0x646d2cb40381ddbf5dbbafd77b163b087c9a11862392f6d8d4c486dce01da02a", + "Nonce": 161041, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 69, + "From": "0x2fc617e933a52713247ce25730f6695920b3befe", + "To": "0x46a4e33fc6e1b0754365b93e6e90166c0fb7f185", + "Value": 14059000000000000, + "Gas": 27300, + "GasPrice": 122455385795, + "Input": "0x" + }, + { + "Hash": "0x25246eb4b697ad930ba94c2a945c1b212673dc28fb880da234a654122595d4b9", + "Nonce": 85, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 70, + "From": "0x33b64dc680ea49b5f47d5034d00df9d9d92ee263", + "To": "0x4fabedbca907636e1a3286667a8323bcd83c93b5", + "Value": 0, + "Gas": 56406, + "GasPrice": 122455385794, + "Input": "0x" + }, + { + "Hash": "0x76cd596c3910beb9f2e381b71d595b53d7ddcfed74e8f1008ffa70b74c4fc2f0", + "Nonce": 289665, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 71, + "From": "0x98db3a41bf8bf4ded2c92a84ec0705689ddeef8b", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 90000, + "GasPrice": 121955385794, + "Input": "0x" + }, + { + "Hash": "0x0e78a1b81c236288fc89a8b5476d486dbcaafbe1376a61b4d8ffcc5785856485", + "Nonce": 1088, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 72, + "From": "0xdf714d2c3b43260269754f9fea6564504f756c9f", + "To": "0xcbb76272cac5bbcdc3b88b5b93a2ee7e3946bdc5", + "Value": 358122450000000000, + "Gas": 21000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xe43fc6373897040b8fc640cf018b788153e556a93528575033721a068b18ae85", + "Nonce": 6261855, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 73, + "From": "0x56eddb7aa87536c09ccc2793473599fd21a8b17f", + "To": "0xf69ddcab9930153836d6b37cf455cd328f0644b4", + "Value": 512000000000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x272dfacd11ea899316556677e7f08656123b5b8c79399e7eb91066406b189916", + "Nonce": 8842508, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 74, + "From": "0x21a31ee1afc51d94c2efccaa2092ad1028285549", + "To": "0xcec7834e0451065080cd3c5611b297ead307a9a0", + "Value": 148446400000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x2d4b6c267d97d94a175e8c2bda8197aec1ede6df7e33c08ec68fc040c4d14565", + "Nonce": 5988288, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 75, + "From": "0x9696f59e4d72e237be84ffd425dcad154bf96976", + "To": "0x52d733bd67699f392299e710f2346ac6337af95a", + "Value": 23000000000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xc8a7508d3f5c80d03d176758523d1954857f9c1b2054e73f210c748aac3cced0", + "Nonce": 3805063, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 76, + "From": "0x4976a4a02f38326660d17bf34b431dc6e2eb2327", + "To": "0xcaabf980e125d7cfb047b7b21acdb527c47d6fcc", + "Value": 9860600000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xca0366c656429247bf3eec2ef6d5f2b9f25b391a8cb16f6bd747eeb2523e3e1e", + "Nonce": 8411594, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 77, + "From": "0xdfd5293d8e347dfe59e90efd55b2956a1343963d", + "To": "0x37ea4264967eab64478c5bbc56b3fa547fe4aeaf", + "Value": 67860130000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x6c9dc3fea4316ced65d3365422c88d14eb0ecf80daefd5032284697d59686f97", + "Nonce": 1052591, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 78, + "From": "0xbf94f0ac752c739f623c463b5210a7fb2cbb420b", + "To": "0x361a5c359d7b67dc114bd1d708d18c8e3517e3a8", + "Value": 10000000000000000, + "Gas": 210000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xacf3259bc2b930d32f629cfacc67ac9e8e2ba869bf87edd55a425feee480a81d", + "Nonce": 151566, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 79, + "From": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "To": "0xfc6b4d139eefaf61454b814a214d45d01b0613ef", + "Value": 6073241024455000, + "Gas": 55000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x192c40daf8c946a5607664ceb307736220f958bb51cc473b3804ab72cb9afe73", + "Nonce": 151567, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 80, + "From": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "To": "0x17b27ee3070832a29c97023ef0b34f129bc97f6b", + "Value": 6073241024455000, + "Gas": 55000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xc0537e5e48798e8f3a8f8e0b6489886ef4d5e7a0d4e278f26679ebfe9dcf56d9", + "Nonce": 910438, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 81, + "From": "0xae2d4617c862309a3d75a0ffb358c7a5009c673f", + "To": "0xa24787320ede4cc19d800bf87b41ab9539c4da9d", + "Value": 0, + "Gas": 500000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x2ddd4e63a1df688c6808c497fadda1b06879967912290f08fe1f23aad975e1d7", + "Nonce": 2557100, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 82, + "From": "0xf89d7b9c864f589bbf53a82105107622b35eaa40", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 90000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xca5f253ea93e39b10548e4e8b75f6f66e9b2954f7b8595efa71e7d867fec62f7", + "Nonce": 2557101, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 83, + "From": "0xf89d7b9c864f589bbf53a82105107622b35eaa40", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 90000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x1f15bedf7d61df00ca3e6b580094868115c308bfd70118c3da1eeaa2b180a5c7", + "Nonce": 9129568, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 84, + "From": "0x28c6c06298d514db089934071355e5743bf21d60", + "To": "0xb189c4ee6030780f0698af82384e207bbe7ab0e7", + "Value": 53500000000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x144624a63ec357b396af1832772bcd8242362fde5346807904d399d1b5c94242", + "Nonce": 8842509, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 85, + "From": "0x21a31ee1afc51d94c2efccaa2092ad1028285549", + "To": "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "Value": 0, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xd28b15d21ee42f03363ff0e4817aef05dd466f4fe772ebbf1fea567ab0747574", + "Nonce": 8411595, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 86, + "From": "0xdfd5293d8e347dfe59e90efd55b2956a1343963d", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x22e332efe98f338b9996b4dc718deef3735e512f42ebd64b6b151d73a68e371a", + "Nonce": 5988289, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 87, + "From": "0x9696f59e4d72e237be84ffd425dcad154bf96976", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 220436, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x7c7811e063e6f7576e2e5becb89ede7beb76978d80884f103bba514740dc462b", + "Nonce": 881, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 88, + "From": "0x92c24932b4e5eba6223510e26ecb2d19e986c312", + "To": "0x8c1059764bfc70ff32347a85eef7e2b8baf6c1d3", + "Value": 32688440712635891, + "Gas": 21000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x759661a778ef8dc3ce963131df12e237303bc93597b9948459cc0412a06052ee", + "Nonce": 8842510, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 89, + "From": "0x21a31ee1afc51d94c2efccaa2092ad1028285549", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 220436, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x87db5874eaa392cf3c9fe2afa529e722f260e6d2c5923d1b9d745dd9411402c9", + "Nonce": 9129569, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 90, + "From": "0x28c6c06298d514db089934071355e5743bf21d60", + "To": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", + "Value": 0, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x38619bc04569d521bb134efab2e6cd8b3803f9e2ad00f093b20adcd1c7aa27b0", + "Nonce": 8411596, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 91, + "From": "0xdfd5293d8e347dfe59e90efd55b2956a1343963d", + "To": "0x4ae712667c2911fba8df6781a207f1b6756bb0fd", + "Value": 132960000000000000, + "Gas": 207128, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xe8a399c20bd75f35a1c491ce6424ddbe5367555fdc41ab633dcde466eaab572f", + "Nonce": 115766, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 92, + "From": "0x7eb6c83ab7d8d9b8618c0ed973cbef71d1921ef2", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 420000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xb431b328e245bcb4e740964352574229d796bae09a6ef16bf7e75362b6d85c5e", + "Nonce": 188666, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 93, + "From": "0x3d55ccb2a943d88d39dd2e62daf767c69fd0179f", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 420000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x8c626bce5c3c708f0d1b3ca4ae53325459e7b39f801b4091cc28caeeb1fdf1ae", + "Nonce": 33229, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 94, + "From": "0xcb38b0a64b3990d214b168418a594498ffc15a40", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 100000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x4f8f54accd7ad647f272d2d14e5209d8dac8ef830cdef8a05327772a2e2b690f", + "Nonce": 52, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 95, + "From": "0x8ff8043b428794d7f124492c4f89657aa6b8d939", + "To": "0x155c4f3fd180cedc30a691c6dd92d257f407651b", + "Value": 97785933755748393, + "Gas": 21000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0xa6f78f35c946d1596f80c2a2776af238d9faa82f730fea6700dcff4128e6832b", + "Nonce": 2041429, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 96, + "From": "0x89e51fa8ca5d66cd220baed62ed01e8951aa7c40", + "To": "0xa24787320ede4cc19d800bf87b41ab9539c4da9d", + "Value": 0, + "Gas": 500000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x7dc6366a4ff0a312a80ff6fa481ad597e484613b46004be2f3e170c16751c597", + "Nonce": 116277, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 97, + "From": "0xcc29be4ca92d4ecc43c8451fba94c200b83991f6", + "To": "0x9405e02996aa6f2176e2748eefbcedd405870cee", + "Value": 0, + "Gas": 740000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x8a3fb3732b80be74cb7c83f05d458ac20970a74fb2d496f589c3ad12e9811e90", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 98, + "From": "0x0eca921919b39918140229a9b1041dbb122a7942", + "To": "0x808d0aee8db7e7c74faf4b264333afe8c9ccdba4", + "Value": 21651336898326000, + "Gas": 21000, + "GasPrice": 121455385794, + "Input": "0x" + }, + { + "Hash": "0x0a30a5e288ead15f2742316bfddfe9c21dd84f36dd871ad08da3c80d86984bbc", + "Nonce": 293, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 99, + "From": "0xfa8c46e6a2cddf7a4e00e89a3d01117a3cc05cc3", + "To": "0xb0999731f7c2581844658a9d2ced1be0077b7397", + "Value": 10000000000000000, + "Gas": 53793, + "GasPrice": 120955385794, + "Input": "0x" + }, + { + "Hash": "0xf1f88c17ac1571067c2e3add4efb53915d0134a943e1b961acdb55c7567e2a3a", + "Nonce": 5302, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 100, + "From": "0xdfa56563c5a718d78a1bbfd426491efd29f71273", + "To": "0x5b7533812759b45c2b44c19e320ba2cd2681b542", + "Value": 0, + "Gas": 37182, + "GasPrice": 120955385794, + "Input": "0x" + }, + { + "Hash": "0x5420f6a275f0e7437ed33a6559a2e421ee5a6c786c4a125f9cb2498b8dd187fd", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 101, + "From": "0xa78c401421ca8e96d61cfb29e0b9ac35bc635e0c", + "To": "0xcb3ca3d170e6fb8d1fc76a258cb57ca20b5df027", + "Value": 10000000000000000, + "Gas": 21000, + "GasPrice": 120658863728, + "Input": "0x" + }, + { + "Hash": "0xb8123a435df3f11a8e44e3c9ff9dafc319383acc99b7161e4877b51bbc475681", + "Nonce": 160282, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 102, + "From": "0x428ab2ba90eba0a4be7af34c9ac451ab061ac010", + "To": "0x5c7bcd6e7de5423a257d81b442095a1a6ced35c5", + "Value": 0, + "Gas": 126869, + "GasPrice": 120655385794, + "Input": "0x" + }, + { + "Hash": "0xe792017cfb0577c2ae85776d45e7701ac86419abe6df77d985c6e06d0e44e7d7", + "Nonce": 26, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 103, + "From": "0xc66dc52dda062e2371a6a5ab66abe5e17dcbcf8a", + "To": "0x0b4b0e21a96b39f0df60245ae5ce696bf54201ce", + "Value": 500000000000000000, + "Gas": 21000, + "GasPrice": 120654515956, + "Input": "0x" + }, + { + "Hash": "0xe6bc80f3dcd3bc4c8bf1d6bc2bc3e9ec8a14dab351dcfe862152aa61d9437e0b", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 104, + "From": "0x707377c791fc9f9b20bf8d63571a6210fe4706b1", + "To": "0x004dc0d34488578e511ad87c336707780701a811", + "Value": 10961324311435000, + "Gas": 21000, + "GasPrice": 120654515956, + "Input": "0x" + }, + { + "Hash": "0xc98cb42fa2adc47e506f6cd8633993fc2b0cda7f30d6d3300bd9d3dda22f3884", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 105, + "From": "0xabd5e6a329937c391728445835b2a0ebcd286c9c", + "To": "0xf411903cbc70a74d22900a5de66a2dda66507255", + "Value": 0, + "Gas": 55200, + "GasPrice": 120654515956, + "Input": "0x" + }, + { + "Hash": "0xa9892ddf4da2eb58d63e0c07b3ca15901c10cde49adbe9e84e181e7c78ee5d83", + "Nonce": 12, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 106, + "From": "0x21dd80edd41243d2600172f574cfdf26b2abfc5e", + "To": "0xed35af169af46a02ee13b9d79eb57d6d68c1749e", + "Value": 0, + "Gas": 59128, + "GasPrice": 120626933937, + "Input": "0x" + }, + { + "Hash": "0xf33709efbcc6227d2db5c1990cbb237207e089c4c66ca984b916f8a62b7dcca5", + "Nonce": 10, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 107, + "From": "0xc0a67e8731ee9f4a92f285270d9b7ee4ce040245", + "To": "0xf6ba6e574f533d90e02f7b211040dc26aed05f85", + "Value": 2058375412118626011, + "Gas": 21000, + "GasPrice": 120626933937, + "Input": "0x" + }, + { + "Hash": "0xff0f4ffaf00351bd4893ab7869ebb5c90c2008157bdf0f8150bd60272b6be95b", + "Nonce": 52566, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 108, + "From": "0x7c29378c480a409648cdf5d72b7277077aa04560", + "To": "0xa3419fb152f8b6bb611295893a1667706c244218", + "Value": 0, + "Gas": 198847, + "GasPrice": 120605385794, + "Input": "0x" + }, + { + "Hash": "0xeb6df81654ef4b41a54013f1b57f9df07c02a119eff9d5d2f8b11bb8b7f7fe16", + "Nonce": 22159, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 109, + "From": "0x9ad4efaf9e326c17c3a7be6f5d167843af0eb30a", + "To": "0xf7134ce138832c1456f2a91d64621ee90c2bddea", + "Value": 0, + "Gas": 2000000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x9b82a755c5c663b5f66904a3c8321c0b58abe65755d24cd2f527a06d7e9b8e51", + "Nonce": 98625, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 110, + "From": "0xdec815281519f6cb080090317e0ba3e446fafe43", + "To": "0xa168bdb04ce4155b83a85b2d810fba9a8bdf0ab9", + "Value": 19838220000000000, + "Gas": 90000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x222c08352b744cbb0db9d74e595e9a3bbeb0a54f5c24f167ca58615068ee9f06", + "Nonce": 432875, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 111, + "From": "0xe4edb277e41dc89ab076a1f049f4a3efa700bce8", + "To": "0x0e7a924d990f543c9810efee6182289fc7bd091f", + "Value": 537350000000000023, + "Gas": 100000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x695626bd81f4b04274a2a94eceb2f90e0bb9d73944b79efb62b72cd9749266af", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 112, + "From": "0xc67019541296ea9253642516a4f48038bec5fe3a", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 598262732427302505, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x68d8f5cb4f670f3262b915b706de5350d2b5e18722eac00c9fb2a44a2a0c6a7e", + "Nonce": 3, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 113, + "From": "0x82702a3b3d38eeaeb0bd47d6e7ba3a32f4f64858", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 2759160199716891000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x3e90cb9a95172743374b8a26e0b4a2ad525ada0d2474f2af54a8978802168674", + "Nonce": 4, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 114, + "From": "0x316cb17916ce6e045ffb6fb85c2a2d73200cc8bc", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 550000000000000000, + "Gas": 238975, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x9061b8d7649d7c15c6d83aa3d237cb434b28360e857026f0643c2db81e208c92", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 115, + "From": "0xa24220df7bcb9faa19bb4f6b53ebea67c1674530", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 67352795920881190, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x88d4fb34c1dc30a6f0ad73db5c153aee2059ed175aa766171ee979f921fc3489", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 116, + "From": "0xd39c72d2268344c411c9550b0350fb3b12b5a22f", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 295961061764733665, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xbd20c990ee8e829799ba75943e751c4981a08da9754d91b40549e6fe10e90e03", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 117, + "From": "0xf7f579257e863cb731e3f60e30a12340855166e5", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 52514117411456000, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x93d62d286cda1ffabe9a72af5af69362d95020929887953e8c1544b9189c6914", + "Nonce": 5, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 118, + "From": "0xe3fba5faa620dfb3e6f7f1e7b8ea71f21f5d2846", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 47609721611969345, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x131bfb9b849302d5f8cfe25504d081e1e7e13d1ebe5f86eee4f50cddd682527b", + "Nonce": 47, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 119, + "From": "0x3970ee7d9d061f12690ec25fed1059a13910f170", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 220825642497478000, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x074e38b7816e9eb1a4fcfc813d87ab51a06914b52ecbeb15fc1677593bbba4ca", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 120, + "From": "0x329bc08a87b3f8eabee9704f34fe5e2092d8c87e", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 169650000000000000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x2e8fe76efd5d44e2932d1313573c4a20db7ad9444a9d38efb9329b4218e3f1ea", + "Nonce": 45, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 121, + "From": "0xa7ccdd7a669638daa01dbabf7c6c137131301224", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 114335106246697170, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xda374dbdcc8c99875a4ed5337dcbd4735fe85dd5036610e7f37834d1cb447231", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 122, + "From": "0xb843c946ca130a26cdd28fbdec959c800f046520", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 65078210006075000, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xbacfe7812a764c67b419f3ac6ba6a72e9a5ea3214fd8ac75202a4d61a9bcca11", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 123, + "From": "0xb481e50576f325c67e8482ecc1c6eb976c52f3f9", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 846074700554818200, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x4cefd7e4d02fc7446bcd468715068d2c06f262746249df55b9bd0927cd66e7bf", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 124, + "From": "0x40f5dd6fae7b6471ad424efbca0559ffd6ee1e02", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 11171155389268000000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x322efda492ae6d9e316986c88a54b5e73c4a126587dcc7fbf46a3552720e4348", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 125, + "From": "0xc12f6f644c9306ffc57df8516df641e97f66f6b7", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 11415823349337530000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xd91c6f7572d6db1811d30ebb6081a25e901c6189f35d3672800b5f605f84b26f", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 126, + "From": "0x0cadf3e1880e9e54c4838c421ab6e8b2ac87a35c", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 797507990946929870, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xc79e65a92866c0df61fad5970e9abaad23e68ec93a94ac97fc7997d4744f4c0c", + "Nonce": 3, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 127, + "From": "0x2ba8e25ddf94ca397c77bf36d4cbad693fccd1ed", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 277295903226178810, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x9ba21b86c4eddda9ede93ce1e7609b6437d7baafea68660f31d92568f8563419", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 128, + "From": "0x3476c5d3d2cf8a4364f0081900c68f95372d14df", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 8260048108902456712, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xbe6e0f0d198c6665d151856c3014ec7deda6f2652100977181a2a4e1df525fab", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 129, + "From": "0x12b6c649f368ec8e5f657bdf9cd3ed01f7952cf1", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 265105181910827000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xabc8c34614ae21c0714738dd85f414dd87c201a98952d75af24eb3d01bc8b1dd", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 130, + "From": "0x87d3ad58faef7a91ffe391219e7dff72137873cc", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 83469242429703939, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x0457784cf2748a82116caf4ed83dc5abd6a9a9478c4ccd28fbec6bfb02dd2dcf", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 131, + "From": "0xb46dabffa72f30e5dd6672c68ae93f196d2cdef0", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 144489896273117144, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xada0415c16434b6df50da4d6d25480f3ad991550cf33a5dc86852b2ec8837b5c", + "Nonce": 110799, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 132, + "From": "0x4675c7e5baafbffbca748158becba61ef3b0a263", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 374857825023165682, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x4973d8544eb842c6aa60e6f6e890716500315cf01d40490742e6b85f9a2a89fa", + "Nonce": 3, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 133, + "From": "0x197ee9a6aac3bebb69ae8f31ace8c4bf61ece993", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3671326981119205, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x408119fed9a9b3387e790b0b0d905a8a83216532c2c76a628e7c5b4c69b97348", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 134, + "From": "0xe5ca3ea7ce0d6c332139de328f56391f86f69ac6", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 42419450799280810, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xa09603b1eb3c40f3dd17f709c6c70ee75c28a168993c3dea274240b6226a46f1", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 135, + "From": "0x4a4297f195da1fdf7607bffcee5d8b04d5fd264b", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 21066357931993618, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xf8d6fd2d6f1dd8bc5d969e474be7d93a845895dfff7d5711fe7b3ff41bc0bcf7", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 136, + "From": "0x565065779b416e7e31e4266f302c3ee5dac75c06", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 11077584284940565, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xc1b52b2f1e1568fff0049325dca07fba4cc40521a58695a83eafcb0bb1f67e8a", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 137, + "From": "0x3c1fada628cf02f73ec6151d77a6d4771ae98225", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 3469117097159000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x78a8895119b2bb1668c564acf43276d642930f64959ee80581d58f867b2738d8", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 138, + "From": "0xb51fc6eff02c683c7e4d148a199c21810992d43d", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 9014783079639000, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xbcc57e5f1dcaafad9feb07cfa6ae29d2419cde94b2a4b0b1cd865a18128c2437", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 139, + "From": "0x33d7ff799264a0f78c3526773de78670a940d9e1", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3962116934441960, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x472e2dd3cf6486617c07027d3df88ae0ce203a330e48ad763087082727a7358e", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 140, + "From": "0x2201021b5127f0d4fa042c15975467d589e73196", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 2825395121662000, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x1c9396a225e6cb8c94fcb5d516eaf8cacd55e629ce1c0a8f18bb41615b84f1ab", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 141, + "From": "0x4e41a8c9edb96b4360b089708e7c27d49588aa85", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3146270656100375, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xac61c48bfae9ae2540713a9e64eabf3e060e2f98368c02765bde49e9343a4348", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 142, + "From": "0x64bd1d4103568d5b2312a13e699cbc1a34872a4d", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3577794732265604, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x2c64d2d39cdf5319977a38af0b21be4099486363279dd602886bc9c56c793074", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 143, + "From": "0x7b7a215694b4c80516a75049c213f1f4b69eb1c0", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3298505200729002, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x3cdf72bc01a3bf7e67239817fec86f50873c777c993c8c61fbeedf73a053c00b", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 144, + "From": "0xcbd0f2a77abb82549aa73a51ba8c5d26c327d875", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 33470275604797140, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x39e13a9b456e2d7bf2852f2d851943cae3b1876bdcd53b1dc2d6caf2050c58df", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 145, + "From": "0x59642331e2db256e273a0ad1b3af67484dcfea8b", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 40672716887404000, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x78b55acc26b469dedde1e66b541ba68fe46a5dba9ba4830cbb3877dcbf633167", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 146, + "From": "0xf003876d248e2393f115a61161e711265e0fcff4", + "To": "0xb739d0895772dbb71a89a3754a160269068f0d45", + "Value": 5583346550868067, + "Gas": 21000, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xbb6f0fa33a643242f5cac4b36b1d89bd266578247d5375142ae14bc53198d1c7", + "Nonce": 1, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 147, + "From": "0x2de9818952bc4ddf3f74fd4106ccb1ec99ec043c", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 4968534333424920, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0xa34f08751fa5304e592641e815d8d7da69775d234acdb75face70433ed65c11f", + "Nonce": 212, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 148, + "From": "0xa65c37fbfdd3cf091082f36ecd0c8cc99677c617", + "To": "0xa9d1e08c7793af67e9d92fe308d5697fb81d3e43", + "Value": 3655191502374135, + "Gas": 23300, + "GasPrice": 120455385794, + "Input": "0x" + }, + { + "Hash": "0x45235f31ec52bf41d000c4105f737e0398408d3410589f1d6bf2c3a9caa0037a", + "Nonce": 2350650, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 149, + "From": "0xd2c82f2e5fa236e114a81173e375a73664610998", + "To": "0x8d1f2ebfaccf1136db76fdd1b86f1dede2d23852", + "Value": 0, + "Gas": 171130, + "GasPrice": 120361239666, + "Input": "0x" + }, + { + "Hash": "0xf807648c0c23be77caf9e25954d48918c9fab0389ebb430fc18e5b59b45ca99f", + "Nonce": 722, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 150, + "From": "0x68a97910b7582428e2b7c28b8744a51d88487e48", + "To": "0x243cacb4d5ff6814ad668c3e225246efa886ad5a", + "Value": 0, + "Gas": 69826, + "GasPrice": 120295385794, + "Input": "0x" + }, + { + "Hash": "0xc948f7d9adc6e424326d1835d04e52bcb660192671941941cf851229e51c956c", + "Nonce": 14, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 151, + "From": "0x283b0f2eb0ca757e7796d269c2e9871993d1074b", + "To": "0x77d228403921ad44fe0f8055bf2d39ac901e77ce", + "Value": 1000000000000000000, + "Gas": 21000, + "GasPrice": 120295385794, + "Input": "0x" + }, + { + "Hash": "0x887b6b0c07464cf95b17cc6f8f2e568e061701d24d9a2b0d54816ad281448671", + "Nonce": 4346202, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 152, + "From": "0xd24400ae8bfebb18ca49be86258a3c749cf46853", + "To": "0xbdf12f677001ee4ca487944eda034b5207bb6ab2", + "Value": 995210000000000000, + "Gas": 90000, + "GasPrice": 120231852978, + "Input": "0x" + }, + { + "Hash": "0xf12036a3aab0bebad4b899d851ea169483b8a373e83886b611d5dfa8967b7508", + "Nonce": 10716655, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 153, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0xd97cef0846066c945dbc042f584964a5adc47fe3797403de68f2dc2bb24679bd", + "Nonce": 10716656, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 154, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0xa1da3d234543f5828a0d907b4b5dbe916aec37d7d6af223d437bf39b57a52054", + "Nonce": 10716657, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 155, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x9964c8f5ea005483f8b9974a8d7899a30939084aa838f980416a975ffed4c73d", + "Nonce": 10716658, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 156, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0xe22af48c3033770b47f04a6f5c68b177fbe4e4871211dee77e9e7a4df0def5c0", + "Nonce": 10716659, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 157, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x547998b8b977a2e8e30c57d50147b07fd75e9cfb1cd3e10c017726542455600a", + "Nonce": 10716660, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 158, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0xdf2856fad5d2a8be9de6f4889b3fd30d44450c752d4b0178b1e00a21d7d68c14", + "Nonce": 10716661, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 159, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x5bc8e009cce7ac968b0e1d9e18c66d15b5dadbf5de815e07d779c7571d7952e9", + "Nonce": 10716662, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 160, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x6500ee4c9636fe698630c15b6c73f0861d75483f33a60a621655e610a5a71f17", + "Nonce": 10716663, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 161, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0x72b11b3f072f337d491e53727e743b507c76f09e", + "Value": 1650290000000000000, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x55d4c41dd5eb8eaeee9d03392f83ba6ea1ef58e29d1b68be04f138101b5b2ef8", + "Nonce": 10716664, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 162, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0x53e376bf4eb6b4fdd8baaff3325962abfc255921e315bfe08e7a8bc18599bd8a", + "Nonce": 10716665, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 163, + "From": "0x46340b20830761efd32832a74d7169b29feb9758", + "To": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "Value": 0, + "Gas": 350000, + "GasPrice": 120134631079, + "Input": "0x" + }, + { + "Hash": "0xae6d7948252a1d1a36cf000488021befe32395169c4fd399a7b681dcdd22837a", + "Nonce": 8, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 164, + "From": "0x4ddca69a3101e777dabd25ffcfe8cf20ad73fa1b", + "To": "0x0aea341cad9921e8277c63ecd281bbcc6d1fda80", + "Value": 1000000000000000000, + "Gas": 25054, + "GasPrice": 120000000000, + "Input": "0x" + }, + { + "Hash": "0xc832b9ec4cdf4653a053da266abd2cca3b28f5548d76633919d099fcd86ce022", + "Nonce": 8, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 165, + "From": "0x7c8b4ae60edf95beeba95b9550d32d0a2a07ba74", + "To": "0x2d3ba037065eb7adc2c0cc44759eea3210aa8782", + "Value": 28400000000000000, + "Gas": 21000, + "GasPrice": 119682363525, + "Input": "0x" + }, + { + "Hash": "0x12948b6edde270e70b62b57b289ef38b0f3e741fe1699e0cd69fa83418542556", + "Nonce": 2223, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 166, + "From": "0xa168b8d6e7c10800373055dab3241c6dd6945b68", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 63440, + "GasPrice": 119575385794, + "Input": "0x" + }, + { + "Hash": "0xee56e85913e69384214c0b7faf812e64bbb87dec25e16b302704ef476daeadfe", + "Nonce": 32979, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 167, + "From": "0x22a82147a80747cfb1562e0f72f6be39f18b5f76", + "To": "0x40864568f679c10ac9e72211500096a5130770fa", + "Value": 0, + "Gas": 12000000, + "GasPrice": 119555385794, + "Input": "0x" + }, + { + "Hash": "0x8eee06aeb95744838f25cf70a568342a8e10c34e472ee2555f32fcc88c9e96ef", + "Nonce": 32980, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 168, + "From": "0x22a82147a80747cfb1562e0f72f6be39f18b5f76", + "To": "0x40864568f679c10ac9e72211500096a5130770fa", + "Value": 0, + "Gas": 12000000, + "GasPrice": 119555385794, + "Input": "0x" + }, + { + "Hash": "0xf3b766fb7167919c526cfaab4f30ec2a25b02ee1c700e152e97789fd6839ab03", + "Nonce": 184, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 169, + "From": "0x6fe5835c929925ca390a308934dd0fec1c04a8f2", + "To": "0x0000000000ca73a6df4c58b84c5b4b847fe8ff39", + "Value": 0, + "Gas": 215397, + "GasPrice": 119554385794, + "Input": "0x" + }, + { + "Hash": "0x7f872c708b9f97bdc865b2ff2c87c0cfe8270f3897c0e039dbda2ed55e4b34ae", + "Nonce": 4728, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 170, + "From": "0xc674e7d16e6e5defc599f250fca70c2f9b37ae34", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 56373, + "GasPrice": 119545749830, + "Input": "0x" + }, + { + "Hash": "0xef7a68c5562cb0fa28ec324de433f64bc84a22975ed86b0cb2ff1637afdff6d3", + "Nonce": 198, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 171, + "From": "0xa651c309aedecd6e6347c9ef390afd18986f25ad", + "To": "0x9417dcfc0af8dcf6ca3b70ede4022e5d45ebae0c", + "Value": 1300000000000000000, + "Gas": 21000, + "GasPrice": 119545749830, + "Input": "0x" + }, + { + "Hash": "0x1ab7211467bdbc5fdfd8191565e3d02b9b12420bb9f496c1f99db49b5d6f83dc", + "Nonce": 26, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 172, + "From": "0xaa0f9e3247e47ca70f45ad38ed1afc2a1f648294", + "To": "0x4a0bc0ea5a4680a15bf4aad94344f5a92694dc81", + "Value": 40645844265751023, + "Gas": 21000, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x0ecf1df84d9550012b231c2b28583e2705a770778a34679626193ae755d7b9d5", + "Nonce": 88, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 173, + "From": "0xafdb23c9200624a3652d66d139c5e3b29de850ca", + "To": "0x1a55953bdd72efb74cdf785796a70fa3177be57c", + "Value": 3000000000000000000, + "Gas": 21000, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x9815950608426da81c07ad7c3c8aac57bf80dcd2993ecbf4060889c472297e8c", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 174, + "From": "0xd27dfd1065ee45cc0a8224041c4fffc347c01126", + "To": "0x3a05e5d33d7ab3864d53aaec93c8301c1fa49115", + "Value": 32000000000000000, + "Gas": 210478, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x4758cc7ef5fd32161bfe44770d661d92b2da529239d4c9f3d58ae0f995ef9143", + "Nonce": 66, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 175, + "From": "0xa235359ccf88da7d75c753b7884efb74ff813577", + "To": "0x017e9db34fc69af0dc7c7b4b33511226971cddc7", + "Value": 0, + "Gas": 47579, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x1e5d933db9fc4d6df42de727d240e5ee0349074016786a26e8ec94e9821e214c", + "Nonce": 142, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 176, + "From": "0x3e7a3f589f87cbd979624a7a576cba3335c132a7", + "To": "0x031745c9008853eb468478e0820e00a90abf564f", + "Value": 2659835174919000, + "Gas": 21000, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x99838ae042595a9504e8f6ac208b31c17a0ec2f98f8eb1082dbc39f31790afcb", + "Nonce": 954, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 177, + "From": "0xa1f276ff68efcd3aea732bd727a8565e675c1e04", + "To": "0xb8d3faf3f33fb61c62a611dfd061bfad7073fb8a", + "Value": 100000000000000000, + "Gas": 21000, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x31e02f7789727731a1bffb80b43f800c441a5b59b8d702fbba460cc6fbc4b407", + "Nonce": 3182, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 178, + "From": "0xe0f94903b58dfb0afb14109fac17e2c41ed78f47", + "To": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "Value": 1570630000000000000, + "Gas": 45418, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0xae4ff4a11e08833039a36c9f0342f08b39381ef6b4b3b582e6767fa5fee510a9", + "Nonce": 50, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 179, + "From": "0x47948484caa53209d76db7d811888d8a0dbe9403", + "To": "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + "Value": 0, + "Gas": 52254, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0xc060f52bf3d62f17f67f48779a486abdc8ab481842990f22a2ff733420fa5eb7", + "Nonce": 716, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 180, + "From": "0xcf04b3328326b24a1903cbd8c6cab8e607594342", + "To": "0x880db4a880d48a397524e327b4978f6a8022bbf1", + "Value": 50000000000000000, + "Gas": 57130, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0xb9e5fd89ceac762c4b1619c3c00369d6d73405ade46d7ad89d2a8dee9ebd99fd", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 181, + "From": "0xfaed24772e81a6d2adc3ccc81faeb9ebdd6813fd", + "To": "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", + "Value": 0, + "Gas": 81633, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x38534ea9dafa9c0bcc813c07653ccbf422da4e04bda0d724c110ee2241505455", + "Nonce": 16, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 182, + "From": "0x0c53d710c28901bc7c2c38216fa5557034b36fa4", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 49225, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0xf6d7aab081f00dd5383522d05355a6a84d93b3e5b562aa96dacdff38efe7badc", + "Nonce": 42, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 183, + "From": "0xa751f7069e5633cd82b76af94c0c9452d7b19b75", + "To": "0xc5d27f27f08d1fd1e3ebbaa50b3442e6c0d50439", + "Value": 0, + "Gas": 46630, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x4c25b43eadd97ed7b97686f760a8fe191c05702d711e4a00f374fd421dc88877", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 184, + "From": "0xe3b716a7f13c4bc7a4e1eb0d1497a6a8dc16a260", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 56373, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x9c0a95a16240f67c8703694f6e635c5146a6b3e1566575791c02b831ecad1bb2", + "Nonce": 402, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 185, + "From": "0xe92ff059c4d0bf5dc410b19d01e075f2cfab25e1", + "To": "0x1151cb3d861920e07a38e03eead12c32178567f6", + "Value": 0, + "Gas": 62318, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x1e77520d5d57d6e0c31e8ce3fac875c39bd50ab804e2adfc2d8d8e6b5d9273ca", + "Nonce": 150, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 186, + "From": "0xcfea44cacd8d3a75a9815146768f5e0244b88ce9", + "To": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "Value": 0, + "Gas": 69651, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0x54a3c086524a106ca71894b09c5359bb5992c4df0c127804aab444dff23ca7de", + "Nonce": 37, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 187, + "From": "0x609e4521a3fbfefb571904e6173425e558cc8ffe", + "To": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "Value": 0, + "Gas": 46410, + "GasPrice": 119542685794, + "Input": "0x" + }, + { + "Hash": "0xc93ac89da02a3beb40b0b59a17edb091de4b48309c828be772aef40a20a7a3ab", + "Nonce": 1236, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 188, + "From": "0x92ca37174eabb49849f4125a9277694816e659e7", + "To": "0xf25304e75026e6a35fedca3b0889ae5c4d3c55d8", + "Value": 0, + "Gas": 112792, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xd519c559f19989fabddeeee5a0957d24c02a339b09c43998197f9e339d6a5924", + "Nonce": 660, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 189, + "From": "0xb058f7b915b4f06e2c74085ccb3bff8c29db3b11", + "To": "0x514910771af9ca656af840dff83e8264ecf986ca", + "Value": 0, + "Gas": 44481, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x6b9e6835774d83a03e6aad31b8d896e725ee7dc70699ac9c2b27194b17ff07be", + "Nonce": 151, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 190, + "From": "0xc284a74386d5a91816e16912c02ccb8558152861", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 41000000000000000000, + "Gas": 243564, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x8c919f9a0203cd02a9f0d0514dc3b1c9566afa060d0503f9c5dd1ca8ee80704d", + "Nonce": 313, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 191, + "From": "0xe19beba11f40fdaaf12fa6fe52ed74da29df9199", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 100000000000000000, + "Gas": 168770, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xc6563986b3c5c95e974a782b4832a1c95e4af4ff95f07745d6338b752d937897", + "Nonce": 108, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 192, + "From": "0x0f1236ccd090bf64506df5a7855299a4097720c7", + "To": "0xc97d6ce45a296030d1eda15ff32180d9449ee392", + "Value": 17446764776422110, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xff26aefb4d487aa81e833a9614c7d8e7c9072ae711ae59b7b0b5ac63f5e8ba58", + "Nonce": 90, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 193, + "From": "0x5cf5e2701567bc35b10f493c1b823efa4624c1b6", + "To": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "Value": 0, + "Gas": 96064, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xe4eb5a5433e0ad11d4fd7f756b8c4249ee3183e0dd0215342ee02ff8ad49566b", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 194, + "From": "0x57b44b3bd50fb3a2aee204ab80ad6a99e7cc42aa", + "To": "0x12512a46d971bd035d9466246bc46daf389e281c", + "Value": 0, + "Gas": 70516, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x47ad4fdd2a63b4de59df9fd8a346352abc2a3d18128c759a315ed9121bc6a9ba", + "Nonce": 0, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 195, + "From": "0xb473bf323b5bd7676dce65079d2ba00bd6aea034", + "To": "0x3f4810046593ba17a1b025a3f9a7296bd13552ce", + "Value": 6711417140347000, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x6e123d852fac59f86320af1932bd072b7927557000bc2851c20a0fc7fed73493", + "Nonce": 46, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 196, + "From": "0xd7dde9d47ba21a7db0073898f46b8eb0759eb83c", + "To": "0x3b8f6d6970a24a58b52374c539297ae02a3c4ae4", + "Value": 0, + "Gas": 111464, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xafb77af592ff617b1d381ffd6231509b5214a375302943d26d0c99c8ceea48ed", + "Nonce": 2, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 197, + "From": "0xa38cd80ebd18e6d7bf979b7bcd7d5551149f4a40", + "To": "0x4a1a52e33bbdcf67af3a22dbd5614a4bc9c8bc81", + "Value": 33958260000000000, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x7115f51c1cd9a3438417ca8983821096033024d569b7c6798cd150337953c4cb", + "Nonce": 5012, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 198, + "From": "0xa51e748875bc57439637afe2968b3fcfe7d78d96", + "To": "0x078c4adf3fee52eb77f6018d9805dfc69e911d39", + "Value": 0, + "Gas": 70069, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xa3a378e38c3b2d1d38a722af1ab122fdf4ba65e8153c5d149d6ec66d51fc489c", + "Nonce": 209, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 199, + "From": "0xe93867f9d40b3de00b20c84766cb5b7687fa70de", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 2630000000000000000, + "Gas": 241410, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xd02e5bbc07966f4af62ac6744cc4fdd3996e2b5074e12cd9cea672ecc3b287aa", + "Nonce": 2898, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 200, + "From": "0x879c405922ffb8251bddf7df06e02a58b10837f3", + "To": "0x802489fe61792107f42a0e1dd1e988c0af8ce193", + "Value": 0, + "Gas": 166344, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x8b3565f10667c2c0fda11f0be90c1b775b1450c56d381dc5704587880ae6e509", + "Nonce": 722, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 201, + "From": "0x00db854ed1366f73d28dd89a85c9e481ddd85328", + "To": "0x9040e41ef5e8b281535a96d9a48acb8cfabd9a48", + "Value": 0, + "Gas": 93308, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xe511e4aa70fcd1cf31770d3e0c690af79cabc2aa8255749ec71e78413d7cca0f", + "Nonce": 62, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 202, + "From": "0x06b6542f6732b6c96214e4119b36b58d9b736ed3", + "To": "0xd8250e0c0a35be3dfb60dc4a3a729377fbb6e2f8", + "Value": 20000000000000000, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x5a8f3c5ed7f611e3807647f3dc41651b2d0f7806a1171a317d7b0c4f58a9759c", + "Nonce": 1255, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 203, + "From": "0xf81ca0c17423ef1e918daee6035eeabb99401a89", + "To": "0xd16abc4fac3ba48d747c9454a52e98a5f05476c3", + "Value": 139728033355876123, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x39a20c52a764ce5c76a0af7ec8bda18c748b5770a8b1d7c147dbab73a655ee69", + "Nonce": 106, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 204, + "From": "0x235a063b48c19a32cd3289802a326b3bbb667e8e", + "To": "0x8ea85cfe287d88f3cc8c292735ede45e6ba9320c", + "Value": 990000000000000000, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xeadd0a366e74ea10582d816e21cb77564b6cdba55ca2d7bade18851916549c9d", + "Nonce": 9, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 205, + "From": "0x129455801b7aa1bc8db4812be47d374070f418f5", + "To": "0x3279397fd6fd2edb38d64beca965ad9277666449", + "Value": 3124263238444321, + "Gas": 21000, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x52564b1d6c74974463db4316de7636a58107820e156cea88c75a966c8d26084d", + "Nonce": 364, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 206, + "From": "0x3473b873e8f75d670766844876328129e8b887f0", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 100000000000000000, + "Gas": 210352, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0xa7b718b2b86690c21548597d036acf93c07f44098134b0d94e0dcce00e0cfa3c", + "Nonce": 36, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 207, + "From": "0x0ed8e782415d51eb7192cf0fce9914a5ed23bce1", + "To": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "Value": 0, + "Gas": 94584, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x610691b62aed66cf6628f147be011597432f4e031236f6079b20f03dbd9d0b30", + "Nonce": 7, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 208, + "From": "0xe5819215bbc37259b02935db6331ce0abda64509", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 0, + "Gas": 248470, + "GasPrice": 119542144489, + "Input": "0x" + }, + { + "Hash": "0x28060a7b44c7527b900227738b9327dbea3071941971107d24b06de4952197b0", + "Nonce": 3654, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 209, + "From": "0x6293e32b8f6725bdc460e962ce94d7a5417a14bf", + "To": "0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad", + "Value": 35000000000000000, + "Gas": 229495, + "GasPrice": 119540066794, + "Input": "0x" + }, + { + "Hash": "0xbde53ea1d1d606c0ef660d8d6475958046773cde62149ccbae4fb4dc070c581c", + "Nonce": 739040, + "BlockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "BlockNumber": 19363323, + "TransactionIndex": 210, + "From": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "To": "0x4675c7e5baafbffbca748158becba61ef3b0a263", + "Value": 237256584842946789, + "Gas": 21000, + "GasPrice": 119455385794, + "Input": "0x" + } + ] +} diff --git a/zetaclient/testdata/evm/chain_1_intx_ethrpc_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json b/zetaclient/testdata/evm/chain_1_intx_ethrpc_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json new file mode 100644 index 0000000000..6c64d67bb7 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_ethrpc_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json @@ -0,0 +1,13 @@ +{ + "Hash": "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", + "Nonce": 1731, + "BlockHash": "0x393e2dd5669ce148982dfaa608f879d8c123aff2455c3826a2dd82717368d3bc", + "BlockNumber": 19320188, + "TransactionIndex": 112, + "From": "0x56bf8d4a6e7b59d2c0e40cba2409a4a30ab4fbe2", + "To": "0x0000030ec64df25301d8414ee5a29588c4b0de10", + "Value": 0, + "Gas": 121195, + "GasPrice": 56374360969, + "Input": "0xe609055e0000000000000000000000000000000000000000000000000000000000000080000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000025391d20000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000001456bf8d4a6e7b59d2c0e40cba2409a4a30ab4fbe20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/zetaclient/testdata/evm/chain_1_intx_ethrpc_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json b/zetaclient/testdata/evm/chain_1_intx_ethrpc_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json new file mode 100644 index 0000000000..8d3063b08d --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_ethrpc_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json @@ -0,0 +1,13 @@ +{ + "Hash": "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", + "Nonce": 18, + "BlockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "BlockNumber": 19330473, + "TransactionIndex": 130, + "From": "0xf829fa7069680b8c37a8086b37d4a24697e5003b", + "To": "0x70e967acfcc17c3941e87562161406d41676fd83", + "Value": 4000000000000000, + "Gas": 21000, + "GasPrice": 47562901998, + "Input": "0x" +} diff --git a/zetaclient/testdata/evm/chain_1_intx_ethrpc_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json b/zetaclient/testdata/evm/chain_1_intx_ethrpc_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json new file mode 100644 index 0000000000..4c03454776 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_ethrpc_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json @@ -0,0 +1,13 @@ +{ + "Hash": "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", + "Nonce": 401, + "BlockHash": "0x68afbd4ae4a74a74e6ce0dd85f208a5422f453f19ef20b36443eb1bba2ba77fa", + "BlockNumber": 19273702, + "TransactionIndex": 189, + "From": "0x2f993766e8e1ef9288b1f33f6aa244911a0a77a7", + "To": "0x000007cf399229b2f5a4d043f20e90c9c98b7c6a", + "Value": 0, + "Gas": 56442, + "GasPrice": 30944882057, + "Input": "0xec02690100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000001b5800000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000001158e460913d00000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000142f993766e8e1ef9288b1f33f6aa244911a0a77a700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/zetaclient/testdata/evm/chain_1_intx_ethrpc_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json b/zetaclient/testdata/evm/chain_1_intx_ethrpc_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json new file mode 100644 index 0000000000..c1ebd55604 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_ethrpc_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json @@ -0,0 +1,13 @@ +{ + "Hash": "0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155", + "Nonce": 2, + "BlockHash": "0x516677e014c66fe3e71ebe9c998323101585fa9fd742dbbdaa83e8cad7190165", + "BlockNumber": 19080405, + "TransactionIndex": 115, + "From": "0xa6f75db2b59fdd8a317b6233599b0b9f0b2cecec", + "To": "0x70e967acfcc17c3941e87562161406d41676fd83", + "Value": 100000000000000000, + "Gas": 30000, + "GasPrice": 11345194354, + "Input": "0x4920616d207269636821" +} diff --git a/zetaclient/testdata/evm/chain_1_intx_receipt_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json b/zetaclient/testdata/evm/chain_1_intx_receipt_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json new file mode 100644 index 0000000000..900ca40f42 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_receipt_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.json @@ -0,0 +1,44 @@ +{ + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xb95b8e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000800000000000000000000000000010000000000000010000000000000000000000000010000000000000000000000000000008000000000000000000000000000002000000000000000000000000000000800000000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000080000000000000000000000000040000000000004000000002000000000000000000000000000000020080000010000000000000000000000800000020000000000000000000800000000000000000000000000000", + "logs": [ + { + "address": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000056bf8d4a6e7b59d2c0e40cba2409a4a30ab4fbe2", + "0x0000000000000000000000000000030ec64df25301d8414ee5a29588c4b0de10" + ], + "data": "0x000000000000000000000000000000000000000000000000000000025391d200", + "blockNumber": "0x126cd7c", + "transactionHash": "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", + "transactionIndex": "0x70", + "blockHash": "0x393e2dd5669ce148982dfaa608f879d8c123aff2455c3826a2dd82717368d3bc", + "logIndex": "0xf8", + "removed": false + }, + { + "address": "0x0000030ec64df25301d8414ee5a29588c4b0de10", + "topics": [ + "0x1dafa057cc5c3bccb5ad974129a2bccd3c74002d9dfd7062404ba9523b18d6ae", + "0x000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000025391d20000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001456bf8d4a6e7b59d2c0e40cba2409a4a30ab4fbe20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x126cd7c", + "transactionHash": "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", + "transactionIndex": "0x70", + "blockHash": "0x393e2dd5669ce148982dfaa608f879d8c123aff2455c3826a2dd82717368d3bc", + "logIndex": "0xf9", + "removed": false + } + ], + "transactionHash": "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1011a", + "blockHash": "0x393e2dd5669ce148982dfaa608f879d8c123aff2455c3826a2dd82717368d3bc", + "blockNumber": "0x126cd7c", + "transactionIndex": "0x70" +} diff --git a/zetaclient/testdata/evm/chain_1_intx_receipt_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json b/zetaclient/testdata/evm/chain_1_intx_receipt_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json new file mode 100644 index 0000000000..2448a4bb41 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_receipt_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.json @@ -0,0 +1,14 @@ +{ + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xff61e7", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": [], + "transactionHash": "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x5f30ff0388dff58fd28c666b6eecdd7642c9b1adfbd943caeb2ae7a6d8ce9eba", + "blockNumber": "0x126f5a9", + "transactionIndex": "0x82" +} diff --git a/zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json b/zetaclient/testdata/evm/chain_1_intx_receipt_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json similarity index 100% rename from zetaclient/testdata/evm/chain_1_receipt_ZetaSent_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json rename to zetaclient/testdata/evm/chain_1_intx_receipt_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.json diff --git a/zetaclient/testdata/evm/chain_1_intx_receipt_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json b/zetaclient/testdata/evm/chain_1_intx_receipt_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json new file mode 100644 index 0000000000..0af0e5f8bb --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_intx_receipt_donation_Gas_0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155.json @@ -0,0 +1,13 @@ +{ + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa4dd2e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": [], + "transactionHash": "0x52f214cf7b10be71f4d274193287d47bc9632b976e69b9d2cdeb527c2ba32155", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x52a8", + "blockHash": "0x516677e014c66fe3e71ebe9c998323101585fa9fd742dbbdaa83e8cad7190165", + "blockNumber": "0x12324d5", + "transactionIndex": "0x73" +} diff --git a/zetaclient/testdata/evm/chain_1_outtx_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json b/zetaclient/testdata/evm/chain_1_outtx_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json new file mode 100644 index 0000000000..9063147890 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_outtx_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json @@ -0,0 +1,15 @@ +{ + "type": "0x0", + "nonce": "0x1c5c", + "gasPrice": "0x37274f0e36", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "gas": "0x5208", + "value": "0x9778b20315e894", + "input": "0x", + "v": "0x26", + "r": "0xb8ec6b365d89b174724e7e92bc2d7ddea9741a412d6d4584cf9c1456b41af837", + "s": "0x48d3410e1c18d1bc0f3a509c769b48f0267464507cf41345924f4ebddfdb0781", + "to": "0x8e62e3e6fbff3e21f725395416a20ea4e2def015", + "hash": "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3" +} diff --git a/zetaclient/testdata/evm/chain_1_outtx_receipt_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json b/zetaclient/testdata/evm/chain_1_outtx_receipt_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json new file mode 100644 index 0000000000..d7cef761b0 --- /dev/null +++ b/zetaclient/testdata/evm/chain_1_outtx_receipt_Gas_0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3.json @@ -0,0 +1,13 @@ +{ + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x631dc0", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": [], + "transactionHash": "0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0xf282f1c3fd833640870a530d8480d50d4f190785e4e3db5f66b12d082428f763", + "blockNumber": "0x12775fb", + "transactionIndex": "0x26" +} diff --git a/zetaclient/testdata/evm/chain_5_blobtx_ethrpc_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json b/zetaclient/testdata/evm/chain_5_blobtx_ethrpc_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json new file mode 100644 index 0000000000..930bab9286 --- /dev/null +++ b/zetaclient/testdata/evm/chain_5_blobtx_ethrpc_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json @@ -0,0 +1,13 @@ +{ + "Hash": "0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da", + "Nonce": 0, + "BlockHash": "0x9e16582456575e4dd234122477b0ba846d098db8cf4d470232ce142d27a7f90b", + "BlockNumber": 10388180, + "TransactionIndex": 7, + "From": "0x2550f186feb886a38a904bb97a3dc210fb3143bf", + "To": "0x9602c1658284eac6d09ebe0b740cc64dc5eb5d3e", + "Value": 0, + "Gas": 21000, + "GasPrice": 2000000010, + "Input": "0x" +} diff --git a/zetaclient/testdata/evm/chain_5_blobtx_receipt_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json b/zetaclient/testdata/evm/chain_5_blobtx_receipt_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json new file mode 100644 index 0000000000..f0b87a787f --- /dev/null +++ b/zetaclient/testdata/evm/chain_5_blobtx_receipt_0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da.json @@ -0,0 +1,14 @@ +{ + "type": "0x3", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x9a1e0", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": [], + "transactionHash": "0xe707854eb6d7fec630d1e7c29683d71fb0a293eb8794be27a07240d69ee201da", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "blockHash": "0x9e16582456575e4dd234122477b0ba846d098db8cf4d470232ce142d27a7f90b", + "blockNumber": "0x9e82d4", + "transactionIndex": "0x7" +} diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index f767f4905a..f3b2cd0691 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -1,9 +1,37 @@ package testutils +import ethcommon "github.com/ethereum/go-ethereum/common" + const ( + // tss addresses TSSAddressEVMMainnet = "0x70e967acFcC17c3941E87562161406d41676FD83" TSSAddressBTCMainnet = "bc1qm24wp577nk8aacckv8np465z3dvmu7ry45el6y" TSSAddressEVMAthens3 = "0x8531a5aB847ff5B22D855633C25ED1DA3255247e" TSSAddressBTCAthens3 = "tb1qy9pqmk2pd9sv63g27jt8r657wy0d9ueeh0nqur" + + // some other address + OtherAddress = "0x21248Decd0B7EcB0F30186297766b8AB6496265b" ) + +// ConnectorAddresses contains constants ERC20 connector addresses for testing +var ConnectorAddresses = map[int64]ethcommon.Address{ + // mainnet + 1: ethcommon.HexToAddress("0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a"), + 56: ethcommon.HexToAddress("0x000063A6e758D9e2f438d430108377564cf4077D"), + + // testnet + 5: ethcommon.HexToAddress("0x00005E3125aBA53C5652f9F0CE1a4Cf91D8B15eA"), + 97: ethcommon.HexToAddress("0x0000ecb8cdd25a18F12DAA23f6422e07fBf8B9E1"), +} + +// CustodyAddresses contains constants ERC20 custody addresses for testing +var CustodyAddresses = map[int64]ethcommon.Address{ + // mainnet + 1: ethcommon.HexToAddress("0x0000030Ec64DF25301d8414eE5a29588C4B0dE10"), + 56: ethcommon.HexToAddress("0x00000fF8fA992424957F97688015814e707A0115"), + + // testnet + 5: ethcommon.HexToAddress("0x000047f11C6E42293F433C82473532E869Ce4Ec5"), + 97: ethcommon.HexToAddress("0x0000a7Db254145767262C6A81a7eE1650684258e"), +} diff --git a/zetaclient/testutils/evm.go b/zetaclient/testutils/evm.go new file mode 100644 index 0000000000..5b252a9ecb --- /dev/null +++ b/zetaclient/testutils/evm.go @@ -0,0 +1,29 @@ +package testutils + +import ( + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" +) + +// ParseReceiptZetaSent parses a ZetaSent event from a receipt +func ParseReceiptZetaSent(receipt *ethtypes.Receipt, connector *zetaconnector.ZetaConnectorNonEth) *zetaconnector.ZetaConnectorNonEthZetaSent { + for _, log := range receipt.Logs { + event, err := connector.ParseZetaSent(*log) + if err == nil && event != nil { + return event // found + } + } + return nil +} + +// ParseReceiptERC20Deposited parses an Deposited event from a receipt +func ParseReceiptERC20Deposited(receipt *ethtypes.Receipt, custody *erc20custody.ERC20Custody) *erc20custody.ERC20CustodyDeposited { + for _, log := range receipt.Logs { + event, err := custody.ParseDeposited(*log) + if err == nil && event != nil { + return event // found + } + } + return nil +} diff --git a/zetaclient/testutils/mempool-client.go b/zetaclient/testutils/mempool_client.go similarity index 100% rename from zetaclient/testutils/mempool-client.go rename to zetaclient/testutils/mempool_client.go diff --git a/zetaclient/testutils/stub/chain_params.go b/zetaclient/testutils/stub/chain_params.go new file mode 100644 index 0000000000..48b45539cf --- /dev/null +++ b/zetaclient/testutils/stub/chain_params.go @@ -0,0 +1,35 @@ +package stub + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/testutils" +) + +func MockChainParams(chainID int64, confirmation uint64) observertypes.ChainParams { + return observertypes.ChainParams{ + ChainId: chainID, + ConfirmationCount: confirmation, + ConnectorContractAddress: testutils.ConnectorAddresses[chainID].Hex(), + Erc20CustodyContractAddress: testutils.CustodyAddresses[chainID].Hex(), + IsSupported: true, + } +} + +func MockConnectorNonEth(chainID int64) *zetaconnector.ZetaConnectorNonEth { + connector, err := zetaconnector.NewZetaConnectorNonEth(testutils.ConnectorAddresses[chainID], ðclient.Client{}) + if err != nil { + panic(err) + } + return connector +} + +func MockERC20Custody(chainID int64) *erc20custody.ERC20Custody { + custody, err := erc20custody.NewERC20Custody(testutils.CustodyAddresses[chainID], ðclient.Client{}) + if err != nil { + panic(err) + } + return custody +} diff --git a/zetaclient/testutils/testdata.go b/zetaclient/testutils/testdata.go new file mode 100644 index 0000000000..8918476217 --- /dev/null +++ b/zetaclient/testutils/testdata.go @@ -0,0 +1,266 @@ +package testutils + +import ( + "encoding/json" + "os" + "path" + "path/filepath" + "testing" + + "github.com/btcsuite/btcd/btcjson" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/onrik/ethrpc" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/config" +) + +const ( + TestDataPathEVM = "testdata/evm" + TestDataPathBTC = "testdata/btc" + TestDataPathCctx = "testdata/cctx" + RestrictedEVMAddressTest = "0x8a81Ba8eCF2c418CAe624be726F505332DF119C6" + RestrictedBtcAddressTest = "bcrt1qzp4gt6fc7zkds09kfzaf9ln9c5rvrzxmy6qmpp" +) + +// SaveObjectToJSONFile saves an object to a file in JSON format +func SaveObjectToJSONFile(obj interface{}, filename string) error { + file, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer file.Close() + + // write the struct to the file + encoder := json.NewEncoder(file) + return encoder.Encode(obj) +} + +// LoadObjectFromJSONFile loads an object from a file in JSON format +func LoadObjectFromJSONFile(obj interface{}, filename string) error { + file, err := os.Open(filepath.Clean(filename)) + if err != nil { + return err + } + defer file.Close() + + // read the struct from the file + decoder := json.NewDecoder(file) + return decoder.Decode(&obj) +} + +func ComplianceConfigTest() *config.ComplianceConfig { + return &config.ComplianceConfig{ + RestrictedAddresses: []string{RestrictedEVMAddressTest, RestrictedBtcAddressTest}, + } +} + +// SaveTrimedEVMBlockTrimTxInput trims tx input data from a block and saves it to a file +func SaveEVMBlockTrimTxInput(block *ethrpc.Block, filename string) error { + for i := range block.Transactions { + block.Transactions[i].Input = "0x" + } + return SaveObjectToJSONFile(block, filename) +} + +// SaveTrimedBTCBlockTrimTx trims tx data from a block and saves it to a file +func SaveBTCBlockTrimTx(blockVb *btcjson.GetBlockVerboseTxResult, filename string) error { + for i := range blockVb.Tx { + // reserve one coinbase tx and one non-coinbase tx + if i >= 2 { + blockVb.Tx[i].Hex = "" + blockVb.Tx[i].Vin = nil + blockVb.Tx[i].Vout = nil + } + } + return SaveObjectToJSONFile(blockVb, filename) +} + +// LoadEVMBlock loads archived evm block from file +func LoadEVMBlock(t *testing.T, chainID int64, blockNumber uint64, trimmed bool) *ethrpc.Block { + name := path.Join("../", TestDataPathEVM, FileNameEVMBlock(chainID, blockNumber, trimmed)) + block := ðrpc.Block{} + err := LoadObjectFromJSONFile(block, name) + require.NoError(t, err) + return block +} + +// LoadBTCTxRawResultNCctx loads archived Bitcoin outtx raw result and corresponding cctx +func LoadBTCTxRawResultNCctx(t *testing.T, chainID int64, nonce uint64) (*btcjson.TxRawResult, *crosschaintypes.CrossChainTx) { + //nameTx := FileNameBTCOuttx(chainID, nonce) + nameTx := path.Join("../", TestDataPathBTC, FileNameBTCOuttx(chainID, nonce)) + rawResult := &btcjson.TxRawResult{} + err := LoadObjectFromJSONFile(rawResult, nameTx) + require.NoError(t, err) + + nameCctx := path.Join("../", TestDataPathCctx, FileNameCctxByNonce(chainID, nonce)) + cctx := &crosschaintypes.CrossChainTx{} + err = LoadObjectFromJSONFile(cctx, nameCctx) + require.NoError(t, err) + return rawResult, cctx +} + +// LoadEVMIntx loads archived intx from file +func LoadEVMIntx( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethrpc.Transaction { + nameTx := path.Join("../", TestDataPathEVM, FileNameEVMIntx(chainID, intxHash, coinType, false)) + + tx := ðrpc.Transaction{} + err := LoadObjectFromJSONFile(&tx, nameTx) + require.NoError(t, err) + return tx +} + +// LoadEVMIntxReceipt loads archived intx receipt from file +func LoadEVMIntxReceipt( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethtypes.Receipt { + nameReceipt := path.Join("../", TestDataPathEVM, FileNameEVMIntxReceipt(chainID, intxHash, coinType, false)) + + receipt := ðtypes.Receipt{} + err := LoadObjectFromJSONFile(&receipt, nameReceipt) + require.NoError(t, err) + return receipt +} + +// LoadEVMIntxCctx loads archived intx cctx from file +func LoadEVMIntxCctx( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *crosschaintypes.CrossChainTx { + nameCctx := path.Join("../", TestDataPathCctx, FileNameEVMIntxCctx(chainID, intxHash, coinType)) + + cctx := &crosschaintypes.CrossChainTx{} + err := LoadObjectFromJSONFile(&cctx, nameCctx) + require.NoError(t, err) + return cctx +} + +// LoadCctxByNonce loads archived cctx by nonce from file +func LoadCctxByNonce( + t *testing.T, + chainID int64, + nonce uint64) *crosschaintypes.CrossChainTx { + nameCctx := path.Join("../", TestDataPathCctx, FileNameCctxByNonce(chainID, nonce)) + + cctx := &crosschaintypes.CrossChainTx{} + err := LoadObjectFromJSONFile(&cctx, nameCctx) + require.NoError(t, err) + return cctx +} + +// LoadEVMIntxNReceipt loads archived intx and receipt from file +func LoadEVMIntxNReceipt( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) (*ethrpc.Transaction, *ethtypes.Receipt) { + // load archived intx and receipt + tx := LoadEVMIntx(t, chainID, intxHash, coinType) + receipt := LoadEVMIntxReceipt(t, chainID, intxHash, coinType) + + return tx, receipt +} + +// LoadEVMIntxDonation loads archived donation intx from file +func LoadEVMIntxDonation( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethrpc.Transaction { + nameTx := path.Join("../", TestDataPathEVM, FileNameEVMIntx(chainID, intxHash, coinType, true)) + + tx := ðrpc.Transaction{} + err := LoadObjectFromJSONFile(&tx, nameTx) + require.NoError(t, err) + return tx +} + +// LoadEVMIntxReceiptDonation loads archived donation intx receipt from file +func LoadEVMIntxReceiptDonation( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethtypes.Receipt { + nameReceipt := path.Join("../", TestDataPathEVM, FileNameEVMIntxReceipt(chainID, intxHash, coinType, true)) + + receipt := ðtypes.Receipt{} + err := LoadObjectFromJSONFile(&receipt, nameReceipt) + require.NoError(t, err) + return receipt +} + +// LoadEVMIntxNReceiptDonation loads archived donation intx and receipt from file +func LoadEVMIntxNReceiptDonation( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) (*ethrpc.Transaction, *ethtypes.Receipt) { + // load archived donation intx and receipt + tx := LoadEVMIntxDonation(t, chainID, intxHash, coinType) + receipt := LoadEVMIntxReceiptDonation(t, chainID, intxHash, coinType) + + return tx, receipt +} + +// LoadTxNReceiptNCctx loads archived intx, receipt and corresponding cctx from file +func LoadEVMIntxNReceiptNCctx( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) (*ethrpc.Transaction, *ethtypes.Receipt, *crosschaintypes.CrossChainTx) { + // load archived intx, receipt and cctx + tx := LoadEVMIntx(t, chainID, intxHash, coinType) + receipt := LoadEVMIntxReceipt(t, chainID, intxHash, coinType) + cctx := LoadEVMIntxCctx(t, chainID, intxHash, coinType) + + return tx, receipt, cctx +} + +// LoadEVMOuttx loads archived evm outtx from file +func LoadEVMOuttx( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethtypes.Transaction { + nameTx := path.Join("../", TestDataPathEVM, FileNameEVMOuttx(chainID, intxHash, coinType)) + + tx := ðtypes.Transaction{} + err := LoadObjectFromJSONFile(&tx, nameTx) + require.NoError(t, err) + return tx +} + +// LoadEVMOuttxReceipt loads archived evm outtx receipt from file +func LoadEVMOuttxReceipt( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) *ethtypes.Receipt { + nameReceipt := path.Join("../", TestDataPathEVM, FileNameEVMOuttxReceipt(chainID, intxHash, coinType)) + + receipt := ðtypes.Receipt{} + err := LoadObjectFromJSONFile(&receipt, nameReceipt) + require.NoError(t, err) + return receipt +} + +// LoadEVMOuttxNReceipt loads archived evm outtx and receipt from file +func LoadEVMOuttxNReceipt( + t *testing.T, + chainID int64, + intxHash string, + coinType common.CoinType) (*ethtypes.Transaction, *ethtypes.Receipt) { + // load archived evm outtx and receipt + tx := LoadEVMOuttx(t, chainID, intxHash, coinType) + receipt := LoadEVMOuttxReceipt(t, chainID, intxHash, coinType) + + return tx, receipt +} diff --git a/zetaclient/testutils/testdata_naming.go b/zetaclient/testutils/testdata_naming.go new file mode 100644 index 0000000000..404b2471eb --- /dev/null +++ b/zetaclient/testutils/testdata_naming.go @@ -0,0 +1,64 @@ +package testutils + +import ( + "fmt" + + "github.com/zeta-chain/zetacore/common" +) + +// FileNameEVMBlock returns unified archive file name for block +func FileNameEVMBlock(chainID int64, blockNumber uint64, trimmed bool) string { + if !trimmed { + return fmt.Sprintf("chain_%d_block_ethrpc_%d.json", chainID, blockNumber) + } + return fmt.Sprintf("chain_%d_block_ethrpc_trimmed_%d.json", chainID, blockNumber) +} + +// FileNameEVMIntx returns unified archive file name for inbound tx +func FileNameEVMIntx(chainID int64, intxHash string, coinType common.CoinType, donation bool) string { + if !donation { + return fmt.Sprintf("chain_%d_intx_ethrpc_%s_%s.json", chainID, coinType, intxHash) + } + return fmt.Sprintf("chain_%d_intx_ethrpc_donation_%s_%s.json", chainID, coinType, intxHash) +} + +// FileNameEVMIntxReceipt returns unified archive file name for inbound tx receipt +func FileNameEVMIntxReceipt(chainID int64, intxHash string, coinType common.CoinType, donation bool) string { + if !donation { + return fmt.Sprintf("chain_%d_intx_receipt_%s_%s.json", chainID, coinType, intxHash) + } + return fmt.Sprintf("chain_%d_intx_receipt_donation_%s_%s.json", chainID, coinType, intxHash) +} + +// FileNameEVMIntxCctx returns unified archive file name for inbound cctx +func FileNameEVMIntxCctx(chainID int64, intxHash string, coinType common.CoinType) string { + return fmt.Sprintf("cctx_intx_%d_%s_%s.json", chainID, coinType, intxHash) +} + +// FileNameBTCIntx returns unified archive file name for inbound tx +func FileNameBTCIntx(chainID int64, intxHash string, donation bool) string { + if !donation { + return fmt.Sprintf("chain_%d_intx_raw_result_%s.json", chainID, intxHash) + } + return fmt.Sprintf("chain_%d_intx_raw_result_donation_%s.json", chainID, intxHash) +} + +// FileNameBTCOuttx returns unified archive file name for outbound tx +func FileNameBTCOuttx(chainID int64, nonce uint64) string { + return fmt.Sprintf("chain_%d_outtx_raw_result_nonce_%d.json", chainID, nonce) +} + +// FileNameCctxByNonce returns unified archive file name for cctx by nonce +func FileNameCctxByNonce(chainID int64, nonce uint64) string { + return fmt.Sprintf("cctx_%d_%d.json", chainID, nonce) +} + +// FileNameEVMOuttx returns unified archive file name for outbound tx +func FileNameEVMOuttx(chainID int64, txHash string, coinType common.CoinType) string { + return fmt.Sprintf("chain_%d_outtx_%s_%s.json", chainID, coinType, txHash) +} + +// FileNameEVMOuttxReceipt returns unified archive file name for outbound tx receipt +func FileNameEVMOuttxReceipt(chainID int64, txHash string, coinType common.CoinType) string { + return fmt.Sprintf("chain_%d_outtx_receipt_%s_%s.json", chainID, coinType, txHash) +} diff --git a/zetaclient/testutils/utils.go b/zetaclient/testutils/utils.go deleted file mode 100644 index 4f00f10bfd..0000000000 --- a/zetaclient/testutils/utils.go +++ /dev/null @@ -1,63 +0,0 @@ -package testutils - -import ( - "encoding/json" - "os" - "path/filepath" - - "github.com/btcsuite/btcd/btcjson" - "github.com/zeta-chain/zetacore/zetaclient/config" -) - -const ( - TestDataPathEVM = "testdata/evm" - TestDataPathBTC = "testdata/btc" - TestDataPathCctx = "testdata/cctx" - RestrictedEVMAddressTest = "0x8a81Ba8eCF2c418CAe624be726F505332DF119C6" - RestrictedBtcAddressTest = "bcrt1qzp4gt6fc7zkds09kfzaf9ln9c5rvrzxmy6qmpp" -) - -// SaveObjectToJSONFile saves an object to a file in JSON format -func SaveObjectToJSONFile(obj interface{}, filename string) error { - file, err := os.Create(filepath.Clean(filename)) - if err != nil { - return err - } - defer file.Close() - - // write the struct to the file - encoder := json.NewEncoder(file) - return encoder.Encode(obj) -} - -// LoadObjectFromJSONFile loads an object from a file in JSON format -func LoadObjectFromJSONFile(obj interface{}, filename string) error { - file, err := os.Open(filepath.Clean(filename)) - if err != nil { - return err - } - defer file.Close() - - // read the struct from the file - decoder := json.NewDecoder(file) - return decoder.Decode(&obj) -} - -// SaveTrimedBTCBlockTrimTx trims tx data from a block and saves it to a file -func SaveBTCBlockTrimTx(blockVb *btcjson.GetBlockVerboseTxResult, filename string) error { - for i := range blockVb.Tx { - // reserve one coinbase tx and one non-coinbase tx - if i >= 2 { - blockVb.Tx[i].Hex = "" - blockVb.Tx[i].Vin = nil - blockVb.Tx[i].Vout = nil - } - } - return SaveObjectToJSONFile(blockVb, filename) -} - -func ComplianceConfigTest() *config.ComplianceConfig { - return &config.ComplianceConfig{ - RestrictedAddresses: []string{RestrictedEVMAddressTest, RestrictedBtcAddressTest}, - } -}