diff --git a/zetaclient/bitcoin_client.go b/zetaclient/bitcoin_client.go index 72bce508cb..3313b76171 100644 --- a/zetaclient/bitcoin_client.go +++ b/zetaclient/bitcoin_client.go @@ -435,6 +435,21 @@ func (ob *BitcoinChainClient) WatchGasPrice() { } } +// EstimateSmartFee returns the fees per kilobyte (BTC/kb) +func (ob *BitcoinChainClient) EstimateGasPrice() (*big.Int, error) { + feeResult, err := ob.rpcClient.EstimateSmartFee(1, &btcjson.EstimateModeConservative) + if err != nil { + return nil, err + } + if feeResult.Errors != nil { + return nil, fmt.Errorf("error estimating smart fee: %s", feeResult.Errors) + } + if feeResult.FeeRate == nil { + return nil, fmt.Errorf("fee rate is nil") + } + return new(big.Int).SetInt64(int64(*feeResult.FeeRate * 1e8)), nil +} + func (ob *BitcoinChainClient) PostGasPrice() error { if ob.chain.ChainId == 18444 { //bitcoin regtest bn, err := ob.rpcClient.GetBlockCount() @@ -451,29 +466,22 @@ func (ob *BitcoinChainClient) PostGasPrice() error { //ob.logger.WatchGasPrice.Debug().Msgf("PostGasPrice zeta tx: %s", zetaHash) return nil } - // EstimateSmartFee returns the fees per kilobyte (BTC/kb) targeting given block confirmation - feeResult, err := ob.rpcClient.EstimateSmartFee(1, &btcjson.EstimateModeConservative) + gasPrice, err := ob.EstimateGasPrice() if err != nil { return err } - if feeResult.Errors != nil || feeResult.FeeRate == nil { - return fmt.Errorf("error getting gas price: %s", feeResult.Errors) - } - gasPrice := big.NewFloat(0) - gasPriceU64, _ := gasPrice.Mul(big.NewFloat(*feeResult.FeeRate), big.NewFloat(1e8)).Uint64() bn, err := ob.rpcClient.GetBlockCount() if err != nil { return err } // #nosec G701 always positive - zetaHash, err := ob.zetaClient.PostGasPrice(ob.chain, gasPriceU64, "100", uint64(bn)) + zetaHash, err := ob.zetaClient.PostGasPrice(ob.chain, gasPrice.Uint64(), "100", uint64(bn)) if err != nil { ob.logger.WatchGasPrice.Err(err).Msg("PostGasPrice:") return err } _ = zetaHash //ob.logger.WatchGasPrice.Debug().Msgf("PostGasPrice zeta tx: %s", zetaHash) - _ = feeResult return nil } diff --git a/zetaclient/btc_signer.go b/zetaclient/btc_signer.go index a31f77bc85..5e9c7d47a4 100644 --- a/zetaclient/btc_signer.go +++ b/zetaclient/btc_signer.go @@ -23,6 +23,9 @@ import ( const ( maxNoOfInputsPerTx = 20 + minFee = 5_000 // 0.00005 BTC + maxFee = 100_000 // 0.001 BTC + oneKSatoshis = 1_000 // 0.00001 BTC ) type BTCSigner struct { @@ -61,7 +64,6 @@ func NewBTCSigner(cfg config.BTCConfig, tssSigner TSSSigner, logger zerolog.Logg // SignWithdrawTx receives utxos sorted by value, amount in BTC, feeRate in BTC per Kb func (signer *BTCSigner) SignWithdrawTx(to *btcutil.AddressWitnessPubKeyHash, amount float64, gasPrice *big.Int, btcClient *BitcoinChainClient, height uint64, nonce uint64, chain *common.Chain) (*wire.MsgTx, error) { estimateFee := 0.0001 // 10,000 sats, should be good for testnet - minFee := 0.00005 nonceMark := NonceMarkAmount(nonce) // refresh unspent UTXOs and continue with keysign regardless of error @@ -97,10 +99,14 @@ func (signer *BTCSigner) SignWithdrawTx(to *btcutil.AddressWitnessPubKeyHash, am fees := new(big.Int).Mul(big.NewInt(int64(tx.SerializeSize())), gasPrice) fees.Div(fees, big.NewInt(1000)) //FIXME: feeRate KB is 1000B or 1024B? // #nosec G701 always in range - if fees.Int64() < int64(minFee*1e8) { - fmt.Printf("fees %d is less than minFee %f; use minFee", fees, minFee*1e8) + if fees.Int64() < minFee { + fmt.Printf("fees %d is less than minFee %d; use minFee", fees, minFee) // #nosec G701 always in range - fees = big.NewInt(int64(minFee * 1e8)) + fees = big.NewInt(minFee) + } else if fees.Int64() > maxFee { + fmt.Printf("fees %d is greater than maxFee %d; use maxFee", fees, maxFee) + // #nosec G701 always in range + fees = big.NewInt(maxFee) } // calculate remaining btc to TSS self @@ -253,11 +259,13 @@ func (signer *BTCSigner) TryProcessOutTx(send *types.CrossChainTx, outTxMan *Out return } - gasprice, ok := new(big.Int).SetString(params.OutboundTxGasPrice, 10) - if !ok { - logger.Error().Msgf("cannot convert gas price %s ", params.OutboundTxGasPrice) + // Estimate the fee rate + estimated, err := btcClient.EstimateGasPrice() + if err != nil { + logger.Error().Err(err).Msgf("cannot estimate fee") return } + gasprice := roundGasPriceUp(estimated, oneKSatoshis) // round up to nearst K satoshis // FIXME: config chain params addr, err := btcutil.DecodeAddress(params.Receiver, config.BitconNetParams) @@ -279,7 +287,7 @@ func (signer *BTCSigner) TryProcessOutTx(send *types.CrossChainTx, outTxMan *Out logger.Warn().Err(err).Msgf("SignOutboundTx error: nonce %d chain %d", outboundTxTssNonce, params.ReceiverChainId) return } - logger.Info().Msgf("Key-sign success: %d => %s, nonce %d", send.InboundTxParams.SenderChainId, btcClient.chain.ChainName, outboundTxTssNonce) + logger.Info().Msgf("Key-sign success: %d => %s, nonce %d, gasprice %d", send.InboundTxParams.SenderChainId, btcClient.chain.ChainName, outboundTxTssNonce, gasprice) // FIXME: add prometheus metrics _, err = zetaBridge.GetObserverList(btcClient.chain) if err != nil { diff --git a/zetaclient/evm_signer.go b/zetaclient/evm_signer.go index 3622a61ffd..fbca4748f4 100644 --- a/zetaclient/evm_signer.go +++ b/zetaclient/evm_signer.go @@ -24,6 +24,10 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +const ( + Gwei = 1_000_000_000 +) + type EVMSigner struct { client *ethclient.Client chain *common.Chain @@ -329,7 +333,7 @@ func (signer *EVMSigner) TryProcessOutTx(send *types.CrossChainTx, outTxMan *Out logger.Error().Err(err).Msgf("cannot get gas price from chain %s ", toChain) return } - gasprice = roundUpToNearestGwei(suggested) + gasprice = roundGasPriceUp(suggested, Gwei) // round up to nearest gwei } else { logger.Error().Err(err).Msgf("cannot convert gas price %s ", send.GetCurrentOutTxParam().OutboundTxGasPrice) return @@ -564,13 +568,3 @@ func (signer *EVMSigner) SignWhitelistTx(action string, _ ethcommon.Address, ass return tx, nil } - -func roundUpToNearestGwei(gasPrice *big.Int) *big.Int { - oneGwei := big.NewInt(1_000_000_000) // 1 Gwei - mod := new(big.Int) - mod.Mod(gasPrice, oneGwei) - if mod.Cmp(big.NewInt(0)) == 0 { // gasprice is already a multiple of 1 Gwei - return gasPrice - } - return new(big.Int).Add(gasPrice, new(big.Int).Sub(oneGwei, mod)) -} diff --git a/zetaclient/utils.go b/zetaclient/utils.go index 56ba3000d9..8b2ef84120 100644 --- a/zetaclient/utils.go +++ b/zetaclient/utils.go @@ -3,6 +3,7 @@ package zetaclient import ( "errors" "math" + "math/big" "time" "github.com/btcsuite/btcd/txscript" @@ -41,6 +42,17 @@ func round(f float64) int64 { return int64(f + 0.5) } +// roundGasPriceUp rounds up the gasPrice to the nearest multiple of base +func roundGasPriceUp(gasPrice *big.Int, base int64) *big.Int { + oneUnit := big.NewInt(base) // e.g. 1 Gwei + mod := new(big.Int) + mod.Mod(gasPrice, oneUnit) + if mod.Cmp(big.NewInt(0)) == 0 { // gasPrice is already a multiple of base + return gasPrice + } + return new(big.Int).Add(gasPrice, new(big.Int).Sub(oneUnit, mod)) +} + func payToWitnessPubKeyHashScript(pubKeyHash []byte) ([]byte, error) { return txscript.NewScriptBuilder().AddOp(txscript.OP_0).AddData(pubKeyHash).Script() }