diff --git a/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/bitcoin.go b/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/bitcoin.go index ad83533dd8..2b13d53b4f 100644 --- a/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/bitcoin.go +++ b/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/bitcoin.go @@ -65,8 +65,11 @@ func bitcoinTestRoutine( bitcoinRunner.WaitForMinedCCTX(txERC20Deposit) // run bitcoin test + // Note: due to the extensive block generation in Bitcoin localnet, block header test is run first + // to make it faster to catch up with the latest block header if err := bitcoinRunner.RunSmokeTestsFromNames( smoketests.AllSmokeTests, + smoketests.TestBlockHeaderBitcoinName, smoketests.TestBitcoinWithdrawName, smoketests.TestSendZetaOutBTCRevertName, smoketests.TestCrosschainSwapName, diff --git a/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/ethereum.go b/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/ethereum.go index 7f338807e9..fc2fc7d46d 100644 --- a/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/ethereum.go +++ b/contrib/localnet/orchestrator/smoketest/cmd/smoketest/local/ethereum.go @@ -59,8 +59,11 @@ func ethereumTestRoutine( ethereumRunner.SetupContextApp() // run ethereum test + // Note: due to the extensive block generation in Ethereum localnet, block header test is run first + // to make it faster to catch up with the latest block header if err := ethereumRunner.RunSmokeTestsFromNames( smoketests.AllSmokeTests, + smoketests.TestBlockHeaderEthereumName, smoketests.TestContextUpgradeName, smoketests.TestEtherDepositAndCallName, smoketests.TestDepositAndCallRefundName, diff --git a/contrib/localnet/orchestrator/smoketest/runner/bitcoin.go b/contrib/localnet/orchestrator/smoketest/runner/bitcoin.go index 8dc555f39f..35f4f63bf9 100644 --- a/contrib/localnet/orchestrator/smoketest/runner/bitcoin.go +++ b/contrib/localnet/orchestrator/smoketest/runner/bitcoin.go @@ -101,10 +101,6 @@ func (sm *SmokeTestRunner) DepositBTC() { break } } - - // prove the two transactions of the deposit - sm.Logger.InfoLoud("Bitcoin Merkle Proof\n") - _ = txHash1 _ = txHash2 //sm.ProveBTCTransaction(txHash1) diff --git a/contrib/localnet/orchestrator/smoketest/smoketests/smoketests.go b/contrib/localnet/orchestrator/smoketest/smoketests/smoketests.go index ba506612db..8981767b0b 100644 --- a/contrib/localnet/orchestrator/smoketest/smoketests/smoketests.go +++ b/contrib/localnet/orchestrator/smoketest/smoketests/smoketests.go @@ -21,7 +21,8 @@ const ( TestUpdateBytecodeName = "update_bytecode" TestEtherDepositAndCallName = "eth_deposit_and_call" TestDepositEtherLiquidityCapName = "deposit_eth_liquidity_cap" - TestBlockHeadersName = "block_headers" + TestBlockHeaderEthereumName = "block_headers_eth" + TestBlockHeaderBitcoinName = "block_headers_bitcoin" TestMyTestName = "my_test" ) @@ -117,11 +118,16 @@ var AllSmokeTests = []runner.SmokeTest{ "deposit Ethers into ZEVM with a liquidity cap", TestDepositEtherLiquidityCap, }, - //{ - // TestBlockHeadersName, - // "fetch block headers of EVM on ZetaChain", - // TestBlockHeaders, - //}, + { + TestBlockHeaderEthereumName, + "test Ethereum tx can be proven on ZetaChain", + TestEthereumMerkelProof, + }, + { + TestBlockHeaderBitcoinName, + "test Bitcoin tx can be proven on ZetaChain", + TestBTCMerkelProof, + }, { TestMyTestName, "performing custom test", diff --git a/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_bitcoin.go b/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_bitcoin.go index a1b5deb291..c55f1721d9 100644 --- a/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_bitcoin.go +++ b/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_bitcoin.go @@ -3,19 +3,67 @@ package smoketests import ( "context" "encoding/hex" + "fmt" + "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcutil" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/bitcoin" "github.com/zeta-chain/zetacore/contrib/localnet/orchestrator/smoketest/runner" observertypes "github.com/zeta-chain/zetacore/x/observer/types" - "time" + "github.com/zeta-chain/zetacore/zetaclient" ) -var blockHeaderTimeout = 30 * time.Second +var blockHeaderBTCTimeout = 5 * time.Minute func TestBTCMerkelProof(sm *runner.SmokeTestRunner) { + // mine blocks + stop := sm.MineBlocks() + + utxos, err := sm.BtcRPCClient.ListUnspent() + if err != nil { + panic(err) + } + spendableAmount := 0.0 + spendableUTXOs := 0 + for _, utxo := range utxos { + if utxo.Spendable { + spendableAmount += utxo.Amount + spendableUTXOs++ + } + } + + if spendableAmount < 1.1 { + panic(fmt.Errorf("not enough spendable BTC to run the test; have %f", spendableAmount)) + } + if spendableUTXOs < 2 { + panic(fmt.Errorf("not enough spendable BTC UTXOs to run the test; have %d", spendableUTXOs)) + } + + sm.Logger.Info("ListUnspent:") + sm.Logger.Info(" spendableAmount: %f", spendableAmount) + sm.Logger.Info(" spendableUTXOs: %d", spendableUTXOs) + sm.Logger.Info("Now sending two txs to TSS address...") + + // send two transactions to the TSS address + txHash, err := sm.SendToTSSFromDeployerToDeposit( + sm.BTCTSSAddress, + 1.1+zetaclient.BtcDepositorFeeMin, + utxos[:2], + sm.BtcRPCClient, + sm.BTCDeployerAddress, + ) + if err != nil { + panic(err) + } + sm.Logger.Info("BTC tx sent: %s", txHash.String()) + + // check that the tx is in the block header + proveBTCTransaction(sm, txHash) + // stop mining + stop <- struct{}{} } func proveBTCTransaction(sm *runner.SmokeTestRunner, txHash *chainhash.Hash) { @@ -81,7 +129,7 @@ func proveBTCTransaction(sm *runner.SmokeTestRunner, txHash *chainhash.Hash) { hash := header.BlockHash() for { // timeout - if time.Since(startTime) > blockHeaderTimeout { + if time.Since(startTime) > blockHeaderBTCTimeout { panic("timed out waiting for block header to show up in observer") } diff --git a/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_ethereum.go b/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_ethereum.go index 18568e9f96..096652b991 100644 --- a/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_ethereum.go +++ b/contrib/localnet/orchestrator/smoketest/smoketests/test_headers_ethereum.go @@ -1,7 +1,99 @@ package smoketests -import "github.com/zeta-chain/zetacore/contrib/localnet/orchestrator/smoketest/runner" +import ( + "context" + "math/big" + "time" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/zetacore/common" + "github.com/zeta-chain/zetacore/common/ethereum" + "github.com/zeta-chain/zetacore/contrib/localnet/orchestrator/smoketest/runner" + "github.com/zeta-chain/zetacore/contrib/localnet/orchestrator/smoketest/utils" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +var blockHeaderETHTimeout = 5 * time.Minute func TestEthereumMerkelProof(sm *runner.SmokeTestRunner) { + // send eth to TSS address + value := big.NewInt(100000000000000000) // in wei (0.1 eth) + signedTx, err := sm.SendEther(sm.TSSAddress, value, nil) + if err != nil { + panic(err) + } + + sm.Logger.Info("GOERLI tx sent: %s; to %s, nonce %d", signedTx.Hash().String(), signedTx.To().Hex(), signedTx.Nonce()) + receipt := utils.MustWaitForTxReceipt(sm.GoerliClient, signedTx, sm.Logger) + if receipt.Status == 0 { + panic("deposit failed") + } + + // check that the tx is in the block header + proveEthTransaction(sm, receipt) +} + +func proveEthTransaction(sm *runner.SmokeTestRunner, receipt *ethtypes.Receipt) { + startTime := time.Now() + + txHash := receipt.TxHash + blockHash := receipt.BlockHash + + // #nosec G701 smoketest - always in range + txIndex := int(receipt.TransactionIndex) + + block, err := sm.GoerliClient.BlockByHash(context.Background(), blockHash) + if err != nil { + panic(err) + } + for { + // check timeout + if time.Since(startTime) > blockHeaderETHTimeout { + panic("timeout waiting for block header") + } + + _, err := sm.ObserverClient.GetBlockHeaderByHash(context.Background(), &observertypes.QueryGetBlockHeaderByHashRequest{ + BlockHash: blockHash.Bytes(), + }) + if err != nil { + sm.Logger.Info("WARN: block header not found; retrying... error: %s", err.Error()) + } else { + sm.Logger.Info("OK: block header found") + break + } + + time.Sleep(2 * time.Second) + } + trie := ethereum.NewTrie(block.Transactions()) + if trie.Hash() != block.Header().TxHash { + panic("tx root hash & block tx root mismatch") + } + txProof, err := trie.GenerateProof(txIndex) + if err != nil { + panic("error generating txProof") + } + val, err := txProof.Verify(block.TxHash(), txIndex) + if err != nil { + panic("error verifying txProof") + } + var txx ethtypes.Transaction + err = txx.UnmarshalBinary(val) + if err != nil { + panic("error unmarshalling txProof'd tx") + } + res, err := sm.ObserverClient.Prove(context.Background(), &observertypes.QueryProveRequest{ + BlockHash: blockHash.Hex(), + TxIndex: int64(txIndex), + TxHash: txHash.Hex(), + Proof: common.NewEthereumProof(txProof), + ChainId: common.GoerliLocalnetChain().ChainId, + }) + if err != nil { + panic(err) + } + if !res.Valid { + panic("txProof invalid") // FIXME: don't do this in production + } + sm.Logger.Info("OK: txProof verified") } diff --git a/contrib/localnet/orchestrator/smoketest/utils/zetacore.go b/contrib/localnet/orchestrator/smoketest/utils/zetacore.go index d5047e7c42..02b73e4d5d 100644 --- a/contrib/localnet/orchestrator/smoketest/utils/zetacore.go +++ b/contrib/localnet/orchestrator/smoketest/utils/zetacore.go @@ -13,7 +13,7 @@ import ( const ( FungibleAdminName = "fungibleadmin" - CctxTimeout = 120 * time.Second + CctxTimeout = 4 * time.Minute ) // WaitCctxMinedByInTxHash waits until cctx is mined; returns the cctxIndex (the last one)