From 75a106bf6c8902bd2707218549213cbefdb89ec1 Mon Sep 17 00:00:00 2001 From: charliec Date: Mon, 9 Oct 2023 15:24:26 -0500 Subject: [PATCH 1/2] special handling to avoid duplicate payment on bitcoin outbound nonce 0 --- zetaclient/bitcoin_client.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zetaclient/bitcoin_client.go b/zetaclient/bitcoin_client.go index 2e8c2fd766..33687a688a 100644 --- a/zetaclient/bitcoin_client.go +++ b/zetaclient/bitcoin_client.go @@ -348,6 +348,15 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64 if !broadcasted { return false, false, nil } + // If the broadcasted outTx is nonce 0, just wait for inclusion and don't schedule more keysign + // Schedule more than one keysign for nonce 0 can lead to duplicate payments. + // One purpose of nonce mark UTXO is to avoid duplicate payment based on the fact that Bitcoin + // prevents double spending of same UTXO. However, for nonce 0, we don't have a prior nonce (e.g., -1) + // for the signer to check against when making the payment. Signer treats nonce 0 as a special case in downstream code. + if nonce == 0 { + return true, false, nil + } + // Get original cctx parameters params, err := ob.GetPendingCctxParams(nonce) if err != nil { From 62a367e14f69278c8b13acb922556cba31c85985 Mon Sep 17 00:00:00 2001 From: charliec Date: Mon, 9 Oct 2023 18:15:38 -0500 Subject: [PATCH 2/2] use cctx amount instead of txResult's amount because it's not available in bitcoin mainnet --- zetaclient/bitcoin_client.go | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/zetaclient/bitcoin_client.go b/zetaclient/bitcoin_client.go index 33687a688a..166c968c8b 100644 --- a/zetaclient/bitcoin_client.go +++ b/zetaclient/bitcoin_client.go @@ -344,6 +344,13 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64 res, included := ob.includedTxResults[outTxID] ob.mu.Unlock() + // Get original cctx parameters + params, err := ob.GetPendingCctxParams(nonce) + if err != nil { + ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: can't find pending cctx for nonce %d", nonce) + return false, false, err + } + if !included { if !broadcasted { return false, false, nil @@ -357,13 +364,6 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64 return true, false, nil } - // Get original cctx parameters - params, err := ob.GetPendingCctxParams(nonce) - if err != nil { - ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: can't find pending cctx for nonce %d", nonce) - return false, false, nil - } - // Try including this outTx broadcasted by myself inMempool, err := ob.checkNSaveIncludedTx(txnHash, params) if err != nil { @@ -385,23 +385,13 @@ func (ob *BitcoinChainClient) IsSendOutTxProcessed(sendHash string, nonce uint64 ob.logger.ObserveOutTx.Info().Msgf("IsSendOutTxProcessed: checkNSaveIncludedTx succeeded for outTx %s", outTxID) } - var amount float64 - if res.Amount > 0 { - ob.logger.ObserveOutTx.Warn().Msg("IsSendOutTxProcessed: res.Amount > 0") - amount = res.Amount - } else if res.Amount == 0 { - ob.logger.ObserveOutTx.Error().Msg("IsSendOutTxProcessed: res.Amount == 0") - return false, false, nil - } else { - amount = -res.Amount - } - - amountInSat, _ := big.NewFloat(amount * 1e8).Int(nil) + // It's safe to use cctx's amount to post confirmation because it has already been verified in observeOutTx() + amountInSat := params.Amount.BigInt() if res.Confirmations < ob.ConfirmationsThreshold(amountInSat) { return true, false, nil } - logger.Debug().Msgf("Bitcoin outTx confirmed: txid %s, amount %f\n", res.TxID, res.Amount) + logger.Debug().Msgf("Bitcoin outTx confirmed: txid %s, amount %s\n", res.TxID, amountInSat.String()) zetaHash, err := ob.zetaClient.PostReceiveConfirmation( sendHash, res.TxID,