Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove btc deposit fee v1 and improve unit tests #2899

Merged
merged 11 commits into from
Sep 23, 2024
Merged
2 changes: 0 additions & 2 deletions e2e/runner/bitcoin.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,15 +267,13 @@ func (r *E2ERunner) SendToTSSFromDeployerWithMemo(
rawtx, err := btcRPC.GetRawTransactionVerbose(txid)
require.NoError(r, err)

depositorFee := zetabitcoin.DefaultDepositorFee
events, err := btcobserver.FilterAndParseIncomingTx(
btcRPC,
[]btcjson.TxRawResult{*rawtx},
0,
r.BTCTSSAddress.EncodeAddress(),
log.Logger,
r.BitcoinParams,
depositorFee,
)
require.NoError(r, err)
r.Logger.Info("bitcoin inbound events:")
Expand Down
7 changes: 7 additions & 0 deletions zetaclient/chains/base/observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,13 @@
return ob
}

// IsBlockConfirmed checks if the given block number is confirmed.
func (ob *Observer) IsBlockConfirmed(blockNumber uint64) bool {
lastBlock := ob.LastBlock()
confBlock := blockNumber + ob.chainParams.ConfirmationCount
return lastBlock >= confBlock

Check warning on line 235 in zetaclient/chains/base/observer.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/base/observer.go#L232-L235

Added lines #L232 - L235 were not covered by tests
}

// LastBlockScanned get last block scanned (not necessarily caught up with the chain; could be slow/paused).
func (ob *Observer) LastBlockScanned() uint64 {
height := atomic.LoadUint64(&ob.lastBlockScanned)
Expand Down
32 changes: 1 addition & 31 deletions zetaclient/chains/bitcoin/fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/pkg/errors"
"github.com/rs/zerolog"

"github.com/zeta-chain/node/pkg/chains"
"github.com/zeta-chain/node/zetaclient/chains/bitcoin/rpc"
Expand Down Expand Up @@ -226,37 +225,8 @@ func CalcBlockAvgFeeRate(blockVb *btcjson.GetBlockVerboseTxResult, netParams *ch
return txsFees / int64(vBytes), nil
}

// CalcDepositorFee calculates the depositor fee for a given block
// CalcDepositorFee calculates the depositor fee for a given tx result
func CalcDepositorFee(
blockVb *btcjson.GetBlockVerboseTxResult,
chainID int64,
netParams *chaincfg.Params,
logger zerolog.Logger,
) float64 {
// use default fee for regnet
if chains.IsBitcoinRegnet(chainID) {
return DefaultDepositorFee
}
// mainnet dynamic fee takes effect only after a planned upgrade height
if chains.IsBitcoinMainnet(chainID) && blockVb.Height < DynamicDepositorFeeHeight {
return DefaultDepositorFee
}

// calculate deposit fee rate
feeRate, err := CalcBlockAvgFeeRate(blockVb, netParams)
if err != nil {
feeRate = defaultDepositorFeeRate // use default fee rate if calculation fails, should not happen
logger.Error().Err(err).Msgf("cannot calculate fee rate for block %d", blockVb.Height)
}

// #nosec G115 always in range
feeRate = int64(float64(feeRate) * clientcommon.BTCOutboundGasPriceMultiplier)

return DepositorFee(feeRate)
}

// CalcDepositorFeeV2 calculates the depositor fee for a given tx result
func CalcDepositorFeeV2(
rpcClient interfaces.BTCRPCClient,
rawResult *btcjson.TxRawResult,
netParams *chaincfg.Params,
Expand Down
149 changes: 101 additions & 48 deletions zetaclient/chains/bitcoin/observer/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@
"github.com/zeta-chain/node/zetaclient/zetacore"
)

// BTCInboundEvent represents an incoming transaction event
type BTCInboundEvent struct {
// FromAddress is the first input address
FromAddress string

// ToAddress is the TSS address
ToAddress string

// Value is the amount of BTC
Value float64
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

// DepositorFee is the deposit fee
DepositorFee float64

// MemoBytes is the memo of inbound
MemoBytes []byte

// BlockNumber is the block number of the inbound
BlockNumber uint64

// TxHash is the hash of the inbound
TxHash string
}

// WatchInbound watches Bitcoin chain for inbounds on a ticker
// It starts a ticker and run ObserveInbound
// TODO(revamp): move all ticker related methods in the same file
Expand Down Expand Up @@ -94,7 +118,6 @@

// #nosec G115 checked positive
lastBlock := uint64(currentBlock)

if lastBlock < ob.LastBlock() {
return fmt.Errorf(
"observeInboundBTC: block number should not decrease: current %d last %d",
Expand All @@ -104,45 +127,35 @@
}
ob.WithLastBlock(lastBlock)

// skip if current height is too low
if lastBlock < ob.GetChainParams().ConfirmationCount {
return fmt.Errorf("observeInboundBTC: skipping observer, current block number %d is too low", currentBlock)
}

// skip if no new block is confirmed
lastScanned := ob.LastBlockScanned()
if lastScanned >= lastBlock-ob.GetChainParams().ConfirmationCount {
// check confirmation
blockNumber := ob.LastBlockScanned() + 1
if !ob.IsBlockConfirmed(blockNumber) {

Check warning on line 132 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L131-L132

Added lines #L131 - L132 were not covered by tests
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

// query incoming gas asset to TSS address
blockNumber := lastScanned + 1
// #nosec G115 always in range
res, err := ob.GetBlockByNumberCached(int64(blockNumber))
if err != nil {
ob.logger.Inbound.Error().Err(err).Msgf("observeInboundBTC: error getting bitcoin block %d", blockNumber)
return err
}
ob.logger.Inbound.Info().Msgf("observeInboundBTC: block %d has %d txs, current block %d, last block %d",
blockNumber, len(res.Block.Tx), currentBlock, lastScanned)
ob.logger.Inbound.Info().Msgf("observeInboundBTC: block %d has %d txs, current block %d, last scanned %d",
blockNumber, len(res.Block.Tx), currentBlock, blockNumber-1)

Check warning on line 144 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L143-L144

Added lines #L143 - L144 were not covered by tests
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

// add block header to zetacore
if len(res.Block.Tx) > 1 {
// get depositor fee
depositorFee := bitcoin.CalcDepositorFee(res.Block, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound)

// filter incoming txs to TSS address
tssAddress := ob.TSS().BTCAddress()

// #nosec G115 always positive
inbounds, err := FilterAndParseIncomingTx(
events, err := FilterAndParseIncomingTx(

Check warning on line 152 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L152

Added line #L152 was not covered by tests
ob.btcClient,
res.Block.Tx,
uint64(res.Block.Height),
tssAddress,
ob.logger.Inbound,
ob.netParams,
depositorFee,
)
if err != nil {
ob.logger.Inbound.Error().
Expand All @@ -152,8 +165,8 @@
}

// post inbound vote message to zetacore
for _, inbound := range inbounds {
msg := ob.GetInboundVoteMessageFromBtcEvent(inbound)
for _, event := range events {
msg := ob.GetInboundVoteMessageFromBtcEvent(event)

Check warning on line 169 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L168-L169

Added lines #L168 - L169 were not covered by tests
if msg != nil {
zetaHash, ballot, err := zetaCoreClient.PostVoteInbound(
ctx,
Expand All @@ -164,11 +177,11 @@
if err != nil {
ob.logger.Inbound.Error().
Err(err).
Msgf("observeInboundBTC: error posting to zetacore for tx %s", inbound.TxHash)
Msgf("observeInboundBTC: error posting to zetacore for tx %s", event.TxHash)

Check warning on line 180 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L180

Added line #L180 was not covered by tests
return err // we have to re-scan this block next time
} else if zetaHash != "" {
ob.logger.Inbound.Info().Msgf("observeInboundBTC: PostVoteInbound zeta tx hash: %s inbound %s ballot %s fee %v",
zetaHash, inbound.TxHash, ballot, depositorFee)
zetaHash, event.TxHash, ballot, event.DepositorFee)

Check warning on line 184 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L184

Added line #L184 was not covered by tests
}
}
}
Expand Down Expand Up @@ -272,12 +285,17 @@
return "", fmt.Errorf("block %d has no transactions", blockVb.Height)
}

depositorFee := bitcoin.CalcDepositorFee(blockVb, ob.Chain().ChainId, ob.netParams, ob.logger.Inbound)
tss, err := ob.ZetacoreClient().GetBTCTSSAddress(ctx, ob.Chain().ChainId)
if err != nil {
return "", err
}

// calculate depositor fee
depositorFee, err := bitcoin.CalcDepositorFee(ob.btcClient, tx, ob.netParams)
if err != nil {
return "", errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid)

Check warning on line 296 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L294-L296

Added lines #L294 - L296 were not covered by tests
}

// #nosec G115 always positive
event, err := GetBtcEvent(
ob.btcClient,
Expand Down Expand Up @@ -316,7 +334,7 @@
return "", err
} else if zetaHash != "" {
ob.logger.Inbound.Info().Msgf("BTC deposit detected and reported: PostVoteInbound zeta tx hash: %s inbound %s ballot %s fee %v",
zetaHash, txHash, ballot, depositorFee)
zetaHash, txHash, ballot, event.DepositorFee)

Check warning on line 337 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L337

Added line #L337 was not covered by tests
}

return msg.Digest(), nil
Expand All @@ -333,26 +351,31 @@
tssAddress string,
logger zerolog.Logger,
netParams *chaincfg.Params,
depositorFee float64,
) ([]*BTCInboundEvent, error) {
inbounds := make([]*BTCInboundEvent, 0)
events := make([]*BTCInboundEvent, 0)

Check warning on line 355 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L355

Added line #L355 was not covered by tests
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
for idx, tx := range txs {
if idx == 0 {
continue // the first tx is coinbase; we do not process coinbase tx
}

inbound, err := GetBtcEvent(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee)
// calculate depositor fee
depositorFee, err := bitcoin.CalcDepositorFee(rpcClient, &txs[idx], netParams)
if err != nil {
return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid)

Check warning on line 364 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L362-L364

Added lines #L362 - L364 were not covered by tests
}

ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
event, err := GetBtcEvent(rpcClient, tx, tssAddress, blockNumber, logger, netParams, depositorFee)

Check warning on line 367 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L367

Added line #L367 was not covered by tests
if err != nil {
// unable to parse the tx, the caller should retry
return nil, errors.Wrapf(err, "error getting btc event for tx %s in block %d", tx.Txid, blockNumber)
}

if inbound != nil {
inbounds = append(inbounds, inbound)
if event != nil {
events = append(events, event)

Check warning on line 374 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L373-L374

Added lines #L373 - L374 were not covered by tests
logger.Info().Msgf("FilterAndParseIncomingTx: found btc event for tx %s in block %d", tx.Txid, blockNumber)
}
}
return inbounds, nil
return events, nil

Check warning on line 378 in zetaclient/chains/bitcoin/observer/inbound.go

View check run for this annotation

Codecov / codecov/patch

zetaclient/chains/bitcoin/observer/inbound.go#L378

Added line #L378 was not covered by tests
}

// GetInboundVoteMessageFromBtcEvent converts a BTCInboundEvent to a MsgVoteInbound to enable voting on the inbound on zetacore
Expand Down Expand Up @@ -434,19 +457,6 @@
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
// 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)
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().
Expand Down Expand Up @@ -476,13 +486,56 @@
}

return &BTCInboundEvent{
FromAddress: fromAddress,
ToAddress: tssAddress,
Value: value,
MemoBytes: memo,
BlockNumber: blockNumber,
TxHash: tx.Txid,
FromAddress: fromAddress,
ToAddress: tssAddress,
Value: value,
DepositorFee: depositorFee,
MemoBytes: memo,
BlockNumber: blockNumber,
TxHash: tx.Txid,
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
}, nil
}
return nil, nil
}

// GetSenderAddressByVin get the sender address from the previous transaction
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
func GetSenderAddressByVin(rpcClient interfaces.BTCRPCClient, vin btcjson.Vin, net *chaincfg.Params) (string, error) {
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved
// query previous raw transaction by txid
hash, err := chainhash.NewHashFromStr(vin.Txid)
if err != nil {
return "", err
}

// this requires running bitcoin node with 'txindex=1'
tx, err := rpcClient.GetRawTransaction(hash)
if err != nil {
return "", errors.Wrapf(err, "error getting raw transaction %s", vin.Txid)
}

// #nosec G115 - always in range
if len(tx.MsgTx().TxOut) <= int(vin.Vout) {
return "", fmt.Errorf("vout index %d out of range for tx %s", vin.Vout, vin.Txid)
}

// decode sender address from previous pkScript
pkScript := tx.MsgTx().TxOut[vin.Vout].PkScript
scriptHex := hex.EncodeToString(pkScript)
if bitcoin.IsPkScriptP2TR(pkScript) {
return bitcoin.DecodeScriptP2TR(scriptHex, net)
}
if bitcoin.IsPkScriptP2WSH(pkScript) {
return bitcoin.DecodeScriptP2WSH(scriptHex, net)
}
if bitcoin.IsPkScriptP2WPKH(pkScript) {
return bitcoin.DecodeScriptP2WPKH(scriptHex, net)
}
if bitcoin.IsPkScriptP2SH(pkScript) {
return bitcoin.DecodeScriptP2SH(scriptHex, net)
}
if bitcoin.IsPkScriptP2PKH(pkScript) {
return bitcoin.DecodeScriptP2PKH(scriptHex, net)
}
ws4charlie marked this conversation as resolved.
Show resolved Hide resolved

// sender address not found, return nil and move on to the next tx
return "", nil
}
Loading
Loading