Skip to content

Commit

Permalink
mempool: add additional test case for inherited RBF replacement
Browse files Browse the repository at this point in the history
In this commit, we add an additional test case for inherited RBF
replacement. This test case asserts that if a parent is marked as being
replaceable, but the child isn't, then the child can still be replaced
as according to BIP 125 it shoudl _inhreit_ the replaceability of its
parent.

The addition of this test case was prompted by the recently discovered
Bitcoin Core "CVE" [1]. It turns out that bitcoind doesn't properly
implement BIP 125. Namely it fails to allow a child to "inherit"
replaceability if its parent is also replaceable. Our implementation
makes this trait rather explicit due to its recursive implementation.
Kudos to the original implementer @wpaulino for getting this correct.

[1]: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2021-May/018893.html.
  • Loading branch information
Roasbeef authored and jcvernaleo committed May 13, 2021
1 parent 7b6c2b3 commit ee5896b
Showing 1 changed file with 41 additions and 0 deletions.
41 changes: 41 additions & 0 deletions mempool/mempool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1749,6 +1749,47 @@ func TestRBF(t *testing.T) {
},
err: "",
},
{
// A transaction that doesn't signal replacement, can
// be replaced if the parent signals replacement.
name: "inherited replacement",
setup: func(ctx *testContext) (*btcutil.Tx, []*btcutil.Tx) {
coinbase := ctx.addCoinbaseTx(1)

// Create an initial parent transaction that
// marks replacement, we won't be replacing
// this directly however.
coinbaseOut := txOutToSpendableOut(coinbase, 0)
outs := []spendableOutput{coinbaseOut}
parent := ctx.addSignedTx(
outs, 1, defaultFee, true, false,
)

// Now create a transaction that spends that
// parent transaction, which is marked as NOT
// being RBF-able.
parentOut := txOutToSpendableOut(parent, 0)
parentOuts := []spendableOutput{parentOut}
childNoReplace := ctx.addSignedTx(
parentOuts, 1, defaultFee, false, false,
)

// Now we'll create another transaction that
// replaces the *child* only. This should work
// as the parent has been marked for RBF, even
// though the child hasn't.
respendOuts := []spendableOutput{parentOut}
childReplace, err := ctx.harness.CreateSignedTx(
respendOuts, 1, defaultFee*3, false,
)
if err != nil {
ctx.t.Fatalf("unable to create child tx: %v", err)
}

return childReplace, []*btcutil.Tx{childNoReplace}
},
err: "",
},
}

for _, testCase := range testCases {
Expand Down

0 comments on commit ee5896b

Please sign in to comment.