In 7e25e002100f57d69a967f475eb484a8637d9dcb "[unit test] parent pay for child is not allowed"
The magic numbers in this test were bothering me, so here's a diff that drops most of them. It refactors CreateValidMempoolTransaction again to have a function CreateValidTransaction that just creates a transaction. This can take a feerate and it will deduct the fee from one of the outputs. This test is then changed to have target feerates for each of the transactions that are all relative to minfeerate.
diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
index 1e00a75a79..1d480eedad 100644
--- a/src/test/txpackage_tests.cpp
+++ b/src/test/txpackage_tests.cpp
@@ -378,16 +378,13 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// Diamond shape:
//
// grandparent
- // 5679sat
- // 5676vB
+ // minfr/5
// ^ ^ ^
// parent1 | parent2
- // 12000sat | 12000sat
- // 112vB | 112vB
+ // minfr*25 | minfr*25
// ^ | ^
// child
- // 2424sat
- // 959vB
+ // minfr - 0.05 s/vB
//
// grandparent is below minfeerate
// {grandparent + parent1} and {grandparent + parent2} are both below minfeerate
@@ -395,71 +392,72 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
// child has a feerate just below minfeerate
// {grandparent + parent1 + parent2 + child} is above minfeerate
// All transactions should be rejected.
- const CAmount grandparent_fee{5679};
+ const CFeeRate grandparent_feerate{minfeerate.GetFeePerK() / 5};
std::vector<CTransactionRef> grandparent_input_txns;
std::vector<COutPoint> grandparent_inputs;
for (auto i{1}; i < 50; ++i) {
grandparent_input_txns.push_back(m_coinbase_txns[i]);
grandparent_inputs.push_back(COutPoint{m_coinbase_txns[i]->GetHash(), 0});
}
- CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN - grandparent_fee;
- auto mtx_grandparent{CreateValidMempoolTransaction(/*input_transactions=*/grandparent_input_txns,
- /*inputs=*/grandparent_inputs,
- /*input_height=*/102,
- /*input_signing_keys=*/{coinbaseKey},
- /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
- CTxOut{last_value, parent_locking_script}},
- /*submit=*/false)};
+ CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN;
+ auto [mtx_grandparent, grandparent_fee] = CreateValidTransaction(/*input_transactions=*/grandparent_input_txns,
+ /*inputs=*/grandparent_inputs,
+ /*input_height=*/102,
+ /*input_signing_keys=*/{coinbaseKey},
+ /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
+ CTxOut{last_value, parent_locking_script}},
+ /*feerate=*/grandparent_feerate,
+ /*fee_output=*/2);
CTransactionRef tx_grandparent = MakeTransactionRef(mtx_grandparent);
package_ppfc.push_back(tx_grandparent);
- const CAmount parent_fee{12000};
- const CAmount parent_value{10*COIN - parent_fee};
- auto mtx_parent1{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/0,
- /*input_height=*/102,
- /*input_signing_key=*/parent_key,
- /*output_destination=*/child_locking_script,
- /*output_amount=*/parent_value, /*submit=*/false)};
+ const CFeeRate parent_feerate{minfeerate.GetFeePerK() * 25};
+ const CAmount parent_value{10*COIN};
+ auto [mtx_parent1, parent_fee1] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 0}},
+ /*input_height=*/102,
+ /*input_signing_keys=*/{parent_key},
+ /*outputs=*/{CTxOut{parent_value, child_locking_script}},
+ /*feerate=*/parent_feerate,
+ /*fee_output=*/0);
CTransactionRef tx_parent1 = MakeTransactionRef(mtx_parent1);
package_ppfc.push_back(tx_parent1);
- auto mtx_parent2{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/1,
- /*input_height=*/102,
- /*input_signing_key=*/parent_key,
- /*output_destination=*/child_locking_script,
- /*output_amount=*/parent_value, /*submit=*/false)};
+ auto [mtx_parent2, parent_fee2] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 1}},
+ /*input_height=*/102,
+ /*input_signing_keys=*/{parent_key},
+ /*outputs=*/{CTxOut{parent_value, child_locking_script}},
+ /*feerate=*/parent_feerate,
+ /*fee_output=*/0);
CTransactionRef tx_parent2 = MakeTransactionRef(mtx_parent2);
package_ppfc.push_back(tx_parent2);
- const CAmount child_fee{minfeerate.GetFee(5676 + 121 + 121 + 227) - grandparent_fee - parent_fee - parent_fee};
- const CAmount child_value{last_value + 2*parent_value - child_fee};
- auto mtx_child{CreateValidMempoolTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
- /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
- /*input_height=*/102,
- /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
- /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
- /*submit=*/false)};
+ const CFeeRate child_feerate{minfeerate.GetFeePerK() - 50};
+ const CAmount child_value{last_value + 2*parent_value};
+ auto [mtx_child, child_fee] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
+ /*input_height=*/102,
+ /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
+ /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
+ /*feerate=*/child_feerate,
+ /*fee_output=*/0);
CTransactionRef tx_child = MakeTransactionRef(mtx_child);
package_ppfc.push_back(tx_child);
- // Magic Number Sanity Checks
- // A little bit of wiggle room because the signature spending the coinbase is not fixed size.
- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_grandparent) / 10, 567);
- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent1), 112);
- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent2), 112);
- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_child), 227);
// Neither parent can pay for the grandparent by itself
- BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee);
- BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee);
+ BOOST_CHECK_EQUAL(parent_fee1, parent_fee2);
+ BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee1);
+ BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee1);
const auto parents_vsize = GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1) + GetVirtualTransactionSize(*tx_parent2);
// Combined, they can pay for the grandparent
- BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee);
+ BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee1);
const auto total_vsize = parents_vsize + GetVirtualTransactionSize(*tx_child);
BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_child)) > child_fee);
// The total package is above feerate, but mostly because of the 2 parents
- BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee + child_fee);
+ BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee1 + child_fee);
// Child feerate is less than the package feerate
- BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee + child_fee, total_vsize));
+ BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee1 + child_fee, total_vsize));
const auto result_ppfc = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_ppfc, /*test_accept=*/false);
BOOST_CHECK(result_ppfc.m_state.IsInvalid());
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 1ca69371b3..11b98cda49 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -343,12 +343,13 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
}
-CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
- const std::vector<COutPoint>& inputs,
- int input_height,
- const std::vector<CKey>& input_signing_keys,
- const std::vector<CTxOut>& outputs,
- bool submit)
+std::pair<CMutableTransaction, CAmount> TestChain100Setup::CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
+ const std::vector<COutPoint>& inputs,
+ int input_height,
+ const std::vector<CKey>& input_signing_keys,
+ const std::vector<CTxOut>& outputs,
+ const std::optional<CFeeRate>& feerate,
+ const std::optional<uint32_t>& fee_output)
{
CMutableTransaction mempool_txn;
mempool_txn.vin.reserve(inputs.size());
@@ -372,17 +373,57 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::
}
// Build Outpoint to Coin map for SignTransaction
std::map<COutPoint, Coin> input_coins;
+ CAmount inputs_amount{0};
for (const auto& outpoint_to_spend : inputs) {
// - Use GetCoin to properly populate utxo_to_spend,
Coin utxo_to_spend;
assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend));
input_coins.insert({outpoint_to_spend, utxo_to_spend});
+ inputs_amount += utxo_to_spend.out.nValue;
}
// - Default signature hashing type
int nHashType = SIGHASH_ALL;
std::map<int, bilingual_str> input_errors;
assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
+ // Calculate fees paid
+ CAmount current_fee = inputs_amount - std::accumulate(outputs.begin(), outputs.end(), CAmount(0),
+ [](const CAmount& acc, const CTxOut& out) {
+ return acc + out.nValue;
+ });
+
+ // Deduct fees from fee_output to meet feerate if set
+ if (feerate.has_value()) {
+ assert(fee_output.has_value());
+ assert(fee_output.value() < mempool_txn.vout.size());
+
+ CAmount target_fee = feerate.value().GetFee(GetVirtualTransactionSize(CTransaction{mempool_txn}));
+ CAmount deduction = target_fee - current_fee;
+
+ if (deduction > 0) {
+ // Only deduct fee if there's anything to deduct.
+ // If the caller has put more fees than the target feerate, don't change the fee.
+ mempool_txn.vout[fee_output.value()].nValue -= deduction;
+
+ // Re-sign since an output has changed
+ input_errors.clear();
+ assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
+ current_fee = target_fee;
+ }
+ }
+
+ return {mempool_txn, current_fee};
+}
+
+CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
+ const std::vector<COutPoint>& inputs,
+ int input_height,
+ const std::vector<CKey>& input_signing_keys,
+ const std::vector<CTxOut>& outputs,
+ bool submit)
+{
+ CMutableTransaction mempool_txn = CreateValidTransaction(input_transactions, inputs, input_height, input_signing_keys, outputs).first;
+
// If submit=true, add transaction to the mempool.
if (submit) {
LOCK(cs_main);
diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
index 106bee6b4b..6c15140ffa 100644
--- a/src/test/util/setup_common.h
+++ b/src/test/util/setup_common.h
@@ -9,6 +9,7 @@
#include <key.h>
#include <node/caches.h>
#include <node/context.h>
+#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <random.h>
@@ -20,10 +21,10 @@
#include <util/vector.h>
#include <functional>
+#include <optional>
#include <type_traits>
#include <vector>
-class CFeeRate;
class Chainstate;
/** This is connected to the logger. Can be used to redirect logs to any other log */
@@ -154,6 +155,27 @@ struct TestChain100Setup : public TestingSetup {
//! Mine a series of new blocks on the active chain.
void mineBlocks(int num_blocks);
+ /**
+ * Create a transaction, optionally setting the fee based on the feerate.
+ * Note: The feerate may not be met exactly depending on whether the signatures can have different sizes.
+ *
+ * [@param](/bitcoin-bitcoin/contributor/param/) input_transactions The transactions to spend
+ * [@param](/bitcoin-bitcoin/contributor/param/) input_height The height of the block that included the input transactions.
+ * [@param](/bitcoin-bitcoin/contributor/param/) inputs Outpoints with which to construct transaction vin.
+ * [@param](/bitcoin-bitcoin/contributor/param/) input_signing_keys The keys to spend the input transactions.
+ * [@param](/bitcoin-bitcoin/contributor/param/) outputs Transaction vout.
+ * [@param](/bitcoin-bitcoin/contributor/param/) feerate The feerate the transaction should pay.
+ * [@param](/bitcoin-bitcoin/contributor/param/) fee_output The index of the output to take the fee from.
+ * [@return](/bitcoin-bitcoin/contributor/return/) The transaction and the fee it pays
+ */
+ std::pair<CMutableTransaction, CAmount> CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
+ const std::vector<COutPoint>& inputs,
+ int input_height,
+ const std::vector<CKey>& input_signing_keys,
+ const std::vector<CTxOut>& outputs,
+ const std::optional<CFeeRate>& feerate = std::nullopt,
+ const std::optional<uint32_t>& fee_output = std::nullopt);
+
/**
* Create a transaction and submit to the mempool.
*