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,server}/btc: BTC Fidelity Bonds #2196

Merged
merged 8 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/asset/bch/bch.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
InitTxSizeBase: dexbtc.InitTxSizeBase,
InitTxSize: dexbtc.InitTxSize,
LegacyBalance: cfg.Type != walletTypeSPV,
LegacySendToAddr: true,
// Bitcoin Cash uses the Cash Address encoding, which is Bech32, but not
// indicative of segwit. We provide a custom encoder and decode to go
// to/from a btcutil.Address and a string.
Expand All @@ -217,6 +216,7 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network)
// then, they modified it from the old Bitcoin Core estimatefee by
// removing the confirmation target argument.
FeeEstimator: estimateFee,
AssetID: BipID,
}

switch cfg.Type {
Expand Down
857 changes: 727 additions & 130 deletions client/asset/btc/btc.go

Large diffs are not rendered by default.

240 changes: 188 additions & 52 deletions client/asset/btc/btc_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !spvlive
//go:build !spvlive && !harness

package btc

Expand Down Expand Up @@ -1395,9 +1395,10 @@ func TestFundEdges(t *testing.T) {

var feeReduction uint64 = swapSize * tBTC.MaxFeeRate
estFeeReduction := swapSize * feeSuggestion
checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees-feeReduction,
totalBytes*feeSuggestion-estFeeReduction,
bestCaseBytes*feeSuggestion)
splitFees := splitTxBaggage * tBTC.MaxFeeRate
checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees+splitFees-feeReduction,
(totalBytes+splitTxBaggage)*feeSuggestion-estFeeReduction,
(bestCaseBytes+splitTxBaggage)*feeSuggestion)

_, _, err := wallet.FundOrder(ord)
if err == nil {
Expand Down Expand Up @@ -1617,9 +1618,10 @@ func TestFundEdgesSegwit(t *testing.T) {

var feeReduction uint64 = swapSize * tBTC.MaxFeeRate
estFeeReduction := swapSize * feeSuggestion
checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees-feeReduction,
totalBytes*feeSuggestion-estFeeReduction,
bestCaseBytes*feeSuggestion)
splitFees := splitTxBaggageSegwit * tBTC.MaxFeeRate
checkMaxOrder(t, wallet, lots-1, swapVal-tLotSize, backingFees+splitFees-feeReduction,
(totalBytes+splitTxBaggageSegwit)*feeSuggestion-estFeeReduction,
(bestCaseBytes+splitTxBaggageSegwit)*feeSuggestion)

_, _, err := wallet.FundOrder(ord)
if err == nil {
Expand Down Expand Up @@ -2387,69 +2389,203 @@ func testSender(t *testing.T, senderType tSenderType, segwit bool, walletType st
}
}

node.signFunc = func(tx *wire.MsgTx) {
signFunc(tx, 0, wallet.segwit)
}

addr := btcAddr(segwit)
fee := float64(1) // BTC
node.setTxFee = true
node.changeAddr = btcAddr(segwit).String()

pkScript, _ := txscript.PayToAddrScript(addr)
tx := makeRawTx([]dex.Bytes{randBytes(5), pkScript}, []*wire.TxIn{dummyInput()})
txHash := tx.TxHash()
const vout = 1
const blockHeight = 2
blockHash, _ := node.addRawTx(blockHeight, tx)

txB, _ := serializeMsgTx(tx)

node.sendToAddress = txHash.String()
node.getTransactionMap = map[string]*GetTransactionResult{
"any": {
BlockHash: blockHash.String(),
BlockIndex: blockHeight,
Hex: txB,
}}
changeAddr := btcAddr(segwit)
node.changeAddr = changeAddr.String()

unspents := []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: 100,
Confirmations: 1,
Vout: vout,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}}
node.listUnspent = unspents
expectedFees := func(numInputs int) uint64 {
txSize := dexbtc.MinimumTxOverhead
if segwit {
txSize += (dexbtc.P2WPKHOutputSize * 2) + (numInputs * dexbtc.RedeemP2WPKHInputTotalSize)
} else {
txSize += (dexbtc.P2PKHOutputSize * 2) + (numInputs * dexbtc.RedeemP2PKHInputSize)
}
return uint64(txSize * feeSuggestion)
}

node.signFunc = func(tx *wire.MsgTx) {
signFunc(tx, 0, wallet.segwit)
expectedSentVal := func(sendVal, fees uint64) uint64 {
if senderType == tSendSender {
return sendVal
}
return sendVal - fees
}

_, err := sender(addr.String(), toSatoshi(fee))
if err != nil {
t.Fatalf("send error: %v", err)
expectedChangeVal := func(totalInput, sendVal, fees uint64) uint64 {
if senderType == tSendSender {
return totalInput - sendVal - fees
}
return totalInput - sendVal
}

// SendToAddress error
node.sendToAddressErr = tErr
_, err = sender(addr.String(), 1e8)
if err == nil {
t.Fatalf("no error for SendToAddress error: %v", err)
requiredVal := func(sendVal, fees uint64) uint64 {
if senderType == tSendSender {
return sendVal + fees
}
return sendVal
}
node.sendToAddressErr = nil

// GetTransaction error
node.getTransactionErr = tErr
_, err = sender(addr.String(), 1e8)
if err == nil {
t.Fatalf("no error for gettransaction error: %v", err)
type test struct {
name string
val uint64
unspents []*ListUnspentResult
bondReservesEnforced int64

expectedInputs []*outPoint
expectSentVal uint64
expectChange uint64
expectErr bool
}
tests := []test{
{
name: "plenty of funds",
val: toSatoshi(5),
unspents: []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: 100,
Confirmations: 1,
Vout: 0,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}},
expectedInputs: []*outPoint{
{txHash: txHash,
vout: 0},
},
expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
expectChange: expectedChangeVal(toSatoshi(100), toSatoshi(5), expectedFees(1)),
},
{
name: "just enough change for bond reserves",
val: toSatoshi(5),
unspents: []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: 5.2,
Confirmations: 1,
Vout: 0,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}},
expectedInputs: []*outPoint{
{txHash: txHash,
vout: 0},
},
expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
expectChange: expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1)),
bondReservesEnforced: int64(expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1))),
},
{
name: "not enough change for bond reserves",
val: toSatoshi(5),
unspents: []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: 5.2,
Confirmations: 1,
Vout: 0,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}},
expectedInputs: []*outPoint{
{txHash: txHash,
vout: 0},
},
bondReservesEnforced: int64(expectedChangeVal(toSatoshi(5.2), toSatoshi(5), expectedFees(1))) + 1,
expectErr: true,
},
{
name: "1 satoshi less than needed",
val: toSatoshi(5),
unspents: []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: toBTC(requiredVal(toSatoshi(5), expectedFees(1)) - 1),
Confirmations: 1,
Vout: 0,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}},
expectErr: true,
},
{
name: "exact amount needed",
val: toSatoshi(5),
unspents: []*ListUnspentResult{{
TxID: txHash.String(),
Address: addr.String(),
Amount: toBTC(requiredVal(toSatoshi(5), expectedFees(1))),
Confirmations: 1,
Vout: 0,
ScriptPubKey: pkScript,
SafePtr: boolPtr(true),
Spendable: true,
}},
expectedInputs: []*outPoint{
{txHash: txHash,
vout: 0},
},
expectSentVal: expectedSentVal(toSatoshi(5), expectedFees(1)),
expectChange: 0,
},
}
node.getTransactionErr = nil

// good again
_, err = sender(addr.String(), toSatoshi(fee))
if err != nil {
t.Fatalf("Send error afterwards: %v", err)
for _, test := range tests {
node.listUnspent = test.unspents
wallet.bondReservesEnforced = test.bondReservesEnforced

_, err := sender(addr.String(), test.val)
if test.expectErr {
if err == nil {
t.Fatalf("%s: no error for expected error", test.name)
}
continue
}
if err != nil {
t.Fatalf("%s: unexpected error: %v", test.name, err)
}

tx := node.sentRawTx
if len(test.expectedInputs) != len(tx.TxIn) {
t.Fatalf("expected %d inputs, got %d", len(test.expectedInputs), len(tx.TxIn))
}

for i, input := range tx.TxIn {
if input.PreviousOutPoint.Hash != test.expectedInputs[i].txHash ||
input.PreviousOutPoint.Index != test.expectedInputs[i].vout {
t.Fatalf("expected input %d to be %v, got %v", i, test.expectedInputs[i], input.PreviousOutPoint)
}
}

if test.expectChange > 0 && len(tx.TxOut) != 2 {
t.Fatalf("expected 2 outputs, got %d", len(tx.TxOut))
}
if test.expectChange == 0 && len(tx.TxOut) != 1 {
t.Fatalf("expected 2 outputs, got %d", len(tx.TxOut))
}

if tx.TxOut[0].Value != int64(test.expectSentVal) {
t.Fatalf("expected sent value to be %d, got %d", test.expectSentVal, tx.TxOut[0].Value)
}

if test.expectChange > 0 && tx.TxOut[1].Value != int64(test.expectChange) {
t.Fatalf("expected change value to be %d, got %d", test.expectChange, tx.TxOut[1].Value)
}
}
}

Expand Down
Loading