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

client: add support for dcr spv wallets #788

Merged
merged 22 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0451243
client/asset/dcr: validate and broadcast txData in AuditContract()
itswisdomagain Jan 15, 2021
8fa63e5
client/asset/dcr: add basic support for spv wallets
itswisdomagain Jan 15, 2021
c3d9007
dcr harness: use spv for trading2 wallet
itswisdomagain Aug 22, 2020
af46a25
client/asset/dcr: use cfilters to find txout confirmations
itswisdomagain Mar 11, 2021
2355326
client/asset/dcr.AuditContract: handle txdata broadcast errors
itswisdomagain Jul 28, 2021
4f5a5e2
multi: re-audit contracts after confirmation, before acting
itswisdomagain Jul 29, 2021
3c11408
fix trade_simnet_test duplicate balance change tracking
itswisdomagain Jul 30, 2021
206f27d
client/asset/dcr: remove added calls to getrawtransaction
itswisdomagain Jul 31, 2021
b0749cf
rebase fix, partial chapp review corrections
itswisdomagain Oct 5, 2021
7e211c8
rework swap confirmations code
itswisdomagain Oct 8, 2021
ad2e26c
more review fixes
itswisdomagain Oct 12, 2021
742432e
pass feeratesuggestion to PayFee, fix txoutspender loop
itswisdomagain Oct 12, 2021
19fd34a
use wallet+external output lookup for audit, refund and swapconfs
itswisdomagain Oct 22, 2021
e569f68
client/asset/dcr: check walletInfo.SPV and handle rpcclient reconnects
itswisdomagain Oct 26, 2021
eb24720
no txdata for post-confirmation repeat audit
itswisdomagain Oct 26, 2021
785973a
trade_simnet_test changes for better btc spv wallet support
itswisdomagain Oct 26, 2021
5a53e29
fix wallet output check for spv wallets
itswisdomagain Oct 27, 2021
ab0d0bd
bump dcr rpc requiredWalletVersion
itswisdomagain Oct 27, 2021
d798528
rework dcr.lookupTxOut{WithBlockFilters}, rename dcr.Wallet.GetTxOut
itswisdomagain Nov 4, 2021
5f884a5
buck's review, btc audit txdata only
itswisdomagain Nov 4, 2021
73ca58c
chapp review
itswisdomagain Nov 6, 2021
b20b179
cleanup
itswisdomagain Nov 8, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 26 additions & 27 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1824,9 +1824,9 @@ func (btc *ExchangeWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys,
return
}

// AuditContract retrieves information about a swap contract on the blockchain.
// AuditContract would be used to audit the counter-party's contract during a
// swap.
// AuditContract retrieves information about a swap contract from the provided
// txData. The extracted information would be used to audit the counter-party's
// contract during a swap.
func (btc *ExchangeWallet) AuditContract(coinID, contract, txData dex.Bytes, since time.Time) (*asset.AuditInfo, error) {
txHash, vout, err := decodeCoinID(coinID)
if err != nil {
Expand All @@ -1837,31 +1837,18 @@ func (btc *ExchangeWallet) AuditContract(coinID, contract, txData dex.Bytes, sin
if err != nil {
return nil, fmt.Errorf("error extracting swap addresses: %w", err)
}
pkScript, err := btc.scriptHashScript(contract)
if err != nil {
return nil, fmt.Errorf("error parsing pubkey script: %w", err)
}
// Get the contracts P2SH address from the tx output's pubkey script.
txOut, _, err := btc.node.getTxOut(txHash, vout, pkScript, since)
if err != nil && !errors.Is(err, asset.CoinNotFoundError) {
return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err)
}

// Even if we haven't found the output, we can perform basic validation
// using the txData. We may also want to broadcast the transaction if using
// an spvWallet. It may be worth separating data validation from coin
// Perform basic validation using the txData.
// It may be worth separating data validation from coin
// retrieval at the asset.Wallet interface level.
coinNotFound := txOut == nil
if coinNotFound {
tx, err := msgTxFromBytes(txData)
if err != nil {
return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err)
}
if len(tx.TxOut) <= int(vout) {
return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash)
}
txOut = tx.TxOut[vout]
tx, err := msgTxFromBytes(txData)
if err != nil {
return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err)
}
if len(tx.TxOut) <= int(vout) {
return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash)
}
txOut := tx.TxOut[vout]

// Check for standard P2SH.
scriptClass, addrs, numReq, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams)
Expand Down Expand Up @@ -1898,9 +1885,13 @@ func (btc *ExchangeWallet) AuditContract(coinID, contract, txData dex.Bytes, sin
contractHash, addr.ScriptAddress())
}

if coinNotFound {
return nil, asset.CoinNotFoundError
// Broadcast the transaction.
if hashSent, err := btc.node.sendRawTransaction(tx); err != nil {
btc.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err)
} else if !hashSent.IsEqual(txHash) {
btc.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent)
}

return &asset.AuditInfo{
Coin: newOutput(txHash, vout, uint64(txOut.Value)),
Recipient: receiver.String(),
Expand Down Expand Up @@ -2191,6 +2182,14 @@ func (btc *ExchangeWallet) Refund(coinID, contract dex.Bytes, feeSuggestion uint
return nil, fmt.Errorf("error parsing pubkey script: %w", err)
}

// TODO: I'd recommend not passing a pkScript without a limited startTime
// to prevent potentially long searches. In this case though, the output
// will be found in the wallet and won't need to be searched for, only
// the spender search will be conducted using the pkScript starting from
// the block containing the original tx. The script can be gotten from
// the wallet tx though and used for the spender search, while not passing
// a script here to ensure no attempt is made to find the output without
// a limited startTime.
utxo, _, err := btc.node.getTxOut(txHash, vout, pkScript, time.Time{})
if err != nil {
return nil, fmt.Errorf("error finding unspent contract: %w", err)
Expand Down
39 changes: 6 additions & 33 deletions client/asset/btc/btc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1909,12 +1909,11 @@ func TestAuditContract(t *testing.T) {
}

func testAuditContract(t *testing.T, segwit bool, walletType string) {
wallet, node, shutdown, err := tNewWallet(segwit, walletType)
wallet, _, shutdown, err := tNewWallet(segwit, walletType)
defer shutdown()
if err != nil {
t.Fatal(err)
}
swapVal := toSatoshi(5)
secretHash, _ := hex.DecodeString("5124208c80d33507befa517c08ed01aa8d33adbf37ecd70fb5f9352f7a51a88d")
lockTime := time.Now().Add(time.Hour * 12)
now := time.Now()
Expand All @@ -1936,31 +1935,16 @@ func testAuditContract(t *testing.T, segwit bool, walletType string) {
contractAddr, _ = btcutil.NewAddressScriptHash(contract, &chaincfg.MainNetParams)
}
pkScript, _ := txscript.PayToAddrScript(contractAddr)

// Prime a blockchain
const tipHeight = 10
const txBlockHeight = 9
for i := int64(1); i < tipHeight; i++ {
node.addRawTx(i, dummyTx())
}

tx := makeRawTx([]dex.Bytes{pkScript}, []*wire.TxIn{dummyInput()})
blockHash, _ := node.addRawTx(txBlockHeight, tx)
node.getCFilterScripts[*blockHash] = [][]byte{pkScript} //spv
node.txOutRes = &btcjson.GetTxOutResult{ // rpc
Confirmations: 2,
Value: float64(swapVal) / 1e8,
ScriptPubKey: btcjson.ScriptPubKeyResult{
Hex: hex.EncodeToString(pkScript),
},
txData, err := serializeMsgTx(tx)
if err != nil {
t.Fatalf("error making contract tx data: %v", err)
}
node.getTransactionErr = WalletTransactionNotFound

txHash := tx.TxHash()
const vout = 0
outPt := newOutPoint(&txHash, vout)

audit, err := wallet.AuditContract(toCoinID(&txHash, vout), contract, nil, now)
audit, err := wallet.AuditContract(toCoinID(&txHash, vout), contract, txData, now)
if err != nil {
t.Fatalf("audit error: %v", err)
}
Expand All @@ -1975,22 +1959,11 @@ func testAuditContract(t *testing.T, segwit bool, walletType string) {
}

// Invalid txid
_, err = wallet.AuditContract(make([]byte, 15), contract, nil, now)
_, err = wallet.AuditContract(make([]byte, 15), contract, txData, now)
if err == nil {
t.Fatalf("no error for bad txid")
}

// GetTxOut error
node.txOutErr = tErr
delete(node.getCFilterScripts, *blockHash)
delete(node.checkpoints, outPt)
_, err = wallet.AuditContract(toCoinID(&txHash, vout), contract, nil, now)
if err == nil {
t.Fatalf("no error for unknown txout")
}
node.txOutErr = nil
node.getCFilterScripts[*blockHash] = [][]byte{pkScript}

// Wrong contract
pkh, _ := hex.DecodeString("c6a704f11af6cbee8738ff19fc28cdc70aba0b82")
wrongAddr, _ := btcutil.NewAddressPubKeyHash(pkh, &chaincfg.MainNetParams)
Expand Down