Skip to content

Commit

Permalink
client/asset: Add confirm tx.
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeGruffins committed Nov 17, 2024
1 parent 50005f4 commit 1a90b2c
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 271 deletions.
77 changes: 46 additions & 31 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ const (
multiSplitBufferKey = "multisplitbuffer"
redeemFeeBumpFee = "redeemfeebump"

// requiredRedeemConfirms is the amount of confirms a redeem transaction
// needs before the trade is considered confirmed. The redeem is
// requiredConfTxConfirms is the amount of confirms a redeem or refund
// transaction needs before the trade is considered confirmed. The tx is
// monitored until this number of confirms is reached.
requiredRedeemConfirms = 1
requiredConfTxConfirms = 1
)

const (
Expand Down Expand Up @@ -5995,7 +5995,7 @@ func (btc *intermediaryWallet) syncTxHistory(tip uint64) {
if tx.BlockNumber > 0 && tip >= tx.BlockNumber {
confs = tip - tx.BlockNumber + 1
}
if confs >= requiredRedeemConfirms {
if confs >= requiredConfTxConfirms {
tx.Confirmed = true
updated = true
}
Expand Down Expand Up @@ -6330,7 +6330,7 @@ func msgTxFromBytes(txB []byte) (*wire.MsgTx, error) {
// possibility that the tx is never mined and eventually purged from the
// mempool. In that case we use the provided fee suggestion to create and send
// a new redeem transaction, returning the new transactions hash.
func (btc *baseWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Redemption, feeSuggestion uint64) (*asset.ConfirmRedemptionStatus, error) {
func (btc *baseWallet) ConfirmTransaction(coinID dex.Bytes, confirmTx *asset.ConfirmTx, feeSuggestion uint64) (*asset.ConfirmTxStatus, error) {
txHash, _, err := decodeCoinID(coinID)
if err != nil {
return nil, err
Expand All @@ -6339,68 +6339,83 @@ func (btc *baseWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Red
_, confs, err := btc.rawWalletTx(txHash)
// redemption transaction found, return its confirms.
//
// TODO: Investigate the case where this redeem has been sitting in the
// TODO: Investigate the case where this tx has been sitting in the
// mempool for a long amount of time, possibly requiring some action by
// us to get it unstuck.
if err == nil {
return &asset.ConfirmRedemptionStatus{
return &asset.ConfirmTxStatus{
Confs: uint64(confs),
Req: requiredRedeemConfirms,
Req: requiredConfTxConfirms,
CoinID: coinID,
}, nil
}

if !errors.Is(err, WalletTransactionNotFound) {
return nil, fmt.Errorf("problem searching for redemption transaction %s: %w", txHash, err)
return nil, fmt.Errorf("problem searching for %v transaction %s: %w", confirmTx.TxType(), txHash, err)
}

// Redemption transaction is missing from the point of view of our node!
// Unlikely, but possible it was redeemed by another transaction. Check
// if the contract is still an unspent output.
// Redemption or refund transaction is missing from the point of view of
// our node! Unlikely, but possible it was spent by another transaction.
// Check if the contract is still an unspent output.

pkScript, err := btc.scriptHashScript(redemption.Spends.Contract)
pkScript, err := btc.scriptHashScript(confirmTx.Contract())
if err != nil {
return nil, fmt.Errorf("error creating contract script: %w", err)
}

swapHash, vout, err := decodeCoinID(redemption.Spends.Coin.ID())
swapHash, vout, err := decodeCoinID(confirmTx.SpendsCoinID())
if err != nil {
return nil, err
}

utxo, _, err := btc.node.getTxOut(swapHash, vout, pkScript, time.Now().Add(-ContractSearchLimit))
if err != nil {
return nil, fmt.Errorf("error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
return nil, fmt.Errorf("error finding unspent contract %s with swap hash %v vout %d: %w", confirmTx.SpendsCoinID(), swapHash, vout, err)
}
if utxo == nil {
// TODO: Spent, but by who. Find the spending tx.
btc.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", redemption.Spends.Coin.ID(), swapHash, vout)
btc.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", confirmTx.SpendsCoinID(), swapHash, vout)
// Incorrect, but we will be in a loop of erroring if we don't
// return something.
return &asset.ConfirmRedemptionStatus{
Confs: requiredRedeemConfirms,
Req: requiredRedeemConfirms,
return &asset.ConfirmTxStatus{
Confs: requiredConfTxConfirms,
Req: requiredConfTxConfirms,
CoinID: coinID,
}, nil
}

// The contract has not yet been redeemed, but it seems the redeeming
// The contract has not yet been spent, but it seems the spending
// tx has disappeared. Assume the fee was too low at the time and it
// was eventually purged from the mempool. Attempt to redeem again with
// was eventually purged from the mempool. Attempt to spend again with
// a currently reasonable fee.
var newCoinID dex.Bytes
if confirmTx.IsRedeem() {
form := &asset.RedeemForm{
Redemptions: []*asset.Redemption{
&asset.Redemption{
Spends: confirmTx.Spends(),
Secret: confirmTx.Secret(),
},
},
FeeSuggestion: feeSuggestion,
}
_, coin, _, err := btc.Redeem(form)
if err != nil {
return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", confirmTx.SpendsCoinID(), swapHash, vout, err)
}
newCoinID = coin.ID()
} else {
spendsCoinID := confirmTx.SpendsCoinID()
newCoinID, err = btc.Refund(spendsCoinID, confirmTx.Contract(), feeSuggestion)
if err != nil {
return nil, fmt.Errorf("unable to re-refund %s: %w", spendsCoinID, err)
}

form := &asset.RedeemForm{
Redemptions: []*asset.Redemption{redemption},
FeeSuggestion: feeSuggestion,
}
_, coin, _, err := btc.Redeem(form)
if err != nil {
return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err)
}
return &asset.ConfirmRedemptionStatus{
return &asset.ConfirmTxStatus{
Confs: 0,
Req: requiredRedeemConfirms,
CoinID: coin.ID(),
Req: requiredConfTxConfirms,
CoinID: newCoinID,
}, nil
}

Expand Down
54 changes: 24 additions & 30 deletions client/asset/btc/btc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5822,10 +5822,7 @@ func TestConfirmRedemption(t *testing.T) {
Expiration: lockTime,
}

redemption := &asset.Redemption{
Spends: ci,
Secret: secret,
}
confirmTx := asset.NewRedeemConfTx(ci, secret)

coinID := coin.ID()

Expand All @@ -5841,7 +5838,7 @@ func TestConfirmRedemption(t *testing.T) {

tests := []struct {
name string
redemption *asset.Redemption
confirmTx *asset.ConfirmTx
coinID []byte
wantErr bool
wantConfs uint64
Expand All @@ -5852,49 +5849,46 @@ func TestConfirmRedemption(t *testing.T) {
}{{
name: "ok and found",
coinID: coinID,
redemption: redemption,
confirmTx: confirmTx,
getTransactionResult: new(GetTransactionResult),
}, {
name: "ok spent by someone but not sure who",
coinID: coinID,
redemption: redemption,
wantConfs: requiredRedeemConfirms,
name: "ok spent by someone but not sure who",
coinID: coinID,
confirmTx: confirmTx,
wantConfs: requiredConfTxConfirms,
}, {
name: "ok but sending new tx",
coinID: coinID,
redemption: redemption,
txOutRes: new(btcjson.GetTxOutResult),
name: "ok but sending new tx",
coinID: coinID,
confirmTx: confirmTx,
txOutRes: new(btcjson.GetTxOutResult),
}, {
name: "decode coin error",
redemption: redemption,
wantErr: true,
name: "decode coin error",
confirmTx: confirmTx,
wantErr: true,
}, {
name: "error finding contract output",
coinID: coinID,
redemption: redemption,
txOutErr: errors.New(""),
wantErr: true,
name: "error finding contract output",
coinID: coinID,
confirmTx: confirmTx,
txOutErr: errors.New(""),
wantErr: true,
}, {
name: "error finding redeem tx",
coinID: coinID,
redemption: redemption,
confirmTx: confirmTx,
getTransactionErr: errors.New(""),
wantErr: true,
}, {
name: "redemption error",
name: "confirmTx error",
coinID: coinID,
redemption: func() *asset.Redemption {
confirmTx: func() *asset.ConfirmTx {
ci := &asset.AuditInfo{
Coin: coin,
// Contract: contract,
Recipient: addr.String(),
Expiration: lockTime,
}

return &asset.Redemption{
Spends: ci,
Secret: secret,
}
return asset.NewRedeemConfTx(ci, secret)
}(),
txOutRes: new(btcjson.GetTxOutResult),
wantErr: true,
Expand All @@ -5905,7 +5899,7 @@ func TestConfirmRedemption(t *testing.T) {
node.getTransactionErr = test.getTransactionErr
node.getTransactionMap[tTxID] = test.getTransactionResult

status, err := wallet.ConfirmRedemption(test.coinID, test.redemption, 0)
status, err := wallet.ConfirmTransaction(test.coinID, test.confirmTx, 0)
if test.wantErr {
if err == nil {
t.Fatalf("%q: expected error", test.name)
Expand Down
2 changes: 1 addition & 1 deletion client/asset/btc/electrum.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ func (btc *ExchangeWalletElectrum) syncTxHistory(tip uint64) {
if tx.BlockNumber > 0 && tip >= tx.BlockNumber {
confs = tip - tx.BlockNumber + 1
}
if confs >= requiredRedeemConfirms {
if confs >= requiredConfTxConfirms {
tx.Confirmed = true
updated = true
}
Expand Down
Loading

0 comments on commit 1a90b2c

Please sign in to comment.