From 2bcdfbc727c12612159ffbf008bb00505a996b4b Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Thu, 28 Mar 2024 15:21:50 -0500 Subject: [PATCH] resolved some PR review feedback --- e2e/e2etests/test_bitcoin_withdraw.go | 25 +++--- x/crosschain/keeper/evm_hooks_test.go | 22 +++++ zetaclient/bitcoin/bitcoin_client_test.go | 32 +++---- zetaclient/bitcoin/bitcoin_signer.go | 10 ++- zetaclient/bitcoin/bitcoin_signer_test.go | 14 +++ zetaclient/bitcoin/fee.go | 67 +++++++------- zetaclient/bitcoin/fee_test.go | 90 ++++++++++++++----- .../bitcoin/{txscript.go => tx_script.go} | 26 +++--- .../{txscript_test.go => tx_script_test.go} | 0 9 files changed, 187 insertions(+), 99 deletions(-) rename zetaclient/bitcoin/{txscript.go => tx_script.go} (94%) rename zetaclient/bitcoin/{txscript_test.go => tx_script_test.go} (100%) diff --git a/e2e/e2etests/test_bitcoin_withdraw.go b/e2e/e2etests/test_bitcoin_withdraw.go index 60871699b4..d74ee43615 100644 --- a/e2e/e2etests/test_bitcoin_withdraw.go +++ b/e2e/e2etests/test_bitcoin_withdraw.go @@ -12,6 +12,7 @@ import ( "github.com/zeta-chain/zetacore/common/bitcoin" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) @@ -30,7 +31,7 @@ func TestBitcoinWithdrawSegWit(r *runner.E2ERunner, args []string) { panic("Invalid receiver address specified for TestBitcoinWithdrawSegWit.") } - WithdrawBitcoin(r, receiver, amount) + withdrawBTCZRC20(r, receiver, amount) } func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { @@ -48,7 +49,7 @@ func TestBitcoinWithdrawTaproot(r *runner.E2ERunner, args []string) { panic("Invalid receiver address specified for TestBitcoinWithdrawTaproot.") } - WithdrawBitcoin(r, receiver, amount) + withdrawBTCZRC20(r, receiver, amount) } func TestBitcoinWithdrawLegacy(r *runner.E2ERunner, args []string) { @@ -66,7 +67,7 @@ func TestBitcoinWithdrawLegacy(r *runner.E2ERunner, args []string) { panic("Invalid receiver address specified for TestBitcoinWithdrawLegacy.") } - WithdrawBitcoin(r, receiver, amount) + withdrawBTCZRC20(r, receiver, amount) } func TestBitcoinWithdrawP2WSH(r *runner.E2ERunner, args []string) { @@ -84,7 +85,7 @@ func TestBitcoinWithdrawP2WSH(r *runner.E2ERunner, args []string) { panic("Invalid receiver address specified for TestBitcoinWithdrawP2WSH.") } - WithdrawBitcoin(r, receiver, amount) + withdrawBTCZRC20(r, receiver, amount) } func TestBitcoinWithdrawP2SH(r *runner.E2ERunner, args []string) { @@ -102,7 +103,7 @@ func TestBitcoinWithdrawP2SH(r *runner.E2ERunner, args []string) { panic("Invalid receiver address specified for TestBitcoinWithdrawP2SH.") } - WithdrawBitcoin(r, receiver, amount) + withdrawBTCZRC20(r, receiver, amount) } func TestBitcoinWithdrawRestricted(r *runner.E2ERunner, args []string) { @@ -123,7 +124,7 @@ func TestBitcoinWithdrawRestricted(r *runner.E2ERunner, args []string) { r.SetBtcAddress(r.Name, false) - WithdrawBitcoinRestricted(r, amount) + withdrawBitcoinRestricted(r, amount) } func parseBitcoinWithdrawArgs(args []string, defaultReceiver string) (btcutil.Address, *big.Int) { @@ -185,7 +186,13 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) panic(err) } + // get cctx and check status cctx := utils.WaitCctxMinedByInTxHash(r.Ctx, receipt.TxHash.Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + if cctx.CctxStatus.Status != crosschaintypes.CctxStatus_OutboundMined { + panic(fmt.Errorf("cctx status is not OutboundMined")) + } + + // get bitcoin tx according to the outTxHash in cctx outTxHash := cctx.GetCurrentOutTxParam().OutboundTxHash hash, err := chainhash.NewHashFromStr(outTxHash) if err != nil { @@ -216,11 +223,7 @@ func withdrawBTCZRC20(r *runner.E2ERunner, to btcutil.Address, amount *big.Int) return rawTx } -func WithdrawBitcoin(r *runner.E2ERunner, receiver btcutil.Address, amount *big.Int) { - withdrawBTCZRC20(r, receiver, amount) -} - -func WithdrawBitcoinRestricted(r *runner.E2ERunner, amount *big.Int) { +func withdrawBitcoinRestricted(r *runner.E2ERunner, amount *big.Int) { // use restricted BTC P2WPKH address addressRestricted, err := common.DecodeBtcAddress(testutils.RestrictedBtcAddressTest, common.BtcRegtestChain().ChainId) if err != nil { diff --git a/x/crosschain/keeper/evm_hooks_test.go b/x/crosschain/keeper/evm_hooks_test.go index 72b44d5804..26755b4872 100644 --- a/x/crosschain/keeper/evm_hooks_test.go +++ b/x/crosschain/keeper/evm_hooks_test.go @@ -715,6 +715,28 @@ func TestKeeper_ProcessLogs(t *testing.T) { require.Len(t, cctxList, 0) }) + t.Run("error returned for invalid event data", func(t *testing.T) { + k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) + + // use the wrong (testnet) chain ID to make the btc address parsing fail + chain := common.BtcTestNetChain() + chainID := chain.ChainId + setSupportedChain(ctx, zk, chainID) + SetupStateForProcessLogs(t, ctx, k, zk, sdkk, chain) + + block := sample.GetInvalidZRC20WithdrawToExternal(t) + gasZRC20 := setupGasCoin(t, ctx, zk.FungibleKeeper, sdkk.EvmKeeper, chainID, "bitcoin", "BTC") + for _, log := range block.Logs { + log.Address = gasZRC20 + } + + err := k.ProcessLogs(ctx, block.Logs, sample.EthAddress(), "") + require.ErrorContains(t, err, "ParseZRC20WithdrawalEvent: invalid address") + cctxList := k.GetAllCrossChainTx(ctx) + require.Len(t, cctxList, 0) + }) + t.Run("error returned if unable to process an event", func(t *testing.T) { k, ctx, sdkk, zk := keepertest.CrosschainKeeper(t) k.GetAuthKeeper().GetModuleAccount(ctx, fungibletypes.ModuleName) diff --git a/zetaclient/bitcoin/bitcoin_client_test.go b/zetaclient/bitcoin/bitcoin_client_test.go index b1fc32b8c7..1cf91c910f 100644 --- a/zetaclient/bitcoin/bitcoin_client_test.go +++ b/zetaclient/bitcoin/bitcoin_client_test.go @@ -41,6 +41,22 @@ func MockBTCClientMainnet() *BTCChainClient { } } +// createRPCClientAndLoadTx is a helper function to load raw tx and feed it to mock rpc client +func createRPCClientAndLoadTx(chainId int64, txHash string) *stub.MockBTCRPCClient { + // file name for the archived MsgTx + nameMsgTx := path.Join("../", testutils.TestDataPathBTC, testutils.FileNameBTCMsgTx(chainId, txHash)) + + // load archived MsgTx + var msgTx wire.MsgTx + testutils.LoadObjectFromJSONFile(&msgTx, nameMsgTx) + tx := btcutil.NewTx(&msgTx) + + // feed tx to mock rpc client + rpcClient := stub.NewMockBTCRPCClient() + rpcClient.WithRawTransaction(tx) + return rpcClient +} + func TestNewBitcoinClient(t *testing.T) { t.Run("should return error because zetacore doesn't update core context", func(t *testing.T) { cfg := config.NewConfig() @@ -357,22 +373,6 @@ func TestCheckTSSVoutCancelled(t *testing.T) { }) } -// createRPCClientAndLoadTx is a helper function to load raw tx and feed it to mock rpc client -func createRPCClientAndLoadTx(chainId int64, txHash string) *stub.MockBTCRPCClient { - // file name for the archived MsgTx - nameMsgTx := path.Join("../", testutils.TestDataPathBTC, testutils.FileNameBTCMsgTx(chainId, txHash)) - - // load archived MsgTx - var msgTx wire.MsgTx - testutils.LoadObjectFromJSONFile(&msgTx, nameMsgTx) - tx := btcutil.NewTx(&msgTx) - - // feed tx to mock rpc client - rpcClient := stub.NewMockBTCRPCClient() - rpcClient.WithRawTransaction(tx) - return rpcClient -} - func TestGetSenderAddressByVin(t *testing.T) { chain := common.BtcMainnetChain() net := &chaincfg.MainNetParams diff --git a/zetaclient/bitcoin/bitcoin_signer.go b/zetaclient/bitcoin/bitcoin_signer.go index 83ef32e7a4..31e2993c56 100644 --- a/zetaclient/bitcoin/bitcoin_signer.go +++ b/zetaclient/bitcoin/bitcoin_signer.go @@ -98,7 +98,10 @@ func (signer *BTCSigner) GetERC20CustodyAddress() ethcommon.Address { return ethcommon.Address{} } -// AddWithdrawTxOutputs adds the outputs to the withdraw tx +// AddWithdrawTxOutputs adds the 3 outputs to the withdraw tx +// 1st output: the nonce-mark btc to TSS itself +// 2nd output: the payment to the recipient +// 3rd output: the remaining btc to TSS itself func (signer *BTCSigner) AddWithdrawTxOutputs( tx *wire.MsgTx, to btcutil.Address, @@ -200,7 +203,10 @@ func (signer *BTCSigner) SignWithdrawTx( // size checking // #nosec G701 always positive - txSize := EstimateOuttxSize(uint64(len(prevOuts)), []btcutil.Address{to}) + txSize, err := EstimateOuttxSize(uint64(len(prevOuts)), []btcutil.Address{to}) + if err != nil { + return nil, err + } if sizeLimit < BtcOutTxBytesWithdrawer { // ZRC20 'withdraw' charged less fee from end user signer.logger.Info().Msgf("sizeLimit %d is less than BtcOutTxBytesWithdrawer %d for nonce %d", sizeLimit, txSize, nonce) } diff --git a/zetaclient/bitcoin/bitcoin_signer_test.go b/zetaclient/bitcoin/bitcoin_signer_test.go index 575b8f0d09..3c72b32662 100644 --- a/zetaclient/bitcoin/bitcoin_signer_test.go +++ b/zetaclient/bitcoin/bitcoin_signer_test.go @@ -292,6 +292,20 @@ func TestAddWithdrawTxOutputs(t *testing.T) { {Value: 80000000, PkScript: tssScript}, }, }, + { + name: "should add outputs without change successfully", + tx: wire.NewMsgTx(wire.TxVersion), + to: to, + total: 0.20012000, + amount: 0.2, + nonce: 10000, + fees: big.NewInt(2000), + fail: false, + txout: []*wire.TxOut{ + {Value: 10000, PkScript: tssScript}, + {Value: 20000000, PkScript: toScript}, + }, + }, { name: "should cancel tx successfully", tx: wire.NewMsgTx(wire.TxVersion), diff --git a/zetaclient/bitcoin/fee.go b/zetaclient/bitcoin/fee.go index 59017448ce..022b5ae993 100644 --- a/zetaclient/bitcoin/fee.go +++ b/zetaclient/bitcoin/fee.go @@ -9,19 +9,17 @@ import ( "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/bitcoin" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" - - "github.com/btcsuite/btcd/wire" - "github.com/pkg/errors" ) const ( bytesPerKB = 1000 - bytesEmptyTx = 10 // an empty tx is 10 bytes bytesPerInput = 41 // each input is 41 bytes bytesPerOutputP2TR = 43 // each P2TR output is 43 bytes bytesPerOutputP2WSH = 43 // each P2WSH output is 43 bytes @@ -39,19 +37,16 @@ const ( ) var ( - BtcOutTxBytesDepositor uint64 - BtcOutTxBytesWithdrawer uint64 - DefaultDepositorFee float64 -) + // The outtx size incurred by the depositor: 68vB + BtcOutTxBytesDepositor = OuttxSizeDepositor() -func init() { - BtcOutTxBytesDepositor = OuttxSizeDepositor() // 68vB, the outtx size incurred by the depositor - BtcOutTxBytesWithdrawer = OuttxSizeWithdrawer() // 177vB, the outtx size incurred by the withdrawer + // The outtx size incurred by the withdrawer: 177vB + BtcOutTxBytesWithdrawer = OuttxSizeWithdrawer() + // The default depositor fee is 0.00001360 BTC (20 * 68vB / 100000000) // default depositor fee calculation is based on a fixed fee rate of 20 sat/byte just for simplicity. - // In reality, the fee rate on UTXO deposit is different from the fee rate when the UTXO is spent. - DefaultDepositorFee = DepositorFee(defaultDepositorFeeRate) // 0.00001360 (20 * 68vB / 100000000) -} + DefaultDepositorFee = DepositorFee(defaultDepositorFeeRate) +) // FeeRateToSatPerByte converts a fee rate in BTC/KB to sat/byte. func FeeRateToSatPerByte(rate float64) *big.Int { @@ -69,9 +64,9 @@ func WiredTxSize(numInputs uint64, numOutputs uint64) uint64 { } // EstimateOuttxSize estimates the size of a outtx in vBytes -func EstimateOuttxSize(numInputs uint64, payees []btcutil.Address) uint64 { +func EstimateOuttxSize(numInputs uint64, payees []btcutil.Address) (uint64, error) { if numInputs == 0 { - return 0 + return 0, nil } // #nosec G701 always positive numOutputs := 2 + uint64(len(payees)) @@ -82,45 +77,49 @@ func EstimateOuttxSize(numInputs uint64, payees []btcutil.Address) uint64 { // calculate the size of the outputs to payees bytesToPayees := uint64(0) for _, to := range payees { - bytesToPayees += GetOutputSizeByAddress(to) + sizeOutput, err := GetOutputSizeByAddress(to) + if err != nil { + return 0, err + } + bytesToPayees += sizeOutput } // calculate the size of the witness bytesWitness := bytes1stWitness + (numInputs-1)*bytesPerWitness // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#transaction-size-calculations // Calculation for signed SegWit tx: blockchain.GetTransactionWeight(tx) / 4 - return bytesWiredTx + bytesInput + bytesOutput + bytesToPayees + bytesWitness/blockchain.WitnessScaleFactor + return bytesWiredTx + bytesInput + bytesOutput + bytesToPayees + bytesWitness/blockchain.WitnessScaleFactor, nil } // GetOutputSizeByAddress returns the size of a tx output in bytes by the given address -func GetOutputSizeByAddress(to btcutil.Address) uint64 { +func GetOutputSizeByAddress(to btcutil.Address) (uint64, error) { switch addr := to.(type) { case *bitcoin.AddressTaproot: if addr == nil { - return 0 + return 0, nil } - return bytesPerOutputP2TR + return bytesPerOutputP2TR, nil case *btcutil.AddressWitnessScriptHash: if addr == nil { - return 0 + return 0, nil } - return bytesPerOutputP2WSH + return bytesPerOutputP2WSH, nil case *btcutil.AddressWitnessPubKeyHash: if addr == nil { - return 0 + return 0, nil } - return bytesPerOutputP2WPKH + return bytesPerOutputP2WPKH, nil case *btcutil.AddressScriptHash: if addr == nil { - return 0 + return 0, nil } - return bytesPerOutputP2SH + return bytesPerOutputP2SH, nil case *btcutil.AddressPubKeyHash: if addr == nil { - return 0 + return 0, nil } - return bytesPerOutputP2PKH + return bytesPerOutputP2PKH, nil default: - return bytesPerOutputP2WPKH + return 0, fmt.Errorf("cannot get output size for address type %T", to) } } @@ -207,18 +206,12 @@ func CalcBlockAvgFeeRate(blockVb *btcjson.GetBlockVerboseTxResult, netParams *ch // CalcDepositorFee calculates the depositor fee for a given block func CalcDepositorFee(blockVb *btcjson.GetBlockVerboseTxResult, chainID int64, netParams *chaincfg.Params, logger zerolog.Logger) float64 { - // use dynamic fee or default - dynamicFee := true - // use default fee for regnet if common.IsBitcoinRegnet(chainID) { - dynamicFee = false + return DefaultDepositorFee } // mainnet dynamic fee takes effect only after a planned upgrade height if common.IsBitcoinMainnet(chainID) && blockVb.Height < DynamicDepositorFeeHeight { - dynamicFee = false - } - if !dynamicFee { return DefaultDepositorFee } diff --git a/zetaclient/bitcoin/fee_test.go b/zetaclient/bitcoin/fee_test.go index 5ed7a6c54e..bf42f32375 100644 --- a/zetaclient/bitcoin/fee_test.go +++ b/zetaclient/bitcoin/fee_test.go @@ -199,7 +199,8 @@ func TestOutTxSize2In3Out(t *testing.T) { // #nosec G701 always positive vError := uint64(1) // 1 vByte error tolerance vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(uint64(len(utxosTxids)), []btcutil.Address{payee}) + vBytesEstimated, err := EstimateOuttxSize(uint64(len(utxosTxids)), []btcutil.Address{payee}) + require.NoError(t, err) if vBytes > vBytesEstimated { require.True(t, vBytes-vBytesEstimated <= vError) } else { @@ -222,7 +223,8 @@ func TestOutTxSize21In3Out(t *testing.T) { // #nosec G701 always positive vError := uint64(21 / 4) // 5 vBytes error tolerance vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(uint64(len(exampleTxids)), []btcutil.Address{payee}) + vBytesEstimated, err := EstimateOuttxSize(uint64(len(exampleTxids)), []btcutil.Address{payee}) + require.NoError(t, err) if vBytes > vBytesEstimated { require.True(t, vBytes-vBytesEstimated <= vError) } else { @@ -245,7 +247,8 @@ func TestOutTxSizeXIn3Out(t *testing.T) { // #nosec G701 always positive vError := uint64(0.25 + float64(x)/4) // 1st witness incurs 0.25 more vByte error than others (which incurs 1/4 vByte per witness) vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(uint64(len(exampleTxids[:x])), []btcutil.Address{payee}) + vBytesEstimated, err := EstimateOuttxSize(uint64(len(exampleTxids[:x])), []btcutil.Address{payee}) + require.NoError(t, err) if vBytes > vBytesEstimated { require.True(t, vBytes-vBytesEstimated <= vError) //fmt.Printf("%d error percentage: %.2f%%\n", float64(vBytes-vBytesEstimated)/float64(vBytes)*100) @@ -259,33 +262,64 @@ func TestOutTxSizeXIn3Out(t *testing.T) { func TestGetOutputSizeByAddress(t *testing.T) { // test nil P2TR address and non-nil P2TR address nilP2TR := (*bitcoin.AddressTaproot)(nil) + sizeNilP2TR, err := GetOutputSizeByAddress(nilP2TR) + require.NoError(t, err) + require.Equal(t, uint64(0), sizeNilP2TR) + addrP2TR := getTestAddrScript(t, ScriptTypeP2TR) - require.Equal(t, uint64(0), GetOutputSizeByAddress(nilP2TR)) - require.Equal(t, uint64(bytesPerOutputP2TR), GetOutputSizeByAddress(addrP2TR)) + sizeP2TR, err := GetOutputSizeByAddress(addrP2TR) + require.NoError(t, err) + require.Equal(t, uint64(bytesPerOutputP2TR), sizeP2TR) // test nil P2WSH address and non-nil P2WSH address nilP2WSH := (*btcutil.AddressWitnessScriptHash)(nil) + sizeNilP2WSH, err := GetOutputSizeByAddress(nilP2WSH) + require.NoError(t, err) + require.Equal(t, uint64(0), sizeNilP2WSH) + addrP2WSH := getTestAddrScript(t, ScriptTypeP2WSH) - require.Equal(t, uint64(0), GetOutputSizeByAddress(nilP2WSH)) - require.Equal(t, uint64(bytesPerOutputP2WSH), GetOutputSizeByAddress(addrP2WSH)) + sizeP2WSH, err := GetOutputSizeByAddress(addrP2WSH) + require.NoError(t, err) + require.Equal(t, uint64(bytesPerOutputP2WSH), sizeP2WSH) // test nil P2WPKH address and non-nil P2WPKH address nilP2WPKH := (*btcutil.AddressWitnessPubKeyHash)(nil) + sizeNilP2WPKH, err := GetOutputSizeByAddress(nilP2WPKH) + require.NoError(t, err) + require.Equal(t, uint64(0), sizeNilP2WPKH) + addrP2WPKH := getTestAddrScript(t, ScriptTypeP2WPKH) - require.Equal(t, uint64(0), GetOutputSizeByAddress(nilP2WPKH)) - require.Equal(t, uint64(bytesPerOutputP2WPKH), GetOutputSizeByAddress(addrP2WPKH)) + sizeP2WPKH, err := GetOutputSizeByAddress(addrP2WPKH) + require.NoError(t, err) + require.Equal(t, uint64(bytesPerOutputP2WPKH), sizeP2WPKH) // test nil P2SH address and non-nil P2SH address nilP2SH := (*btcutil.AddressScriptHash)(nil) + sizeNilP2SH, err := GetOutputSizeByAddress(nilP2SH) + require.NoError(t, err) + require.Equal(t, uint64(0), sizeNilP2SH) + addrP2SH := getTestAddrScript(t, ScriptTypeP2SH) - require.Equal(t, uint64(0), GetOutputSizeByAddress(nilP2SH)) - require.Equal(t, uint64(bytesPerOutputP2SH), GetOutputSizeByAddress(addrP2SH)) + sizeP2SH, err := GetOutputSizeByAddress(addrP2SH) + require.NoError(t, err) + require.Equal(t, uint64(bytesPerOutputP2SH), sizeP2SH) // test nil P2PKH address and non-nil P2PKH address nilP2PKH := (*btcutil.AddressPubKeyHash)(nil) + sizeNilP2PKH, err := GetOutputSizeByAddress(nilP2PKH) + require.NoError(t, err) + require.Equal(t, uint64(0), sizeNilP2PKH) + addrP2PKH := getTestAddrScript(t, ScriptTypeP2PKH) - require.Equal(t, uint64(0), GetOutputSizeByAddress(nilP2PKH)) - require.Equal(t, uint64(bytesPerOutputP2PKH), GetOutputSizeByAddress(addrP2PKH)) + sizeP2PKH, err := GetOutputSizeByAddress(addrP2PKH) + require.NoError(t, err) + require.Equal(t, uint64(bytesPerOutputP2PKH), sizeP2PKH) + + // test unsupported address type + nilP2PK := (*btcutil.AddressPubKey)(nil) + sizeP2PK, err := GetOutputSizeByAddress(nilP2PK) + require.ErrorContains(t, err, "cannot get output size for address type") + require.Equal(t, uint64(0), sizeP2PK) } func TestOutputSizeP2TR(t *testing.T) { @@ -303,7 +337,8 @@ func TestOutputSizeP2TR(t *testing.T) { // Estimate the tx size in vByte // #nosec G701 always positive vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(2, payees) + vBytesEstimated, err := EstimateOuttxSize(2, payees) + require.NoError(t, err) require.Equal(t, vBytes, vBytesEstimated) } @@ -322,7 +357,8 @@ func TestOutputSizeP2WSH(t *testing.T) { // Estimate the tx size in vByte // #nosec G701 always positive vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(2, payees) + vBytesEstimated, err := EstimateOuttxSize(2, payees) + require.NoError(t, err) require.Equal(t, vBytes, vBytesEstimated) } @@ -341,7 +377,8 @@ func TestOutputSizeP2SH(t *testing.T) { // Estimate the tx size in vByte // #nosec G701 always positive vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(2, payees) + vBytesEstimated, err := EstimateOuttxSize(2, payees) + require.NoError(t, err) require.Equal(t, vBytes, vBytesEstimated) } @@ -360,7 +397,8 @@ func TestOutputSizeP2PKH(t *testing.T) { // Estimate the tx size in vByte // #nosec G701 always positive vBytes := uint64(blockchain.GetTransactionWeight(btcutil.NewTx(tx)) / blockchain.WitnessScaleFactor) - vBytesEstimated := EstimateOuttxSize(2, payees) + vBytesEstimated, err := EstimateOuttxSize(2, payees) + require.NoError(t, err) require.Equal(t, vBytes, vBytesEstimated) } @@ -377,7 +415,9 @@ func TestOuttxSizeBreakdown(t *testing.T) { // add all outtx sizes paying to each address txSizeTotal := uint64(0) for _, payee := range payees { - txSizeTotal += EstimateOuttxSize(2, []btcutil.Address{payee}) + sizeOutput, err := EstimateOuttxSize(2, []btcutil.Address{payee}) + require.NoError(t, err) + txSizeTotal += sizeOutput } // calculate the average outtx size @@ -401,16 +441,24 @@ func TestOuttxSizeBreakdown(t *testing.T) { require.Equal(t, depositFee, 0.00001360) } -func TestOuttxSizeMinMax(t *testing.T) { +func TestOuttxSizeMinMaxError(t *testing.T) { // P2TR output is the largest in size; P2WPKH is the smallest toP2TR := getTestAddrScript(t, ScriptTypeP2TR) toP2WPKH := getTestAddrScript(t, ScriptTypeP2WPKH) // Estimate the largest outtx size in vByte - sizeMax := EstimateOuttxSize(21, []btcutil.Address{toP2TR}) + sizeMax, err := EstimateOuttxSize(21, []btcutil.Address{toP2TR}) + require.NoError(t, err) require.Equal(t, outTxBytesMax, sizeMax) // Estimate the smallest outtx size in vByte - sizeMin := EstimateOuttxSize(2, []btcutil.Address{toP2WPKH}) + sizeMin, err := EstimateOuttxSize(2, []btcutil.Address{toP2WPKH}) + require.NoError(t, err) require.Equal(t, outTxBytesMin, sizeMin) + + // Estimate unknown address type + nilP2PK := (*btcutil.AddressPubKey)(nil) + size, err := EstimateOuttxSize(1, []btcutil.Address{nilP2PK}) + require.Error(t, err) + require.Equal(t, uint64(0), size) } diff --git a/zetaclient/bitcoin/txscript.go b/zetaclient/bitcoin/tx_script.go similarity index 94% rename from zetaclient/bitcoin/txscript.go rename to zetaclient/bitcoin/tx_script.go index 73750f1803..c031a282a7 100644 --- a/zetaclient/bitcoin/txscript.go +++ b/zetaclient/bitcoin/tx_script.go @@ -11,20 +11,27 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcutil" "github.com/cosmos/btcutil/base58" + "github.com/pkg/errors" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/bitcoin" "golang.org/x/crypto/ripemd160" - - "github.com/pkg/errors" ) const ( - // Length of P2TR, P2WSH, P2WPKH, P2SH, P2PKH scripts - LengthScriptP2TR = 34 - LengthScriptP2WSH = 34 + // Lenth of P2TR script [OP_1 0x20 <32-byte-hash>] + LengthScriptP2TR = 34 + + // Length of P2WSH script [OP_0 0x20 <32-byte-hash>] + LengthScriptP2WSH = 34 + + // Length of P2WPKH script [OP_0 0x14 <20-byte-hash>] LengthScriptP2WPKH = 22 - LengthScriptP2SH = 23 - LengthScriptP2PKH = 25 + + // Length of P2SH script [OP_HASH160 0x14 <20-byte-hash> OP_EQUAL] + LengthScriptP2SH = 23 + + // Length of P2PKH script [OP_DUP OP_HASH160 0x14 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG] + LengthScriptP2PKH = 25 ) // PayToAddrScript creates a new script to pay a transaction output to a the @@ -40,25 +47,21 @@ func PayToAddrScript(addr btcutil.Address) ([]byte, error) { // IsPkScriptP2TR checks if the given script is a P2TR script func IsPkScriptP2TR(script []byte) bool { - // [OP_1 0x20 <32-byte-hash>] return len(script) == LengthScriptP2TR && script[0] == txscript.OP_1 && script[1] == 0x20 } // IsPkScriptP2WSH checks if the given script is a P2WSH script func IsPkScriptP2WSH(script []byte) bool { - // [OP_0 0x20 <32-byte-hash>] return len(script) == LengthScriptP2WSH && script[0] == txscript.OP_0 && script[1] == 0x20 } // IsPkScriptP2WPKH checks if the given script is a P2WPKH script func IsPkScriptP2WPKH(script []byte) bool { - // [OP_0 0x14 <20-byte-hash>] return len(script) == LengthScriptP2WPKH && script[0] == txscript.OP_0 && script[1] == 0x14 } // IsPkScriptP2SH checks if the given script is a P2SH script func IsPkScriptP2SH(script []byte) bool { - // [OP_HASH160 0x14 <20-byte-hash> OP_EQUAL] return len(script) == LengthScriptP2SH && script[0] == txscript.OP_HASH160 && script[1] == 0x14 && @@ -67,7 +70,6 @@ func IsPkScriptP2SH(script []byte) bool { // IsPkScriptP2PKH checks if the given script is a P2PKH script func IsPkScriptP2PKH(script []byte) bool { - // [OP_DUP OP_HASH160 0x14 <20-byte-hash> OP_EQUALVERIFY OP_CHECKSIG] return len(script) == LengthScriptP2PKH && script[0] == txscript.OP_DUP && script[1] == txscript.OP_HASH160 && diff --git a/zetaclient/bitcoin/txscript_test.go b/zetaclient/bitcoin/tx_script_test.go similarity index 100% rename from zetaclient/bitcoin/txscript_test.go rename to zetaclient/bitcoin/tx_script_test.go