Skip to content

Commit 5ce822e

Browse files
committed
Conservatively accept RBF bumps bumping one tx at the package limits
Accept RBF bumps of single transactions (ie which conflict with 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.
1 parent 68da549 commit 5ce822e

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
@@ -609,17 +609,55 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
609609
REJECT_HIGHFEE, "absurdly-high-fee",
610610
strprintf("%d > %d", nFees, nAbsurdFee));
611611

612+
const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
612613
// Calculate in-mempool ancestors, up to a limit.
613614
CTxMemPool::setEntries setAncestors;
614615
size_t nLimitAncestors = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
615616
size_t nLimitAncestorSize = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT)*1000;
616617
size_t nLimitDescendants = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
617618
size_t nLimitDescendantSize = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT)*1000;
619+
620+
if (setConflicts.size() == 1) {
621+
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
622+
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
623+
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
624+
// changed mempool entries. Due to both implementation and runtime complexity concerns, this isn't
625+
// very realistic, thus we only ensure a limited set of transactions are RBF'able despite mempool
626+
// conflicts here. Importantly, we need to ensure that some transactions which were accepted using
627+
// the below carve-out are able to be RBF'ed, without impacting the security the carve-out provides
628+
// for off-chain contract systems (see link in the comment below).
629+
//
630+
// Specifically, the subset of RBF transactions which we allow despite chain limits are those which
631+
// conflict directly with exactly one other transaction (but may evict children of said transaction),
632+
// and which are not adding any new mempool dependencies. Note that the "no new mempool dependencies"
633+
// check is accomplished later, so we don't bother doing anything about it here, but if BIP 125 is
634+
// amended, we may need to move that check to here instead of removing it wholesale.
635+
//
636+
// Such transactions are clearly not merging any existing packages, so we are only concerned with
637+
// ensuring that (a) no package is growing past the package size (not count) limits and (b) we are
638+
// not allowing something to effectively use the (below) carve-out spot when it shouldn't be allowed
639+
// to.
640+
//
641+
// To check these we first check if we meet the RBF criteria, above, and increment the descendant
642+
// limits by the direct conflict and its descendants (as these are recalculated in
643+
// CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no
644+
// removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as
645+
// the ancestor limits should be the same for both our new transaction and any conflicts).
646+
// We don't bother incrementing nLimitDescendants by the full removal count as that limit never comes
647+
// into force here (as we're only adding a single transaction).
648+
assert(setIterConflicting.size() == 1);
649+
CTxMemPool::txiter conflict = *setIterConflicting.begin();
650+
651+
nLimitDescendants += 1;
652+
nLimitDescendantSize += conflict->GetSizeWithDescendants();
653+
}
654+
618655
std::string errString;
619656
if (!pool.CalculateMemPoolAncestors(entry, setAncestors, nLimitAncestors, nLimitAncestorSize, nLimitDescendants, nLimitDescendantSize, errString)) {
620657
setAncestors.clear();
621658
// If CalculateMemPoolAncestors fails second time, we want the original error string.
622659
std::string dummy_err_string;
660+
// Contracting/payment channels CPFP carve-out:
623661
// If the new transaction is relatively small (up to 40k weight)
624662
// and has at most one ancestor (ie ancestor limit of 2, including
625663
// the new transaction), allow it if its parent has exactly the
@@ -668,7 +706,6 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
668706
CFeeRate newFeeRate(nModifiedFees, nSize);
669707
std::set<uint256> setConflictsParents;
670708
const int maxDescendantsToVisit = 100;
671-
const CTxMemPool::setEntries setIterConflicting = pool.GetIterSet(setConflicts);
672709
for (const auto& mi : setIterConflicting) {
673710
// Don't allow the replacement to reduce the feerate of the
674711
// mempool.
@@ -728,6 +765,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
728765
// feerate junk to be mined first. Ideally we'd keep track of
729766
// the ancestor feerates and make the decision based on that,
730767
// but for now requiring all new inputs to be confirmed works.
768+
//
769+
// Note that if you relax this to make RBF a little more useful,
770+
// this may break the CalculateMempoolAncestors RBF relaxation,
771+
// above. See the comment above the first CalculateMempoolAncestors
772+
// call for more info.
731773
if (!setConflictsParents.count(tx.vin[j].prevout.hash))
732774
{
733775
// 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)