From 2e9452916e9a308de168b831443c3b43e398a97c Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Thu, 19 Sep 2019 14:59:07 +0200 Subject: [PATCH] lnwallet/interface_test: add RBF test cases to testPublishTransaction Checks that we get ErrDoubleSpend as expected when publishing a conflicting mempool transaction with the same fee as the existing one, and that we can publish a replacement with a higher fee successfully. --- lnwallet/interface_test.go | 206 ++++++++++++++++++++----------------- 1 file changed, 114 insertions(+), 92 deletions(-) diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 8377d95b..deda71e7 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -22,6 +22,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/integration/rpctest" + "github.com/btcsuite/btcd/mempool" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -1472,8 +1473,8 @@ func mineAndAssert(r *rpctest.Harness, tx *wire.MsgTx) error { // txFromOutput takes a tx paying to fromPubKey, and creates a new tx that // spends the output from this tx, to an address derived from payToPubKey. func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, - payToPubKey *btcec.PublicKey, txFee btcutil.Amount) ( - *wire.MsgTx, error) { + payToPubKey *btcec.PublicKey, txFee btcutil.Amount, + rbf bool) (*wire.MsgTx, error) { // Generate the script we want to spend from. keyScript, err := scriptFromKey(fromPubKey) @@ -1494,13 +1495,20 @@ func txFromOutput(tx *wire.MsgTx, signer input.Signer, fromPubKey, // referenced output. tx1 := wire.NewMsgTx(2) + // If we want to create a tx that signals replacement, set its + // sequence number to the max one that signals replacement. + // Otherwise we just use the standard max sequence. + sequence := wire.MaxTxInSequenceNum + if rbf { + sequence = mempool.MaxRBFSequence + } + tx1.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: tx.TxHash(), Index: outputIndex, }, - // We don't support RBF, so set sequence to max. - Sequence: wire.MaxTxInSequenceNum, + Sequence: sequence, }) // Create a script to pay to. @@ -1585,7 +1593,7 @@ func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey, // Create a new unconfirmed tx that spends this output. txFee := btcutil.Amount(0.1 * btcutil.SatoshiPerBitcoin) tx1, err := txFromOutput( - tx, alice.Cfg.Signer, pubKey, pubKey, txFee, + tx, alice.Cfg.Signer, pubKey, pubKey, txFee, rbf, ) if err != nil { t.Fatal(err) @@ -1657,87 +1665,109 @@ func testPublishTransaction(r *rpctest.Harness, t.Fatalf("unable to publish: %v", err) } - // Now we'll try to double spend an output with a different - // transaction. Create a new tx and publish it. This is the output - // we'll try to double spend. - tx3 := newTx(t, r, keyDesc.PubKey, alice, false) - if err := alice.PublishTransaction(tx3); err != nil { - t.Fatalf("unable to publish: %v", err) - } - - // Mine the transaction. - if err := mineAndAssert(r, tx3); err != nil { - t.Fatalf("unable to mine tx: %v", err) - } - - // Now we create a transaction that spends the output from the tx just - // mined. - txFee := btcutil.Amount(0.05 * btcutil.SatoshiPerBitcoin) - tx4, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, keyDesc.PubKey, - txFee, - ) - if err != nil { - t.Fatal(err) - } - - // This should be accepted into the mempool. - if err := alice.PublishTransaction(tx4); err != nil { - t.Fatalf("unable to publish: %v", err) - } - - txid4 := tx4.TxHash() - err = waitForMempoolTx(r, &txid4) - if err != nil { - t.Fatalf("tx not relayed to miner: %v", err) - } - - // Create a new key we'll pay to, to ensure we create a unique - // transaction. - keyDesc2, err := alice.DeriveNextKey( - keychain.KeyFamilyMultiSig, - ) - if err != nil { - t.Fatalf("unable to obtain public key: %v", err) - } - - // Create a new transaction that spends the output from tx3, and that - // pays to a different address. We expect this to be rejected because - // it is a double spend. - tx5, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, keyDesc2.PubKey, - txFee, - ) - if err != nil { - t.Fatal(err) - } - - err = alice.PublishTransaction(tx5) - if err != lnwallet.ErrDoubleSpend { - t.Fatalf("expected ErrDoubleSpend, got: %v", err) - } - - // Create another transaction that spends the same output, but has a - // higher fee. We expect also this tx to be rejected, since the - // sequence number of tx3 is set to Max, indicating it is not - // replacable. - pubKey3, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig) - if err != nil { - t.Fatalf("unable to obtain public key: %v", err) - } - tx6, err := txFromOutput( - tx3, alice.Cfg.Signer, keyDesc.PubKey, - pubKey3.PubKey, 2*txFee, + // We'll do the next mempool check on both RBF and non-RBF enabled + // transactions. + var ( + txFee = btcutil.Amount(0.05 * btcutil.SatoshiPerBitcoin) + tx3, tx3Spend *wire.MsgTx ) - if err != nil { - t.Fatal(err) - } + for _, rbf := range []bool{false, true} { + // Now we'll try to double spend an output with a different + // transaction. Create a new tx and publish it. This is the + // output we'll try to double spend. + tx3 = newTx(t, r, keyDesc.PubKey, alice, false) + if err := alice.PublishTransaction(tx3); err != nil { + t.Fatalf("unable to publish: %v", err) + } - // Expect rejection. - err = alice.PublishTransaction(tx6) - if err != lnwallet.ErrDoubleSpend { - t.Fatalf("expected ErrDoubleSpend, got: %v", err) + // Mine the transaction. + if err := mineAndAssert(r, tx3); err != nil { + t.Fatalf("unable to mine tx: %v", err) + } + + // Now we create a transaction that spends the output from the + // tx just mined. + tx4, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + keyDesc.PubKey, txFee, rbf, + ) + if err != nil { + t.Fatal(err) + } + + // This should be accepted into the mempool. + if err := alice.PublishTransaction(tx4); err != nil { + t.Fatalf("unable to publish: %v", err) + } + + // Keep track of the last successfully published tx to spend + // tx3. + tx3Spend = tx4 + + txid4 := tx4.TxHash() + err = waitForMempoolTx(r, &txid4) + if err != nil { + t.Fatalf("tx not relayed to miner: %v", err) + } + + // Create a new key we'll pay to, to ensure we create a unique + // transaction. + keyDesc2, err := alice.DeriveNextKey( + keychain.KeyFamilyMultiSig, + ) + if err != nil { + t.Fatalf("unable to obtain public key: %v", err) + } + + // Create a new transaction that spends the output from tx3, + // and that pays to a different address. We expect this to be + // rejected because it is a double spend. + tx5, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + keyDesc2.PubKey, txFee, rbf, + ) + if err != nil { + t.Fatal(err) + } + + err = alice.PublishTransaction(tx5) + if err != lnwallet.ErrDoubleSpend { + t.Fatalf("expected ErrDoubleSpend, got: %v", err) + } + + // Create another transaction that spends the same output, but + // has a higher fee. We expect also this tx to be rejected for + // non-RBF enabled transactions, while it should succeed + // otherwise. + pubKey3, err := alice.DeriveNextKey(keychain.KeyFamilyMultiSig) + if err != nil { + t.Fatalf("unable to obtain public key: %v", err) + } + tx6, err := txFromOutput( + tx3, alice.Cfg.Signer, keyDesc.PubKey, + pubKey3.PubKey, 2*txFee, rbf, + ) + if err != nil { + t.Fatal(err) + } + + // Expect rejection in non-RBF case. + expErr := lnwallet.ErrDoubleSpend + if rbf { + // Expect success in rbf case. + expErr = nil + tx3Spend = tx6 + } + err = alice.PublishTransaction(tx6) + if err != expErr { + t.Fatalf("expected ErrDoubleSpend, got: %v", err) + } + + // Mine the tx spending tx3. + if err := mineAndAssert(r, tx3Spend); err != nil { + t.Fatalf("unable to mine tx: %v", err) + } } // At last we try to spend an output already spent by a confirmed @@ -1747,11 +1777,6 @@ func testPublishTransaction(r *rpctest.Harness, // accept it. Should look into if this is the behavior also for // bitcoind, and update test accordingly. if alice.BackEnd() != "neutrino" { - // Mine the tx spending tx3. - if err := mineAndAssert(r, tx4); err != nil { - t.Fatalf("unable to mine tx: %v", err) - } - // Create another tx spending tx3. pubKey4, err := alice.DeriveNextKey( keychain.KeyFamilyMultiSig, @@ -1761,7 +1786,7 @@ func testPublishTransaction(r *rpctest.Harness, } tx7, err := txFromOutput( tx3, alice.Cfg.Signer, keyDesc.PubKey, - pubKey4.PubKey, txFee, + pubKey4.PubKey, txFee, false, ) if err != nil { @@ -1774,9 +1799,6 @@ func testPublishTransaction(r *rpctest.Harness, t.Fatalf("expected ErrDoubleSpend, got: %v", err) } } - - // TODO(halseth): test replaceable transactions when btcd gets RBF - // support. } func testSignOutputUsingTweaks(r *rpctest.Harness,