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
.
0diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp
1index 1e00a75a79..1d480eedad 100644
2--- a/src/test/txpackage_tests.cpp
3+++ b/src/test/txpackage_tests.cpp
4@@ -378,16 +378,13 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
5 // Diamond shape:
6 //
7 // grandparent
8- // 5679sat
9- // 5676vB
10+ // minfr/5
11 // ^ ^ ^
12 // parent1 | parent2
13- // 12000sat | 12000sat
14- // 112vB | 112vB
15+ // minfr*25 | minfr*25
16 // ^ | ^
17 // child
18- // 2424sat
19- // 959vB
20+ // minfr - 0.05 s/vB
21 //
22 // grandparent is below minfeerate
23 // {grandparent + parent1} and {grandparent + parent2} are both below minfeerate
24@@ -395,71 +392,72 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
25 // child has a feerate just below minfeerate
26 // {grandparent + parent1 + parent2 + child} is above minfeerate
27 // All transactions should be rejected.
28- const CAmount grandparent_fee{5679};
29+ const CFeeRate grandparent_feerate{minfeerate.GetFeePerK() / 5};
30 std::vector<CTransactionRef> grandparent_input_txns;
31 std::vector<COutPoint> grandparent_inputs;
32 for (auto i{1}; i < 50; ++i) {
33 grandparent_input_txns.push_back(m_coinbase_txns[i]);
34 grandparent_inputs.push_back(COutPoint{m_coinbase_txns[i]->GetHash(), 0});
35 }
36- CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN - grandparent_fee;
37- auto mtx_grandparent{CreateValidMempoolTransaction(/*input_transactions=*/grandparent_input_txns,
38- /*inputs=*/grandparent_inputs,
39- /*input_height=*/102,
40- /*input_signing_keys=*/{coinbaseKey},
41- /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
42- CTxOut{last_value, parent_locking_script}},
43- /*submit=*/false)};
44+ CAmount last_value = grandparent_inputs.size()*50*COIN - 10*COIN - 10*COIN;
45+ auto [mtx_grandparent, grandparent_fee] = CreateValidTransaction(/*input_transactions=*/grandparent_input_txns,
46+ /*inputs=*/grandparent_inputs,
47+ /*input_height=*/102,
48+ /*input_signing_keys=*/{coinbaseKey},
49+ /*outputs=*/{CTxOut{10*COIN, parent_locking_script}, CTxOut{10*COIN, parent_locking_script},
50+ CTxOut{last_value, parent_locking_script}},
51+ /*feerate=*/grandparent_feerate,
52+ /*fee_output=*/2);
53 CTransactionRef tx_grandparent = MakeTransactionRef(mtx_grandparent);
54 package_ppfc.push_back(tx_grandparent);
55
56- const CAmount parent_fee{12000};
57- const CAmount parent_value{10*COIN - parent_fee};
58- auto mtx_parent1{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/0,
59- /*input_height=*/102,
60- /*input_signing_key=*/parent_key,
61- /*output_destination=*/child_locking_script,
62- /*output_amount=*/parent_value, /*submit=*/false)};
63+ const CFeeRate parent_feerate{minfeerate.GetFeePerK() * 25};
64+ const CAmount parent_value{10*COIN};
65+ auto [mtx_parent1, parent_fee1] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
66+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 0}},
67+ /*input_height=*/102,
68+ /*input_signing_keys=*/{parent_key},
69+ /*outputs=*/{CTxOut{parent_value, child_locking_script}},
70+ /*feerate=*/parent_feerate,
71+ /*fee_output=*/0);
72 CTransactionRef tx_parent1 = MakeTransactionRef(mtx_parent1);
73 package_ppfc.push_back(tx_parent1);
74- auto mtx_parent2{CreateValidMempoolTransaction(/*input_transaction=*/tx_grandparent, /*input_vout=*/1,
75- /*input_height=*/102,
76- /*input_signing_key=*/parent_key,
77- /*output_destination=*/child_locking_script,
78- /*output_amount=*/parent_value, /*submit=*/false)};
79+ auto [mtx_parent2, parent_fee2] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent},
80+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 1}},
81+ /*input_height=*/102,
82+ /*input_signing_keys=*/{parent_key},
83+ /*outputs=*/{CTxOut{parent_value, child_locking_script}},
84+ /*feerate=*/parent_feerate,
85+ /*fee_output=*/0);
86 CTransactionRef tx_parent2 = MakeTransactionRef(mtx_parent2);
87 package_ppfc.push_back(tx_parent2);
88
89
90- const CAmount child_fee{minfeerate.GetFee(5676 + 121 + 121 + 227) - grandparent_fee - parent_fee - parent_fee};
91- const CAmount child_value{last_value + 2*parent_value - child_fee};
92- auto mtx_child{CreateValidMempoolTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
93- /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
94- /*input_height=*/102,
95- /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
96- /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
97- /*submit=*/false)};
98+ const CFeeRate child_feerate{minfeerate.GetFeePerK() - 50};
99+ const CAmount child_value{last_value + 2*parent_value};
100+ auto [mtx_child, child_fee] = CreateValidTransaction(/*input_transactions=*/{tx_grandparent, tx_parent1, tx_parent2},
101+ /*inputs=*/{COutPoint{tx_grandparent->GetHash(), 2}, COutPoint{tx_parent1->GetHash(), 0}, COutPoint{tx_parent2->GetHash(), 0}},
102+ /*input_height=*/102,
103+ /*input_signing_keys=*/{parent_key, child_key, grandchild_key},
104+ /*outputs=*/{CTxOut{child_value, grandchild_locking_script}},
105+ /*feerate=*/child_feerate,
106+ /*fee_output=*/0);
107 CTransactionRef tx_child = MakeTransactionRef(mtx_child);
108 package_ppfc.push_back(tx_child);
109
110- // Magic Number Sanity Checks
111- // A little bit of wiggle room because the signature spending the coinbase is not fixed size.
112- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_grandparent) / 10, 567);
113- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent1), 112);
114- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_parent2), 112);
115- BOOST_CHECK_EQUAL(GetVirtualTransactionSize(*tx_child), 227);
116 // Neither parent can pay for the grandparent by itself
117- BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee);
118- BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee);
119+ BOOST_CHECK_EQUAL(parent_fee1, parent_fee2);
120+ BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1)) > grandparent_fee + parent_fee1);
121+ BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent2)) > grandparent_fee + parent_fee1);
122 const auto parents_vsize = GetVirtualTransactionSize(*tx_grandparent) + GetVirtualTransactionSize(*tx_parent1) + GetVirtualTransactionSize(*tx_parent2);
123 // Combined, they can pay for the grandparent
124- BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee);
125+ BOOST_CHECK(minfeerate.GetFee(parents_vsize) <= grandparent_fee + 2 * parent_fee1);
126 const auto total_vsize = parents_vsize + GetVirtualTransactionSize(*tx_child);
127 BOOST_CHECK(minfeerate.GetFee(GetVirtualTransactionSize(*tx_child)) > child_fee);
128 // The total package is above feerate, but mostly because of the 2 parents
129- BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee + child_fee);
130+ BOOST_CHECK(minfeerate.GetFee(total_vsize) <= grandparent_fee + 2 * parent_fee1 + child_fee);
131 // Child feerate is less than the package feerate
132- BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee + child_fee, total_vsize));
133+ BOOST_CHECK(CFeeRate(child_fee, GetVirtualTransactionSize(*tx_child)) < CFeeRate(grandparent_fee + 2 * parent_fee1 + child_fee, total_vsize));
134
135 const auto result_ppfc = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool, package_ppfc, /*test_accept=*/false);
136 BOOST_CHECK(result_ppfc.m_state.IsInvalid());
137diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
138index 1ca69371b3..11b98cda49 100644
139--- a/src/test/util/setup_common.cpp
140+++ b/src/test/util/setup_common.cpp
141@@ -343,12 +343,13 @@ CBlock TestChain100Setup::CreateAndProcessBlock(
142 }
143
144
145-CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
146- const std::vector<COutPoint>& inputs,
147- int input_height,
148- const std::vector<CKey>& input_signing_keys,
149- const std::vector<CTxOut>& outputs,
150- bool submit)
151+std::pair<CMutableTransaction, CAmount> TestChain100Setup::CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
152+ const std::vector<COutPoint>& inputs,
153+ int input_height,
154+ const std::vector<CKey>& input_signing_keys,
155+ const std::vector<CTxOut>& outputs,
156+ const std::optional<CFeeRate>& feerate,
157+ const std::optional<uint32_t>& fee_output)
158 {
159 CMutableTransaction mempool_txn;
160 mempool_txn.vin.reserve(inputs.size());
161@@ -372,17 +373,57 @@ CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::
162 }
163 // Build Outpoint to Coin map for SignTransaction
164 std::map<COutPoint, Coin> input_coins;
165+ CAmount inputs_amount{0};
166 for (const auto& outpoint_to_spend : inputs) {
167 // - Use GetCoin to properly populate utxo_to_spend,
168 Coin utxo_to_spend;
169 assert(coins_cache.GetCoin(outpoint_to_spend, utxo_to_spend));
170 input_coins.insert({outpoint_to_spend, utxo_to_spend});
171+ inputs_amount += utxo_to_spend.out.nValue;
172 }
173 // - Default signature hashing type
174 int nHashType = SIGHASH_ALL;
175 std::map<int, bilingual_str> input_errors;
176 assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
177
178+ // Calculate fees paid
179+ CAmount current_fee = inputs_amount - std::accumulate(outputs.begin(), outputs.end(), CAmount(0),
180+ [](const CAmount& acc, const CTxOut& out) {
181+ return acc + out.nValue;
182+ });
183+
184+ // Deduct fees from fee_output to meet feerate if set
185+ if (feerate.has_value()) {
186+ assert(fee_output.has_value());
187+ assert(fee_output.value() < mempool_txn.vout.size());
188+
189+ CAmount target_fee = feerate.value().GetFee(GetVirtualTransactionSize(CTransaction{mempool_txn}));
190+ CAmount deduction = target_fee - current_fee;
191+
192+ if (deduction > 0) {
193+ // Only deduct fee if there's anything to deduct.
194+ // If the caller has put more fees than the target feerate, don't change the fee.
195+ mempool_txn.vout[fee_output.value()].nValue -= deduction;
196+
197+ // Re-sign since an output has changed
198+ input_errors.clear();
199+ assert(SignTransaction(mempool_txn, &keystore, input_coins, nHashType, input_errors));
200+ current_fee = target_fee;
201+ }
202+ }
203+
204+ return {mempool_txn, current_fee};
205+}
206+
207+CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(const std::vector<CTransactionRef>& input_transactions,
208+ const std::vector<COutPoint>& inputs,
209+ int input_height,
210+ const std::vector<CKey>& input_signing_keys,
211+ const std::vector<CTxOut>& outputs,
212+ bool submit)
213+{
214+ CMutableTransaction mempool_txn = CreateValidTransaction(input_transactions, inputs, input_height, input_signing_keys, outputs).first;
215+
216 // If submit=true, add transaction to the mempool.
217 if (submit) {
218 LOCK(cs_main);
219diff --git a/src/test/util/setup_common.h b/src/test/util/setup_common.h
220index 106bee6b4b..6c15140ffa 100644
221--- a/src/test/util/setup_common.h
222+++ b/src/test/util/setup_common.h
223@@ -9,6 +9,7 @@
224 #include <key.h>
225 #include <node/caches.h>
226 #include <node/context.h>
227+#include <policy/feerate.h>
228 #include <primitives/transaction.h>
229 #include <pubkey.h>
230 #include <random.h>
231@@ -20,10 +21,10 @@
232 #include <util/vector.h>
233
234 #include <functional>
235+#include <optional>
236 #include <type_traits>
237 #include <vector>
238
239-class CFeeRate;
240 class Chainstate;
241
242 /** This is connected to the logger. Can be used to redirect logs to any other log */
243@@ -154,6 +155,27 @@ struct TestChain100Setup : public TestingSetup {
244 //! Mine a series of new blocks on the active chain.
245 void mineBlocks(int num_blocks);
246
247+ /**
248+ * Create a transaction, optionally setting the fee based on the feerate.
249+ * Note: The feerate may not be met exactly depending on whether the signatures can have different sizes.
250+ *
251+ * [@param](/bitcoin-bitcoin/contributor/param/) input_transactions The transactions to spend
252+ * [@param](/bitcoin-bitcoin/contributor/param/) input_height The height of the block that included the input transactions.
253+ * [@param](/bitcoin-bitcoin/contributor/param/) inputs Outpoints with which to construct transaction vin.
254+ * [@param](/bitcoin-bitcoin/contributor/param/) input_signing_keys The keys to spend the input transactions.
255+ * [@param](/bitcoin-bitcoin/contributor/param/) outputs Transaction vout.
256+ * [@param](/bitcoin-bitcoin/contributor/param/) feerate The feerate the transaction should pay.
257+ * [@param](/bitcoin-bitcoin/contributor/param/) fee_output The index of the output to take the fee from.
258+ * [@return](/bitcoin-bitcoin/contributor/return/) The transaction and the fee it pays
259+ */
260+ std::pair<CMutableTransaction, CAmount> CreateValidTransaction(const std::vector<CTransactionRef>& input_transactions,
261+ const std::vector<COutPoint>& inputs,
262+ int input_height,
263+ const std::vector<CKey>& input_signing_keys,
264+ const std::vector<CTxOut>& outputs,
265+ const std::optional<CFeeRate>& feerate = std::nullopt,
266+ const std::optional<uint32_t>& fee_output = std::nullopt);
267+
268 /**
269 * Create a transaction and submit to the mempool.
270 *