12#include <boost/test/unit_test.hpp>
26 tx.
vin.resize(inputs.size());
27 tx.
vout.resize(num_outputs);
28 for (
size_t i = 0; i < inputs.size(); ++i) {
29 tx.
vin[i].prevout = inputs[i];
31 for (
size_t i = 0; i < num_outputs; ++i) {
40static inline bool sanity_check(
const std::vector<CTransactionRef>& transactions,
41 const std::map<COutPoint, CAmount>& bumpfees)
44 for (
const auto& [outpoint,
fee] : bumpfees) {
45 if (
fee < 0)
return false;
46 if (
fee == 0)
continue;
47 auto outpoint_ = outpoint;
48 const bool found = std::any_of(transactions.cbegin(), transactions.cend(), [&](
const auto& tx) {
49 return outpoint_.hash == tx->GetHash() && outpoint_.n < tx->vout.size();
51 if (!found)
return false;
53 for (
const auto& tx : transactions) {
55 if (tx->vout.size() > 1) {
56 std::set<CAmount> distinct_bumpfees;
57 for (
size_t i{0}; i < tx->vout.size(); ++i) {
58 const auto bumpfee = bumpfees.find(
COutPoint{tx->GetHash(),
static_cast<uint32_t
>(i)});
59 if (
bumpfee != bumpfees.end()) distinct_bumpfees.insert(
bumpfee->second);
61 if (distinct_bumpfees.size() > 1)
return false;
67template <
typename Key,
typename Value>
68Value
Find(
const std::map<Key, Value>& map,
const Key& key)
70 auto it = map.find(key);
71 BOOST_CHECK_MESSAGE(it != map.end(),
strprintf(
"Cannot find %s", key.ToString()));
82 const CAmount positive_base_fee{1000};
83 const CAmount negative_fee_delta{-50000};
84 const CAmount negative_modified_fees{positive_base_fee + negative_fee_delta};
86 const auto tx_mod_negative =
make_tx({
COutPoint{m_coinbase_txns[4]->GetHash(), 0}}, 1);
89 const COutPoint only_outpoint{tx_mod_negative->GetHash(), 0};
93 BOOST_CHECK(mini_miner_target0.IsReadyToCalculate());
95 mini_miner_target0.BuildMockTemplate(feerate_zero);
98 BOOST_CHECK(mini_miner_target0.GetMockTemplateTxids().empty());
102 BOOST_CHECK(mini_miner_no_target.IsReadyToCalculate());
103 mini_miner_no_target.BuildMockTemplate(std::nullopt);
104 const auto template_txids{mini_miner_no_target.GetMockTemplateTxids()};
106 BOOST_CHECK(template_txids.count(tx_mod_negative->GetHash().ToUint256()) > 0);
144 std::vector<COutPoint> all_unspent_outpoints({
154 for (
const auto& outpoint : all_unspent_outpoints)
BOOST_CHECK(!pool.
isSpent(outpoint));
156 std::vector<COutPoint> all_spent_outpoints({
164 std::vector<COutPoint> all_parent_outputs({
176 std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
177 struct TxDimensions {
180 std::map<uint256, TxDimensions> tx_dims;
181 for (
const auto& tx : all_transactions) {
183 tx_dims.emplace(tx->GetHash(), TxDimensions{entry.GetTxSize(), entry.GetModifiedFee(),
184 CFeeRate(entry.GetModifiedFee(), entry.GetTxSize())});
194 for (
const auto& outpoint : nonexistent_outpoints)
BOOST_CHECK(!pool.
isSpent(outpoint));
195 for (
const auto& feerate : various_normal_feerates) {
201 BOOST_CHECK(bump_fees.size() == nonexistent_outpoints.size());
202 for (
const auto& outpoint: nonexistent_outpoints) {
203 auto it = bump_fees.find(outpoint);
210 for (
const auto& target_feerate : various_normal_feerates) {
219 const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
221 if (target_feerate <= tx0_dimensions.feerate) {
225 BOOST_CHECK_EQUAL(bumpfee0, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
229 const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
230 const TxDimensions& tx3_dimensions = tx_dims.find(tx3->GetHash())->second;
231 const CFeeRate tx2_feerate =
CFeeRate(tx2_dimensions.mod_fee + tx3_dimensions.mod_fee, tx2_dimensions.vsize + tx3_dimensions.vsize);
233 if (target_feerate <= tx2_feerate) {
238 BOOST_CHECK_EQUAL(bumpfee2, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
249 const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
250 const TxDimensions& tx5_dimensions = tx_dims.find(tx5->GetHash())->second;
251 const CFeeRate tx4_feerate =
CFeeRate(tx4_dimensions.mod_fee + tx5_dimensions.mod_fee, tx4_dimensions.vsize + tx5_dimensions.vsize);
253 if (target_feerate <= tx4_feerate) {
258 BOOST_CHECK_EQUAL(bumpfee4, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
264 for (
const auto& target_feerate : various_normal_feerates) {
267 auto bump_fees_all_spent = mini_miner_all_spent.
CalculateBumpFees(target_feerate);
272 auto bump_fees_all_parents = mini_miner_all_parents.
CalculateBumpFees(target_feerate);
275 for (
auto& bump_fees : {bump_fees_all_parents, bump_fees_all_spent}) {
281 const TxDimensions& tx0_dimensions = tx_dims.find(tx0->GetHash())->second;
283 if (target_feerate <= tx0_dimensions.feerate) {
287 BOOST_CHECK_EQUAL(it0_spent, target_feerate.GetFee(tx0_dimensions.vsize) - tx0_dimensions.mod_fee);
291 const TxDimensions& tx2_dimensions = tx_dims.find(tx2->GetHash())->second;
292 const CFeeRate tx2_feerate_unbumped = tx2_dimensions.feerate;
293 auto it2_spent =
Find(bump_fees,
COutPoint{tx2->GetHash(), 0});
294 if (target_feerate <= tx2_feerate_unbumped) {
298 BOOST_CHECK_EQUAL(it2_spent, target_feerate.GetFee(tx2_dimensions.vsize) - tx2_dimensions.mod_fee);
302 const TxDimensions& tx4_dimensions = tx_dims.find(tx4->GetHash())->second;
303 const CFeeRate tx4_feerate_unbumped = tx4_dimensions.feerate;
304 auto it4_spent =
Find(bump_fees,
COutPoint{tx4->GetHash(), 0});
305 if (target_feerate <= tx4_feerate_unbumped) {
309 BOOST_CHECK_EQUAL(it4_spent, target_feerate.GetFee(tx4_dimensions.vsize) - tx4_dimensions.mod_fee);
317 std::vector<node::MiniMinerMempoolEntry> miniminer_info;
319 const int32_t tx0_vsize{tx_dims.at(tx0->GetHash()).vsize};
320 const int32_t tx1_vsize{tx_dims.at(tx1->GetHash()).vsize};
321 const int32_t tx2_vsize{tx_dims.at(tx2->GetHash()).vsize};
322 const int32_t tx3_vsize{tx_dims.at(tx3->GetHash()).vsize};
323 const int32_t tx4_vsize{tx_dims.at(tx4->GetHash()).vsize};
324 const int32_t tx5_vsize{tx_dims.at(tx5->GetHash()).vsize};
325 const int32_t tx6_vsize{tx_dims.at(tx6->GetHash()).vsize};
326 const int32_t tx7_vsize{tx_dims.at(tx7->GetHash()).vsize};
328 miniminer_info.emplace_back(tx0,tx0_vsize,tx0_vsize,
med_fee,
med_fee);
329 miniminer_info.emplace_back(tx1, tx1_vsize, tx0_vsize + tx1_vsize,
med_fee, 2*
med_fee);
330 miniminer_info.emplace_back(tx2, tx2_vsize, tx2_vsize,
low_fee,
low_fee);
332 miniminer_info.emplace_back(tx4, tx4_vsize, tx4_vsize,
low_fee,
low_fee);
333 miniminer_info.emplace_back(tx5, tx5_vsize, tx4_vsize + tx5_vsize, tx5_mod_fee,
low_fee + tx5_mod_fee);
337 std::map<Txid, std::set<Txid>> descendant_caches;
338 descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx1->GetHash()});
339 descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash()});
340 descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
341 descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
342 descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash()});
343 descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash()});
344 descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash(), tx7->GetHash()});
345 descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
352 for (
const auto& sequences : {miniminer_manual.
Linearize(), miniminer_pool.
Linearize()}) {
426 std::vector<CTransactionRef> all_transactions{tx0, tx1, tx2, tx3, tx4, tx5, tx6, tx7};
427 std::vector<int64_t> tx_vsizes;
428 tx_vsizes.reserve(all_transactions.size());
431 std::vector<COutPoint> all_unspent_outpoints({
443 for (
const auto& outpoint : all_unspent_outpoints)
BOOST_CHECK(!pool.
isSpent(outpoint));
451 BOOST_CHECK(tx3_anc_feerate ==
CFeeRate(tx3_entry.GetModFeesWithAncestors(), tx3_entry.GetSizeWithAncestors()));
455 BOOST_CHECK(tx6_anc_feerate ==
CFeeRate(tx6_entry.GetModFeesWithAncestors(), tx6_entry.GetSizeWithAncestors()));
458 BOOST_CHECK(tx7_anc_feerate ==
CFeeRate(tx7_entry.GetModFeesWithAncestors(), tx7_entry.GetSizeWithAncestors()));
472 const auto tx0_bumpfee = bump_fees.find(
COutPoint{tx0->GetHash(), 1});
475 const auto tx3_bumpfee = bump_fees.find(
COutPoint{tx3->GetHash(), 0});
479 const auto tx6_bumpfee = bump_fees.find(
COutPoint{tx6->GetHash(), 0});
483 const auto tx7_bumpfee = bump_fees.find(
COutPoint{tx7->GetHash(), 0});
489 BOOST_CHECK(mini_miner_total_tx3.IsReadyToCalculate());
490 const auto tx3_bump_fee = mini_miner_total_tx3.CalculateTotalBumpFees(very_high_feerate);
491 BOOST_CHECK(!mini_miner_total_tx3.IsReadyToCalculate());
497 BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
498 const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(very_high_feerate);
499 BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
506 const auto just_below_tx4 =
CFeeRate(tx4_feerate.GetFeePerK() - 5);
513 const auto tx6_bumpfee = bump_fees.find(
COutPoint{tx6->GetHash(), 0});
516 const auto tx7_bumpfee = bump_fees.find(
COutPoint{tx7->GetHash(), 0});
521 BOOST_CHECK(mini_miner_tx6_tx7.IsReadyToCalculate());
522 const auto tx6_tx7_bumpfee = mini_miner_tx6_tx7.CalculateTotalBumpFees(just_below_tx4);
523 BOOST_CHECK(!mini_miner_tx6_tx7.IsReadyToCalculate());
537 const auto tx6_bumpfee = bump_fees.find(
COutPoint{tx6->GetHash(), 0});
540 const auto tx7_bumpfee = bump_fees.find(
COutPoint{tx7->GetHash(), 0});
545 std::vector<node::MiniMinerMempoolEntry> miniminer_info;
546 miniminer_info.emplace_back(tx0,tx_vsizes[0], tx_vsizes[0],
low_fee,
low_fee);
547 miniminer_info.emplace_back(tx1, tx_vsizes[1], tx_vsizes[1],
med_fee,
med_fee);
548 miniminer_info.emplace_back(tx2, tx_vsizes[2], tx_vsizes[2],
high_fee,
high_fee);
550 miniminer_info.emplace_back(tx4, tx_vsizes[4], tx_vsizes[4],
high_fee,
high_fee);
555 std::map<Txid, std::set<Txid>> descendant_caches;
556 descendant_caches.emplace(tx0->GetHash(), std::set<Txid>{tx0->GetHash(), tx3->GetHash()});
557 descendant_caches.emplace(tx1->GetHash(), std::set<Txid>{tx1->GetHash(), tx3->GetHash()});
558 descendant_caches.emplace(tx2->GetHash(), std::set<Txid>{tx2->GetHash(), tx3->GetHash()});
559 descendant_caches.emplace(tx3->GetHash(), std::set<Txid>{tx3->GetHash()});
560 descendant_caches.emplace(tx4->GetHash(), std::set<Txid>{tx4->GetHash(), tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
561 descendant_caches.emplace(tx5->GetHash(), std::set<Txid>{tx5->GetHash(), tx6->GetHash(), tx7->GetHash()});
562 descendant_caches.emplace(tx6->GetHash(), std::set<Txid>{tx6->GetHash()});
563 descendant_caches.emplace(tx7->GetHash(), std::set<Txid>{tx7->GetHash()});
570 for (
const auto& sequences : {miniminer_manual.
Linearize(), miniminer_pool.
Linearize()}) {
594 auto convert_to_uint256_vec = [](
const std::vector<Txid>& vec) -> std::vector<uint256> {
595 std::vector<uint256>
out;
596 std::transform(vec.begin(), vec.end(), std::back_inserter(
out),
597 [](
const Txid& txid) { return txid.ToUint256(); });
603 std::vector<Txid> chain_txids;
604 auto& lasttx = m_coinbase_txns[0];
605 for (
auto i{0}; i < 500; ++i) {
608 chain_txids.push_back(tx->GetHash());
611 const auto cluster_500tx = pool.
GatherClusters({lasttx->GetHash()});
614 const auto vec_iters_500 = pool.
GetIterVec(convert_to_uint256_vec(chain_txids));
615 for (
const auto& iter : vec_iters_500)
BOOST_CHECK(cluster_500tx_set.count(iter));
620 const auto cluster_501 = pool.
GatherClusters({tx_501->GetHash()});
629 std::vector<Txid> zigzag_txids;
630 for (
auto p{0}; p < 50; ++p) {
633 zigzag_txids.push_back(txp->GetHash());
635 for (
auto c{0}; c < 49; ++c) {
638 zigzag_txids.push_back(txc->GetHash());
640 const auto vec_iters_zigzag = pool.
GetIterVec(convert_to_uint256_vec(zigzag_txids));
642 const std::vector<size_t> indices{0, 22, 72, zigzag_txids.size() - 1};
643 for (
const auto index : indices) {
648 for (
const auto& iter : vec_iters_zigzag)
BOOST_CHECK(clusterset.count(iter));
659 auto grandparent_zero_fee =
make_tx({{m_coinbase_txns.at(0)->GetHash(), 0}}, 1);
660 auto parent_high_feerate =
make_tx({{grandparent_zero_fee->GetHash(), 0}}, 1);
662 auto grandparent_double_low_feerate =
make_tx({{m_coinbase_txns.at(2)->GetHash(), 0}}, 1);
663 auto parent_med_feerate =
make_tx({{grandparent_double_low_feerate->GetHash(), 0}}, 1);
665 auto grandparent_low_feerate =
make_tx({{m_coinbase_txns.at(1)->GetHash(), 0}}, 1);
666 auto parent_double_low_feerate =
make_tx({{grandparent_low_feerate->GetHash(), 0}}, 1);
668 auto child =
make_tx({{parent_high_feerate->GetHash(), 0}, {parent_double_low_feerate->GetHash(), 0}, {parent_med_feerate->GetHash(), 0}}, 1);
671 const int64_t tx_vsize{100};
672 const int64_t child_vsize{1000};
674 std::vector<node::MiniMinerMempoolEntry> miniminer_info;
675 miniminer_info.emplace_back(grandparent_zero_fee, tx_vsize,tx_vsize, 0,0);
676 miniminer_info.emplace_back(parent_high_feerate, tx_vsize, 2*tx_vsize,
high_fee,
high_fee);
677 miniminer_info.emplace_back(grandparent_double_low_feerate, tx_vsize, tx_vsize, 2*
low_fee, 2*
low_fee);
679 miniminer_info.emplace_back(grandparent_low_feerate, tx_vsize, tx_vsize,
low_fee,
low_fee);
680 miniminer_info.emplace_back(parent_double_low_feerate, tx_vsize, 2*tx_vsize, 2*
low_fee, 3*
low_fee);
682 std::map<Txid, std::set<Txid>> descendant_caches;
683 descendant_caches.emplace(grandparent_zero_fee->GetHash(), std::set<Txid>{grandparent_zero_fee->GetHash(), parent_high_feerate->GetHash(), child->GetHash()});
684 descendant_caches.emplace(grandparent_low_feerate->GetHash(), std::set<Txid>{grandparent_low_feerate->GetHash(), parent_double_low_feerate->GetHash(), child->GetHash()});
685 descendant_caches.emplace(grandparent_double_low_feerate->GetHash(), std::set<Txid>{grandparent_double_low_feerate->GetHash(), parent_med_feerate->GetHash(), child->GetHash()});
686 descendant_caches.emplace(parent_high_feerate->GetHash(), std::set<Txid>{parent_high_feerate->GetHash(), child->GetHash()});
687 descendant_caches.emplace(parent_med_feerate->GetHash(), std::set<Txid>{parent_med_feerate->GetHash(), child->GetHash()});
688 descendant_caches.emplace(parent_double_low_feerate->GetHash(), std::set<Txid>{parent_double_low_feerate->GetHash(), child->GetHash()});
689 descendant_caches.emplace(child->GetHash(), std::set<Txid>{child->GetHash()});
693 const auto sequences{miniminer_manual.
Linearize()};
int64_t CAmount
Amount in satoshis (Can be negative)
static constexpr CAmount COIN
The amount of satoshis in one BTC.
#define Assert(val)
Identity function.
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
CAmount GetFee(uint32_t num_bytes) const
Return the fee in satoshis for the given vsize in vbytes.
An outpoint - a combination of a transaction hash and an index n into its vout.
Serialized script, used inside transaction inputs and outputs.
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
void PrioritiseTransaction(const uint256 &hash, const CAmount &nFeeDelta)
Affect CreateNewBlock prioritisation of transactions.
std::vector< txiter > GetIterVec(const std::vector< uint256 > &txids) const EXCLUSIVE_LOCKS_REQUIRED(cs)
Translate a list of hashes into a list of mempool iterators to avoid repeated lookups.
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
std::vector< txiter > GatherClusters(const std::vector< uint256 > &txids) const EXCLUSIVE_LOCKS_REQUIRED(cs)
Collect the entire cluster of connected transactions for each transaction in txids.
std::set< txiter, CompareIteratorByHash > setEntries
const CTransaction * GetConflictTx(const COutPoint &prevout) const EXCLUSIVE_LOCKS_REQUIRED(cs)
Get the transaction in the pool that spends the same prevout.
bool isSpent(const COutPoint &outpoint) const
const CTxMemPoolEntry * GetEntry(const Txid &txid) const LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(cs)
A minimal version of BlockAssembler, using the same ancestor set scoring algorithm.
std::map< Txid, uint32_t > Linearize()
Construct a new block template with all of the transactions and calculate the order in which they are...
std::map< COutPoint, CAmount > CalculateBumpFees(const CFeeRate &target_feerate)
Construct a new block template and, for each outpoint corresponding to a transaction that did not mak...
bool IsReadyToCalculate() const
Returns true if CalculateBumpFees may be called, false if not.
static transaction_identifier FromUint256(const uint256 &id)
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
AddToMempool(pool, CTxMemPoolEntry(tx, fee, nTime, nHeight, sequence, spendsCoinbase, sigOpCost, lp))
BOOST_FIXTURE_TEST_CASE(miniminer_negative, TestChain100Setup)
static bool sanity_check(const std::vector< CTransactionRef > &transactions, const std::map< COutPoint, CAmount > &bumpfees)
static CTransactionRef make_tx(const std::vector< COutPoint > &inputs, size_t num_outputs)
Value Find(const std::map< Key, Value > &map, const Key &key)
#define BOOST_CHECK_EQUAL(v1, v2)
#define BOOST_CHECK(expr)
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop)
Compute the virtual transaction size (weight reinterpreted as bytes).
static CTransactionRef MakeTransactionRef(Tx &&txIn)
std::shared_ptr< const CTransaction > CTransactionRef
uint256 GetRandHash() noexcept
Generate a random uint256.
static constexpr CAmount CENT
A mutable version of CTransaction.
std::vector< CTxOut > vout
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
CTxMemPoolEntry FromTx(const CMutableTransaction &tx) const
TestMemPoolEntryHelper & Fee(CAmount _fee)
Testing setup that configures a complete environment.
std::unique_ptr< CTxMemPool > mempool