From f2755af4a740458737ae807c02b88446196e86bb Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Thu, 28 Mar 2024 22:33:16 -0500 Subject: [PATCH] added live test for intx sender address parsing --- ...pc_test.go => bitcoin_client_live_test.go} | 73 +++++++++++++++++++ zetaclient/testutils/mempool_client.go | 55 +++++++++++++- 2 files changed, 125 insertions(+), 3 deletions(-) rename zetaclient/bitcoin/{bitcoin_client_rpc_test.go => bitcoin_client_live_test.go} (85%) diff --git a/zetaclient/bitcoin/bitcoin_client_rpc_test.go b/zetaclient/bitcoin/bitcoin_client_live_test.go similarity index 85% rename from zetaclient/bitcoin/bitcoin_client_rpc_test.go rename to zetaclient/bitcoin/bitcoin_client_live_test.go index 1fe18d14a5..d4118a8b73 100644 --- a/zetaclient/bitcoin/bitcoin_client_rpc_test.go +++ b/zetaclient/bitcoin/bitcoin_client_live_test.go @@ -348,3 +348,76 @@ func LiveTestAvgFeeRateTestnetMempoolSpace(t *testing.T) { compareAvgFeeRate(t, client, startBlock, endBlock, true) } + +// Remove prefix "Live" to run this live test +func LiveTestGetSenderByVin(t *testing.T) { + // setup Bitcoin client + chainID := int64(8332) + client, err := getRPCClient(chainID) + require.NoError(t, err) + + // net params + net, err := common.GetBTCChainParams(chainID) + require.NoError(t, err) + testnet := false + if chainID == common.BtcTestNetChain().ChainId { + testnet = true + } + + // calculates block range to test + startBlock, err := client.GetBlockCount() + require.NoError(t, err) + endBlock := startBlock - 5000 + + // loop through mempool.space blocks in descending order +BLOCKLOOP: + for bn := startBlock; bn >= endBlock; { + // get block hash + blkHash, err := client.GetBlockHash(int64(bn)) + if err != nil { + fmt.Printf("error GetBlockHash for block %d: %s\n", bn, err) + time.Sleep(3 * time.Second) + continue + } + + // get mempool.space txs for the block + mempoolTxs, err := testutils.GetBlockTxs(context.Background(), blkHash.String(), testnet) + if err != nil { + fmt.Printf("error GetBlockTxs %d: %s\n", bn, err) + time.Sleep(10 * time.Second) + continue + } + + // loop through each tx in the block + for i, mptx := range mempoolTxs { + // sample 10 txs per block + if i >= 10 { + break + } + for _, mpvin := range mptx.Vin { + // skip coinbase tx + if mpvin.IsCoinbase { + continue + } + // get sender address for each vin + vin := btcjson.Vin{ + Txid: mpvin.TxID, + Vout: mpvin.Vout, + } + senderAddr, err := GetSenderAddressByVin(client, vin, net) + if err != nil { + fmt.Printf("error GetSenderAddressByVin for block %d, tx %s vout %d: %s\n", bn, vin.Txid, vin.Vout, err) + time.Sleep(3 * time.Second) + continue BLOCKLOOP // retry the block + } + if senderAddr != mpvin.Prevout.ScriptpubkeyAddress { + panic(fmt.Sprintf("block %d, tx %s, vout %d: want %s, got %s\n", bn, vin.Txid, vin.Vout, mpvin.Prevout.ScriptpubkeyAddress, senderAddr)) + } else { + fmt.Printf("block: %d sender address type: %s\n", bn, mpvin.Prevout.ScriptpubkeyType) + } + } + } + bn-- + time.Sleep(500 * time.Millisecond) + } +} diff --git a/zetaclient/testutils/mempool_client.go b/zetaclient/testutils/mempool_client.go index 09b48b8a53..03c79120dc 100644 --- a/zetaclient/testutils/mempool_client.go +++ b/zetaclient/testutils/mempool_client.go @@ -9,8 +9,10 @@ import ( ) const ( - APIURLBlocks = "https://mempool.space/api/v1/blocks" - APIUrlBlocksTestnet = "https://mempool.space/testnet/api/v1/blocks" + APIURLBlocks = "https://mempool.space/api/v1/blocks" + APIURLBlockTxs = "https://mempool.space/api/block/%s/txs" + APIURLBlocksTestnet = "https://mempool.space/testnet/api/v1/blocks" + APIURLBlockTxsTestnet = "https://mempool.space/testnet/api/block/%s/txs" ) type MempoolBlock struct { @@ -30,6 +32,39 @@ type MempoolBlock struct { Extras BlockExtra `json:"extras"` } +type Vin struct { + TxID string `json:"txid"` + Vout uint32 `json:"vout"` + Prevout struct { + Scriptpubkey string `json:"scriptpubkey"` + ScriptpubkeyAsm string `json:"scriptpubkey_asm"` + ScriptpubkeyType string `json:"scriptpubkey_type"` + ScriptpubkeyAddress string `json:"scriptpubkey_address"` + Value int64 `json:"value"` + } `json:"prevout"` + Scriptsig string `json:"scriptsig"` + IsCoinbase bool `json:"is_coinbase"` + Sequence uint32 `json:"sequence"` +} + +type Vout struct { + Scriptpubkey string `json:"scriptpubkey"` + ScriptpubkeyAsm string `json:"scriptpubkey_asm"` + ScriptpubkeyType string `json:"scriptpubkey_type"` + Value int64 `json:"value"` +} + +type MempoolTx struct { + TxID string `json:"txid"` + Version int `json:"version"` + LockTime int `json:"locktime"` + Vin []Vin `json:"vin"` + Vout []Vout `json:"vout"` + Size int `json:"size"` + Weight int `json:"weight"` + Fee int `json:"fee"` +} + type BlockExtra struct { TotalFees int `json:"totalFees"` MedianFee float64 `json:"medianFee"` @@ -91,10 +126,11 @@ func Get(ctx context.Context, path string, v interface{}) error { return json.NewDecoder(r.Body).Decode(v) } +// GetBlocks returns return 15 mempool.space blocks [n-14, n] per request func GetBlocks(ctx context.Context, n int, testnet bool) ([]MempoolBlock, error) { path := fmt.Sprintf("%s/%d", APIURLBlocks, n) if testnet { - path = fmt.Sprintf("%s/%d", APIUrlBlocksTestnet, n) + path = fmt.Sprintf("%s/%d", APIURLBlocksTestnet, n) } blocks := make([]MempoolBlock, 0) if err := Get(ctx, path, &blocks); err != nil { @@ -102,3 +138,16 @@ func GetBlocks(ctx context.Context, n int, testnet bool) ([]MempoolBlock, error) } return blocks, nil } + +// GetBlockTxs a list of transactions in the block (up to 25 transactions beginning at index 0) +func GetBlockTxs(ctx context.Context, blockHash string, testnet bool) ([]MempoolTx, error) { + path := fmt.Sprintf(APIURLBlockTxs, blockHash) + if testnet { + path = fmt.Sprintf(APIURLBlockTxsTestnet, blockHash) + } + txs := make([]MempoolTx, 0) + if err := Get(ctx, path, &txs); err != nil { + return nil, err + } + return txs, nil +}