Skip to content

Commit

Permalink
feat: bitcoin dynamic depositor fee (#1747)
Browse files Browse the repository at this point in the history
* initiated bitcoin dynamic depositor fee

* fix gosec

* added unit tests for func CalcBlockAvgFeeRate

* renamed file of test data

* ignore gosec error in test

* skip gosec error in test

* code update according to review comments
  • Loading branch information
ws4charlie authored Feb 13, 2024
1 parent 7f24c5a commit 165fe5d
Show file tree
Hide file tree
Showing 16 changed files with 40,947 additions and 77 deletions.
4 changes: 4 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

* `zetaclientd start` : 2 inputs required from stdin

### Features

* [1698](https://github.com/zeta-chain/node/issues/1698) - bitcoin dynamic depositor fee

### Docs

* [1731](https://github.com/zeta-chain/node/pull/1731) added doc for hotkey and tss key-share password prompts.
Expand Down
10 changes: 10 additions & 0 deletions common/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,13 @@ func BitcoinNetParamsFromChainID(chainID int64) (*chaincfg.Params, error) {
return nil, fmt.Errorf("no Bitcoin net params for chain ID: %d", chainID)
}
}

// IsBitcoinRegnet returns true if the chain id is for the regnet
func IsBitcoinRegnet(chainID int64) bool {
return chainID == BtcRegtestChain().ChainId
}

// IsBitcoinMainnet returns true if the chain id is for the mainnet
func IsBitcoinMainnet(chainID int64) bool {
return chainID == BtcMainnetChain().ChainId
}
6 changes: 6 additions & 0 deletions common/constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package common

const (
// DefaultGasPriceMultiplier is the default gas price multiplier for outbond txs
DefaultGasPriceMultiplier = 2
)
14 changes: 6 additions & 8 deletions contrib/localnet/orchestrator/smoketest/runner/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (sm *SmokeTestRunner) DepositBTCWithAmount(amount float64) (txHash *chainha
sm.Logger.Info(" spendableUTXOs: %d", spendableUTXOs)
sm.Logger.Info("Now sending two txs to TSS address...")

amount = amount + zetabitcoin.BtcDepositorFeeMin
amount = amount + zetabitcoin.DefaultDepositorFee
txHash, err = sm.SendToTSSFromDeployerToDeposit(sm.BTCTSSAddress, amount, utxos, sm.BtcRPCClient, sm.BTCDeployerAddress)
if err != nil {
panic(err)
Expand Down Expand Up @@ -101,12 +101,12 @@ func (sm *SmokeTestRunner) DepositBTC(testHeader bool) {
sm.Logger.Info("Now sending two txs to TSS address...")

// send two transactions to the TSS address
amount1 := 1.1 + zetabitcoin.BtcDepositorFeeMin
amount1 := 1.1 + zetabitcoin.DefaultDepositorFee
txHash1, err := sm.SendToTSSFromDeployerToDeposit(sm.BTCTSSAddress, amount1, utxos[:2], btc, sm.BTCDeployerAddress)
if err != nil {
panic(err)
}
amount2 := 0.05 + zetabitcoin.BtcDepositorFeeMin
amount2 := 0.05 + zetabitcoin.DefaultDepositorFee
txHash2, err := sm.SendToTSSFromDeployerToDeposit(sm.BTCTSSAddress, amount2, utxos[2:4], btc, sm.BTCDeployerAddress)
if err != nil {
panic(err)
Expand Down Expand Up @@ -266,16 +266,14 @@ func (sm *SmokeTestRunner) SendToTSSFromDeployerWithMemo(
panic(err)
}

btcChainID, err := common.GetBTCChainIDFromChainParams(sm.BitcoinParams)
if err != nil {
panic(err)
}
depositorFee := zetabitcoin.DefaultDepositorFee
events := zetabitcoin.FilterAndParseIncomingTx(
[]btcjson.TxRawResult{*rawtx},
0,
sm.BTCTSSAddress.EncodeAddress(),
&log.Logger,
btcChainID,
sm.BitcoinParams,
depositorFee,
)
sm.Logger.Info("bitcoin intx events:")
for _, event := range events {
Expand Down
88 changes: 49 additions & 39 deletions zetaclient/bitcoin/bitcoin_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

cosmosmath "cosmossdk.io/math"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/wire"
Expand All @@ -36,6 +37,10 @@ import (
"gorm.io/gorm/logger"
)

const (
DynamicDepositorFeeHeight = 832000 // Bitcoin block height to switch to dynamic depositor fee
)

var _ interfaces.ChainClient = &BTCChainClient{}

type BTCLog struct {
Expand All @@ -52,6 +57,7 @@ type BTCChainClient struct {
*metricsPkg.ChainMetrics

chain common.Chain
netParams *chaincfg.Params
rpcClient interfaces.BTCRPCClient
zetaClient interfaces.ZetaCoreBridger
Tss interfaces.TSSSigner
Expand Down Expand Up @@ -141,6 +147,11 @@ func NewBitcoinClient(
}
ob.stop = make(chan struct{})
ob.chain = chain
netParams, err := common.BitcoinNetParamsFromChainID(ob.chain.ChainId)
if err != nil {
return nil, fmt.Errorf("error getting net params for chain %d: %s", ob.chain.ChainId, err)
}
ob.netParams = netParams
ob.Mu = &sync.Mutex{}
chainLogger := logger.With().Str("chain", chain.ChainName.String()).Logger()
ob.logger = BTCLog{
Expand Down Expand Up @@ -429,25 +440,33 @@ func (ob *BTCChainClient) observeInTx() error {
}
}

tssAddress := ob.Tss.BTCAddress()
// #nosec G701 always positive
inTxs := FilterAndParseIncomingTx(
res.Block.Tx,
uint64(res.Block.Height),
tssAddress,
&ob.logger.WatchInTx,
ob.chain.ChainId,
)

// post inbound vote message to zetabridge
for _, inTx := range inTxs {
msg := ob.GetInboundVoteMessageFromBtcEvent(inTx)
zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg)
if err != nil {
ob.logger.WatchInTx.Error().Err(err).Msgf("observeInTxBTC: error posting to zeta core for tx %s", inTx.TxHash)
return err // we have to re-scan this block next time
} else if zetaHash != "" {
ob.logger.WatchInTx.Info().Msgf("observeInTxBTC: PostVoteInbound zeta tx hash: %s inTx %s ballot %s", zetaHash, inTx.TxHash, ballot)
if len(res.Block.Tx) > 1 {
// get depositor fee
depositorFee := CalcDepositorFee(res.Block, ob.chain.ChainId, ob.netParams, ob.logger.WatchInTx)

// filter incoming txs to TSS address
tssAddress := ob.Tss.BTCAddress()
// #nosec G701 always positive
inTxs := FilterAndParseIncomingTx(
res.Block.Tx,
uint64(res.Block.Height),
tssAddress,
&ob.logger.WatchInTx,
ob.netParams,
depositorFee,
)

// post inbound vote message to zetacore
for _, inTx := range inTxs {
msg := ob.GetInboundVoteMessageFromBtcEvent(inTx)
zetaHash, ballot, err := ob.zetaClient.PostVoteInbound(zetabridge.PostVoteInboundGasLimit, zetabridge.PostVoteInboundExecutionGasLimit, msg)
if err != nil {
ob.logger.WatchInTx.Error().Err(err).Msgf("observeInTxBTC: error posting to zeta core for tx %s", inTx.TxHash)
return err // we have to re-scan this block next time
} else if zetaHash != "" {
ob.logger.WatchInTx.Info().Msgf("observeInTxBTC: PostVoteInbound zeta tx hash: %s inTx %s ballot %s fee %v",
zetaHash, inTx.TxHash, ballot, depositorFee)
}
}
}

Expand Down Expand Up @@ -636,14 +655,15 @@ func FilterAndParseIncomingTx(
blockNumber uint64,
targetAddress string,
logger *zerolog.Logger,
chainID int64,
netParams *chaincfg.Params,
depositorFee float64,
) []*BTCInTxEvnet {
inTxs := make([]*BTCInTxEvnet, 0)
for idx, tx := range txs {
if idx == 0 {
continue // the first tx is coinbase; we do not process coinbase tx
}
inTx, err := GetBtcEvent(tx, targetAddress, blockNumber, logger, chainID)
inTx, err := GetBtcEvent(tx, targetAddress, blockNumber, logger, netParams, depositorFee)
if err != nil {
logger.Error().Err(err).Msgf("FilterAndParseIncomingTx: error getting btc event for tx %s in block %d", tx.Txid, blockNumber)
continue
Expand Down Expand Up @@ -685,7 +705,8 @@ func GetBtcEvent(
targetAddress string,
blockNumber uint64,
logger *zerolog.Logger,
chainID int64,
netParams *chaincfg.Params,
depositorFee float64,
) (*BTCInTxEvnet, error) {
found := false
var value float64
Expand All @@ -699,23 +720,18 @@ func GetBtcEvent(
if err != nil {
return nil, err
}

bitcoinNetParams, err := common.BitcoinNetParamsFromChainID(chainID)
if err != nil {
return nil, fmt.Errorf("btc: error getting bitcoin net params : %v", err)
}
wpkhAddress, err := btcutil.NewAddressWitnessPubKeyHash(hash, bitcoinNetParams)
wpkhAddress, err := btcutil.NewAddressWitnessPubKeyHash(hash, netParams)
if err != nil {
return nil, err
}
if wpkhAddress.EncodeAddress() != targetAddress {
return nil, nil // irrelevant tx to us, skip
}
// deposit amount has to be no less than the minimum depositor fee
if out.Value < BtcDepositorFeeMin {
return nil, fmt.Errorf("btc deposit amount %v in txid %s is less than minimum depositor fee %v", value, tx.Txid, BtcDepositorFeeMin)
if out.Value < depositorFee {
return nil, fmt.Errorf("btc deposit amount %v in txid %s is less than depositor fee %v", value, tx.Txid, depositorFee)
}
value = out.Value - BtcDepositorFeeMin
value = out.Value - depositorFee

out = tx.Vout[1]
script = out.ScriptPubKey.Hex
Expand Down Expand Up @@ -754,13 +770,7 @@ func GetBtcEvent(
return nil, errors.Wrapf(err, "error decoding pubkey")
}
hash := btcutil.Hash160(pkBytes)

bitcoinNetParams, err := common.BitcoinNetParamsFromChainID(chainID)
if err != nil {
return nil, fmt.Errorf("btc: error getting bitcoin net params : %v", err)
}

addr, err := btcutil.NewAddressWitnessPubKeyHash(hash, bitcoinNetParams)
addr, err := btcutil.NewAddressWitnessPubKeyHash(hash, netParams)
if err != nil {
return nil, errors.Wrapf(err, "error decoding pubkey hash")
}
Expand Down Expand Up @@ -845,7 +855,7 @@ func (ob *BTCChainClient) FetchUTXOS() error {
utxosFiltered := make([]btcjson.ListUnspentResult, 0)
for _, utxo := range utxos {
// UTXOs big enough to cover the cost of spending themselves
if utxo.Amount < BtcDepositorFeeMin {
if utxo.Amount < DefaultDepositorFee {
continue
}
// we don't want to spend other people's unconfirmed UTXOs as they may not be safe to spend
Expand Down
Loading

0 comments on commit 165fe5d

Please sign in to comment.