Bitcoin ABC 0.26.3
P2P Digital Currency
Loading...
Searching...
No Matches
chained_tx.cpp
Go to the documentation of this file.
1// Copyright (c) 2021 The Bitcoin developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <bench/bench.h>
6#include <common/system.h>
7#include <config.h>
8#include <consensus/amount.h>
9#include <node/context.h>
10#include <node/miner.h>
12#include <script/script.h>
13#include <txmempool.h>
14#include <util/string.h>
15#include <validation.h>
16
17#include <test/util/mining.h>
18#include <test/util/setup_common.h>
19
20#include <list>
21#include <queue>
22#include <vector>
23
26
28
29static const CScript SCRIPT_PUB_KEY =
31 << OP_EQUAL;
32
33static const CScript SCRIPT_SIG = CScript() << std::vector<uint8_t>(100, 0xff)
35
37static std::vector<CTxIn> createUTXOs(const Config &config, size_t n,
39 std::vector<CTxIn> utxos;
40 utxos.reserve(n);
41
42 for (size_t i = 0; i < n; ++i) {
43 utxos.emplace_back(MineBlock(config, node, SCRIPT_PUB_KEY));
44 }
45
46 for (size_t i = 0; i < COINBASE_MATURITY + 1; ++i) {
48 }
49
50 return utxos;
51}
52
54static CTransactionRef toTx(const Config &config, CTxIn txin) {
56 tx.vin.emplace_back(txin);
57 tx.vin.back().scriptSig = SCRIPT_SIG;
58 tx.vout.emplace_back(25 * COIN - 1337 * SATOSHI, SCRIPT_PUB_KEY);
59 return MakeTransactionRef(tx);
60}
61
63static std::vector<CTransactionRef>
64oneInOneOutChain(const Config &config, CTxIn utxo, const size_t chainLength) {
65 auto firstTx = toTx(config, std::move(utxo));
66
67 // Build the chain
68 std::vector<CTransactionRef> chain = {firstTx};
69 chain.reserve(chainLength);
70 while (chain.size() < chainLength) {
71 const COutPoint parent(chain.back()->GetId(), 0);
72 const Amount inAmount(chain.back()->vout[0].nValue);
74 tx.vin.emplace_back(CTxIn(parent, SCRIPT_SIG));
75 tx.vout.emplace_back(inAmount - 1337 * SATOSHI, SCRIPT_PUB_KEY);
76 chain.emplace_back(MakeTransactionRef(tx));
77 }
78 assert(chain.size() == chainLength);
79 return chain;
80}
81
85static std::vector<CTransactionRef> twoInOneOutTree(const Config &config,
87 const size_t treeDepth) {
88 // Total number of txs is the sum of nodes at each depth of a binary tree.
89 size_t txs = 0;
90 for (size_t i = 0; i <= treeDepth; ++i) {
91 txs += std::pow(2, i);
92 }
93 const size_t leafs = std::pow(2, treeDepth);
94
95 std::vector<CTransactionRef> chain;
96 chain.reserve(txs);
97
98 std::queue<CTransactionRef> queue;
99 for (auto txin : createUTXOs(config, leafs, node)) {
100 auto tx = toTx(config, std::move(txin));
101 queue.push(tx);
102 chain.emplace_back(tx);
103 }
104
105 while (true) {
107
108 const CTransactionRef txin1 = queue.front();
109 queue.pop();
110 const CTransactionRef txin2 = queue.front();
111 queue.pop();
112
113 const Amount inAmount = txin1->vout[0].nValue + txin2->vout[0].nValue;
114
115 tx.vin.emplace_back(CTxIn(COutPoint(txin1->GetId(), 0), SCRIPT_SIG));
116 tx.vin.emplace_back(CTxIn(COutPoint(txin2->GetId(), 0), SCRIPT_SIG));
117 tx.vout.emplace_back(inAmount - 1337 * SATOSHI, SCRIPT_PUB_KEY);
118
120 chain.push_back(txref);
121 if (queue.empty()) {
122 break;
123 }
124 queue.emplace(txref);
125 }
126 assert(chain.size() == txs);
127 return chain;
128}
129
132 const std::vector<CTransactionRef> chainedTxs) {
133 auto chainman = Assert(node.chainman.get());
134 Chainstate &activeChainState = chainman->ActiveChainstate();
135
136 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
137 assert(mempool.size() == 0);
138
139 bench.run([&] {
141 for (const auto &tx : chainedTxs) {
142 MempoolAcceptResult result =
144 /*bypass_limits=*/false);
145 assert(result.m_result_type ==
147 }
148 mempool.clear();
149 });
150}
151
157static void benchReorg(const Config &config, node::NodeContext &node,
160 auto utxos = createUTXOs(config, reorgDepth, node);
161 std::vector<std::vector<CTransactionRef>> chains;
162 for (auto &utxo : utxos) {
163 chains.emplace_back(
164 oneInOneOutChain(config, std::move(utxo), chainSizePerBlock));
165 }
166
167 auto chainman = Assert(node.chainman.get());
168 Chainstate &activeChainState = chainman->ActiveChainstate();
169
170 // Current tip will be last valid block.
172 assert(tipBeforeInvalidate != nullptr);
173
175
176 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
177 assert(mempool.size() == 0);
178
179 // Build blocks
181 entry.nFee = 1337 * SATOSHI;
182 for (const auto &chain : chains) {
183 {
184 LOCK2(cs_main, mempool.cs);
185 for (const auto &tx : chain) {
186 mempool.addUnchecked(entry.FromTx(tx));
187 }
188 }
189 assert(mempool.size() == chain.size());
190 MineBlock(config, node, SCRIPT_PUB_KEY);
191 assert(mempool.size() == 0);
192
193 assert(activeChainState.m_chain.Tip()->nTx ==
194 chain.size() + 1 /* coinbase */);
195
196 if (blockToInvalidate == nullptr) {
197 blockToInvalidate = activeChainState.m_chain.Tip();
198 }
199 }
201
202 bench.run([&] {
204
205 // Disconnect blocks with long transaction chains
206 activeChainState.InvalidateBlock(state, blockToInvalidate);
207 assert(state.IsValid());
208
209 activeChainState.ActivateBestChain(state);
210 assert(state.IsValid());
212
213 // Transactions should be stuffed back into the mempool.
214 assert(mempool.size() == reorgDepth * chainSizePerBlock);
215
217 // As of writing this test, removing transactions from mempool
218 // during re-connect takes significant amount of time, so we allow
219 // to test both with and without this process.
220 mempool.clear();
221 }
222
223 // Reconnect block
224 {
225 LOCK(cs_main);
226 activeChainState.ResetBlockFailureFlags(blockToInvalidate);
227 }
228
229 activeChainState.ActivateBestChain(state);
230 assert(state.IsValid());
231 assert(activeChainState.m_chain.Tip() == mostWorkTip);
232 assert(mempool.size() == 0);
233 });
234}
235
236static void
239 const std::vector<std::vector<CTransactionRef>> &chains) {
241 entry.nFee = 1337 * SATOSHI;
242
243 auto chainman = Assert(node.chainman.get());
244 Chainstate &activeChainState = chainman->ActiveChainstate();
245 CTxMemPool &mempool{*Assert(activeChainState.GetMempool())};
246
247 // Fill mempool
248 size_t txCount = 0;
249 for (const auto &chain : chains) {
250 LOCK2(cs_main, mempool.cs);
251 for (const auto &tx : chain) {
252 mempool.addUnchecked(entry.FromTx(tx));
253 ++txCount;
254 }
255 }
256 assert(mempool.size() == txCount);
257
258 const CScript dummy = CScript() << OP_TRUE;
259 bench.run([&] {
260 auto blocktemplate =
261 node::BlockAssembler{config, activeChainState, &mempool}
262 .CreateNewBlock(dummy);
264 // +1 for coinbase
265 assert(blocktemplate->block.vtx.size() == txCount + 1);
266 });
267}
268
269static void
271 const std::vector<std::vector<CTransactionRef>> &chains,
272 bool revFee = true) {
273 std::list<CTxMemPool> pools;
274
275 // Note: in order to isolate how long eviction takes (as opposed to add +
276 // eviction), we are forced to pre-create all the pools we will be needing
277 // up front.
278
279 bench.epochs(2).epochIterations(1);
280
281 for (uint64_t i = 0; i < bench.epochs() * bench.epochIterations() + 1;
282 ++i) {
284 .check_ratio = 0,
285 };
286 pools.emplace_back(mempool_opts);
287 CTxMemPool &pool = pools.back();
289 // Fill mempool
290 size_t txCount = 0;
291 entry.nFee = 1337 * SATOSHI;
292 // add in order of decreasing fee if revFee, increasing otherwise
293 const Amount feeBump =
294 revFee ? int64_t(-1) * SATOSHI : int64_t(1) * SATOSHI;
295 for (const auto &chain : chains) {
296 if (revFee) {
297 entry.nFee += int64_t(chain.size()) * SATOSHI;
298 }
299 LOCK2(cs_main, pool.cs);
300 for (const auto &tx : chain) {
301 pool.addUnchecked(entry.FromTx(tx));
302 entry.nFee += feeBump;
303 // Setting spendCoinbase to false here assumes it's a chain of
304 // 1-in-1-out transaction chain.
305 ++txCount;
306 }
307 if (revFee) {
308 entry.nFee += int64_t(chain.size()) * SATOSHI;
309 }
310 }
311 assert(pool.size() == txCount);
312 }
313
314 auto it = pools.begin();
315
316 bench.run([&] {
317 assert(it != pools.end());
318 auto &pool = *it++;
319 LOCK2(cs_main, pool.cs);
320 while (auto prevSize = pool.size()) {
321 pool.TrimToSize(pool.DynamicMemoryUsage() * 99 / 100);
322 assert(pool.size() < prevSize);
323 }
324 });
325}
326
330 const Config &config = test_setup.m_node.chainman->GetConfig();
331 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
332 config, createUTXOs(config, 1, test_setup.m_node).back(), 50);
334}
335
339 const Config &config = test_setup.m_node.chainman->GetConfig();
340 const std::vector<CTransactionRef> chainedTxs = oneInOneOutChain(
341 config, createUTXOs(config, 1, test_setup.m_node).back(), 500);
343}
344
348 const Config &config = test_setup.m_node.chainman->GetConfig();
349 const std::vector<CTransactionRef> chainedTxs =
350 twoInOneOutTree(config, test_setup.m_node, 5);
351 assert(chainedTxs.size() == 63);
353}
354
358 const Config &config = test_setup.m_node.chainman->GetConfig();
359 const std::vector<CTransactionRef> chainedTxs =
360 twoInOneOutTree(config, test_setup.m_node, 8);
361 assert(chainedTxs.size() == 511);
363}
364
369 const Config &config = test_setup.m_node.chainman->GetConfig();
370 benchReorg(config, test_setup.m_node, bench, 10, 50, true);
371}
372
377 const Config &config = test_setup.m_node.chainman->GetConfig();
378 benchReorg(config, test_setup.m_node, bench, 10, 500, true);
379}
380
386 const Config &config = test_setup.m_node.chainman->GetConfig();
387 benchReorg(config, test_setup.m_node, bench, 10, 50, false);
388}
389
395 const Config &config = test_setup.m_node.chainman->GetConfig();
396 benchReorg(config, test_setup.m_node, bench, 10, 500, false);
397}
398
402 const Config &config = test_setup.m_node.chainman->GetConfig();
403 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
404 benchGenerateNewBlock(config, test_setup.m_node, bench,
405 {oneInOneOutChain(config, std::move(utxo), 50)});
406}
407
411 const Config &config = test_setup.m_node.chainman->GetConfig();
412 CTxIn utxo = createUTXOs(config, 1, test_setup.m_node).back();
413 benchGenerateNewBlock(config, test_setup.m_node, bench,
414 {oneInOneOutChain(config, std::move(utxo), 500)});
415}
416
421 const Config &config = test_setup.m_node.chainman->GetConfig();
422 // create 2000 chains of 50 1-in-1-out each
423 std::vector<std::vector<CTransactionRef>> chains;
424 constexpr int NChains = 2000;
425 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
426 for (int i = 0; i < NChains; ++i) {
427 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
428 }
429 benchEviction(config, bench, chains, false);
430}
431
436 const Config &config = test_setup.m_node.chainman->GetConfig();
437 // create 2000 chains of 50 1-in-1-out each
438 std::vector<std::vector<CTransactionRef>> chains;
439 constexpr int NChains = 2000;
440 const auto utxos = createUTXOs(config, NChains, test_setup.m_node);
441 for (int i = 0; i < NChains; ++i) {
442 chains.push_back(oneInOneOutChain(config, utxos[i], 50));
443 }
444 benchEviction(config, bench, chains, true);
445}
446
451
456
459
static constexpr Amount SATOSHI
Definition amount.h:143
static constexpr Amount COIN
Definition amount.h:144
#define BENCHMARK(n)
Definition bench.h:65
static void benchEviction(const Config &, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains, bool revFee=true)
static void EvictChained50Tx(benchmark::Bench &bench)
Fill a mempool then evict 2000 x 50 1-input-1-output transactions, CTxMemPool version,...
static void Reorg10BlocksWith500TxChain(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 500 tx 1-input-1-output chain.
static void MempoolAcceptance50ChainedTxs(benchmark::Bench &bench)
Tests a chain of 50 1-input-1-output transactions.
static void MempoolAcceptance511TxTree(benchmark::Bench &bench)
Test a tree of 511 2-inputs-1-output transactions.
static void MempoolAcceptance63TxTree(benchmark::Bench &bench)
Test a tree of 63 2-inputs-1-output transactions.
static void EvictChained50TxRev(benchmark::Bench &bench)
Fill a mempool then evict 2000 x 50 1-input-1-output transactions, CTxMemPool version,...
static void Reorg10BlocksWith500TxChainSkipMempool(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 500 tx 1-input-1-output chain,...
static void MempoolAcceptance500ChainedTxs(benchmark::Bench &bench)
Tests a chain of 500 1-input-1-output transactions.
static void benchGenerateNewBlock(const Config &config, node::NodeContext &node, benchmark::Bench &bench, const std::vector< std::vector< CTransactionRef > > &chains)
static void GenerateBlock50ChainedTxs(benchmark::Bench &bench)
Generate a block with 50 1-input-1-output transactions.
static void Reorg10BlocksWith50TxChain(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 50 tx 1-input-1-output chain.
static void benchReorg(const Config &config, node::NodeContext &node, benchmark::Bench &bench, size_t reorgDepth, size_t chainSizePerBlock, bool includeMempoolTxRemoval)
Run benchmark that reorganizes blocks with one-input-one-output transaction chains in them.
static const CScript REDEEM_SCRIPT
This file contains benchmarks focusing on chained transactions in the mempool.
static std::vector< CTxIn > createUTXOs(const Config &config, size_t n, node::NodeContext &node)
Mine new utxos.
static void GenerateBlock500ChainedTxs(benchmark::Bench &bench)
Generate a block with 500 1-input-1-output transactions.
static std::vector< CTransactionRef > twoInOneOutTree(const Config &config, node::NodeContext &node, const size_t treeDepth)
Creates a tree of transactions with 2-inputs-1-output.
static std::vector< CTransactionRef > oneInOneOutChain(const Config &config, CTxIn utxo, const size_t chainLength)
Creates a chain of transactions with 1-input-1-output.
static const CScript SCRIPT_PUB_KEY
static void Reorg10BlocksWith50TxChainSkipMempool(benchmark::Bench &bench)
Try to reorg a chain of depth 10 where each block has a 50 tx 1-input-1-output chain,...
static CTransactionRef toTx(const Config &config, CTxIn txin)
Create a transaction spending a coinbase utxo.
static const CScript SCRIPT_SIG
static void benchATMP(node::NodeContext &node, benchmark::Bench &bench, const std::vector< CTransactionRef > chainedTxs)
Run benchmark on AcceptToMemoryPool.
#define Assert(val)
Identity function.
Definition check.h:84
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition blockindex.h:25
A mutable version of CTransaction.
std::vector< CTxOut > vout
std::vector< CTxIn > vin
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition transaction.h:20
Serialized script, used inside transaction inputs and outputs.
Definition script.h:431
A reference to a CScript: the Hash160 of its serialization (see script.h)
Definition standard.h:24
An input of a transaction.
Definition transaction.h:59
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
Definition txmempool.h:212
RecursiveMutex cs
This mutex needs to be locked when accessing mapTx or other members that are guarded by it.
Definition txmempool.h:307
void check(const CCoinsViewCache &active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(void addUnchecked(CTxMemPoolEntryRef entry) EXCLUSIVE_LOCKS_REQUIRED(cs
If sanity-checking is turned on, check makes sure the pool is consistent (does not contain two transa...
Definition txmempool.h:372
unsigned long size() const
Definition txmempool.h:488
Chainstate stores and provides an API to update our local knowledge of the current best chain.
Definition validation.h:699
bool IsValid() const
Definition validation.h:119
Main entry point to nanobench's benchmarking facility.
Definition nanobench.h:616
Generate a new block, without valid proof-of-work.
Definition miner.h:53
static const int COINBASE_MATURITY
Coinbase transaction outputs can only be spent after this number of new blocks (network rule).
Definition consensus.h:32
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
Definition cs_main.cpp:7
Definition init.h:28
static CTransactionRef MakeTransactionRef()
std::shared_ptr< const CTransaction > CTransactionRef
T GetRand(T nMax=std::numeric_limits< T >::max()) noexcept
Generate a uniform random integer of type T in the range [0..nMax) nMax defaults to std::numeric_limi...
Definition random.h:85
@ OP_EQUAL
Definition script.h:119
@ OP_HASH160
Definition script.h:160
@ OP_TRUE
Definition script.h:57
@ OP_DROP
Definition script.h:97
std::vector< uint8_t > ToByteVector(const T &in)
Definition script.h:42
Validation result for a transaction evaluated by MemPoolAccept (single or package).
Definition validation.h:207
const ResultType m_result_type
Result type.
Definition validation.h:218
@ VALID
Fully validated, valid.
Options struct containing options for constructing a CTxMemPool.
int check_ratio
The ratio used to determine how often sanity checks will run.
NodeContext struct containing references to chain state and connection state.
Definition context.h:43
#define LOCK2(cs1, cs2)
Definition sync.h:309
#define LOCK(cs)
Definition sync.h:306
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition time.cpp:109
MempoolAcceptResult AcceptToMemoryPool(Chainstate &active_chainstate, const CTransactionRef &tx, int64_t accept_time, bool bypass_limits, bool test_accept, unsigned int heightOverride)
Try to add a transaction to the mempool.
assert(!tx.IsCoinBase())