Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ BITCOIN_CORE_H = \
node/coinstats.h \
node/connection_types.h \
node/context.h \
node/disconnected_transactions.h \
node/eviction.h \
node/miner.h \
node/minisketchwrapper.h \
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.bench.include
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ bench_bench_dash_SOURCES = \
bench/checkqueue.cpp \
bench/data.h \
bench/data.cpp \
bench/descriptors.cpp \
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this line or add this file!

bench/disconnected_transactions.cpp \
bench/duplicate_inputs.cpp \
bench/ecdsa.cpp \
bench/ellswift.cpp \
Expand Down
130 changes: 130 additions & 0 deletions src/bench/disconnected_transactions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) 2023 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 <bench/bench.h>
#include <kernel/disconnected_transactions.h>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix include path to match Dash file location

The include path should reference node/ instead of kernel/ since Dash places this file in src/node/.

Apply this diff:

-#include <kernel/disconnected_transactions.h>
+#include <node/disconnected_transactions.h>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <kernel/disconnected_transactions.h>
#include <node/disconnected_transactions.h>
🤖 Prompt for AI Agents
In src/bench/disconnected_transactions.cpp at line 6, the include path
incorrectly uses "kernel/disconnected_transactions.h". Change this to
"node/disconnected_transactions.h" to match the actual file location under
src/node/. Update the include directive accordingly to fix the path.

#include <primitives/block.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>

constexpr size_t BLOCK_VTX_COUNT{4000};
constexpr size_t BLOCK_VTX_COUNT_10PERCENT{400};

using BlockTxns = decltype(CBlock::vtx);

/** Reorg where 1 block is disconnected and 2 blocks are connected. */
struct ReorgTxns {
/** Disconnected block. */
BlockTxns disconnected_txns;
/** First connected block. */
BlockTxns connected_txns_1;
/** Second connected block, new chain tip. Has no overlap with disconnected_txns. */
BlockTxns connected_txns_2;
/** Transactions shared between disconnected_txns and connected_txns_1. */
size_t num_shared;
};

static BlockTxns CreateRandomTransactions(size_t num_txns)
{
// Ensure every transaction has a different txid by having each one spend the previous one.
static uint256 prevout_hash{uint256::ZERO};

BlockTxns txns;
txns.reserve(num_txns);
// Simplest spk for every tx
CScript spk = CScript() << OP_TRUE;
for (uint32_t i = 0; i < num_txns; ++i) {
CMutableTransaction tx;
tx.vin.emplace_back(CTxIn{COutPoint{prevout_hash, 0}});
tx.vout.emplace_back(CTxOut{CENT, spk});
auto ptx{MakeTransactionRef(tx)};
txns.emplace_back(ptx);
prevout_hash = ptx->GetHash();
}
return txns;
}

/** Creates blocks for a Reorg, each with BLOCK_VTX_COUNT transactions. Between the disconnected
* block and the first connected block, there will be num_not_shared transactions that are
* different, and all other transactions the exact same. The second connected block has all unique
* transactions. This is to simulate a reorg in which all but num_not_shared transactions are
* confirmed in the new chain. */
static ReorgTxns CreateBlocks(size_t num_not_shared)
{
auto num_shared{BLOCK_VTX_COUNT - num_not_shared};
const auto shared_txns{CreateRandomTransactions(/*num_txns=*/num_shared)};

// Create different sets of transactions...
auto disconnected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(disconnected_block_txns));

auto connected_block_txns{CreateRandomTransactions(/*num_txns=*/num_not_shared)};
std::copy(shared_txns.begin(), shared_txns.end(), std::back_inserter(connected_block_txns));

assert(disconnected_block_txns.size() == BLOCK_VTX_COUNT);
assert(connected_block_txns.size() == BLOCK_VTX_COUNT);

return ReorgTxns{/*disconnected_txns=*/disconnected_block_txns,
/*connected_txns_1=*/connected_block_txns,
/*connected_txns_2=*/CreateRandomTransactions(BLOCK_VTX_COUNT),
/*num_shared=*/num_shared};
}

static void Reorg(const ReorgTxns& reorg)
{
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_SIZE * 1000};
// Disconnect block
const auto evicted = disconnectpool.AddTransactionsFromBlock(reorg.disconnected_txns);
assert(evicted.empty());

// Connect first block
disconnectpool.removeForBlock(reorg.connected_txns_1);
// Connect new tip
disconnectpool.removeForBlock(reorg.connected_txns_2);

// Sanity Check
assert(disconnectpool.size() == BLOCK_VTX_COUNT - reorg.num_shared);

disconnectpool.clear();
}

/** Add transactions from DisconnectedBlockTransactions, remove all but one (the disconnected
* block's coinbase transaction) of them, and then pop from the front until empty. This is a reorg
* in which all of the non-coinbase transactions in the disconnected chain also exist in the new
* chain. */
static void AddAndRemoveDisconnectedBlockTransactionsAll(benchmark::Bench& bench)
{
const auto chains{CreateBlocks(/*num_not_shared=*/1)};
assert(chains.num_shared == BLOCK_VTX_COUNT - 1);

bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
Reorg(chains);
});
}

/** Add transactions from DisconnectedBlockTransactions, remove 90% of them, and then pop from the front until empty. */
static void AddAndRemoveDisconnectedBlockTransactions90(benchmark::Bench& bench)
{
const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT_10PERCENT)};
assert(chains.num_shared == BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT);

bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
Reorg(chains);
});
}

/** Add transactions from DisconnectedBlockTransactions, remove 10% of them, and then pop from the front until empty. */
static void AddAndRemoveDisconnectedBlockTransactions10(benchmark::Bench& bench)
{
const auto chains{CreateBlocks(/*num_not_shared=*/BLOCK_VTX_COUNT - BLOCK_VTX_COUNT_10PERCENT)};
assert(chains.num_shared == BLOCK_VTX_COUNT_10PERCENT);

bench.minEpochIterations(10).run([&]() NO_THREAD_SAFETY_ANALYSIS {
Reorg(chains);
});
}

BENCHMARK(AddAndRemoveDisconnectedBlockTransactionsAll, benchmark::PriorityLevel::HIGH);
BENCHMARK(AddAndRemoveDisconnectedBlockTransactions90, benchmark::PriorityLevel::HIGH);
BENCHMARK(AddAndRemoveDisconnectedBlockTransactions10, benchmark::PriorityLevel::HIGH);
16 changes: 16 additions & 0 deletions src/memusage.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <cassert>
#include <cstdlib>
#include <list>
#include <map>
#include <memory>
#include <set>
Expand Down Expand Up @@ -148,6 +149,21 @@ static inline size_t DynamicUsage(const std::shared_ptr<X>& p)
return p ? MallocUsage(sizeof(X)) + MallocUsage(sizeof(stl_shared_counter)) : 0;
}

template<typename X>
struct list_node
{
private:
void* ptr_next;
void* ptr_prev;
X x;
};

template<typename X>
static inline size_t DynamicUsage(const std::list<X>& l)
{
return MallocUsage(sizeof(list_node<X>)) * l.size();
}

template<typename X>
struct unordered_node : private X
{
Expand Down
137 changes: 137 additions & 0 deletions src/node/disconnected_transactions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) 2023 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_KERNEL_DISCONNECTED_TRANSACTIONS_H
#define BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
Comment on lines +5 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update header guard to reflect Dash project and file location

The header guard should use DASH prefix and match the actual file location in src/node/.

Apply this diff:

-#ifndef BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
-#define BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
+#ifndef DASH_NODE_DISCONNECTED_TRANSACTIONS_H
+#define DASH_NODE_DISCONNECTED_TRANSACTIONS_H
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#ifndef BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
#define BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
#ifndef DASH_NODE_DISCONNECTED_TRANSACTIONS_H
#define DASH_NODE_DISCONNECTED_TRANSACTIONS_H
🤖 Prompt for AI Agents
In src/node/disconnected_transactions.h at lines 5 to 6, update the header guard
to use the prefix DASH instead of BITCOIN and ensure it reflects the file path
under src/node. Change the guard to something like
DASH_NODE_DISCONNECTED_TRANSACTIONS_H to correctly represent the project and
location.


#include <core_memusage.h>
#include <memusage.h>
#include <primitives/transaction.h>
#include <util/hasher.h>

#include <list>
#include <unordered_map>
#include <vector>

/** Maximum kilobytes for transactions to store for processing during reorg */
static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20'000;
/**
* DisconnectedBlockTransactions

* During the reorg, it's desirable to re-add previously confirmed transactions
* to the mempool, so that anything not re-confirmed in the new chain is
* available to be mined. However, it's more efficient to wait until the reorg
* is complete and process all still-unconfirmed transactions at that time,
* since we expect most confirmed transactions to (typically) still be
* confirmed in the new chain, and re-accepting to the memory pool is expensive
* (and therefore better to not do in the middle of reorg-processing).
* Instead, store the disconnected transactions (in order!) as we go, remove any
* that are included in blocks in the new chain, and then process the remaining
* still-unconfirmed transactions at the end.
*
* Order of queuedTx:
* The front of the list should be the most recently-confirmed transactions (transactions at the
* end of vtx of blocks closer to the tip). If memory usage grows too large, we trim from the front
* of the list. After trimming, transactions can be re-added to the mempool from the back of the
* list to the front without running into missing inputs.
*/
class DisconnectedBlockTransactions {
private:
/** Cached dynamic memory usage for the CTransactions (memory for the shared pointers is
* included in the container calculations). */
uint64_t cachedInnerUsage = 0;
const size_t m_max_mem_usage;
std::list<CTransactionRef> queuedTx;
using TxList = decltype(queuedTx);
std::unordered_map<uint256, TxList::iterator, SaltedTxidHasher> iters_by_txid;

/** Trim the earliest-added entries until we are within memory bounds. */
std::vector<CTransactionRef> LimitMemoryUsage()
{
std::vector<CTransactionRef> evicted;

while (!queuedTx.empty() && DynamicMemoryUsage() > m_max_mem_usage) {
evicted.emplace_back(queuedTx.front());
cachedInnerUsage -= RecursiveDynamicUsage(*queuedTx.front());
iters_by_txid.erase(queuedTx.front()->GetHash());
queuedTx.pop_front();
}
return evicted;
}

public:
DisconnectedBlockTransactions(size_t max_mem_usage) : m_max_mem_usage{max_mem_usage} {}

// It's almost certainly a logic bug if we don't clear out queuedTx before
// destruction, as we add to it while disconnecting blocks, and then we
// need to re-process remaining transactions to ensure mempool consistency.
// For now, assert() that we've emptied out this object on destruction.
// This assert() can always be removed if the reorg-processing code were
// to be refactored such that this assumption is no longer true (for
// instance if there was some other way we cleaned up the mempool after a
// reorg, besides draining this object).
~DisconnectedBlockTransactions() {
assert(queuedTx.empty());
assert(iters_by_txid.empty());
assert(cachedInnerUsage == 0);
}

size_t DynamicMemoryUsage() const {
return cachedInnerUsage + memusage::DynamicUsage(iters_by_txid) + memusage::DynamicUsage(queuedTx);
}

/** Add transactions from the block, iterating through vtx in reverse order. Callers should call
* this function for blocks in descending order by block height.
* We assume that callers never pass multiple transactions with the same txid, otherwise things
* can go very wrong in removeForBlock due to queuedTx containing an item without a
* corresponding entry in iters_by_txid.
* @returns vector of transactions that were evicted for size-limiting.
*/
[[nodiscard]] std::vector<CTransactionRef> AddTransactionsFromBlock(const std::vector<CTransactionRef>& vtx)
{
iters_by_txid.reserve(iters_by_txid.size() + vtx.size());
for (auto block_it = vtx.rbegin(); block_it != vtx.rend(); ++block_it) {
auto it = queuedTx.insert(queuedTx.end(), *block_it);
iters_by_txid.emplace((*block_it)->GetHash(), it);
cachedInnerUsage += RecursiveDynamicUsage(**block_it);
}
return LimitMemoryUsage();
}

/** Remove any entries that are in this block. */
void removeForBlock(const std::vector<CTransactionRef>& vtx)
{
// Short-circuit in the common case of a block being added to the tip
if (queuedTx.empty()) {
return;
}
for (const auto& tx : vtx) {
auto iter = iters_by_txid.find(tx->GetHash());
if (iter != iters_by_txid.end()) {
auto list_iter = iter->second;
iters_by_txid.erase(iter);
cachedInnerUsage -= RecursiveDynamicUsage(**list_iter);
queuedTx.erase(list_iter);
}
}
}

size_t size() const { return queuedTx.size(); }

void clear()
{
cachedInnerUsage = 0;
iters_by_txid.clear();
queuedTx.clear();
}

/** Clear all data structures and return the list of transactions. */
std::list<CTransactionRef> take()
{
std::list<CTransactionRef> ret = std::move(queuedTx);
clear();
return ret;
}
};
#endif // BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update endif comment to match header guard

The endif comment should match the updated header guard.

Apply this diff:

-#endif // BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
+#endif // DASH_NODE_DISCONNECTED_TRANSACTIONS_H
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#endif // BITCOIN_KERNEL_DISCONNECTED_TRANSACTIONS_H
#endif // DASH_NODE_DISCONNECTED_TRANSACTIONS_H
🤖 Prompt for AI Agents
In src/node/disconnected_transactions.h at line 137, update the endif comment to
exactly match the header guard used at the top of the file. Replace the current
comment with the correct header guard name to ensure consistency and clarity.

1 change: 1 addition & 0 deletions src/test/validation_chainstatemanager_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <llmq/blockprocessor.h>
#include <llmq/context.h>
#include <node/chainstate.h>
#include <node/disconnected_transactions.h>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Unclear need for the new include

<node/disconnected_transactions.h> isn't referenced directly in this test file.
Please confirm the include is required for transitive dependencies; otherwise, consider removing it to keep compilation units lean.


🏁 Script executed:

#!/bin/bash
set -e
# Locate the test file
file=$(fd --full-path validation_chainstatemanager_tests.cpp | head -n1)
echo "Found file: $file"
echo "----- File contents (first 200 lines) -----"
sed -n '1,200p' "$file"
echo "----- References to disconnected_transactions -----"
grep -R "disconnected_transactions" -n "$file" || echo "No direct references found."

Length of output: 7871


Remove unused include <node/disconnected_transactions.h>

The header isn’t referenced anywhere in validation_chainstatemanager_tests.cpp—it can be safely removed to keep the compilation unit lean.

• In src/test/validation_chainstatemanager_tests.cpp, delete:

- #include <node/disconnected_transactions.h>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <node/disconnected_transactions.h>
🤖 Prompt for AI Agents
In src/test/validation_chainstatemanager_tests.cpp at line 12, remove the unused
include directive for <node/disconnected_transactions.h> since it is not
referenced anywhere in the file, which will help keep the compilation unit clean
and lean.

#include <node/utxo_snapshot.h>
#include <random.h>
#include <rpc/blockchain.h>
Expand Down
Loading
Loading