mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-09 20:59:38 +02:00
[policy] add v3 policy rules
Co-authored-by: Suhas Daftuar <sdaftuar@gmail.com>
This commit is contained in:
parent
9a29d470fb
commit
eb8d5a2e7d
@ -238,6 +238,7 @@ BITCOIN_CORE_H = \
|
||||
node/validation_cache_args.h \
|
||||
noui.h \
|
||||
outputtype.h \
|
||||
policy/v3_policy.h \
|
||||
policy/feerate.h \
|
||||
policy/fees.h \
|
||||
policy/fees_args.h \
|
||||
@ -439,6 +440,7 @@ libbitcoin_node_a_SOURCES = \
|
||||
node/utxo_snapshot.cpp \
|
||||
node/validation_cache_args.cpp \
|
||||
noui.cpp \
|
||||
policy/v3_policy.cpp \
|
||||
policy/fees.cpp \
|
||||
policy/fees_args.cpp \
|
||||
policy/packages.cpp \
|
||||
@ -694,6 +696,7 @@ libbitcoin_common_a_SOURCES = \
|
||||
netbase.cpp \
|
||||
net_permissions.cpp \
|
||||
outputtype.cpp \
|
||||
policy/v3_policy.cpp \
|
||||
policy/feerate.cpp \
|
||||
policy/policy.cpp \
|
||||
protocol.cpp \
|
||||
@ -953,6 +956,7 @@ libbitcoinkernel_la_SOURCES = \
|
||||
node/blockstorage.cpp \
|
||||
node/chainstate.cpp \
|
||||
node/utxo_snapshot.cpp \
|
||||
policy/v3_policy.cpp \
|
||||
policy/feerate.cpp \
|
||||
policy/packages.cpp \
|
||||
policy/policy.cpp \
|
||||
|
220
src/policy/v3_policy.cpp
Normal file
220
src/policy/v3_policy.cpp
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <policy/v3_policy.h>
|
||||
|
||||
#include <coins.h>
|
||||
#include <consensus/amount.h>
|
||||
#include <logging.h>
|
||||
#include <tinyformat.h>
|
||||
#include <util/check.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
/** Helper for PackageV3Checks: Returns a vector containing the indices of transactions (within
|
||||
* package) that are direct parents of ptx. */
|
||||
std::vector<size_t> FindInPackageParents(const Package& package, const CTransactionRef& ptx)
|
||||
{
|
||||
std::vector<size_t> in_package_parents;
|
||||
|
||||
std::set<Txid> possible_parents;
|
||||
for (auto &input : ptx->vin) {
|
||||
possible_parents.insert(input.prevout.hash);
|
||||
}
|
||||
|
||||
for (size_t i{0}; i < package.size(); ++i) {
|
||||
const auto& tx = package.at(i);
|
||||
// We assume the package is sorted, so that we don't need to continue
|
||||
// looking past the transaction itself.
|
||||
if (&(*tx) == &(*ptx)) break;
|
||||
if (possible_parents.count(tx->GetHash())) {
|
||||
in_package_parents.push_back(i);
|
||||
}
|
||||
}
|
||||
return in_package_parents;
|
||||
}
|
||||
|
||||
/** Helper for PackageV3Checks, storing info for a mempool or package parent. */
|
||||
struct ParentInfo {
|
||||
/** Txid used to identify this parent by prevout */
|
||||
const Txid& m_txid;
|
||||
/** Wtxid used for debug string */
|
||||
const Wtxid& m_wtxid;
|
||||
/** nVersion used to check inheritance of v3 and non-v3 */
|
||||
decltype(CTransaction::nVersion) m_version;
|
||||
/** If parent is in mempool, whether it has any descendants in mempool. */
|
||||
bool m_has_mempool_descendant;
|
||||
|
||||
ParentInfo() = delete;
|
||||
ParentInfo(const Txid& txid, const Wtxid& wtxid, decltype(CTransaction::nVersion) version, bool has_mempool_descendant) :
|
||||
m_txid{txid}, m_wtxid{wtxid}, m_version{version},
|
||||
m_has_mempool_descendant{has_mempool_descendant}
|
||||
{}
|
||||
};
|
||||
|
||||
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
|
||||
const Package& package,
|
||||
const CTxMemPool::setEntries& mempool_ancestors)
|
||||
{
|
||||
// This function is specialized for these limits, and must be reimplemented if they ever change.
|
||||
static_assert(V3_ANCESTOR_LIMIT == 2);
|
||||
static_assert(V3_DESCENDANT_LIMIT == 2);
|
||||
|
||||
const auto in_package_parents{FindInPackageParents(package, ptx)};
|
||||
|
||||
// Now we have all ancestors, so we can start checking v3 rules.
|
||||
if (ptx->nVersion == 3) {
|
||||
if (mempool_ancestors.size() + in_package_parents.size() + 1 > V3_ANCESTOR_LIMIT) {
|
||||
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
|
||||
}
|
||||
|
||||
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) {
|
||||
return 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);
|
||||
}
|
||||
|
||||
const auto parent_info = [&] {
|
||||
if (mempool_ancestors.size() > 0) {
|
||||
// There's a parent in the mempool.
|
||||
auto& mempool_parent = *mempool_ancestors.begin();
|
||||
Assume(mempool_parent->GetCountWithDescendants() == 1);
|
||||
return ParentInfo{mempool_parent->GetTx().GetHash(),
|
||||
mempool_parent->GetTx().GetWitnessHash(),
|
||||
mempool_parent->GetTx().nVersion,
|
||||
/*has_mempool_descendant=*/mempool_parent->GetCountWithDescendants() > 1};
|
||||
} else {
|
||||
// Ancestor must be in the package. Find it.
|
||||
auto& parent_index = in_package_parents.front();
|
||||
auto& package_parent = package.at(parent_index);
|
||||
return ParentInfo{package_parent->GetHash(),
|
||||
package_parent->GetWitnessHash(),
|
||||
package_parent->nVersion,
|
||||
/*has_mempool_descendant=*/false};
|
||||
}
|
||||
}();
|
||||
|
||||
// If there is a parent, it must have the right version.
|
||||
if (parent_info.m_version != 3) {
|
||||
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
|
||||
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
|
||||
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString());
|
||||
}
|
||||
|
||||
for (const auto& package_tx : package) {
|
||||
// Skip same tx.
|
||||
if (&(*package_tx) == &(*ptx)) continue;
|
||||
|
||||
for (auto& input : package_tx->vin) {
|
||||
// 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) {
|
||||
return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
|
||||
parent_info.m_txid.ToString(),
|
||||
parent_info.m_wtxid.ToString());
|
||||
}
|
||||
|
||||
// This tx can't have both a parent and an in-package child.
|
||||
if (input.prevout.hash == ptx->GetHash()) {
|
||||
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It shouldn't be possible to have any mempool siblings at this point. SingleV3Checks
|
||||
// catches mempool siblings. Also, if the package consists of connected transactions,
|
||||
// any tx having a mempool ancestor would mean the package exceeds ancestor limits.
|
||||
if (!Assume(!parent_info.m_has_mempool_descendant)) {
|
||||
return strprintf("tx %u would exceed descendant count limit", parent_info.m_wtxid.ToString());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-v3 transactions cannot have v3 parents.
|
||||
for (auto it : mempool_ancestors) {
|
||||
if (it->GetTx().nVersion == 3) {
|
||||
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
|
||||
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
|
||||
it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString());
|
||||
}
|
||||
}
|
||||
for (const auto& index: in_package_parents) {
|
||||
if (package.at(index)->nVersion == 3) {
|
||||
return strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
|
||||
ptx->GetHash().ToString(),
|
||||
ptx->GetWitnessHash().ToString(),
|
||||
package.at(index)->GetHash().ToString(),
|
||||
package.at(index)->GetWitnessHash().ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> SingleV3Checks(const CTransactionRef& ptx,
|
||||
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) {
|
||||
return 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());
|
||||
} else if (ptx->nVersion == 3 && entry->GetTx().nVersion != 3) {
|
||||
return strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
|
||||
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
|
||||
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// This function is specialized for these limits, and must be reimplemented if they ever change.
|
||||
static_assert(V3_ANCESTOR_LIMIT == 2);
|
||||
static_assert(V3_DESCENDANT_LIMIT == 2);
|
||||
|
||||
// The rest of the rules only apply to transactions with nVersion=3.
|
||||
if (ptx->nVersion != 3) return std::nullopt;
|
||||
|
||||
// Check that V3_ANCESTOR_LIMIT would not be violated, including both in-package and in-mempool.
|
||||
if (mempool_ancestors.size() + 1 > V3_ANCESTOR_LIMIT) {
|
||||
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
return 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);
|
||||
}
|
||||
|
||||
// Check the descendant counts of in-mempool ancestors.
|
||||
const auto& parent_entry = *mempool_ancestors.begin();
|
||||
// If there are any ancestors, this is the only child allowed. The parent cannot have any
|
||||
// other descendants. We handle the possibility of multiple children as that case is
|
||||
// possible through a reorg.
|
||||
const auto& children = parent_entry->GetMemPoolChildrenConst();
|
||||
// Don't double-count a transaction that is going to be replaced. This logic assumes that
|
||||
// any descendant of the V3 transaction is a direct child, which makes sense because a V3
|
||||
// transaction can only have 1 descendant.
|
||||
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) {
|
||||
return strprintf("tx %u (wtxid=%s) would exceed descendant count limit",
|
||||
parent_entry->GetSharedTx()->GetHash().ToString(),
|
||||
parent_entry->GetSharedTx()->GetWitnessHash().ToString());
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
83
src/policy/v3_policy.h
Normal file
83
src/policy/v3_policy.h
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_POLICY_V3_POLICY_H
|
||||
#define BITCOIN_POLICY_V3_POLICY_H
|
||||
|
||||
#include <consensus/amount.h>
|
||||
#include <policy/packages.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <txmempool.h>
|
||||
#include <util/result.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
// This module enforces rules for transactions with nVersion=3 ("v3 transactions") which help make
|
||||
// RBF abilities more robust.
|
||||
|
||||
// v3 only allows 1 parent and 1 child when unconfirmed.
|
||||
/** Maximum number of transactions including an unconfirmed tx and its descendants. */
|
||||
static constexpr unsigned int V3_DESCENDANT_LIMIT{2};
|
||||
/** Maximum number of transactions including a V3 tx and all its mempool ancestors. */
|
||||
static constexpr unsigned int V3_ANCESTOR_LIMIT{2};
|
||||
|
||||
/** Maximum sigop-adjusted virtual size of a tx which spends from an unconfirmed v3 transaction. */
|
||||
static constexpr int64_t V3_CHILD_MAX_VSIZE{1000};
|
||||
// These limits are within the default ancestor/descendant limits.
|
||||
static_assert(V3_CHILD_MAX_VSIZE + MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR <= DEFAULT_ANCESTOR_SIZE_LIMIT_KVB * 1000);
|
||||
static_assert(V3_CHILD_MAX_VSIZE + MAX_STANDARD_TX_WEIGHT / WITNESS_SCALE_FACTOR <= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB * 1000);
|
||||
|
||||
/** Must be called for every transaction, even if not v3. Not strictly necessary for transactions
|
||||
* accepted through AcceptMultipleTransactions.
|
||||
*
|
||||
* Checks the following rules:
|
||||
* 1. A v3 tx must only have v3 unconfirmed ancestors.
|
||||
* 2. A non-v3 tx must only have non-v3 unconfirmed ancestors.
|
||||
* 3. A v3's ancestor set, including itself, must be within V3_ANCESTOR_LIMIT.
|
||||
* 4. A v3's descendant set, including itself, must be within V3_DESCENDANT_LIMIT.
|
||||
* 5. If a v3 tx has any unconfirmed ancestors, the tx's sigop-adjusted vsize must be within
|
||||
* V3_CHILD_MAX_VSIZE.
|
||||
*
|
||||
*
|
||||
* @param[in] mempool_ancestors The in-mempool ancestors of ptx.
|
||||
* @param[in] direct_conflicts In-mempool transactions this tx conflicts with. These conflicts
|
||||
* are used to more accurately calculate the resulting descendant
|
||||
* count of in-mempool ancestors.
|
||||
* @param[in] vsize The sigop-adjusted virtual size of ptx.
|
||||
*
|
||||
* @returns debug string if an error occurs, std::nullopt otherwise.
|
||||
*/
|
||||
std::optional<std::string> SingleV3Checks(const CTransactionRef& ptx,
|
||||
const CTxMemPool::setEntries& mempool_ancestors,
|
||||
const std::set<Txid>& direct_conflicts,
|
||||
int64_t vsize);
|
||||
|
||||
/** Must be called for every transaction that is submitted within a package, even if not v3.
|
||||
*
|
||||
* For each transaction in a package:
|
||||
* If it's not a v3 transaction, verify it has no direct v3 parents in the mempool or the package.
|
||||
|
||||
* If it is a v3 transaction, verify that any direct parents in the mempool or the package are v3.
|
||||
* If such a parent exists, verify that parent has no other children in the package or the mempool,
|
||||
* and that the transaction itself has no children in the package.
|
||||
*
|
||||
* If any v3 violations in the package exist, this test will fail for one of them:
|
||||
* - if a v3 transaction T has a parent in the mempool and a child in the package, then PV3C(T) will fail
|
||||
* - if a v3 transaction T has a parent in the package and a child in the package, then PV3C(T) will fail
|
||||
* - if a v3 transaction T and a v3 (sibling) transaction U have some parent in the mempool,
|
||||
* then PV3C(T) and PV3C(U) will fail
|
||||
* - if a v3 transaction T and a v3 (sibling) transaction U have some parent in the package,
|
||||
* then PV3C(T) and PV3C(U) will fail
|
||||
* - if a v3 transaction T has a parent P and a grandparent G in the package, then
|
||||
* PV3C(P) will fail (though PV3C(G) and PV3C(T) might succeed).
|
||||
*
|
||||
* @returns debug string if an error occurs, std::nullopt otherwise.
|
||||
* */
|
||||
std::optional<std::string> PackageV3Checks(const CTransactionRef& ptx, int64_t vsize,
|
||||
const Package& package,
|
||||
const CTxMemPool::setEntries& mempool_ancestors);
|
||||
|
||||
#endif // BITCOIN_POLICY_V3_POLICY_H
|
@ -4,11 +4,14 @@
|
||||
|
||||
#include <consensus/validation.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/v3_policy.h>
|
||||
#include <policy/packages.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <random.h>
|
||||
#include <script/script.h>
|
||||
#include <test/util/setup_common.h>
|
||||
#include <test/util/txmempool.h>
|
||||
#include <validation.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
@ -48,4 +51,290 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
|
||||
BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
|
||||
BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
|
||||
}
|
||||
|
||||
// Generate a number of random, nonexistent outpoints.
|
||||
static inline std::vector<COutPoint> random_outpoints(size_t num_outpoints) {
|
||||
std::vector<COutPoint> outpoints;
|
||||
for (size_t i{0}; i < num_outpoints; ++i) {
|
||||
outpoints.emplace_back(Txid::FromUint256(GetRandHash()), 0);
|
||||
}
|
||||
return outpoints;
|
||||
}
|
||||
|
||||
static inline std::vector<CPubKey> random_keys(size_t num_keys) {
|
||||
std::vector<CPubKey> keys;
|
||||
keys.reserve(num_keys);
|
||||
for (size_t i{0}; i < num_keys; ++i) {
|
||||
CKey key;
|
||||
key.MakeNewKey(true);
|
||||
keys.emplace_back(key.GetPubKey());
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Creates a placeholder tx (not valid) with 25 outputs. Specify the nVersion and the inputs.
|
||||
static inline CTransactionRef make_tx(const std::vector<COutPoint>& inputs, int32_t version)
|
||||
{
|
||||
CMutableTransaction mtx = CMutableTransaction{};
|
||||
mtx.nVersion = version;
|
||||
mtx.vin.resize(inputs.size());
|
||||
mtx.vout.resize(25);
|
||||
for (size_t i{0}; i < inputs.size(); ++i) {
|
||||
mtx.vin[i].prevout = inputs[i];
|
||||
}
|
||||
for (auto i{0}; i < 25; ++i) {
|
||||
mtx.vout[i].scriptPubKey = CScript() << OP_TRUE;
|
||||
mtx.vout[i].nValue = 10000;
|
||||
}
|
||||
return MakeTransactionRef(mtx);
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)
|
||||
{
|
||||
// Test V3 policy helper functions
|
||||
CTxMemPool& pool = *Assert(m_node.mempool);
|
||||
LOCK2(cs_main, pool.cs);
|
||||
TestMemPoolEntryHelper entry;
|
||||
std::set<Txid> empty_conflicts_set;
|
||||
CTxMemPool::setEntries empty_ancestors;
|
||||
|
||||
auto mempool_tx_v3 = make_tx(random_outpoints(1), /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx_v3));
|
||||
auto mempool_tx_v2 = make_tx(random_outpoints(1), /*version=*/2);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx_v2));
|
||||
// Default values.
|
||||
CTxMemPool::Limits m_limits{};
|
||||
|
||||
// Cannot spend from an unconfirmed v3 transaction unless this tx is also v3.
|
||||
{
|
||||
// mempool_tx_v3
|
||||
// ^
|
||||
// tx_v2_from_v3
|
||||
auto tx_v2_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/2);
|
||||
auto ancestors_v2_from_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v3), m_limits)};
|
||||
const auto expected_error_str{strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
|
||||
tx_v2_from_v3->GetHash().ToString(), tx_v2_from_v3->GetWitnessHash().ToString(),
|
||||
mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK(*SingleV3Checks(tx_v2_from_v3, *ancestors_v2_from_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v3)) == expected_error_str);
|
||||
|
||||
Package package_v3_v2{mempool_tx_v3, tx_v2_from_v3};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v3, GetVirtualTransactionSize(*tx_v2_from_v3), package_v3_v2, empty_ancestors), expected_error_str);
|
||||
|
||||
// mempool_tx_v3 mempool_tx_v2
|
||||
// ^ ^
|
||||
// tx_v2_from_v2_and_v3
|
||||
auto tx_v2_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
|
||||
auto ancestors_v2_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2_and_v3), m_limits)};
|
||||
const auto expected_error_str_2{strprintf("non-v3 tx %s (wtxid=%s) cannot spend from v3 tx %s (wtxid=%s)",
|
||||
tx_v2_from_v2_and_v3->GetHash().ToString(), tx_v2_from_v2_and_v3->GetWitnessHash().ToString(),
|
||||
mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK(*SingleV3Checks(tx_v2_from_v2_and_v3, *ancestors_v2_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3))
|
||||
== expected_error_str_2);
|
||||
|
||||
Package package_v3_v2_v2{mempool_tx_v3, mempool_tx_v2, tx_v2_from_v2_and_v3};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v2_from_v2_and_v3, GetVirtualTransactionSize(*tx_v2_from_v2_and_v3), package_v3_v2_v2, empty_ancestors), expected_error_str_2);
|
||||
}
|
||||
|
||||
// V3 cannot spend from an unconfirmed non-v3 transaction.
|
||||
{
|
||||
// mempool_tx_v2
|
||||
// ^
|
||||
// tx_v3_from_v2
|
||||
auto tx_v3_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
|
||||
auto ancestors_v3_from_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2), m_limits)};
|
||||
const auto expected_error_str{strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
|
||||
tx_v3_from_v2->GetHash().ToString(), tx_v3_from_v2->GetWitnessHash().ToString(),
|
||||
mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK(*SingleV3Checks(tx_v3_from_v2, *ancestors_v3_from_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2)) == expected_error_str);
|
||||
|
||||
Package package_v2_v3{mempool_tx_v2, tx_v3_from_v2};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2, GetVirtualTransactionSize(*tx_v3_from_v2), package_v2_v3, empty_ancestors), expected_error_str);
|
||||
|
||||
// mempool_tx_v3 mempool_tx_v2
|
||||
// ^ ^
|
||||
// tx_v3_from_v2_and_v3
|
||||
auto tx_v3_from_v2_and_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}, COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/3);
|
||||
auto ancestors_v3_from_both{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v2_and_v3), m_limits)};
|
||||
const auto expected_error_str_2{strprintf("v3 tx %s (wtxid=%s) cannot spend from non-v3 tx %s (wtxid=%s)",
|
||||
tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString(),
|
||||
mempool_tx_v2->GetHash().ToString(), mempool_tx_v2->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK(*SingleV3Checks(tx_v3_from_v2_and_v3, *ancestors_v3_from_both, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3))
|
||||
== expected_error_str_2);
|
||||
|
||||
// tx_v3_from_v2_and_v3 also violates V3_ANCESTOR_LIMIT.
|
||||
const auto expected_error_str_3{strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
tx_v3_from_v2_and_v3->GetHash().ToString(), tx_v3_from_v2_and_v3->GetWitnessHash().ToString())};
|
||||
Package package_v3_v2_v3{mempool_tx_v3, mempool_tx_v2, tx_v3_from_v2_and_v3};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_from_v2_and_v3, GetVirtualTransactionSize(*tx_v3_from_v2_and_v3), package_v3_v2_v3, empty_ancestors), expected_error_str_3);
|
||||
}
|
||||
// V3 from V3 is ok, and non-V3 from non-V3 is ok.
|
||||
{
|
||||
// mempool_tx_v3
|
||||
// ^
|
||||
// tx_v3_from_v3
|
||||
auto tx_v3_from_v3 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
|
||||
auto ancestors_v3{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_from_v3), m_limits)};
|
||||
BOOST_CHECK(SingleV3Checks(tx_v3_from_v3, *ancestors_v3, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_from_v3))
|
||||
== std::nullopt);
|
||||
|
||||
Package package_v3_v3{mempool_tx_v3, tx_v3_from_v3};
|
||||
BOOST_CHECK(PackageV3Checks(tx_v3_from_v3, GetVirtualTransactionSize(*tx_v3_from_v3), package_v3_v3, empty_ancestors) == std::nullopt);
|
||||
|
||||
// mempool_tx_v2
|
||||
// ^
|
||||
// tx_v2_from_v2
|
||||
auto tx_v2_from_v2 = make_tx({COutPoint{mempool_tx_v2->GetHash(), 0}}, /*version=*/2);
|
||||
auto ancestors_v2{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v2_from_v2), m_limits)};
|
||||
BOOST_CHECK(SingleV3Checks(tx_v2_from_v2, *ancestors_v2, empty_conflicts_set, GetVirtualTransactionSize(*tx_v2_from_v2))
|
||||
== std::nullopt);
|
||||
|
||||
Package package_v2_v2{mempool_tx_v2, tx_v2_from_v2};
|
||||
BOOST_CHECK(PackageV3Checks(tx_v2_from_v2, GetVirtualTransactionSize(*tx_v2_from_v2), package_v2_v2, empty_ancestors) == std::nullopt);
|
||||
}
|
||||
|
||||
// Tx spending v3 cannot have too many mempool ancestors
|
||||
// Configuration where the tx has multiple direct parents.
|
||||
{
|
||||
Package package_multi_parents;
|
||||
std::vector<COutPoint> mempool_outpoints;
|
||||
mempool_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
|
||||
package_multi_parents.emplace_back(mempool_tx_v3);
|
||||
for (size_t i{0}; i < 2; ++i) {
|
||||
auto mempool_tx = make_tx(random_outpoints(i + 1), /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx));
|
||||
mempool_outpoints.emplace_back(mempool_tx->GetHash(), 0);
|
||||
package_multi_parents.emplace_back(mempool_tx);
|
||||
}
|
||||
auto tx_v3_multi_parent = make_tx(mempool_outpoints, /*version=*/3);
|
||||
package_multi_parents.emplace_back(tx_v3_multi_parent);
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_parent), m_limits)};
|
||||
BOOST_CHECK_EQUAL(ancestors->size(), 3);
|
||||
const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
tx_v3_multi_parent->GetHash().ToString(), tx_v3_multi_parent->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK_EQUAL(*SingleV3Checks(tx_v3_multi_parent, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_parent)),
|
||||
expected_error_str);
|
||||
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_multi_parent, GetVirtualTransactionSize(*tx_v3_multi_parent), package_multi_parents, empty_ancestors),
|
||||
expected_error_str);
|
||||
}
|
||||
|
||||
// Configuration where the tx is in a multi-generation chain.
|
||||
{
|
||||
Package package_multi_gen;
|
||||
CTransactionRef middle_tx;
|
||||
auto last_outpoint{random_outpoints(1)[0]};
|
||||
for (size_t i{0}; i < 2; ++i) {
|
||||
auto mempool_tx = make_tx({last_outpoint}, /*version=*/3);
|
||||
pool.addUnchecked(entry.FromTx(mempool_tx));
|
||||
last_outpoint = COutPoint{mempool_tx->GetHash(), 0};
|
||||
package_multi_gen.emplace_back(mempool_tx);
|
||||
if (i == 1) middle_tx = mempool_tx;
|
||||
}
|
||||
auto tx_v3_multi_gen = make_tx({last_outpoint}, /*version=*/3);
|
||||
package_multi_gen.emplace_back(tx_v3_multi_gen);
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_multi_gen), m_limits)};
|
||||
const auto expected_error_str{strprintf("tx %s (wtxid=%s) would have too many ancestors",
|
||||
tx_v3_multi_gen->GetHash().ToString(), tx_v3_multi_gen->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK_EQUAL(*SingleV3Checks(tx_v3_multi_gen, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_multi_gen)),
|
||||
expected_error_str);
|
||||
|
||||
// Middle tx is what triggers a failure for the grandchild:
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(middle_tx, GetVirtualTransactionSize(*middle_tx), package_multi_gen, empty_ancestors), expected_error_str);
|
||||
BOOST_CHECK(PackageV3Checks(tx_v3_multi_gen, GetVirtualTransactionSize(*tx_v3_multi_gen), package_multi_gen, empty_ancestors) == std::nullopt);
|
||||
}
|
||||
|
||||
// Tx spending v3 cannot be too large in virtual size.
|
||||
auto many_inputs{random_outpoints(100)};
|
||||
many_inputs.emplace_back(mempool_tx_v3->GetHash(), 0);
|
||||
{
|
||||
auto tx_v3_child_big = make_tx(many_inputs, /*version=*/3);
|
||||
const auto vsize{GetVirtualTransactionSize(*tx_v3_child_big)};
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child_big), m_limits)};
|
||||
const auto expected_error_str{strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
|
||||
tx_v3_child_big->GetHash().ToString(), tx_v3_child_big->GetWitnessHash().ToString(), vsize, V3_CHILD_MAX_VSIZE)};
|
||||
BOOST_CHECK_EQUAL(*SingleV3Checks(tx_v3_child_big, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child_big)),
|
||||
expected_error_str);
|
||||
|
||||
Package package_child_big{mempool_tx_v3, tx_v3_child_big};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_child_big, GetVirtualTransactionSize(*tx_v3_child_big), package_child_big, empty_ancestors),
|
||||
expected_error_str);
|
||||
}
|
||||
|
||||
// Tx spending v3 cannot have too many sigops.
|
||||
// This child has 10 P2WSH multisig inputs.
|
||||
auto multisig_outpoints{random_outpoints(10)};
|
||||
multisig_outpoints.emplace_back(mempool_tx_v3->GetHash(), 0);
|
||||
auto keys{random_keys(2)};
|
||||
CScript script_multisig;
|
||||
script_multisig << OP_1;
|
||||
for (const auto& key : keys) {
|
||||
script_multisig << ToByteVector(key);
|
||||
}
|
||||
script_multisig << OP_2 << OP_CHECKMULTISIG;
|
||||
{
|
||||
CMutableTransaction mtx_many_sigops = CMutableTransaction{};
|
||||
mtx_many_sigops.nVersion = 3;
|
||||
for (const auto& outpoint : multisig_outpoints) {
|
||||
mtx_many_sigops.vin.emplace_back(outpoint);
|
||||
mtx_many_sigops.vin.back().scriptWitness.stack.emplace_back(script_multisig.begin(), script_multisig.end());
|
||||
}
|
||||
mtx_many_sigops.vout.resize(1);
|
||||
mtx_many_sigops.vout.back().scriptPubKey = CScript() << OP_TRUE;
|
||||
mtx_many_sigops.vout.back().nValue = 10000;
|
||||
auto tx_many_sigops{MakeTransactionRef(mtx_many_sigops)};
|
||||
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_many_sigops), m_limits)};
|
||||
// legacy uses fAccurate = false, and the maximum number of multisig keys is used
|
||||
const int64_t total_sigops{static_cast<int64_t>(tx_many_sigops->vin.size()) * static_cast<int64_t>(script_multisig.GetSigOpCount(/*fAccurate=*/false))};
|
||||
BOOST_CHECK_EQUAL(total_sigops, tx_many_sigops->vin.size() * MAX_PUBKEYS_PER_MULTISIG);
|
||||
const int64_t bip141_vsize{GetVirtualTransactionSize(*tx_many_sigops)};
|
||||
// Weight limit is not reached...
|
||||
BOOST_CHECK(SingleV3Checks(tx_many_sigops, *ancestors, empty_conflicts_set, bip141_vsize) == std::nullopt);
|
||||
// ...but sigop limit is.
|
||||
const auto expected_error_str{strprintf("v3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
|
||||
tx_many_sigops->GetHash().ToString(), tx_many_sigops->GetWitnessHash().ToString(),
|
||||
total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, V3_CHILD_MAX_VSIZE)};
|
||||
BOOST_CHECK_EQUAL(*SingleV3Checks(tx_many_sigops, *ancestors, empty_conflicts_set,
|
||||
GetVirtualTransactionSize(*tx_many_sigops, /*nSigOpCost=*/total_sigops, /*bytes_per_sigop=*/ DEFAULT_BYTES_PER_SIGOP)),
|
||||
expected_error_str);
|
||||
|
||||
Package package_child_sigops{mempool_tx_v3, tx_many_sigops};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_many_sigops, total_sigops * DEFAULT_BYTES_PER_SIGOP / WITNESS_SCALE_FACTOR, package_child_sigops, empty_ancestors),
|
||||
expected_error_str);
|
||||
}
|
||||
|
||||
// Parent + child with v3 in the mempool. Child is allowed as long as it is under V3_CHILD_MAX_VSIZE.
|
||||
auto tx_mempool_v3_child = make_tx({COutPoint{mempool_tx_v3->GetHash(), 0}}, /*version=*/3);
|
||||
{
|
||||
BOOST_CHECK(GetTransactionWeight(*tx_mempool_v3_child) <= V3_CHILD_MAX_VSIZE * WITNESS_SCALE_FACTOR);
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_mempool_v3_child), m_limits)};
|
||||
BOOST_CHECK(SingleV3Checks(tx_mempool_v3_child, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_mempool_v3_child)) == std::nullopt);
|
||||
pool.addUnchecked(entry.FromTx(tx_mempool_v3_child));
|
||||
|
||||
Package package_v3_1p1c{mempool_tx_v3, tx_mempool_v3_child};
|
||||
BOOST_CHECK(PackageV3Checks(tx_mempool_v3_child, GetVirtualTransactionSize(*tx_mempool_v3_child), package_v3_1p1c, empty_ancestors) == std::nullopt);
|
||||
}
|
||||
|
||||
// A v3 transaction cannot have more than 1 descendant.
|
||||
// Configuration where tx has multiple direct children.
|
||||
{
|
||||
auto tx_v3_child2 = make_tx({COutPoint{mempool_tx_v3->GetHash(), 1}}, /*version=*/3);
|
||||
auto ancestors{pool.CalculateMemPoolAncestors(entry.FromTx(tx_v3_child2), m_limits)};
|
||||
const auto expected_error_str{strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
|
||||
mempool_tx_v3->GetHash().ToString(), mempool_tx_v3->GetWitnessHash().ToString())};
|
||||
BOOST_CHECK_EQUAL(*SingleV3Checks(tx_v3_child2, *ancestors, empty_conflicts_set, GetVirtualTransactionSize(*tx_v3_child2)),
|
||||
expected_error_str);
|
||||
// If replacing the child, make sure there is no double-counting.
|
||||
BOOST_CHECK(SingleV3Checks(tx_v3_child2, *ancestors, {tx_mempool_v3_child->GetHash()}, GetVirtualTransactionSize(*tx_v3_child2))
|
||||
== std::nullopt);
|
||||
|
||||
Package package_v3_1p2c{mempool_tx_v3, tx_mempool_v3_child, tx_v3_child2};
|
||||
BOOST_CHECK_EQUAL(*PackageV3Checks(tx_v3_child2, GetVirtualTransactionSize(*tx_v3_child2), package_v3_1p2c, empty_ancestors),
|
||||
expected_error_str);
|
||||
}
|
||||
|
||||
// Configuration where tx has multiple generations of descendants is not tested because that is
|
||||
// equivalent to the tx with multiple generations of ancestors.
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
x
Reference in New Issue
Block a user