Merge bitcoin/bitcoin#32025: validation, fix: Use wtxid instead of txid in CheckEphemeralSpends

e637dc2c01 refactor: Replace uint256 type with Wtxid in PackageMempoolAcceptResult struct (marcofleon)
a3baead7cb validation: use wtxid instead of txid in CheckEphemeralSpends (marcofleon)

Pull request description:

  This PR addresses a small bug in [`AcceptMultipleTransactions`](45719390a1/src/validation.cpp (L1598)) where a txid was being inserted into a map that should only hold wtxids.  `CheckEphemeralSpends` has an out parameter on failure that records that the child transaction did not spend the parent's dust. Instead of using the txid of this child, use its wtxid.

  The second commit in this PR is a refactor of the `PackageMempoolAcceptResult` struct to use the `Wtxid` type instead of `uint256`. This helps to prevent errors like this in the future.

ACKs for top commit:
  instagibbs:
    ACK e637dc2c01
  glozow:
    ACK e637dc2c01, hooray for type safety
  dergoegge:
    Code review ACK e637dc2c01

Tree-SHA512: 17039efbb241b7741e2610be5a6d6f88f4c1cbe22d476931ec99e43f993d259a1a5e9334e1042651aff49edbdf7b9e1c1cd070a28dcba5724be6db842e4ad1e0
This commit is contained in:
merge-script 2025-03-12 19:57:58 +08:00
commit 8cb6ab0b97
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
7 changed files with 80 additions and 79 deletions

View file

@ -75,11 +75,11 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
uint32_t iteration{0};
TxValidationState dummy_state;
Txid dummy_txid;
Wtxid dummy_wtxid;
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool, dummy_state, dummy_txid);
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool, dummy_state, dummy_wtxid);
iteration++;
});
}

View file

@ -30,7 +30,7 @@ bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmou
return true;
}
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid)
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Wtxid& out_child_wtxid)
{
if (!Assume(std::ranges::all_of(package, [](const auto& tx){return tx != nullptr;}))) {
// Bail out of spend checks if caller gave us an invalid package
@ -83,9 +83,10 @@ bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, cons
}
if (!unspent_parent_dust.empty()) {
out_child_txid = tx->GetHash();
const Txid& out_child_txid = tx->GetHash();
out_child_wtxid = tx->GetWitnessHash();
out_child_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "missing-ephemeral-spends",
strprintf("tx %s did not spend parent's ephemeral dust", out_child_txid.ToString()));
strprintf("tx %s (wtxid=%s) did not spend parent's ephemeral dust", out_child_txid.ToString(), out_child_wtxid.ToString()));
return false;
}
}

View file

@ -50,9 +50,9 @@ bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmou
/** Must be called for each transaction(package) if any dust is in the package.
* Checks that each transaction's parents have their dust spent by the child,
* where parents are either in the mempool or in the package itself.
* Sets out_child_state and out_child_txid on failure.
* Sets out_child_state and out_child_wtxid on failure.
* @returns true if all dust is properly spent.
*/
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid);
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Wtxid& out_child_wtxid);
#endif // BITCOIN_POLICY_EPHEMERAL_POLICY_H

View file

@ -118,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
CTxMemPool::setEntries empty_ancestors;
TxValidationState child_state;
Txid child_txid;
Wtxid child_wtxid;
// Arbitrary non-0 feerate for these tests
CFeeRate dustrelay(DUST_RELAY_TX_FEE);
@ -133,143 +133,143 @@ BOOST_FIXTURE_TEST_CASE(ephemeral_tests, RegTestingSetup)
// We first start with nothing "in the mempool", using package checks
// Trivial single transaction with no dust
BOOST_CHECK(CheckEphemeralSpends({dust_spend}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({dust_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Now with dust, ok because the tx has no dusty parents
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Dust checks pass
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, CFeeRate(0), pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, CFeeRate(0), pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, dust_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
auto dust_non_spend = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
// Child spending non-dust only from parent should be disallowed even if dust otherwise spent
const auto dust_non_spend_txid{dust_non_spend->GetHash()};
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend, dust_spend}, dustrelay, pool, child_state, child_txid));
const auto dust_non_spend_wtxid{dust_non_spend->GetWitnessHash()};
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend, dust_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(!child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, dust_non_spend_txid);
BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
child_state = TxValidationState();
child_txid = Txid();
child_wtxid = Wtxid();
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_spend, dust_non_spend}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_spend, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(!child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, dust_non_spend_txid);
BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
child_state = TxValidationState();
child_txid = Txid();
child_wtxid = Wtxid();
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, dust_non_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(!child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, dust_non_spend_txid);
BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_wtxid);
child_state = TxValidationState();
child_txid = Txid();
child_wtxid = Wtxid();
auto grandparent_tx_2 = make_ephemeral_tx(random_outpoints(1), /*version=*/2);
const auto dust_txid_2 = grandparent_tx_2->GetHash();
// Spend dust from one but not another is ok, as long as second grandparent has no child
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
auto dust_non_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX - 1}}, /*version=*/2);
// But if we spend from the parent, it must spend dust
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_non_spend_both_parents}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(!CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(!child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, dust_non_spend_both_parents->GetHash());
BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
child_state = TxValidationState();
child_txid = Txid();
child_wtxid = Wtxid();
auto dust_spend_both_parents = make_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_both_parents}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Spending other outputs is also correct, as long as the dusty one is spent
const std::vector<COutPoint> all_outpoints{COutPoint(dust_txid, 0), COutPoint(dust_txid, 1), COutPoint(dust_txid, 2),
COutPoint(dust_txid_2, 0), COutPoint(dust_txid_2, 1), COutPoint(dust_txid_2, 2)};
auto dust_spend_all_outpoints = make_tx(all_outpoints, /*version=*/2);
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_all_outpoints}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, dust_spend_all_outpoints}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// 2 grandparents with dust <- 1 dust-spending parent with dust <- child with no dust
auto parent_with_dust = make_ephemeral_tx({COutPoint{dust_txid, EPHEMERAL_DUST_INDEX}, COutPoint{dust_txid_2, EPHEMERAL_DUST_INDEX}}, /*version=*/2);
// Ok for parent to have dust
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
auto child_no_dust = make_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_no_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_no_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// 2 grandparents with dust <- 1 dust-spending parent with dust <- child with dust
auto child_with_dust = make_ephemeral_tx({COutPoint{parent_with_dust->GetHash(), EPHEMERAL_DUST_INDEX}}, /*version=*/2);
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_with_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1, grandparent_tx_2, parent_with_dust, child_with_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Tests with parents in mempool
// Nothing in mempool, this should pass for any transaction
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_1}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Add first grandparent to mempool and fetch entry
AddToMempool(pool, entry.FromTx(grandparent_tx_1));
// Ignores ancestors that aren't direct parents
BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Valid spend of dust with grandparent in mempool
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Second grandparent in same package
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust, grandparent_tx_2}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust, grandparent_tx_2}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Order in package doesn't matter
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({grandparent_tx_2, parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Add second grandparent to mempool
AddToMempool(pool, entry.FromTx(grandparent_tx_2));
// Only spends single dust out of two direct parents
BOOST_CHECK(!CheckEphemeralSpends({dust_non_spend_both_parents}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(!CheckEphemeralSpends({dust_non_spend_both_parents}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(!child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, dust_non_spend_both_parents->GetHash());
BOOST_CHECK_EQUAL(child_wtxid, dust_non_spend_both_parents->GetWitnessHash());
child_state = TxValidationState();
child_txid = Txid();
child_wtxid = Wtxid();
// Spends both parents' dust
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({parent_with_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
// Now add dusty parent to mempool
AddToMempool(pool, entry.FromTx(parent_with_dust));
// Passes dust checks even with non-parent ancestors
BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_txid));
BOOST_CHECK(CheckEphemeralSpends({child_no_dust}, dustrelay, pool, child_state, child_wtxid));
BOOST_CHECK(child_state.IsValid());
BOOST_CHECK_EQUAL(child_txid, Txid());
BOOST_CHECK_EQUAL(child_wtxid, Wtxid());
}
BOOST_FIXTURE_TEST_CASE(version3_tests, RegTestingSetup)

View file

@ -696,7 +696,7 @@ private:
// cache - should only be called after successful validation of all transactions in the package.
// Does not call LimitMempoolSize(), so mempool max_size_bytes may be temporarily exceeded.
bool SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& workspaces, PackageValidationState& package_state,
std::map<uint256, MempoolAcceptResult>& results)
std::map<Wtxid, MempoolAcceptResult>& results)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Compare a package's feerate against minimum allowed.
@ -1338,7 +1338,7 @@ void MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args)
bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& workspaces,
PackageValidationState& package_state,
std::map<uint256, MempoolAcceptResult>& results)
std::map<Wtxid, MempoolAcceptResult>& results)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
@ -1438,8 +1438,8 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
}
if (m_pool.m_opts.require_standard) {
Txid dummy_txid;
if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_txid)) {
Wtxid dummy_wtxid;
if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_wtxid)) {
return MempoolAcceptResult::Failure(ws.m_state);
}
}
@ -1512,7 +1512,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
workspaces.reserve(txns.size());
std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces),
[](const auto& tx) { return Workspace(tx); });
std::map<uint256, MempoolAcceptResult> results;
std::map<Wtxid, MempoolAcceptResult> results;
LOCK(m_pool.cs);
@ -1592,10 +1592,10 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// Now that we've bounded the resulting possible ancestry count, check package for dust spends
if (m_pool.m_opts.require_standard) {
TxValidationState child_state;
Txid child_txid;
if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_txid)) {
Wtxid child_wtxid;
if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_wtxid)) {
package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust");
results.emplace(child_txid, MempoolAcceptResult::Failure(child_state));
results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state));
return PackageMempoolAcceptResult(package_state, std::move(results));
}
}
@ -1751,11 +1751,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
LOCK(m_pool.cs);
// Stores results from which we will create the returned PackageMempoolAcceptResult.
// A result may be changed if a mempool transaction is evicted later due to LimitMempoolSize().
std::map<uint256, MempoolAcceptResult> results_final;
std::map<Wtxid, MempoolAcceptResult> results_final;
// Results from individual validation which will be returned if no other result is available for
// this transaction. "Nonfinal" because if a transaction fails by itself but succeeds later
// (i.e. when evaluated with a fee-bumping child), the result in this map may be discarded.
std::map<uint256, MempoolAcceptResult> individual_results_nonfinal;
std::map<Wtxid, MempoolAcceptResult> individual_results_nonfinal;
// Tracks whether we think package submission could result in successful entry to the mempool
bool quit_early{false};
std::vector<CTransactionRef> txns_package_eval;

View file

@ -154,7 +154,7 @@ struct MempoolAcceptResult {
const std::optional<std::vector<Wtxid>> m_wtxids_fee_calculations;
/** The wtxid of the transaction in the mempool which has the same txid but different witness. */
const std::optional<uint256> m_other_wtxid;
const std::optional<Wtxid> m_other_wtxid;
static MempoolAcceptResult Failure(TxValidationState state) {
return MempoolAcceptResult(state);
@ -179,7 +179,7 @@ struct MempoolAcceptResult {
return MempoolAcceptResult(vsize, fees);
}
static MempoolAcceptResult MempoolTxDifferentWitness(const uint256& other_wtxid) {
static MempoolAcceptResult MempoolTxDifferentWitness(const Wtxid& other_wtxid) {
return MempoolAcceptResult(other_wtxid);
}
@ -218,7 +218,7 @@ private:
: m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize}, m_base_fees(fees) {}
/** Constructor for witness-swapped case. */
explicit MempoolAcceptResult(const uint256& other_wtxid)
explicit MempoolAcceptResult(const Wtxid& other_wtxid)
: m_result_type(ResultType::DIFFERENT_WITNESS), m_other_wtxid(other_wtxid) {}
};
@ -234,18 +234,18 @@ struct PackageMempoolAcceptResult
* present, it means validation was unfinished for that transaction. If there
* was a package-wide error (see result in m_state), m_tx_results will be empty.
*/
std::map<uint256, MempoolAcceptResult> m_tx_results;
std::map<Wtxid, MempoolAcceptResult> m_tx_results;
explicit PackageMempoolAcceptResult(PackageValidationState state,
std::map<uint256, MempoolAcceptResult>&& results)
std::map<Wtxid, MempoolAcceptResult>&& results)
: m_state{state}, m_tx_results(std::move(results)) {}
explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate,
std::map<uint256, MempoolAcceptResult>&& results)
std::map<Wtxid, MempoolAcceptResult>&& results)
: m_state{state}, m_tx_results(std::move(results)) {}
/** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */
explicit PackageMempoolAcceptResult(const uint256& wtxid, const MempoolAcceptResult& result)
explicit PackageMempoolAcceptResult(const Wtxid& wtxid, const MempoolAcceptResult& result)
: m_tx_results{ {wtxid, result} } {}
};

View file

@ -233,8 +233,8 @@ class EphemeralDustTest(BitcoinTestFramework):
unspent_sweep_tx = self.wallet.create_self_transfer_multi(fee_per_output=2000, utxos_to_spend=[dusty_tx["new_utxos"][0]], version=3)
assert_greater_than(unspent_sweep_tx["fee"], sweep_tx["fee"])
res = self.nodes[0].submitpackage([dusty_tx["hex"], unspent_sweep_tx["hex"]])
assert_equal(res["tx-results"][unspent_sweep_tx["wtxid"]]["error"], f"missing-ephemeral-spends, tx {unspent_sweep_tx['txid']} did not spend parent's ephemeral dust")
assert_raises_rpc_error(-26, f"missing-ephemeral-spends, tx {unspent_sweep_tx['txid']} did not spend parent's ephemeral dust", self.nodes[0].sendrawtransaction, unspent_sweep_tx["hex"])
assert_equal(res["tx-results"][unspent_sweep_tx["wtxid"]]["error"], f"missing-ephemeral-spends, tx {unspent_sweep_tx['txid']} (wtxid={unspent_sweep_tx['wtxid']}) did not spend parent's ephemeral dust")
assert_raises_rpc_error(-26, f"missing-ephemeral-spends, tx {unspent_sweep_tx['txid']} (wtxid={unspent_sweep_tx['wtxid']}) did not spend parent's ephemeral dust", self.nodes[0].sendrawtransaction, unspent_sweep_tx["hex"])
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx["tx"]])
# Spend works with dust spent
@ -405,7 +405,7 @@ class EphemeralDustTest(BitcoinTestFramework):
res = self.nodes[0].submitpackage([dusty_tx["hex"] for dusty_tx in dusty_txs] + [insufficient_sweep_tx["hex"]])
assert_equal(res['package_msg'], "transaction failed")
assert_equal(res['tx-results'][insufficient_sweep_tx['wtxid']]['error'], f"missing-ephemeral-spends, tx {insufficient_sweep_tx['txid']} did not spend parent's ephemeral dust")
assert_equal(res['tx-results'][insufficient_sweep_tx['wtxid']]['error'], f"missing-ephemeral-spends, tx {insufficient_sweep_tx['txid']} (wtxid={insufficient_sweep_tx['wtxid']}) did not spend parent's ephemeral dust")
# Everything got in except for insufficient spend
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"] for dusty_tx in dusty_txs])
@ -418,7 +418,7 @@ class EphemeralDustTest(BitcoinTestFramework):
res = self.nodes[0].submitpackage([dusty_tx["hex"] for dusty_tx in dusty_txs] + [insufficient_sweep_tx["hex"]])
assert_equal(res['package_msg'], "transaction failed")
assert_equal(res['tx-results'][insufficient_sweep_tx["wtxid"]]["error"], f"missing-ephemeral-spends, tx {insufficient_sweep_tx['txid']} did not spend parent's ephemeral dust")
assert_equal(res['tx-results'][insufficient_sweep_tx["wtxid"]]["error"], f"missing-ephemeral-spends, tx {insufficient_sweep_tx['txid']} (wtxid={insufficient_sweep_tx['wtxid']}) did not spend parent's ephemeral dust")
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"] for dusty_tx in dusty_txs] + [sweep_all_but_one_tx["tx"]])
# Cycle out the partial sweep to avoid triggering package RBF behavior which limits package to no in-mempool ancestors