Skip to content

Commit 0d20c42

Browse files
committed
Merge #16421: Conservatively accept RBF bumps bumping one tx at the package limits
5ce822e Conservatively accept RBF bumps bumping one tx at the package limits (Matt Corallo) Pull request description: Based on #15681, this adds support for some simple cases of RBF inside of large packages. Issue pointed out by sdaftuar in #15681, and this fix (or a broader one) is required ot make #15681 fully useful. Accept RBF bumps of single transactions (ie which evict exactly one transaction) even when that transaction is a member of a package which is currently at the package limit iff the new transaction does not add any additional mempool dependencies from the original. This could be made a bit looser in the future and still be safe, but for now this fixes the case that a transaction which was accepted by the carve-out rule will not be directly RBF'able ACKs for top commit: instagibbs: re-ACK bitcoin/bitcoin@5ce822e ajtowns: ACK 5ce822e ; GetSizeWithDescendants is only change and makes sense sipa: Code review ACK 5ce822e. I haven't thought hard about the effect on potential DoS issues this policy change may have. Tree-SHA512: 1cee3bc57393940a30206679eb60c3ec8cb4f4825d27d40d1f062c86bd22542dd5944fa5567601c74c8d9fd425333ed3e686195170925cfc68777e861844bd55
2 parents 46494b0 + 5ce822e commit 0d20c42

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

src/validation.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,17 +615,55 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
615615
REJECT_HIGHFEE, "absurdly-high-fee",
616616
strprintf("%d > %d", nFees, nAbsurdFee));
617617

618+
const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
618619
// Calculate in-mempool ancestors, up to a limit.
619620
CTxMemPool::setEntries setAncestors;
620621
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
621622
size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
622623
size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
623624
size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
625+
626+
if (setConflicts.size() == 1) {
627+
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
628+
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
629+
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
630+
// changed mempool entries. Due to both implementation and runtime complexity concerns, this isn't
631+
// very realistic, thus we only ensure a limited set of transactions are RBF'able despite mempool
632+
// conflicts here. Importantly, we need to ensure that some transactions which were accepted using
633+
// the below carve-out are able to be RBF'ed, without impacting the security the carve-out provides
634+
// for off-chain contract systems (see link in the comment below).
635+
//
636+
// Specifically, the subset of RBF transactions which we allow despite chain limits are those which
637+
// conflict directly with exactly one other transaction (but may evict children of said transaction),
638+
// and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies"
639+
// check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is
640+
// amended, we may need to move that check to here instead of removing it wholesale.
641+
//
642+
// Such transactions are clearly not merging any existing packages, so we are only concerned with
643+
// ensuring that (a) no package is growing past the package size (not count) limits and (b) we are
644+
// not allowing something to effectively use the (below) carve-out spot when it shouldn't be allowed
645+
// to.
646+
//
647+
// To check these we first check if we meet the RBF criteria, above, and increment the descendant
648+
// limits by the direct conflict and its descendants (as these are recalculated in
649+
// CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no
650+
// removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as
651+
// the ancestor limits should be the same for both our new transaction and any conflicts).
652+
// We don't bother incrementing nLimitDescendants by the full removal count as that limit never comes
653+
// into force here (as we're only adding a single transaction).
654+
assert(setIterConflicting.size() == 1);
655+
CTxMemPool::txiter conflict = *setIterConflicting.begin();
656+
657+
nLimitDescendants += 1;
658+
nLimitDescendantSize += conflict->GetSizeWithDescendants();
659+
}
660+
624661
std::string errString;
625662
if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
626663
setAncestors.clear();
627664
// If CalculateMemPoolAncestors fails second time, we want the original error string.
628665
std::string dummy_err_string;
666+
// Contracting/payment channels CPFP carve-out:
629667
// If the new transaction is relatively small (up to 40k weight)
630668
// and has at most one ancestor (ie ancestor limit of 2, including
631669
// the new transaction), allow it if its parent has exactly the
@@ -674,7 +712,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
674712
CFeeRate newFeeRate(nModifiedFees, nSize);
675713
std::set<uint256> setConflictsParents;
676714
const int maxDescendantsToVisit = 100;
677-
const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
678715
for (const auto& mi : setIterConflicting) {
679716
// Don't allow the replacement to reduce the feerate of the
680717
// mempool.
@@ -734,6 +771,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
734771
// feerate junk to be mined first. Ideally we'd keep track of
735772
// the ancestor feerates and make the decision based on that,
736773
// but for now requiring all new inputs to be confirmed works.
774+
//
775+
// Note that if you relax this to make RBF a little more useful,
776+
// this may break the CalculateMempoolAncestors RBF relaxation,
777+
// above. See the comment above the first CalculateMempoolAncestors
778+
// call for more info.
737779
if (!setConflictsParents.count(tx.vin[j].prevout.hash))
738780
{
739781
// Rather than check the UTXO set - potentially expensive -

test/functional/mempool_package_onemore.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def chain_transaction(self, node, parent_txids, vouts, value, fee, num_outputs):
3333
outputs = {}
3434
for i in range(num_outputs):
3535
outputs[node.getnewaddress()] = send_value
36-
rawtx = node.createrawtransaction(inputs, outputs)
36+
rawtx = node.createrawtransaction(inputs, outputs, 0, True)
3737
signedtx = node.signrawtransactionwithwallet(rawtx)
3838
txid = node.sendrawtransaction(signedtx['hex'])
3939
fulltx = node.getrawtransaction(txid, 1)
@@ -75,10 +75,16 @@ def run_test(self):
7575
# ...especially if its > 40k weight
7676
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_transaction, self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 350)
7777
# But not if it chains directly off the first transaction
78-
self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
78+
(replacable_txid, replacable_orig_value) = self.chain_transaction(self.nodes[0], [chain[0][0]], [1], chain[0][1], fee, 1)
7979
# and the second chain should work just fine
8080
self.chain_transaction(self.nodes[0], [second_chain], [0], second_chain_value, fee, 1)
8181

82+
# Make sure we can RBF the chain which used our carve-out rule
83+
second_tx_outputs = {self.nodes[0].getrawtransaction(replacable_txid, True)["vout"][0]['scriptPubKey']['addresses'][0]: replacable_orig_value - (Decimal(1) / Decimal(100))}
84+
second_tx = self.nodes[0].createrawtransaction([{'txid': chain[0][0], 'vout': 1}], second_tx_outputs)
85+
signed_second_tx = self.nodes[0].signrawtransactionwithwallet(second_tx)
86+
self.nodes[0].sendrawtransaction(signed_second_tx['hex'])
87+
8288
# Finally, check that we added two transactions
8389
assert_equal(len(self.nodes[0].getrawmempool(True)), MAX_ANCESTORS + 3)
8490

0 commit comments

Comments
 (0)