mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 23:09:44 -04:00
cpfp carveout is excluded in packages
The behavior is not new, but this rule exits earlier than before. Previously, a carve out could have been granted in PreChecks() but then nullified in PackageMempoolChecks() when CheckPackageLimits() is called with the default limits.
This commit is contained in:
parent
69f7ab05ba
commit
cbbfe719b2
3 changed files with 41 additions and 14 deletions
|
@ -48,8 +48,13 @@ The following rules are enforced for all packages:
|
||||||
heavily connected, i.e. some transaction in the package is the ancestor or descendant of all
|
heavily connected, i.e. some transaction in the package is the ancestor or descendant of all
|
||||||
the other transactions.
|
the other transactions.
|
||||||
|
|
||||||
The following rules are only enforced for packages to be submitted to the mempool (not enforced for
|
* [CPFP Carve Out](./mempool-limits.md#CPFP-Carve-Out) is disabled in packaged contexts. (#21800)
|
||||||
test accepts):
|
|
||||||
|
- *Rationale*: This carve out cannot be accurately applied when there are multiple transactions'
|
||||||
|
ancestors and descendants being considered at the same time.
|
||||||
|
|
||||||
|
The following rules are only enforced for packages to be submitted to the mempool (not
|
||||||
|
enforced for test accepts):
|
||||||
|
|
||||||
* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at
|
* Packages must be child-with-unconfirmed-parents packages. This also means packages must contain at
|
||||||
least 2 transactions. (#22674)
|
least 2 transactions. (#22674)
|
||||||
|
|
|
@ -478,6 +478,9 @@ public:
|
||||||
*/
|
*/
|
||||||
const std::optional<CFeeRate> m_client_maxfeerate;
|
const std::optional<CFeeRate> m_client_maxfeerate;
|
||||||
|
|
||||||
|
/** Whether CPFP carveout and RBF carveout are granted. */
|
||||||
|
const bool m_allow_carveouts;
|
||||||
|
|
||||||
/** Parameters for single transaction mempool validation. */
|
/** Parameters for single transaction mempool validation. */
|
||||||
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
|
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
|
||||||
bool bypass_limits, std::vector<COutPoint>& coins_to_uncache,
|
bool bypass_limits, std::vector<COutPoint>& coins_to_uncache,
|
||||||
|
@ -492,6 +495,7 @@ public:
|
||||||
/* m_package_submission */ false,
|
/* m_package_submission */ false,
|
||||||
/* m_package_feerates */ false,
|
/* m_package_feerates */ false,
|
||||||
/* m_client_maxfeerate */ {}, // checked by caller
|
/* m_client_maxfeerate */ {}, // checked by caller
|
||||||
|
/* m_allow_carveouts */ true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,6 +512,7 @@ public:
|
||||||
/* m_package_submission */ false, // not submitting to mempool
|
/* m_package_submission */ false, // not submitting to mempool
|
||||||
/* m_package_feerates */ false,
|
/* m_package_feerates */ false,
|
||||||
/* m_client_maxfeerate */ {}, // checked by caller
|
/* m_client_maxfeerate */ {}, // checked by caller
|
||||||
|
/* m_allow_carveouts */ false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,6 +529,7 @@ public:
|
||||||
/* m_package_submission */ true,
|
/* m_package_submission */ true,
|
||||||
/* m_package_feerates */ true,
|
/* m_package_feerates */ true,
|
||||||
/* m_client_maxfeerate */ client_maxfeerate,
|
/* m_client_maxfeerate */ client_maxfeerate,
|
||||||
|
/* m_allow_carveouts */ false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -539,6 +545,7 @@ public:
|
||||||
/* m_package_submission */ true, // do not LimitMempoolSize in Finalize()
|
/* m_package_submission */ true, // do not LimitMempoolSize in Finalize()
|
||||||
/* m_package_feerates */ false, // only 1 transaction
|
/* m_package_feerates */ false, // only 1 transaction
|
||||||
/* m_client_maxfeerate */ package_args.m_client_maxfeerate,
|
/* m_client_maxfeerate */ package_args.m_client_maxfeerate,
|
||||||
|
/* m_allow_carveouts */ false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,7 +561,8 @@ public:
|
||||||
bool allow_sibling_eviction,
|
bool allow_sibling_eviction,
|
||||||
bool package_submission,
|
bool package_submission,
|
||||||
bool package_feerates,
|
bool package_feerates,
|
||||||
std::optional<CFeeRate> client_maxfeerate)
|
std::optional<CFeeRate> client_maxfeerate,
|
||||||
|
bool allow_carveouts)
|
||||||
: m_chainparams{chainparams},
|
: m_chainparams{chainparams},
|
||||||
m_accept_time{accept_time},
|
m_accept_time{accept_time},
|
||||||
m_bypass_limits{bypass_limits},
|
m_bypass_limits{bypass_limits},
|
||||||
|
@ -564,7 +572,8 @@ public:
|
||||||
m_allow_sibling_eviction{allow_sibling_eviction},
|
m_allow_sibling_eviction{allow_sibling_eviction},
|
||||||
m_package_submission{package_submission},
|
m_package_submission{package_submission},
|
||||||
m_package_feerates{package_feerates},
|
m_package_feerates{package_feerates},
|
||||||
m_client_maxfeerate{client_maxfeerate}
|
m_client_maxfeerate{client_maxfeerate},
|
||||||
|
m_allow_carveouts{allow_carveouts}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -917,7 +926,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts.limits;
|
CTxMemPool::Limits maybe_rbf_limits = m_pool.m_opts.limits;
|
||||||
|
|
||||||
// Calculate in-mempool ancestors, up to a limit.
|
// Calculate in-mempool ancestors, up to a limit.
|
||||||
if (ws.m_conflicts.size() == 1) {
|
if (ws.m_conflicts.size() == 1 && args.m_allow_carveouts) {
|
||||||
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
|
// In general, when we receive an RBF transaction with mempool conflicts, we want to know whether we
|
||||||
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
|
// would meet the chain limits after the conflicts have been removed. However, there isn't a practical
|
||||||
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
|
// way to do this short of calculating the ancestor and descendant sets with an overlay cache of
|
||||||
|
@ -956,6 +965,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
ws.m_ancestors = std::move(*ancestors);
|
ws.m_ancestors = std::move(*ancestors);
|
||||||
} else {
|
} else {
|
||||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||||
|
const auto error_message{util::ErrorString(ancestors).original};
|
||||||
|
|
||||||
|
// Carve-out is not allowed in this context; fail
|
||||||
|
if (!args.m_allow_carveouts) {
|
||||||
|
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||||
|
}
|
||||||
|
|
||||||
// Contracting/payment channels CPFP carve-out:
|
// Contracting/payment channels CPFP carve-out:
|
||||||
// If the new transaction is relatively small (up to 40k weight)
|
// If the new transaction is relatively small (up to 40k weight)
|
||||||
// and has at most one ancestor (ie ancestor limit of 2, including
|
// and has at most one ancestor (ie ancestor limit of 2, including
|
||||||
|
@ -974,7 +990,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||||
.descendant_count = maybe_rbf_limits.descendant_count + 1,
|
.descendant_count = maybe_rbf_limits.descendant_count + 1,
|
||||||
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
|
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
|
||||||
};
|
};
|
||||||
const auto error_message{util::ErrorString(ancestors).original};
|
|
||||||
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) {
|
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) {
|
||||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
|
||||||
}
|
}
|
||||||
|
@ -1431,9 +1446,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
|
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
|
||||||
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
|
// because it's unnecessary.
|
||||||
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
|
|
||||||
std::string err_string;
|
|
||||||
if (txns.size() > 1 && !PackageMempoolChecks(txns, m_total_vsize, package_state)) {
|
if (txns.size() > 1 && !PackageMempoolChecks(txns, m_total_vsize, package_state)) {
|
||||||
return PackageMempoolAcceptResult(package_state, std::move(results));
|
return PackageMempoolAcceptResult(package_state, std::move(results));
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,21 +47,30 @@ class MempoolPackagesTest(BitcoinTestFramework):
|
||||||
|
|
||||||
# Adding one more transaction on to the chain should fail.
|
# Adding one more transaction on to the chain should fail.
|
||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
|
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many unconfirmed ancestors [limit: 25]", self.chain_tx, [utxo])
|
||||||
# ...even if it chains on from some point in the middle of the chain.
|
# ... or if it chains on from some point in the middle of the chain.
|
||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]])
|
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[2]])
|
||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]])
|
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[1]])
|
||||||
# ...even if it chains on to two parent transactions with one in the chain.
|
# ...even if it chains on to two parent transactions with one in the chain.
|
||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain])
|
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0], second_chain])
|
||||||
# ...especially if its > 40k weight
|
# ...especially if its > 40k weight
|
||||||
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350)
|
assert_raises_rpc_error(-26, "too-long-mempool-chain, too many descendants", self.chain_tx, [chain[0]], num_outputs=350)
|
||||||
|
# ...even if it's submitted with other transactions
|
||||||
|
replaceable_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[chain[0]])
|
||||||
|
txns = [replaceable_tx["tx"], self.wallet.create_self_transfer_multi(utxos_to_spend=replaceable_tx["new_utxos"])["tx"]]
|
||||||
|
txns_hex = [tx.serialize().hex() for tx in txns]
|
||||||
|
assert_equal(self.nodes[0].testmempoolaccept(txns_hex)[0]["reject-reason"], "too-long-mempool-chain")
|
||||||
|
pkg_result = self.nodes[0].submitpackage(txns_hex)
|
||||||
|
assert "too-long-mempool-chain" in pkg_result["tx-results"][txns[0].getwtxid()]["error"]
|
||||||
|
assert_equal(pkg_result["tx-results"][txns[1].getwtxid()]["error"], "bad-txns-inputs-missingorspent")
|
||||||
# But not if it chains directly off the first transaction
|
# But not if it chains directly off the first transaction
|
||||||
replacable_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[chain[0]])['tx']
|
self.nodes[0].sendrawtransaction(replaceable_tx["hex"])
|
||||||
# and the second chain should work just fine
|
# and the second chain should work just fine
|
||||||
self.chain_tx([second_chain])
|
self.chain_tx([second_chain])
|
||||||
|
|
||||||
# Make sure we can RBF the chain which used our carve-out rule
|
# Ensure an individual transaction with single direct conflict can RBF the chain which used our carve-out rule
|
||||||
replacable_tx.vout[0].nValue -= 1000000
|
replacement_tx = replaceable_tx["tx"]
|
||||||
self.nodes[0].sendrawtransaction(replacable_tx.serialize().hex())
|
replacement_tx.vout[0].nValue -= 1000000
|
||||||
|
self.nodes[0].sendrawtransaction(replacement_tx.serialize().hex())
|
||||||
|
|
||||||
# Finally, check that we added two transactions
|
# Finally, check that we added two transactions
|
||||||
assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 3)
|
assert_equal(len(self.nodes[0].getrawmempool()), DEFAULT_ANCESTOR_LIMIT + 3)
|
||||||
|
|
Loading…
Add table
Reference in a new issue