From ad4fc253868c52aa507e61fa1828300e99eba47e Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Thu, 22 Aug 2024 17:53:41 -0500 Subject: [PATCH 1/7] implementation of bitcoin depositor fee V2 --- zetaclient/chains/bitcoin/fee.go | 72 +++- zetaclient/chains/bitcoin/observer/inbound.go | 12 + .../chains/bitcoin/observer/observer.go | 3 +- zetaclient/chains/bitcoin/rpc/rpc.go | 89 +++-- .../chains/bitcoin/rpc/rpc_live_test.go | 360 +++++++++++------- 5 files changed, 351 insertions(+), 185 deletions(-) diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index aec41407d9..55cdaf0c6a 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -15,6 +15,8 @@ import ( "github.com/rs/zerolog" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/rpc" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" clientcommon "github.com/zeta-chain/zetacore/zetaclient/common" ) @@ -35,7 +37,11 @@ const ( OutboundBytesMax = uint64(1543) // 1543v == EstimateSegWitTxSize(21, 2, toP2TR) OutboundBytesAvg = uint64(245) // 245vB is a suggested gas limit for zetacore - DynamicDepositorFeeHeight = 834500 // DynamicDepositorFeeHeight contains the starting height (Bitcoin mainnet) from which dynamic depositor fee will take effect + DynamicDepositorFeeHeight = 834500 // the mainnet height from which dynamic depositor fee V1 is applied + DynamicDepositorFeeHeightV2 = 863400 // the mainnet height from which dynamic depositor fee V2 is applied + + feeRateCountBackBlocks = 2 // the default number of blocks to look back for fee rate estimation + defaultTestnetFeeRate = 10 // the default fee rate for testnet, 10 sat/vB ) var ( @@ -239,3 +245,67 @@ func CalcDepositorFee( return DepositorFee(feeRate) } + +// CalcDepositorFeeV2 calculates the depositor fee for a given tx result +func CalcDepositorFeeV2( + rpcClient interfaces.BTCRPCClient, + rawResult *btcjson.TxRawResult, + netParams *chaincfg.Params, +) (float64, error) { + // use default fee for regnet + if netParams.Name == chaincfg.RegressionNetParams.Name { + return DefaultDepositorFee, nil + } + + // get fee rate of the transaction + _, feeRate, err := rpc.GetTransactionFeeAndRate(rpcClient, rawResult) + if err != nil { + return 0, errors.Wrapf(err, "error getting fee rate for tx %s", rawResult.Txid) + } + + // apply gas price multiplier + // #nosec G115 always in range + feeRate = int64(float64(feeRate) * clientcommon.BTCOutboundGasPriceMultiplier) + + return DepositorFee(feeRate), nil +} + +// GetRecentFeeRate gets the highest fee rate from recent blocks +// Note: this method should be used for testnet ONLY +func GetRecentFeeRate(rpcClient interfaces.BTCRPCClient, netParams *chaincfg.Params) (uint64, error) { + blockNumber, err := rpcClient.GetBlockCount() + if err != nil { + return 0, err + } + + // get the highest fee rate among recent 'countBack' blocks to avoid underestimation + highestRate := int64(0) + for i := int64(0); i < feeRateCountBackBlocks; i++ { + // get the block + hash, err := rpcClient.GetBlockHash(blockNumber - i) + if err != nil { + return 0, err + } + block, err := rpcClient.GetBlockVerboseTx(hash) + if err != nil { + return 0, err + } + + // computes the average fee rate of the block and take the higher rate + avgFeeRate, err := CalcBlockAvgFeeRate(block, netParams) + if err != nil { + return 0, err + } + if avgFeeRate > highestRate { + highestRate = avgFeeRate + } + } + + // use 10 sat/byte as default estimation if recent fee rate drops to 0 + if highestRate == 0 { + highestRate = defaultTestnetFeeRate + } + + // #nosec G115 always in range + return uint64(highestRate), nil +} diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index a7dc5afe3d..370948d6a5 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -421,6 +421,18 @@ func GetBtcEvent( return nil, nil } + // switch to depositor fee V2 if + // 1. it is bitcoin testnet, or + // 2. it is bitcoin mainnet and upgrade height is reached + // TODO: remove CalcDepositorFeeV1 and below conditions after the upgrade height + if netParams.Name == chaincfg.TestNet3Params.Name || + (netParams.Name == chaincfg.MainNetParams.Name && blockNumber >= bitcoin.DynamicDepositorFeeHeightV2) { + depositorFee, err = bitcoin.CalcDepositorFeeV2(rpcClient, &tx, netParams) + if err != nil { + return nil, errors.Wrapf(err, "error calculating depositor fee V2 for inbound: %s", tx.Txid) + } + } + // deposit amount has to be no less than the minimum depositor fee if vout0.Value < depositorFee { logger.Info(). diff --git a/zetaclient/chains/bitcoin/observer/observer.go b/zetaclient/chains/bitcoin/observer/observer.go index 6a15173c33..d8b7378b58 100644 --- a/zetaclient/chains/bitcoin/observer/observer.go +++ b/zetaclient/chains/bitcoin/observer/observer.go @@ -23,7 +23,6 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" - "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/rpc" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/zetaclient/metrics" @@ -628,7 +627,7 @@ func (ob *Observer) specialHandleFeeRate() (uint64, error) { // hardcode gas price for regnet return 1, nil case chains.NetworkType_testnet: - feeRateEstimated, err := rpc.GetRecentFeeRate(ob.btcClient, ob.netParams) + feeRateEstimated, err := bitcoin.GetRecentFeeRate(ob.btcClient, ob.netParams) if err != nil { return 0, errors.Wrapf(err, "error GetRecentFeeRate") } diff --git a/zetaclient/chains/bitcoin/rpc/rpc.go b/zetaclient/chains/bitcoin/rpc/rpc.go index 079b6fa298..9b9b36842f 100644 --- a/zetaclient/chains/bitcoin/rpc/rpc.go +++ b/zetaclient/chains/bitcoin/rpc/rpc.go @@ -4,24 +4,15 @@ import ( "fmt" "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcutil" "github.com/pkg/errors" - "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/config" ) -const ( - // feeRateCountBackBlocks is the default number of blocks to look back for fee rate estimation - feeRateCountBackBlocks = 2 - - // defaultTestnetFeeRate is the default fee rate for testnet, 10 sat/byte - defaultTestnetFeeRate = 10 -) - // NewRPCClient creates a new RPC client by the given config. func NewRPCClient(btcConfig config.BTCConfig) (*rpcclient.Client, error) { connCfg := &rpcclient.ConnConfig{ @@ -63,6 +54,20 @@ func GetTxResultByHash( return hash, txResult, nil } +// GetTXRawResultByHash gets the raw transaction by hash +func GetRawTxByHash(rpcClient interfaces.BTCRPCClient, txID string) (*btcutil.Tx, error) { + hash, err := chainhash.NewHashFromStr(txID) + if err != nil { + return nil, errors.Wrapf(err, "GetRawTxByHash: error NewHashFromStr: %s", txID) + } + + tx, err := rpcClient.GetRawTransaction(hash) + if err != nil { + return nil, errors.Wrapf(err, "GetRawTxByHash: error GetRawTransaction %s", txID) + } + return tx, nil +} + // GetBlockHeightByHash gets the block height by block hash func GetBlockHeightByHash( rpcClient interfaces.BTCRPCClient, @@ -118,42 +123,48 @@ func GetRawTxResult( return btcjson.TxRawResult{}, fmt.Errorf("GetRawTxResult: tx %s not included yet", hash) } -// GetRecentFeeRate gets the highest fee rate from recent blocks -// Note: this method is only used for testnet -func GetRecentFeeRate(rpcClient interfaces.BTCRPCClient, netParams *chaincfg.Params) (uint64, error) { - blockNumber, err := rpcClient.GetBlockCount() - if err != nil { - return 0, err - } +// GetTransactionFeeAndRate gets the transaction fee and rate for a given tx result +func GetTransactionFeeAndRate(rpcClient interfaces.BTCRPCClient, rawResult *btcjson.TxRawResult) (int64, int64, error) { + var ( + totalInputValue int64 + totalOutputValue int64 + ) - // get the highest fee rate among recent 'countBack' blocks to avoid underestimation - highestRate := int64(0) - for i := int64(0); i < feeRateCountBackBlocks; i++ { - // get the block - hash, err := rpcClient.GetBlockHash(blockNumber - i) - if err != nil { - return 0, err - } - block, err := rpcClient.GetBlockVerboseTx(hash) + // sum up total input value + for _, vin := range rawResult.Vin { + prevTx, err := GetRawTxByHash(rpcClient, vin.Txid) if err != nil { - return 0, err + return 0, 0, errors.Wrapf(err, "failed to get previous tx: %s", vin.Txid) } + totalInputValue += prevTx.MsgTx().TxOut[vin.Vout].Value + } - // computes the average fee rate of the block and take the higher rate - avgFeeRate, err := bitcoin.CalcBlockAvgFeeRate(block, netParams) - if err != nil { - return 0, err - } - if avgFeeRate > highestRate { - highestRate = avgFeeRate - } + // query the raw tx + tx, err := GetRawTxByHash(rpcClient, rawResult.Txid) + if err != nil { + return 0, 0, errors.Wrapf(err, "failed to get tx: %s", rawResult.Txid) } - // use 10 sat/byte as default estimation if recent fee rate drops to 0 - if highestRate == 0 { - highestRate = defaultTestnetFeeRate + // sum up total output value + for _, vout := range tx.MsgTx().TxOut { + totalOutputValue += vout.Value } + // calculate the transaction fee in satoshis + fee := totalInputValue - totalOutputValue + if fee < 0 { // never happens + return 0, 0, fmt.Errorf("got negative fee: %d", fee) + } + + // Note: the calculation uses 'Vsize' returned by RPC to simplify dev experience: + // - 1. the devs could use the same value returned by their RPC endpoints to estimate deposit fee. + // - 2. the devs don't have to bother 'Vsize' calculation, even though there is more accurate formula. + // Moreoever, the accurate 'Vsize' is usually an adjusted size (float value) by Bitcoin Core. + // - 3. the 'Vsize' calculation could depend on program language and the library used. + // + // calculate the fee rate in satoshis/vByte // #nosec G115 always in range - return uint64(highestRate), nil + feeRate := fee / int64(rawResult.Vsize) + + return fee, feeRate, nil } diff --git a/zetaclient/chains/bitcoin/rpc/rpc_live_test.go b/zetaclient/chains/bitcoin/rpc/rpc_live_test.go index 54964d7403..3eb6759551 100644 --- a/zetaclient/chains/bitcoin/rpc/rpc_live_test.go +++ b/zetaclient/chains/bitcoin/rpc/rpc_live_test.go @@ -10,91 +10,24 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" - "github.com/ethereum/go-ethereum/crypto" "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "github.com/zeta-chain/zetacore/zetaclient/db" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/rpc" + "github.com/zeta-chain/zetacore/zetaclient/common" "github.com/zeta-chain/zetacore/zetaclient/config" "github.com/zeta-chain/zetacore/zetaclient/testutils" - "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" ) -type BitcoinObserverTestSuite struct { - suite.Suite - rpcClient *rpcclient.Client -} - -func (suite *BitcoinObserverTestSuite) SetupTest() { - // test private key with EVM address - //// EVM: 0x236C7f53a90493Bb423411fe4117Cb4c2De71DfB - // BTC testnet3: muGe9prUBjQwEnX19zG26fVRHNi8z7kSPo - skHex := "7b8507ba117e069f4a3f456f505276084f8c92aee86ac78ae37b4d1801d35fa8" - privateKey, err := crypto.HexToECDSA(skHex) - suite.Require().NoError(err) - pkBytes := crypto.FromECDSAPub(&privateKey.PublicKey) - suite.T().Logf("pubkey: %d", len(pkBytes)) - - tss := &mocks.TSS{ - PrivKey: privateKey, - } - - // create mock arguments for constructor - chain := chains.BitcoinMainnet - params := mocks.MockChainParams(chain.ChainId, 10) - btcClient := mocks.NewMockBTCRPCClient() - - database, err := db.NewFromSqliteInMemory(true) - suite.Require().NoError(err) - - // create observer - ob, err := observer.NewObserver(chain, btcClient, params, nil, tss, database, base.DefaultLogger(), nil) - - suite.Require().NoError(err) - suite.Require().NotNil(ob) - suite.rpcClient, err = createRPCClient(18332) - suite.Require().NoError(err) - skBytes, err := hex.DecodeString(skHex) - suite.Require().NoError(err) - suite.T().Logf("skBytes: %d", len(skBytes)) - - _, err = btcClient.CreateWallet("e2e") - suite.Require().NoError(err) - addr, err := btcClient.GetNewAddress("test") - suite.Require().NoError(err) - suite.T().Logf("deployer address: %s", addr) - //err = btc.ImportPrivKey(privkeyWIF) - //suite.Require().NoError(err) - - btcClient.GenerateToAddress(101, addr, nil) - suite.Require().NoError(err) - - bal, err := btcClient.GetBalance("*") - suite.Require().NoError(err) - suite.T().Logf("balance: %f", bal.ToBTC()) - - utxo, err := btcClient.ListUnspent() - suite.Require().NoError(err) - suite.T().Logf("utxo: %d", len(utxo)) - for _, u := range utxo { - suite.T().Logf("utxo: %s %f", u.Address, u.Amount) - } -} - -func (suite *BitcoinObserverTestSuite) TearDownSuite() { -} - // createRPCClient creates a new Bitcoin RPC client for given chainID func createRPCClient(chainID int64) (*rpcclient.Client, error) { var connCfg *rpcclient.ConnConfig @@ -126,6 +59,7 @@ func createRPCClient(chainID int64) (*rpcclient.Client, error) { return rpcclient.New(connCfg, nil) } +// getFeeRate is a helper function to get fee rate for a given confirmation target func getFeeRate( client *rpcclient.Client, confTarget int64, @@ -144,28 +78,60 @@ func getFeeRate( return new(big.Int).SetInt64(int64(*feeResult.FeeRate * 1e8)), nil } -// All methods that begin with "Test" are run as tests within a -// suite. -func (suite *BitcoinObserverTestSuite) Test1() { - feeResult, err := suite.rpcClient.EstimateSmartFee(1, nil) - suite.Require().NoError(err) - suite.T().Logf("fee result: %f", *feeResult.FeeRate) - bn, err := suite.rpcClient.GetBlockCount() - suite.Require().NoError(err) - suite.T().Logf("block %d", bn) +// getMempoolSpaceTxsByBlock gets mempool.space txs for a given block +func getMempoolSpaceTxsByBlock( + t *testing.T, + client *rpcclient.Client, + blkNumber int64, + testnet bool, +) (*chainhash.Hash, []testutils.MempoolTx, error) { + blkHash, err := client.GetBlockHash(blkNumber) + if err != nil { + t.Logf("error GetBlockHash for block %d: %s\n", blkNumber, err) + return nil, nil, err + } + + // get mempool.space txs for the block + mempoolTxs, err := testutils.GetBlockTxs(context.Background(), blkHash.String(), testnet) + if err != nil { + t.Logf("error GetBlockTxs %d: %s\n", blkNumber, err) + return nil, nil, err + } + + return blkHash, mempoolTxs, nil +} + +// Test_BitcoinLive is a phony test to run each live test individually +func Test_BitcoinLive(t *testing.T) { + // LiveTest_FilterAndParseIncomingTx(t) + // LiveTest_FilterAndParseIncomingTx_Nop(t) + // LiveTest_NewRPCClient(t) + // LiveTest_GetBlockHeightByHash(t) + // LiveTest_BitcoinFeeRate(t) + // LiveTest_AvgFeeRateMainnetMempoolSpace(t) + // LiveTest_AvgFeeRateTestnetMempoolSpace(t) + // LiveTest_GetRecentFeeRate(t) + // LiveTest_GetSenderByVin(t) + // LiveTest_GetTransactionFeeRate(t) + // LiveTest_CalcDepositorFeeV2(t) +} +func LiveTest_FilterAndParseIncomingTx(t *testing.T) { + // setup Bitcoin client + client, err := createRPCClient(chains.BitcoinTestnet.ChainId) + require.NoError(t, err) + + // get the block that contains the incoming tx hashStr := "0000000000000032cb372f5d5d99c1ebf4430a3059b67c47a54dd626550fb50d" - var hash chainhash.Hash - err = chainhash.Decode(&hash, hashStr) - suite.Require().NoError(err) + hash, err := chainhash.NewHashFromStr(hashStr) + require.NoError(t, err) - block, err := suite.rpcClient.GetBlockVerboseTx(&hash) - suite.Require().NoError(err) - suite.T().Logf("block confirmation %d", block.Confirmations) - suite.T().Logf("block txs len %d", len(block.Tx)) + block, err := client.GetBlockVerboseTx(hash) + require.NoError(t, err) + // filter incoming tx inbounds, err := observer.FilterAndParseIncomingTx( - suite.rpcClient, + client, block.Tx, uint64(block.Height), "tb1qsa222mn2rhdq9cruxkz8p2teutvxuextx3ees2", @@ -173,36 +139,37 @@ func (suite *BitcoinObserverTestSuite) Test1() { &chaincfg.TestNet3Params, 0.0, ) - suite.Require().NoError(err) - suite.Require().Equal(1, len(inbounds)) - suite.Require().Equal(inbounds[0].Value, 0.0001) - suite.Require().Equal(inbounds[0].ToAddress, "tb1qsa222mn2rhdq9cruxkz8p2teutvxuextx3ees2") + require.NoError(t, err) + require.Len(t, inbounds, 1) + require.Equal(t, inbounds[0].Value, 0.0001) + require.Equal(t, inbounds[0].ToAddress, "tb1qsa222mn2rhdq9cruxkz8p2teutvxuextx3ees2") + // the text memo is base64 std encoded string:DSRR1RmDCwWmxqY201/TMtsJdmA= // see https://blockstream.info/testnet/tx/889bfa69eaff80a826286d42ec3f725fd97c3338357ddc3a1f543c2d6266f797 - memo, err := hex.DecodeString("0d2451D519830B05a6C6a636d35fd332dB097660") - suite.Require().NoError(err) - suite.Require().Equal((inbounds[0].MemoBytes), memo) - suite.Require().Equal(inbounds[0].FromAddress, "tb1qyslx2s8evalx67n88wf42yv7236303ezj3tm2l") - suite.T().Logf("from: %s", inbounds[0].FromAddress) - suite.Require().Equal(inbounds[0].BlockNumber, uint64(2406185)) - suite.Require().Equal(inbounds[0].TxHash, "889bfa69eaff80a826286d42ec3f725fd97c3338357ddc3a1f543c2d6266f797") + memo, err := hex.DecodeString("4453525231526d444377576d7871593230312f544d74734a646d413d") + require.NoError(t, err) + require.Equal(t, inbounds[0].MemoBytes, memo) + require.Equal(t, inbounds[0].FromAddress, "tb1qyslx2s8evalx67n88wf42yv7236303ezj3tm2l") + require.Equal(t, inbounds[0].BlockNumber, uint64(2406185)) + require.Equal(t, inbounds[0].TxHash, "889bfa69eaff80a826286d42ec3f725fd97c3338357ddc3a1f543c2d6266f797") } -// a tx with memo around 81B (is this allowed1?) -func (suite *BitcoinObserverTestSuite) Test2() { +func LiveTest_FilterAndParseIncomingTx_Nop(t *testing.T) { + // setup Bitcoin client + client, err := createRPCClient(chains.BitcoinTestnet.ChainId) + require.NoError(t, err) + + // get a block that contains no incoming tx hashStr := "000000000000002fd8136dbf91708898da9d6ae61d7c354065a052568e2f2888" - var hash chainhash.Hash - err := chainhash.Decode(&hash, hashStr) - suite.Require().NoError(err) + hash, err := chainhash.NewHashFromStr(hashStr) + require.NoError(t, err) - block, err := suite.rpcClient.GetBlockVerboseTx(&hash) - suite.Require().NoError(err) - suite.T().Logf("block confirmation %d", block.Confirmations) - suite.T().Logf("block height %d", block.Height) - suite.T().Logf("block txs len %d", len(block.Tx)) + block, err := client.GetBlockVerboseTx(hash) + require.NoError(t, err) + // filter incoming tx inbounds, err := observer.FilterAndParseIncomingTx( - suite.rpcClient, + client, block.Tx, uint64(block.Height), "tb1qsa222mn2rhdq9cruxkz8p2teutvxuextx3ees2", @@ -210,25 +177,12 @@ func (suite *BitcoinObserverTestSuite) Test2() { &chaincfg.TestNet3Params, 0.0, ) - suite.Require().NoError(err) - suite.Require().Equal(0, len(inbounds)) -} - -// TestBitcoinObserverLive is a phony test to run each live test individually -func TestBitcoinObserverLive(t *testing.T) { - // suite.Run(t, new(BitcoinClientTestSuite)) - - // LiveTestNewRPCClient(t) - // LiveTestGetBlockHeightByHash(t) - // LiveTestBitcoinFeeRate(t) - // LiveTestAvgFeeRateMainnetMempoolSpace(t) - // LiveTestAvgFeeRateTestnetMempoolSpace(t) - // LiveTestGetRecentFeeRate(t) - // LiveTestGetSenderByVin(t) + require.NoError(t, err) + require.Empty(t, inbounds) } // LiveTestNewRPCClient creates a new Bitcoin RPC client -func LiveTestNewRPCClient(t *testing.T) { +func LiveTest_NewRPCClient(t *testing.T) { btcConfig := config.BTCConfig{ RPCUsername: "user", RPCPassword: "pass", @@ -247,7 +201,7 @@ func LiveTestNewRPCClient(t *testing.T) { } // LiveTestGetBlockHeightByHash queries Bitcoin block height by hash -func LiveTestGetBlockHeightByHash(t *testing.T) { +func LiveTest_GetBlockHeightByHash(t *testing.T) { // setup Bitcoin client client, err := createRPCClient(chains.BitcoinMainnet.ChainId) require.NoError(t, err) @@ -269,7 +223,7 @@ func LiveTestGetBlockHeightByHash(t *testing.T) { // LiveTestBitcoinFeeRate query Bitcoin mainnet fee rate every 5 minutes // and compares Conservative and Economical fee rates for different block targets (1 and 2) -func LiveTestBitcoinFeeRate(t *testing.T) { +func LiveTest_BitcoinFeeRate(t *testing.T) { // setup Bitcoin client client, err := createRPCClient(chains.BitcoinMainnet.ChainId) require.NoError(t, err) @@ -394,7 +348,7 @@ func compareAvgFeeRate(t *testing.T, client *rpcclient.Client, startBlock int, e } // LiveTestAvgFeeRateMainnetMempoolSpace compares calculated fee rate with mempool.space fee rate for mainnet -func LiveTestAvgFeeRateMainnetMempoolSpace(t *testing.T) { +func LiveTest_AvgFeeRateMainnetMempoolSpace(t *testing.T) { // setup Bitcoin client client, err := createRPCClient(chains.BitcoinMainnet.ChainId) require.NoError(t, err) @@ -408,7 +362,7 @@ func LiveTestAvgFeeRateMainnetMempoolSpace(t *testing.T) { } // LiveTestAvgFeeRateTestnetMempoolSpace compares calculated fee rate with mempool.space fee rate for testnet -func LiveTestAvgFeeRateTestnetMempoolSpace(t *testing.T) { +func LiveTest_AvgFeeRateTestnetMempoolSpace(t *testing.T) { // setup Bitcoin client client, err := createRPCClient(chains.BitcoinTestnet.ChainId) require.NoError(t, err) @@ -422,13 +376,13 @@ func LiveTestAvgFeeRateTestnetMempoolSpace(t *testing.T) { } // LiveTestGetRecentFeeRate gets the highest fee rate from recent blocks -func LiveTestGetRecentFeeRate(t *testing.T) { +func LiveTest_GetRecentFeeRate(t *testing.T) { // setup Bitcoin testnet client client, err := createRPCClient(chains.BitcoinTestnet.ChainId) require.NoError(t, err) // get fee rate from recent blocks - feeRate, err := rpc.GetRecentFeeRate(client, &chaincfg.TestNet3Params) + feeRate, err := bitcoin.GetRecentFeeRate(client, &chaincfg.TestNet3Params) require.NoError(t, err) require.Greater(t, feeRate, uint64(0)) } @@ -453,22 +407,13 @@ func LiveTestGetSenderByVin(t *testing.T) { require.NoError(t, err) endBlock := startBlock - 5000 - // loop through mempool.space blocks in descending order + // loop through mempool.space blocks backwards 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) + _, mempoolTxs, err := getMempoolSpaceTxsByBlock(t, client, bn, testnet) if err != nil { - fmt.Printf("error GetBlockTxs %d: %s\n", bn, err) - time.Sleep(10 * time.Second) + time.Sleep(3 * time.Second) continue } @@ -502,6 +447,135 @@ BLOCKLOOP: } } bn-- - time.Sleep(500 * time.Millisecond) + time.Sleep(100 * time.Millisecond) } } + +// LiveTestGetTransactionFeeAndRate gets the transaction fee and rate for each tx and compares with mempool.space fee rate +func Test_GetTransactionFeeAndRate(t *testing.T) { + // setup Bitcoin client + chainID := chains.BitcoinTestnet.ChainId + client, err := createRPCClient(chainID) + require.NoError(t, err) + + // testnet or mainnet + testnet := false + if chainID == chains.BitcoinTestnet.ChainId { + testnet = true + } + + // calculates block range to test + startBlock, err := client.GetBlockCount() + require.NoError(t, err) + endBlock := startBlock - 100 // go back whatever blocks as needed + + // loop through mempool.space blocks backwards + for bn := startBlock; bn >= endBlock; { + // get mempool.space txs for the block + blkHash, mempoolTxs, err := getMempoolSpaceTxsByBlock(t, client, bn, testnet) + if err != nil { + time.Sleep(3 * time.Second) + continue + } + + // get the block from rpc client + block, err := client.GetBlockVerboseTx(blkHash) + if err != nil { + time.Sleep(3 * time.Second) + continue + } + + // loop through each tx in the block (skip coinbase tx) + for i := 1; i < len(block.Tx); { + // sample 20 txs per block + if i >= 20 { + break + } + + // the two txs from two different sources + tx := block.Tx[i] + mpTx := mempoolTxs[i] + require.Equal(t, tx.Txid, mpTx.TxID) + + // get transaction fee rate for the raw result + fee, feeRate, err := rpc.GetTransactionFeeAndRate(client, &tx) + if err != nil { + t.Logf("error GetTransactionFeeRate %s: %s\n", mpTx.TxID, err) + continue + } + require.EqualValues(t, mpTx.Fee, fee) + require.EqualValues(t, mpTx.Weight, tx.Weight) + + // calculate mempool.space fee rate + vBytes := mpTx.Weight / blockchain.WitnessScaleFactor + mpFeeRate := int64(mpTx.Fee / vBytes) + + // compare our fee rate with mempool.space fee rate + var diff int64 + var diffPercent float64 + if feeRate == mpFeeRate { + fmt.Printf("tx %s: [our rate] %5d == %5d [mempool.space]", mpTx.TxID, feeRate, mpFeeRate) + } else if feeRate > mpFeeRate { + diff = feeRate - mpFeeRate + fmt.Printf("tx %s: [our rate] %5d > %5d [mempool.space]", mpTx.TxID, feeRate, mpFeeRate) + } else { + diff = mpFeeRate - feeRate + fmt.Printf("tx %s: [our rate] %5d < %5d [mempool.space]", mpTx.TxID, feeRate, mpFeeRate) + } + + // print the diff percentage + diffPercent = float64(diff) / float64(mpFeeRate) * 100 + if diff > 0 { + fmt.Printf(", diff: %f%%\n", diffPercent) + } else { + fmt.Printf("\n") + } + + // the expected diff percentage should be within 5% + if mpFeeRate >= 20 { + require.LessOrEqual(t, diffPercent, 5.0) + } else { + // for small fee rate, the absolute diff should be within 1 satoshi/vByte + require.LessOrEqual(t, diff, int64(1)) + } + + // next tx + i++ + } + + bn-- + time.Sleep(100 * time.Millisecond) + } +} + +func LiveTest_CalcDepositorFeeV2(t *testing.T) { + // setup Bitcoin client + client, err := createRPCClient(chains.BitcoinMainnet.ChainId) + require.NoError(t, err) + + // test tx hash + // https://mempool.space/tx/8dc0d51f83810cec7fcb5b194caebfc5fc64b10f9fe21845dfecc621d2a28538 + hash, err := chainhash.NewHashFromStr("8dc0d51f83810cec7fcb5b194caebfc5fc64b10f9fe21845dfecc621d2a28538") + require.NoError(t, err) + + // get the raw transaction result + rawResult, err := client.GetRawTransactionVerbose(hash) + require.NoError(t, err) + + t.Run("should return default depositor fee", func(t *testing.T) { + depositorFee, err := bitcoin.CalcDepositorFeeV2(client, rawResult, &chaincfg.RegressionNetParams) + require.NoError(t, err) + require.Equal(t, bitcoin.DefaultDepositorFee, depositorFee) + }) + + t.Run("should return correct depositor fee for a given tx", func(t *testing.T) { + depositorFee, err := bitcoin.CalcDepositorFeeV2(client, rawResult, &chaincfg.MainNetParams) + require.NoError(t, err) + + // the actual fee rate is 860 sat/vByte + // #nosec G115 always in range + expectedRate := int64(float64(860) * common.BTCOutboundGasPriceMultiplier) + expectedFee := bitcoin.DepositorFee(expectedRate) + require.Equal(t, expectedFee, depositorFee) + }) +} From 625bc1192faf631fea2e3b6d003421918fe5f3c5 Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 23 Aug 2024 00:05:09 -0500 Subject: [PATCH 2/7] add changelog entry --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 98a09fb06e..51fa015214 100644 --- a/changelog.md +++ b/changelog.md @@ -70,6 +70,7 @@ * [2524](https://github.com/zeta-chain/node/pull/2524) - add inscription envelop parsing * [2560](https://github.com/zeta-chain/node/pull/2560) - add support for Solana SOL token withdraw * [2533](https://github.com/zeta-chain/node/pull/2533) - parse memo from both OP_RETURN and inscription +* [2765](https://github.com/zeta-chain/node/pull/2765) - bitcoin depositor fee improvement ### Refactor From b0447c29c974a47e424fb1a60283cb6557c3101d Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 23 Aug 2024 10:59:38 -0500 Subject: [PATCH 3/7] attached tracking issue# for TODO; formatted comments to be go idiomatic --- zetaclient/chains/bitcoin/fee.go | 78 ++++++++++++++----- zetaclient/chains/bitcoin/observer/inbound.go | 1 + 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index 55cdaf0c6a..aa1db3b449 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -21,27 +21,63 @@ import ( ) const ( - bytesPerKB = 1000 - bytesPerInput = 41 // each input is 41 bytes - bytesPerOutputP2TR = 43 // each P2TR output is 43 bytes - bytesPerOutputP2WSH = 43 // each P2WSH output is 43 bytes - bytesPerOutputP2WPKH = 31 // each P2WPKH output is 31 bytes - bytesPerOutputP2SH = 32 // each P2SH output is 32 bytes - bytesPerOutputP2PKH = 34 // each P2PKH output is 34 bytes - bytesPerOutputAvg = 37 // average size of all above types of outputs (36.6 bytes) - bytes1stWitness = 110 // the 1st witness incurs about 110 bytes and it may vary - bytesPerWitness = 108 // each additional witness incurs about 108 bytes and it may vary - defaultDepositorFeeRate = 20 // 20 sat/byte is the default depositor fee rate - - OutboundBytesMin = uint64(239) // 239vB == EstimateSegWitTxSize(2, 2, toP2WPKH) - OutboundBytesMax = uint64(1543) // 1543v == EstimateSegWitTxSize(21, 2, toP2TR) - OutboundBytesAvg = uint64(245) // 245vB is a suggested gas limit for zetacore - - DynamicDepositorFeeHeight = 834500 // the mainnet height from which dynamic depositor fee V1 is applied - DynamicDepositorFeeHeightV2 = 863400 // the mainnet height from which dynamic depositor fee V2 is applied - - feeRateCountBackBlocks = 2 // the default number of blocks to look back for fee rate estimation - defaultTestnetFeeRate = 10 // the default fee rate for testnet, 10 sat/vB + + // bytesPerKB is the number of bytes in a kilobyte + bytesPerKB = 1000 + + // bytesPerInput is the size of each input + bytesPerInput = 41 + + // bytesPerOutputP2TR is the size of each P2TR output + bytesPerOutputP2TR = 43 + + // bytesPerOutputP2WSH is the size of each P2WSH output + bytesPerOutputP2WSH = 43 + + // bytesPerOutputP2WPKH is the size of each P2WPKH output + bytesPerOutputP2WPKH = 31 + + // bytesPerOutputP2SH is the size of each P2SH output + bytesPerOutputP2SH = 32 + + // bytesPerOutputP2PKH is the size of each P2PKH output + bytesPerOutputP2PKH = 34 + + // bytesPerOutputAvg is the average size of above 5 types of outputs (36.6 bytes) + bytesPerOutputAvg = 37 + + // bytes1stWitness is the size incurred by the 1st witness and it may vary + bytes1stWitness = 110 + + // bytesPerWitness is the size incurred (approx.) by each additional witness and it may vary + bytesPerWitness = 108 + + // defaultDepositorFeeRate is the default fee rate for depositor, 20 sat/vB + defaultDepositorFeeRate = 20 + + // OutboundBytesMin is the minimum size of an outbound in vBytes + // 239vB == EstimateSegWitTxSize(2, 2, toP2WPKH) + OutboundBytesMin = uint64(239) + + // OutboundBytesMax is the maximum size of an outbound in vBytes + // 1543v == EstimateSegWitTxSize(21, 2, toP2TR) + OutboundBytesMax = uint64(1543) + + // OutboundBytesAvg is the average size of an outbound in vBytes + // 245vB is a suggested gas limit for zetacore + OutboundBytesAvg = uint64(245) + + // DynamicDepositorFeeHeight is the mainnet height from which dynamic depositor fee V1 is applied + DynamicDepositorFeeHeight = 834500 + + // DynamicDepositorFeeHeightV2 is the mainnet height from which dynamic depositor fee V2 is applied + DynamicDepositorFeeHeightV2 = 863400 + + // feeRateCountBackBlocks is the default number of blocks to look back for fee rate estimation + feeRateCountBackBlocks = 2 + + // defaultTestnetFeeRate is the default fee rate for testnet, 10 sat/vB + defaultTestnetFeeRate = 10 ) var ( diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index 370948d6a5..2035340e73 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -425,6 +425,7 @@ func GetBtcEvent( // 1. it is bitcoin testnet, or // 2. it is bitcoin mainnet and upgrade height is reached // TODO: remove CalcDepositorFeeV1 and below conditions after the upgrade height + // https://github.com/zeta-chain/node/issues/2766 if netParams.Name == chaincfg.TestNet3Params.Name || (netParams.Name == chaincfg.MainNetParams.Name && blockNumber >= bitcoin.DynamicDepositorFeeHeightV2) { depositorFee, err = bitcoin.CalcDepositorFeeV2(rpcClient, &tx, netParams) From 9abefc79dba3b1423c061eb3abc1ff252419f356 Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 23 Aug 2024 11:12:03 -0500 Subject: [PATCH 4/7] disable live test in CI --- zetaclient/chains/bitcoin/rpc/rpc_live_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zetaclient/chains/bitcoin/rpc/rpc_live_test.go b/zetaclient/chains/bitcoin/rpc/rpc_live_test.go index 3eb6759551..d82e3c0833 100644 --- a/zetaclient/chains/bitcoin/rpc/rpc_live_test.go +++ b/zetaclient/chains/bitcoin/rpc/rpc_live_test.go @@ -112,7 +112,7 @@ func Test_BitcoinLive(t *testing.T) { // LiveTest_AvgFeeRateTestnetMempoolSpace(t) // LiveTest_GetRecentFeeRate(t) // LiveTest_GetSenderByVin(t) - // LiveTest_GetTransactionFeeRate(t) + // LiveTest_GetTransactionFeeAndRate(t) // LiveTest_CalcDepositorFeeV2(t) } @@ -452,7 +452,7 @@ BLOCKLOOP: } // LiveTestGetTransactionFeeAndRate gets the transaction fee and rate for each tx and compares with mempool.space fee rate -func Test_GetTransactionFeeAndRate(t *testing.T) { +func LiveTest_GetTransactionFeeAndRate(t *testing.T) { // setup Bitcoin client chainID := chains.BitcoinTestnet.ChainId client, err := createRPCClient(chainID) From 345c27ea109a4cd2a03c5f9c3465d032c14fc656 Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 23 Aug 2024 15:40:55 -0500 Subject: [PATCH 5/7] group transaction size related constants to make comments more concise --- zetaclient/chains/bitcoin/fee.go | 59 +++++++++----------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index aa1db3b449..1750557be9 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -22,51 +22,24 @@ import ( const ( - // bytesPerKB is the number of bytes in a kilobyte - bytesPerKB = 1000 - - // bytesPerInput is the size of each input - bytesPerInput = 41 - - // bytesPerOutputP2TR is the size of each P2TR output - bytesPerOutputP2TR = 43 - - // bytesPerOutputP2WSH is the size of each P2WSH output - bytesPerOutputP2WSH = 43 - - // bytesPerOutputP2WPKH is the size of each P2WPKH output - bytesPerOutputP2WPKH = 31 - - // bytesPerOutputP2SH is the size of each P2SH output - bytesPerOutputP2SH = 32 - - // bytesPerOutputP2PKH is the size of each P2PKH output - bytesPerOutputP2PKH = 34 - - // bytesPerOutputAvg is the average size of above 5 types of outputs (36.6 bytes) - bytesPerOutputAvg = 37 - - // bytes1stWitness is the size incurred by the 1st witness and it may vary - bytes1stWitness = 110 - - // bytesPerWitness is the size incurred (approx.) by each additional witness and it may vary - bytesPerWitness = 108 - - // defaultDepositorFeeRate is the default fee rate for depositor, 20 sat/vB + // constants related to transaction size calculations + bytesPerKB = 1000 + bytesPerInput = 41 // each input is 41 bytes + bytesPerOutputP2TR = 43 // each P2TR output is 43 bytes + bytesPerOutputP2WSH = 43 // each P2WSH output is 43 bytes + bytesPerOutputP2WPKH = 31 // each P2WPKH output is 31 bytes + bytesPerOutputP2SH = 32 // each P2SH output is 32 bytes + bytesPerOutputP2PKH = 34 // each P2PKH output is 34 bytes + bytesPerOutputAvg = 37 // average size of all above types of outputs (36.6 bytes) + bytes1stWitness = 110 // the 1st witness incurs about 110 bytes and it may vary + bytesPerWitness = 108 // each additional witness incurs about 108 bytes and it may vary + OutboundBytesMin = uint64(239) // 239vB == EstimateSegWitTxSize(2, 2, toP2WPKH) + OutboundBytesMax = uint64(1543) // 1543v == EstimateSegWitTxSize(21, 2, toP2TR) + OutboundBytesAvg = uint64(245) // 245vB is a suggested gas limit for zetacore + + // defaultDepositorFeeRate is the default fee rate for depositor fee, 20 sat/vB defaultDepositorFeeRate = 20 - // OutboundBytesMin is the minimum size of an outbound in vBytes - // 239vB == EstimateSegWitTxSize(2, 2, toP2WPKH) - OutboundBytesMin = uint64(239) - - // OutboundBytesMax is the maximum size of an outbound in vBytes - // 1543v == EstimateSegWitTxSize(21, 2, toP2TR) - OutboundBytesMax = uint64(1543) - - // OutboundBytesAvg is the average size of an outbound in vBytes - // 245vB is a suggested gas limit for zetacore - OutboundBytesAvg = uint64(245) - // DynamicDepositorFeeHeight is the mainnet height from which dynamic depositor fee V1 is applied DynamicDepositorFeeHeight = 834500 From 89cab47cb1b31854b62e9cd4dcf484e2ec786b46 Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Fri, 23 Aug 2024 15:44:22 -0500 Subject: [PATCH 6/7] reorder bitcoin fee related constants --- zetaclient/chains/bitcoin/fee.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index 1750557be9..6be21fdcad 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -21,7 +21,6 @@ import ( ) const ( - // constants related to transaction size calculations bytesPerKB = 1000 bytesPerInput = 41 // each input is 41 bytes @@ -40,17 +39,17 @@ const ( // defaultDepositorFeeRate is the default fee rate for depositor fee, 20 sat/vB defaultDepositorFeeRate = 20 + // defaultTestnetFeeRate is the default fee rate for testnet, 10 sat/vB + defaultTestnetFeeRate = 10 + + // feeRateCountBackBlocks is the default number of blocks to look back for fee rate estimation + feeRateCountBackBlocks = 2 + // DynamicDepositorFeeHeight is the mainnet height from which dynamic depositor fee V1 is applied DynamicDepositorFeeHeight = 834500 // DynamicDepositorFeeHeightV2 is the mainnet height from which dynamic depositor fee V2 is applied DynamicDepositorFeeHeightV2 = 863400 - - // feeRateCountBackBlocks is the default number of blocks to look back for fee rate estimation - feeRateCountBackBlocks = 2 - - // defaultTestnetFeeRate is the default fee rate for testnet, 10 sat/vB - defaultTestnetFeeRate = 10 ) var ( From 3780897e91245faf820dab0c3efd404ab92ad8bf Mon Sep 17 00:00:00 2001 From: Charlie Chen Date: Mon, 26 Aug 2024 23:42:24 -0500 Subject: [PATCH 7/7] replace GetRecentBlockhash with GetLatestBlockhash; add check on Vsize; misc --- e2e/runner/solana.go | 2 +- zetaclient/chains/bitcoin/fee.go | 7 +++++++ zetaclient/chains/bitcoin/rpc/rpc.go | 5 +++++ zetaclient/chains/interfaces/interfaces.go | 2 +- zetaclient/chains/solana/signer/withdraw.go | 4 ++-- zetaclient/testutils/mocks/solana_rpc.go | 16 ++++++++-------- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/e2e/runner/solana.go b/e2e/runner/solana.go index ba6f240279..30e089406e 100644 --- a/e2e/runner/solana.go +++ b/e2e/runner/solana.go @@ -65,7 +65,7 @@ func (r *E2ERunner) CreateSignedTransaction( privateKey solana.PrivateKey, ) *solana.Transaction { // get a recent blockhash - recent, err := r.SolanaClient.GetRecentBlockhash(r.Ctx, rpc.CommitmentFinalized) + recent, err := r.SolanaClient.GetLatestBlockhash(r.Ctx, rpc.CommitmentFinalized) require.NoError(r, err) // create the initialize transaction diff --git a/zetaclient/chains/bitcoin/fee.go b/zetaclient/chains/bitcoin/fee.go index 6be21fdcad..3293727b47 100644 --- a/zetaclient/chains/bitcoin/fee.go +++ b/zetaclient/chains/bitcoin/fee.go @@ -49,6 +49,7 @@ const ( DynamicDepositorFeeHeight = 834500 // DynamicDepositorFeeHeightV2 is the mainnet height from which dynamic depositor fee V2 is applied + // Height 863400 is approximately a month away (2024-09-28) from the time of writing, allowing enough time for the upgrade DynamicDepositorFeeHeightV2 = 863400 ) @@ -281,6 +282,12 @@ func CalcDepositorFeeV2( // GetRecentFeeRate gets the highest fee rate from recent blocks // Note: this method should be used for testnet ONLY func GetRecentFeeRate(rpcClient interfaces.BTCRPCClient, netParams *chaincfg.Params) (uint64, error) { + // should avoid using this method for mainnet + if netParams.Name == chaincfg.MainNetParams.Name { + return 0, errors.New("GetRecentFeeRate should not be used for mainnet") + } + + // get the current block number blockNumber, err := rpcClient.GetBlockCount() if err != nil { return 0, err diff --git a/zetaclient/chains/bitcoin/rpc/rpc.go b/zetaclient/chains/bitcoin/rpc/rpc.go index 9b9b36842f..29972aa26d 100644 --- a/zetaclient/chains/bitcoin/rpc/rpc.go +++ b/zetaclient/chains/bitcoin/rpc/rpc.go @@ -130,6 +130,11 @@ func GetTransactionFeeAndRate(rpcClient interfaces.BTCRPCClient, rawResult *btcj totalOutputValue int64 ) + // make sure the tx Vsize is not zero (should not happen) + if rawResult.Vsize <= 0 { + return 0, 0, fmt.Errorf("tx %s has non-positive Vsize: %d", rawResult.Txid, rawResult.Vsize) + } + // sum up total input value for _, vin := range rawResult.Vin { prevTx, err := GetRawTxByHash(rpcClient, vin.Txid) diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index 5e1148f81b..f3189deee2 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -200,7 +200,7 @@ type SolanaRPCClient interface { account solana.PublicKey, commitment solrpc.CommitmentType, ) (*solrpc.GetBalanceResult, error) - GetRecentBlockhash(ctx context.Context, commitment solrpc.CommitmentType) (*solrpc.GetRecentBlockhashResult, error) + GetLatestBlockhash(ctx context.Context, commitment solrpc.CommitmentType) (*solrpc.GetLatestBlockhashResult, error) GetRecentPrioritizationFees( ctx context.Context, accounts solana.PublicKeySlice, diff --git a/zetaclient/chains/solana/signer/withdraw.go b/zetaclient/chains/solana/signer/withdraw.go index f44dc3fc30..8b91dc8f23 100644 --- a/zetaclient/chains/solana/signer/withdraw.go +++ b/zetaclient/chains/solana/signer/withdraw.go @@ -69,9 +69,9 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithd attachWithdrawAccounts(&inst, privkey.PublicKey(), signer.pda, msg.To(), signer.gatewayID) // get a recent blockhash - recent, err := signer.client.GetRecentBlockhash(ctx, rpc.CommitmentFinalized) + recent, err := signer.client.GetLatestBlockhash(ctx, rpc.CommitmentFinalized) if err != nil { - return nil, errors.Wrap(err, "GetRecentBlockhash error") + return nil, errors.Wrap(err, "GetLatestBlockhash error") } // create a transaction that wraps the instruction diff --git a/zetaclient/testutils/mocks/solana_rpc.go b/zetaclient/testutils/mocks/solana_rpc.go index fad147037c..26b923abb3 100644 --- a/zetaclient/testutils/mocks/solana_rpc.go +++ b/zetaclient/testutils/mocks/solana_rpc.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.38.0. DO NOT EDIT. +// Code generated by mockery v2.42.2. DO NOT EDIT. package mocks @@ -135,24 +135,24 @@ func (_m *SolanaRPCClient) GetHealth(ctx context.Context) (string, error) { return r0, r1 } -// GetRecentBlockhash provides a mock function with given fields: ctx, commitment -func (_m *SolanaRPCClient) GetRecentBlockhash(ctx context.Context, commitment rpc.CommitmentType) (*rpc.GetRecentBlockhashResult, error) { +// GetLatestBlockhash provides a mock function with given fields: ctx, commitment +func (_m *SolanaRPCClient) GetLatestBlockhash(ctx context.Context, commitment rpc.CommitmentType) (*rpc.GetLatestBlockhashResult, error) { ret := _m.Called(ctx, commitment) if len(ret) == 0 { - panic("no return value specified for GetRecentBlockhash") + panic("no return value specified for GetLatestBlockhash") } - var r0 *rpc.GetRecentBlockhashResult + var r0 *rpc.GetLatestBlockhashResult var r1 error - if rf, ok := ret.Get(0).(func(context.Context, rpc.CommitmentType) (*rpc.GetRecentBlockhashResult, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context, rpc.CommitmentType) (*rpc.GetLatestBlockhashResult, error)); ok { return rf(ctx, commitment) } - if rf, ok := ret.Get(0).(func(context.Context, rpc.CommitmentType) *rpc.GetRecentBlockhashResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, rpc.CommitmentType) *rpc.GetLatestBlockhashResult); ok { r0 = rf(ctx, commitment) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*rpc.GetRecentBlockhashResult) + r0 = ret.Get(0).(*rpc.GetLatestBlockhashResult) } }