Support manually overriding TRUC policy checks

This commit is contained in:
Luke Dashjr 2024-05-31 17:27:50 +00:00
parent 1ea5e9fbd7
commit b1eafd1dae
4 changed files with 22 additions and 18 deletions

View File

@ -57,6 +57,7 @@ struct ParentInfo {
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const Package& package,
const CTxMemPool::setEntries& mempool_ancestors)
{
@ -69,13 +70,13 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
// Now we have all ancestors, so we can start checking v3 rules.
if (ptx->nVersion == 3) {
// SingleV3Checks should have checked this already.
if (!Assume(vsize <= V3_MAX_VSIZE)) {
if (vsize > V3_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "vsize-toobig")) {
out_reason = reason_prefix + "vsize-toobig";
return strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_MAX_VSIZE);
}
if (mempool_ancestors.size() + in_package_parents.size() + 1 > V3_ANCESTOR_LIMIT) {
if (mempool_ancestors.size() + in_package_parents.size() + 1 > V3_ANCESTOR_LIMIT && !ignore_rejects.count(reason_prefix + "ancestors-toomany")) {
out_reason = reason_prefix + "ancestors-toomany";
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
@ -84,7 +85,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0};
if (has_parent) {
// A v3 child cannot be too large.
if (vsize > V3_CHILD_MAX_VSIZE) {
if (vsize > V3_CHILD_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "child-toobig")) {
out_reason = reason_prefix + "child-toobig";
return strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
@ -111,7 +112,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
}();
// If there is a parent, it must have the right version.
if (parent_info.m_version != 3) {
if (parent_info.m_version != 3 && !ignore_rejects.count(reason_prefix + "spends-nontruc")) {
out_reason = reason_prefix + "spends-nontruc";
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
@ -126,7 +127,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
// Fail if we find another tx with the same parent. We don't check whether the
// sibling is to-be-replaced (done in SingleV3Checks) because these transactions
// are within the same package.
if (input.prevout.hash == parent_info.m_txid) {
if (input.prevout.hash == parent_info.m_txid && !ignore_rejects.count(reason_prefix + "sibling-known")) {
out_reason = reason_prefix + "sibling-known";
return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
parent_info.m_txid.ToString(),
@ -134,7 +135,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
}
// This tx can't have both a parent and an in-package child.
if (input.prevout.hash == ptx->GetHash()) {
if (input.prevout.hash == ptx->GetHash() && !ignore_rejects.count(reason_prefix + "parent-and-child-both")) {
out_reason = reason_prefix + "parent-and-child-both";
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString());
@ -154,7 +155,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
} else {
// Non-v3 transactions cannot have v3 parents.
for (auto it : mempool_ancestors) {
if (it->GetTx().nVersion == 3) {
if (it->GetTx().nVersion == 3 && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
@ -162,7 +163,7 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
}
}
for (const auto& index: in_package_parents) {
if (package.at(index)->nVersion == 3) {
if (package.at(index)->nVersion == 3 && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(),
@ -177,19 +178,20 @@ std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t v
std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTransactionRef& ptx,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts,
int64_t vsize)
{
// Check v3 and non-v3 inheritance.
for (const auto& entry : mempool_ancestors) {
if (ptx->nVersion != 3 && entry->GetTx().nVersion == 3) {
if (ptx->nVersion != 3 && entry->GetTx().nVersion == 3 && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return std::make_pair(strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
nullptr);
} else if (ptx->nVersion == 3 && entry->GetTx().nVersion != 3) {
} else if (ptx->nVersion == 3 && entry->GetTx().nVersion != 3 && !ignore_rejects.count(reason_prefix + "spends-nontruc")) {
out_reason = reason_prefix + "spends-nontruc";
return std::make_pair(strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
@ -205,7 +207,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
// The rest of the rules only apply to transactions with nVersion=3.
if (ptx->nVersion != 3) return std::nullopt;
if (vsize > V3_MAX_VSIZE) {
if (vsize > V3_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "vsize-toobig")) {
out_reason = reason_prefix + "vsize-toobig";
return std::make_pair(strprintf("v3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_MAX_VSIZE),
@ -213,7 +215,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
}
// Check that V3_ANCESTOR_LIMIT would not be violated.
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT) {
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT && !ignore_rejects.count(reason_prefix + "ancestors-toomany")) {
out_reason = reason_prefix + "ancestors-toomany";
return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()),
@ -223,7 +225,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
// Remaining checks only pertain to transactions with unconfirmed ancestors.
if (mempool_ancestors.size() > 0) {
// If this transaction spends V3 parents, it cannot be too large.
if (vsize > V3_CHILD_MAX_VSIZE) {
if (vsize > V3_CHILD_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "child-toobig")) {
out_reason = reason_prefix + "child-toobig";
return std::make_pair(strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, V3_CHILD_MAX_VSIZE),
@ -242,7 +244,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
const bool child_will_be_replaced = !children.empty() &&
std::any_of(children.cbegin(), children.cend(),
[&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;});
if (parent_entry->GetCountWithDescendants() + 1 > V3_DESCENDANT_LIMIT && !child_will_be_replaced) {
if (parent_entry->GetCountWithDescendants() + 1 > V3_DESCENDANT_LIMIT && (!child_will_be_replaced) && !ignore_rejects.count(reason_prefix + "descendants-toomany")) {
// Allow sibling eviction for v3 transaction: if another child already exists, even if
// we don't conflict inputs with it, consider evicting it under RBF rules. We rely on v3 rules
// only permitting 1 descendant, as otherwise we would need to have logic for deciding

View File

@ -54,6 +54,7 @@ static_assert(V3_MAX_VSIZE + V3_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_LIMIT
*/
std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTransactionRef& ptx,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts,
int64_t vsize);
@ -81,6 +82,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTra
* */
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const Package& package,
const CTxMemPool::setEntries& mempool_ancestors);

View File

@ -22,13 +22,13 @@ BOOST_AUTO_TEST_SUITE(txvalidation_tests)
std::optional<std::pair<std::string, CTransactionRef>> SingleV3Checks(const CTransactionRef& ptx, const CTxMemPool::setEntries& mempool_ancestors, const std::set<Txid>& direct_conflicts, int64_t vsize)
{
std::string dummy;
return SingleV3Checks(ptx, dummy, dummy, mempool_ancestors, direct_conflicts, vsize);
return SingleV3Checks(ptx, dummy, dummy, empty_ignore_rejects, mempool_ancestors, direct_conflicts, vsize);
}
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize, const Package& package, const CTxMemPool::setEntries& mempool_ancestors)
{
std::string dummy;
return PackageV3Checks(ptx, vsize, dummy, dummy, package, mempool_ancestors);
return PackageV3Checks(ptx, vsize, dummy, dummy, empty_ignore_rejects, package, mempool_ancestors);
}
/**

View File

@ -985,7 +985,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Even though just checking direct mempool parents for inheritance would be sufficient, we
// check using the full ancestor set here because it's more convenient to use what we have
// already calculated.
if (const auto err{SingleV3Checks(ws.m_ptx, "truc-", reason, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
if (const auto err{SingleV3Checks(ws.m_ptx, "truc-", reason, ignore_rejects, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
// Disabled within package validation.
if (err->second != nullptr && args.m_allow_replacement) {
// Potential sibling eviction. Add the sibling to our list of mempool conflicts to be
@ -1376,7 +1376,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// Run the v3 checks on the package.
std::string reason;
for (Workspace& ws : workspaces) {
if (auto err{PackageV3Checks(ws.m_ptx, ws.m_vsize, "truc-", reason, txns, ws.m_ancestors)}) {
if (auto err{PackageV3Checks(ws.m_ptx, ws.m_vsize, "truc-", reason, args.m_ignore_rejects, txns, ws.m_ancestors)}) {
package_state.Invalid(PackageValidationResult::PCKG_POLICY, reason, err.value());
return PackageMempoolAcceptResult(package_state, {});
}