Skip to content

Commit

Permalink
screen out unconfirmed utxos that are not created by TSS itself
Browse files Browse the repository at this point in the history
  • Loading branch information
ws4charlie committed Jan 11, 2024
1 parent 3283191 commit bc33c8d
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 12 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

### Fixes

* [1554](https://github.com/zeta-chain/node/pull/1554) - Screen out unconfirmed UTXOs that are not created by TSS itself
* [1530](https://github.com/zeta-chain/node/pull/1530) - Outbound tx confirmation/inclusion enhancement
* [1496](https://github.com/zeta-chain/node/issues/1496) - post block header for enabled EVM chains only
* [1518](https://github.com/zeta-chain/node/pull/1518) - Avoid duplicate keysign if an outTx is already pending
Expand Down
41 changes: 29 additions & 12 deletions zetaclient/bitcoin_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type BitcoinChainClient struct {

Mu *sync.Mutex // lock for all the maps, utxos and core params
pendingNonce uint64
includedTxHashes map[string]uint64 // key: tx hash
includedTxHashes map[string]bool // key: tx hash
includedTxResults map[string]*btcjson.GetTransactionResult // key: chain-tss-nonce
broadcastedTx map[string]string // key: chain-tss-nonce, value: outTx hash
utxos []btcjson.ListUnspentResult
Expand Down Expand Up @@ -147,7 +147,7 @@ func NewBitcoinClient(

ob.zetaClient = bridge
ob.Tss = tss
ob.includedTxHashes = make(map[string]uint64)
ob.includedTxHashes = make(map[string]bool)
ob.includedTxResults = make(map[string]*btcjson.GetTransactionResult)
ob.broadcastedTx = make(map[string]string)
ob.params = btcCfg.ChainParams
Expand Down Expand Up @@ -755,16 +755,13 @@ func (ob *BitcoinChainClient) FetchUTXOS() error {
}
maxConfirmations := int(bh)

// List unspent.
// List all unspent UTXOs (160ms)
tssAddr := ob.Tss.BTCAddress()
address, err := common.DecodeBtcAddress(tssAddr, ob.chain.ChainId)
if err != nil {
return fmt.Errorf("btc: error decoding wallet address (%s) : %s", tssAddr, err.Error())
}
addresses := []btcutil.Address{address}

// fetching all TSS utxos takes 160ms
utxos, err := ob.rpcClient.ListUnspentMinMaxAddresses(0, maxConfirmations, addresses)
utxos, err := ob.rpcClient.ListUnspentMinMaxAddresses(0, maxConfirmations, []btcutil.Address{address})
if err != nil {
return err
}
Expand All @@ -780,12 +777,20 @@ func (ob *BitcoinChainClient) FetchUTXOS() error {
return utxos[i].Amount < utxos[j].Amount
})

// filter UTXOs big enough to cover the cost of spending themselves
// filter UTXOs good to spend for next TSS transaction
utxosFiltered := make([]btcjson.ListUnspentResult, 0)
for _, utxo := range utxos {
if utxo.Amount >= BtcDepositorFeeMin {
utxosFiltered = append(utxosFiltered, utxo)
// UTXOs big enough to cover the cost of spending themselves
if utxo.Amount < BtcDepositorFeeMin {
continue
}
// we don't want to spend other people's unconfirmed UTXOs as they may not be safe to spend
if utxo.Confirmations == 0 {
if !ob.isTssTransaction(utxo.TxID) {
continue
}
}
utxosFiltered = append(utxosFiltered, utxo)
}

ob.Mu.Lock()
Expand All @@ -795,6 +800,13 @@ func (ob *BitcoinChainClient) FetchUTXOS() error {
return nil
}

// isTssTransaction checks if a given transaction was sent by TSS itself.
// An unconfirmed transaction is safe to spend only if it was sent by TSS and verified by ourselves.
func (ob *BitcoinChainClient) isTssTransaction(txid string) bool {
_, found := ob.includedTxHashes[txid]
return found
}

// refreshPendingNonce tries increasing the artificial pending nonce of outTx (if lagged behind).
// There could be many (unpredictable) reasons for a pending nonce lagging behind, for example:
// 1. The zetaclient gets restarted.
Expand Down Expand Up @@ -1081,6 +1093,7 @@ func (ob *BitcoinChainClient) setIncludedTx(nonce uint64, getTxResult *btcjson.G
res, found := ob.includedTxResults[outTxID]

if !found { // not found.
ob.includedTxHashes[txHash] = true
ob.includedTxResults[outTxID] = getTxResult // include new outTx and enforce rigid 1-to-1 mapping: nonce <===> txHash
if nonce >= ob.pendingNonce { // try increasing pending nonce on every newly included outTx
ob.pendingNonce = nonce + 1
Expand All @@ -1105,11 +1118,15 @@ func (ob *BitcoinChainClient) getIncludedTx(nonce uint64) *btcjson.GetTransactio
return ob.includedTxResults[ob.GetTxID(nonce)]
}

// removeIncludedTx removes included tx's result from memory
// removeIncludedTx removes included tx from memory
func (ob *BitcoinChainClient) removeIncludedTx(nonce uint64) {
ob.Mu.Lock()
defer ob.Mu.Unlock()
delete(ob.includedTxResults, ob.GetTxID(nonce))
txResult, found := ob.includedTxResults[ob.GetTxID(nonce)]
if found {
delete(ob.includedTxHashes, txResult.TxID)
delete(ob.includedTxResults, ob.GetTxID(nonce))
}
}

// Basic TSS outTX checks:
Expand Down

0 comments on commit bc33c8d

Please sign in to comment.