mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
fa3c787b62
fuzz: Abort when global PRNG is used before SeedRand::ZEROS (MarcoFalke) Pull request description: This adds one more check to abort when global PRNG is used before SeedRand::ZEROS in fuzz tests. This is achieved by carving out the two remaining uses. First, `g_rng_temp_path_init`, and second the random fallback for `RANDOM_CTX_SEED`, which isn't used in fuzz tests anyway. Requested in https://github.com/bitcoin/bitcoin/pull/31521#issuecomment-2554669015 Can be tested by revertingfadd568931
and observing an abort when running the `utxo_total_supply` fuzz target. ACKs for top commit: marcofleon: ACKfa3c787b62
hodlinator: re-ACKfa3c787b62
ryanofsky: Code review ACKfa3c787b62
. This adds a new check to make that sure that RNG is never seeded during fuzzing after the RNG has been used. Together with existing checks which ensure RNG can only be seeded with zeroes during fuzzing, and that RNG must was seeded at some point if used after fuzzing, this implies it must have been seeded by zeros before being used. Tree-SHA512: 2614928d31c310309bd9021b3e5637b35f64196020fbf9409e978628799691d0efd3f4cf606be9a2db0ef60b010f890c2e70c910eaa2934a7fbf64cd1598fe22
619 lines
31 KiB
C++
619 lines
31 KiB
C++
// Copyright (c) 2011-present 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 <test/util/setup_common.h>
|
|
|
|
#include <addrman.h>
|
|
#include <banman.h>
|
|
#include <chainparams.h>
|
|
#include <common/system.h>
|
|
#include <consensus/consensus.h>
|
|
#include <consensus/params.h>
|
|
#include <consensus/validation.h>
|
|
#include <crypto/sha256.h>
|
|
#include <init.h>
|
|
#include <init/common.h>
|
|
#include <interfaces/chain.h>
|
|
#include <kernel/mempool_entry.h>
|
|
#include <logging.h>
|
|
#include <net.h>
|
|
#include <net_processing.h>
|
|
#include <node/blockstorage.h>
|
|
#include <node/chainstate.h>
|
|
#include <node/context.h>
|
|
#include <node/kernel_notifications.h>
|
|
#include <node/mempool_args.h>
|
|
#include <node/miner.h>
|
|
#include <node/peerman_args.h>
|
|
#include <node/warnings.h>
|
|
#include <noui.h>
|
|
#include <policy/fees.h>
|
|
#include <pow.h>
|
|
#include <random.h>
|
|
#include <rpc/blockchain.h>
|
|
#include <rpc/register.h>
|
|
#include <rpc/server.h>
|
|
#include <scheduler.h>
|
|
#include <script/sigcache.h>
|
|
#include <streams.h>
|
|
#include <test/util/net.h>
|
|
#include <test/util/random.h>
|
|
#include <test/util/txmempool.h>
|
|
#include <txdb.h>
|
|
#include <txmempool.h>
|
|
#include <util/chaintype.h>
|
|
#include <util/check.h>
|
|
#include <util/fs_helpers.h>
|
|
#include <util/rbf.h>
|
|
#include <util/strencodings.h>
|
|
#include <util/string.h>
|
|
#include <util/thread.h>
|
|
#include <util/threadnames.h>
|
|
#include <util/time.h>
|
|
#include <util/translation.h>
|
|
#include <util/vector.h>
|
|
#include <validation.h>
|
|
#include <validationinterface.h>
|
|
#include <walletinitinterface.h>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <stdexcept>
|
|
|
|
using namespace util::hex_literals;
|
|
using kernel::BlockTreeDB;
|
|
using node::ApplyArgsManOptions;
|
|
using node::BlockAssembler;
|
|
using node::BlockManager;
|
|
using node::KernelNotifications;
|
|
using node::LoadChainstate;
|
|
using node::RegenerateCommitments;
|
|
using node::VerifyLoadedChainstate;
|
|
|
|
const TranslateFn G_TRANSLATION_FUN{nullptr};
|
|
|
|
constexpr inline auto TEST_DIR_PATH_ELEMENT{"test_common bitcoin"}; // Includes a space to catch possible path escape issues.
|
|
/** Random context to get unique temp data dirs. Separate from m_rng, which can be seeded from a const env var */
|
|
static FastRandomContext g_rng_temp_path;
|
|
static const bool g_rng_temp_path_init{[] {
|
|
// Must be initialized before any SeedRandomForTest
|
|
Assert(!g_used_g_prng);
|
|
(void)g_rng_temp_path.rand64();
|
|
g_used_g_prng = false;
|
|
return true;
|
|
}()};
|
|
|
|
struct NetworkSetup
|
|
{
|
|
NetworkSetup()
|
|
{
|
|
Assert(SetupNetworking());
|
|
}
|
|
};
|
|
static NetworkSetup g_networksetup_instance;
|
|
|
|
void SetupCommonTestArgs(ArgsManager& argsman)
|
|
{
|
|
argsman.AddArg("-testdatadir", strprintf("Custom data directory (default: %s<random_string>)", fs::PathToString(fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / "")),
|
|
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
|
|
}
|
|
|
|
/** Test setup failure */
|
|
static void ExitFailure(std::string_view str_err)
|
|
{
|
|
std::cerr << str_err << std::endl;
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
|
|
: m_args{}
|
|
{
|
|
if constexpr (!G_FUZZING) {
|
|
SeedRandomForTest(SeedRand::FIXED_SEED);
|
|
}
|
|
m_node.shutdown_signal = &m_interrupt;
|
|
m_node.shutdown_request = [this]{ return m_interrupt(); };
|
|
m_node.args = &gArgs;
|
|
std::vector<const char*> arguments = Cat(
|
|
{
|
|
"dummy",
|
|
"-printtoconsole=0",
|
|
"-logsourcelocations",
|
|
"-logtimemicros",
|
|
"-logthreadnames",
|
|
"-loglevel=trace",
|
|
"-debug",
|
|
"-debugexclude=libevent",
|
|
"-debugexclude=leveldb",
|
|
},
|
|
opts.extra_args);
|
|
if (G_TEST_COMMAND_LINE_ARGUMENTS) {
|
|
arguments = Cat(arguments, G_TEST_COMMAND_LINE_ARGUMENTS());
|
|
}
|
|
util::ThreadRename("test");
|
|
gArgs.ClearPathCache();
|
|
{
|
|
SetupServerArgs(*m_node.args);
|
|
SetupCommonTestArgs(*m_node.args);
|
|
std::string error;
|
|
if (!m_node.args->ParseParameters(arguments.size(), arguments.data(), error)) {
|
|
m_node.args->ClearArgs();
|
|
throw std::runtime_error{error};
|
|
}
|
|
}
|
|
|
|
const std::string test_name{G_TEST_GET_FULL_NAME ? G_TEST_GET_FULL_NAME() : ""};
|
|
if (!m_node.args->IsArgSet("-testdatadir")) {
|
|
// To avoid colliding with a leftover prior datadir, and to allow
|
|
// tests, such as the fuzz tests to run in several processes at the
|
|
// same time, add a random element to the path. Keep it small enough to
|
|
// avoid a MAX_PATH violation on Windows.
|
|
const auto rand{HexStr(g_rng_temp_path.randbytes(10))};
|
|
m_path_root = fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / test_name / rand;
|
|
TryCreateDirectories(m_path_root);
|
|
} else {
|
|
// Custom data directory
|
|
m_has_custom_datadir = true;
|
|
fs::path root_dir{m_node.args->GetPathArg("-testdatadir")};
|
|
if (root_dir.empty()) ExitFailure("-testdatadir argument is empty, please specify a path");
|
|
|
|
root_dir = fs::absolute(root_dir);
|
|
m_path_lock = root_dir / TEST_DIR_PATH_ELEMENT / fs::PathFromString(test_name);
|
|
m_path_root = m_path_lock / "datadir";
|
|
|
|
// Try to obtain the lock; if unsuccessful don't disturb the existing test.
|
|
TryCreateDirectories(m_path_lock);
|
|
if (util::LockDirectory(m_path_lock, ".lock", /*probe_only=*/false) != util::LockResult::Success) {
|
|
ExitFailure("Cannot obtain a lock on test data lock directory " + fs::PathToString(m_path_lock) + '\n' + "The test executable is probably already running.");
|
|
}
|
|
|
|
// Always start with a fresh data directory; this doesn't delete the .lock file located one level above.
|
|
fs::remove_all(m_path_root);
|
|
if (!TryCreateDirectories(m_path_root)) ExitFailure("Cannot create test data directory");
|
|
|
|
// Print the test directory name if custom.
|
|
std::cout << "Test directory (will not be deleted): " << m_path_root << std::endl;
|
|
}
|
|
m_args.ForceSetArg("-datadir", fs::PathToString(m_path_root));
|
|
gArgs.ForceSetArg("-datadir", fs::PathToString(m_path_root));
|
|
|
|
SelectParams(chainType);
|
|
if (G_TEST_LOG_FUN) LogInstance().PushBackCallback(G_TEST_LOG_FUN);
|
|
InitLogging(*m_node.args);
|
|
AppInitParameterInteraction(*m_node.args);
|
|
LogInstance().StartLogging();
|
|
m_node.warnings = std::make_unique<node::Warnings>();
|
|
m_node.kernel = std::make_unique<kernel::Context>();
|
|
m_node.ecc_context = std::make_unique<ECC_Context>();
|
|
SetupEnvironment();
|
|
|
|
m_node.chain = interfaces::MakeChain(m_node);
|
|
static bool noui_connected = false;
|
|
if (!noui_connected) {
|
|
noui_connect();
|
|
noui_connected = true;
|
|
}
|
|
}
|
|
|
|
BasicTestingSetup::~BasicTestingSetup()
|
|
{
|
|
m_node.ecc_context.reset();
|
|
m_node.kernel.reset();
|
|
if constexpr (!G_FUZZING) {
|
|
SetMockTime(0s); // Reset mocktime for following tests
|
|
}
|
|
LogInstance().DisconnectTestLogger();
|
|
if (m_has_custom_datadir) {
|
|
// Only remove the lock file, preserve the data directory.
|
|
UnlockDirectory(m_path_lock, ".lock");
|
|
fs::remove(m_path_lock / ".lock");
|
|
} else {
|
|
fs::remove_all(m_path_root);
|
|
}
|
|
gArgs.ClearArgs();
|
|
}
|
|
|
|
ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts)
|
|
: BasicTestingSetup(chainType, opts)
|
|
{
|
|
const CChainParams& chainparams = Params();
|
|
|
|
// We have to run a scheduler thread to prevent ActivateBestChain
|
|
// from blocking due to queue overrun.
|
|
if (opts.setup_validation_interface) {
|
|
m_node.scheduler = std::make_unique<CScheduler>();
|
|
m_node.scheduler->m_service_thread = std::thread(util::TraceThread, "scheduler", [&] { m_node.scheduler->serviceQueue(); });
|
|
m_node.validation_signals = std::make_unique<ValidationSignals>(std::make_unique<SerialTaskRunner>(*m_node.scheduler));
|
|
}
|
|
|
|
bilingual_str error{};
|
|
m_node.mempool = std::make_unique<CTxMemPool>(MemPoolOptionsForTest(m_node), error);
|
|
Assert(error.empty());
|
|
m_node.warnings = std::make_unique<node::Warnings>();
|
|
|
|
m_node.notifications = std::make_unique<KernelNotifications>(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings));
|
|
|
|
m_make_chainman = [this, &chainparams, opts] {
|
|
Assert(!m_node.chainman);
|
|
ChainstateManager::Options chainman_opts{
|
|
.chainparams = chainparams,
|
|
.datadir = m_args.GetDataDirNet(),
|
|
.check_block_index = 1,
|
|
.notifications = *m_node.notifications,
|
|
.signals = m_node.validation_signals.get(),
|
|
.worker_threads_num = 2,
|
|
};
|
|
if (opts.min_validation_cache) {
|
|
chainman_opts.script_execution_cache_bytes = 0;
|
|
chainman_opts.signature_cache_bytes = 0;
|
|
}
|
|
const BlockManager::Options blockman_opts{
|
|
.chainparams = chainman_opts.chainparams,
|
|
.blocks_dir = m_args.GetBlocksDirPath(),
|
|
.notifications = chainman_opts.notifications,
|
|
};
|
|
m_node.chainman = std::make_unique<ChainstateManager>(*Assert(m_node.shutdown_signal), chainman_opts, blockman_opts);
|
|
LOCK(m_node.chainman->GetMutex());
|
|
m_node.chainman->m_blockman.m_block_tree_db = std::make_unique<BlockTreeDB>(DBParams{
|
|
.path = m_args.GetDataDirNet() / "blocks" / "index",
|
|
.cache_bytes = m_kernel_cache_sizes.block_tree_db,
|
|
.memory_only = true,
|
|
});
|
|
};
|
|
m_make_chainman();
|
|
}
|
|
|
|
ChainTestingSetup::~ChainTestingSetup()
|
|
{
|
|
if (m_node.scheduler) m_node.scheduler->stop();
|
|
if (m_node.validation_signals) m_node.validation_signals->FlushBackgroundCallbacks();
|
|
m_node.connman.reset();
|
|
m_node.banman.reset();
|
|
m_node.addrman.reset();
|
|
m_node.netgroupman.reset();
|
|
m_node.args = nullptr;
|
|
m_node.mempool.reset();
|
|
Assert(!m_node.fee_estimator); // Each test must create a local object, if they wish to use the fee_estimator
|
|
m_node.chainman.reset();
|
|
m_node.validation_signals.reset();
|
|
m_node.scheduler.reset();
|
|
}
|
|
|
|
void ChainTestingSetup::LoadVerifyActivateChainstate()
|
|
{
|
|
auto& chainman{*Assert(m_node.chainman)};
|
|
node::ChainstateLoadOptions options;
|
|
options.mempool = Assert(m_node.mempool.get());
|
|
options.block_tree_db_in_memory = m_block_tree_db_in_memory;
|
|
options.coins_db_in_memory = m_coins_db_in_memory;
|
|
options.wipe_block_tree_db = m_args.GetBoolArg("-reindex", false);
|
|
options.wipe_chainstate_db = m_args.GetBoolArg("-reindex", false) || m_args.GetBoolArg("-reindex-chainstate", false);
|
|
options.prune = chainman.m_blockman.IsPruneMode();
|
|
options.check_blocks = m_args.GetIntArg("-checkblocks", DEFAULT_CHECKBLOCKS);
|
|
options.check_level = m_args.GetIntArg("-checklevel", DEFAULT_CHECKLEVEL);
|
|
options.require_full_verification = m_args.IsArgSet("-checkblocks") || m_args.IsArgSet("-checklevel");
|
|
auto [status, error] = LoadChainstate(chainman, m_kernel_cache_sizes, options);
|
|
assert(status == node::ChainstateLoadStatus::SUCCESS);
|
|
|
|
std::tie(status, error) = VerifyLoadedChainstate(chainman, options);
|
|
assert(status == node::ChainstateLoadStatus::SUCCESS);
|
|
|
|
BlockValidationState state;
|
|
if (!chainman.ActiveChainstate().ActivateBestChain(state)) {
|
|
throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", state.ToString()));
|
|
}
|
|
}
|
|
|
|
TestingSetup::TestingSetup(
|
|
const ChainType chainType,
|
|
TestOpts opts)
|
|
: ChainTestingSetup(chainType, opts)
|
|
{
|
|
m_coins_db_in_memory = opts.coins_db_in_memory;
|
|
m_block_tree_db_in_memory = opts.block_tree_db_in_memory;
|
|
// Ideally we'd move all the RPC tests to the functional testing framework
|
|
// instead of unit tests, but for now we need these here.
|
|
RegisterAllCoreRPCCommands(tableRPC);
|
|
|
|
LoadVerifyActivateChainstate();
|
|
|
|
if (!opts.setup_net) return;
|
|
|
|
m_node.netgroupman = std::make_unique<NetGroupManager>(/*asmap=*/std::vector<bool>());
|
|
m_node.addrman = std::make_unique<AddrMan>(*m_node.netgroupman,
|
|
/*deterministic=*/false,
|
|
m_node.args->GetIntArg("-checkaddrman", 0));
|
|
m_node.banman = std::make_unique<BanMan>(m_args.GetDataDirBase() / "banlist", nullptr, DEFAULT_MISBEHAVING_BANTIME);
|
|
m_node.connman = std::make_unique<ConnmanTestMsg>(0x1337, 0x1337, *m_node.addrman, *m_node.netgroupman, Params()); // Deterministic randomness for tests.
|
|
PeerManager::Options peerman_opts;
|
|
ApplyArgsManOptions(*m_node.args, peerman_opts);
|
|
peerman_opts.deterministic_rng = true;
|
|
m_node.peerman = PeerManager::make(*m_node.connman, *m_node.addrman,
|
|
m_node.banman.get(), *m_node.chainman,
|
|
*m_node.mempool, *m_node.warnings,
|
|
peerman_opts);
|
|
|
|
{
|
|
CConnman::Options options;
|
|
options.m_msgproc = m_node.peerman.get();
|
|
m_node.connman->Init(options);
|
|
}
|
|
}
|
|
|
|
TestChain100Setup::TestChain100Setup(
|
|
const ChainType chain_type,
|
|
TestOpts opts)
|
|
: TestingSetup{ChainType::REGTEST, opts}
|
|
{
|
|
SetMockTime(1598887952);
|
|
constexpr std::array<unsigned char, 32> vchKey = {
|
|
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}};
|
|
coinbaseKey.Set(vchKey.begin(), vchKey.end(), true);
|
|
|
|
// Generate a 100-block chain:
|
|
this->mineBlocks(COINBASE_MATURITY);
|
|
|
|
{
|
|
LOCK(::cs_main);
|
|
assert(
|
|
m_node.chainman->ActiveChain().Tip()->GetBlockHash().ToString() ==
|
|
"571d80a9967ae599cec0448b0b0ba1cfb606f584d8069bd7166b86854ba7a191");
|
|
}
|
|
}
|
|
|
|
void TestChain100Setup::mineBlocks(int num_blocks)
|
|
{
|
|
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
|
|
for (int i = 0; i < num_blocks; i++) {
|
|
std::vector<CMutableTransaction> noTxns;
|
|
CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey);
|
|
SetMockTime(GetTime() + 1);
|
|
m_coinbase_txns.push_back(b.vtx[0]);
|
|
}
|
|
}
|
|
|
|
CBlock TestChain100Setup::CreateBlock(
|
|
const std::vector<CMutableTransaction>& txns,
|
|
const CScript& scriptPubKey,
|
|
Chainstate& chainstate)
|
|
{
|
|
BlockAssembler::Options options;
|
|
options.coinbase_output_script = scriptPubKey;
|
|
CBlock block = BlockAssembler{chainstate, nullptr, options}.CreateNewBlock()->block;
|
|
|
|
Assert(block.vtx.size() == 1);
|
|
for (const CMutableTransaction& tx : txns) {
|
|
block.vtx.push_back(MakeTransactionRef(tx));
|
|
}
|
|
RegenerateCommitments(block, *Assert(m_node.chainman));
|
|
|
|
while (!CheckProofOfWork(block.GetHash(), block.nBits, m_node.chainman->GetConsensus())) ++block.nNonce;
|
|
|
|
return block;
|
|
}
|
|
|
|
CBlock TestChain100Setup::CreateAndProcessBlock(
|
|
const std::vector<CMutableTransaction>& txns,
|
|
const CScript& scriptPubKey,
|
|
Chainstate* chainstate)
|
|
{
|
|
if (!chainstate) {
|
|
chainstate = &Assert(m_node.chainman)->ActiveChainstate();
|
|
}
|
|
|
|
CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
|
|
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
|
|
Assert(m_node.chainman)->ProcessNewBlock(shared_pblock, true, true, nullptr);
|
|
|
|
return block;
|
|
}
|
|
|
|
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());
|
|
mempool_txn.vout.reserve(outputs.size());
|
|
|
|
for (const auto& outpoint : inputs) {
|
|
mempool_txn.vin.emplace_back(outpoint, CScript(), MAX_BIP125_RBF_SEQUENCE);
|
|
}
|
|
mempool_txn.vout = outputs;
|
|
|
|
// - Add the signing key to a keystore
|
|
FillableSigningProvider keystore;
|
|
for (const auto& input_signing_key : input_signing_keys) {
|
|
keystore.AddKey(input_signing_key);
|
|
}
|
|
// - Populate a CoinsViewCache with the unspent output
|
|
CCoinsView coins_view;
|
|
CCoinsViewCache coins_cache(&coins_view);
|
|
for (const auto& input_transaction : input_transactions) {
|
|
AddCoins(coins_cache, *input_transaction.get(), input_height);
|
|
}
|
|
// 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
|
|
auto utxo_to_spend{coins_cache.GetCoin(outpoint_to_spend).value()};
|
|
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));
|
|
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, std::nullopt, std::nullopt).first;
|
|
// If submit=true, add transaction to the mempool.
|
|
if (submit) {
|
|
LOCK(cs_main);
|
|
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(mempool_txn));
|
|
assert(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
|
|
}
|
|
return mempool_txn;
|
|
}
|
|
|
|
CMutableTransaction TestChain100Setup::CreateValidMempoolTransaction(CTransactionRef input_transaction,
|
|
uint32_t input_vout,
|
|
int input_height,
|
|
CKey input_signing_key,
|
|
CScript output_destination,
|
|
CAmount output_amount,
|
|
bool submit)
|
|
{
|
|
COutPoint input{input_transaction->GetHash(), input_vout};
|
|
CTxOut output{output_amount, output_destination};
|
|
return CreateValidMempoolTransaction(/*input_transactions=*/{input_transaction},
|
|
/*inputs=*/{input},
|
|
/*input_height=*/input_height,
|
|
/*input_signing_keys=*/{input_signing_key},
|
|
/*outputs=*/{output},
|
|
/*submit=*/submit);
|
|
}
|
|
|
|
std::vector<CTransactionRef> TestChain100Setup::PopulateMempool(FastRandomContext& det_rand, size_t num_transactions, bool submit)
|
|
{
|
|
std::vector<CTransactionRef> mempool_transactions;
|
|
std::deque<std::pair<COutPoint, CAmount>> unspent_prevouts;
|
|
std::transform(m_coinbase_txns.begin(), m_coinbase_txns.end(), std::back_inserter(unspent_prevouts),
|
|
[](const auto& tx){ return std::make_pair(COutPoint(tx->GetHash(), 0), tx->vout[0].nValue); });
|
|
while (num_transactions > 0 && !unspent_prevouts.empty()) {
|
|
// The number of inputs and outputs are random, between 1 and 24.
|
|
CMutableTransaction mtx = CMutableTransaction();
|
|
const size_t num_inputs = det_rand.randrange(24) + 1;
|
|
CAmount total_in{0};
|
|
for (size_t n{0}; n < num_inputs; ++n) {
|
|
if (unspent_prevouts.empty()) break;
|
|
const auto& [prevout, amount] = unspent_prevouts.front();
|
|
mtx.vin.emplace_back(prevout, CScript());
|
|
total_in += amount;
|
|
unspent_prevouts.pop_front();
|
|
}
|
|
const size_t num_outputs = det_rand.randrange(24) + 1;
|
|
const CAmount fee = 100 * det_rand.randrange(30);
|
|
const CAmount amount_per_output = (total_in - fee) / num_outputs;
|
|
for (size_t n{0}; n < num_outputs; ++n) {
|
|
CScript spk = CScript() << CScriptNum(num_transactions + n);
|
|
mtx.vout.emplace_back(amount_per_output, spk);
|
|
}
|
|
CTransactionRef ptx = MakeTransactionRef(mtx);
|
|
mempool_transactions.push_back(ptx);
|
|
if (amount_per_output > 3000) {
|
|
// If the value is high enough to fund another transaction + fees, keep track of it so
|
|
// it can be used to build a more complex transaction graph. Insert randomly into
|
|
// unspent_prevouts for extra randomness in the resulting structures.
|
|
for (size_t n{0}; n < num_outputs; ++n) {
|
|
unspent_prevouts.emplace_back(COutPoint(ptx->GetHash(), n), amount_per_output);
|
|
std::swap(unspent_prevouts.back(), unspent_prevouts[det_rand.randrange(unspent_prevouts.size())]);
|
|
}
|
|
}
|
|
if (submit) {
|
|
LOCK2(cs_main, m_node.mempool->cs);
|
|
LockPoints lp;
|
|
auto changeset = m_node.mempool->GetChangeSet();
|
|
changeset->StageAddition(ptx, /*fee=*/(total_in - num_outputs * amount_per_output),
|
|
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
|
/*spends_coinbase=*/false, /*sigops_cost=*/4, lp);
|
|
changeset->Apply();
|
|
}
|
|
--num_transactions;
|
|
}
|
|
return mempool_transactions;
|
|
}
|
|
|
|
void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
|
|
{
|
|
LOCK2(cs_main, m_node.mempool->cs);
|
|
// Transactions in the mempool will affect the new minimum feerate.
|
|
assert(m_node.mempool->size() == 0);
|
|
// The target feerate cannot be too low...
|
|
// ...otherwise the transaction's feerate will need to be negative.
|
|
assert(target_feerate > m_node.mempool->m_opts.incremental_relay_feerate);
|
|
// ...otherwise this is not meaningful. The feerate policy uses the maximum of both feerates.
|
|
assert(target_feerate > m_node.mempool->m_opts.min_relay_feerate);
|
|
|
|
// Manually create an invalid transaction. Manually set the fee in the CTxMemPoolEntry to
|
|
// achieve the exact target feerate.
|
|
CMutableTransaction mtx = CMutableTransaction();
|
|
mtx.vin.emplace_back(COutPoint{Txid::FromUint256(m_rng.rand256()), 0});
|
|
mtx.vout.emplace_back(1 * COIN, GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)));
|
|
const auto tx{MakeTransactionRef(mtx)};
|
|
LockPoints lp;
|
|
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.
|
|
const auto tx_fee = target_feerate.GetFee(GetVirtualTransactionSize(*tx)) -
|
|
m_node.mempool->m_opts.incremental_relay_feerate.GetFee(GetVirtualTransactionSize(*tx));
|
|
{
|
|
auto changeset = m_node.mempool->GetChangeSet();
|
|
changeset->StageAddition(tx, /*fee=*/tx_fee,
|
|
/*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0,
|
|
/*spends_coinbase=*/true, /*sigops_cost=*/1, lp);
|
|
changeset->Apply();
|
|
}
|
|
m_node.mempool->TrimToSize(0);
|
|
assert(m_node.mempool->GetMinFee() == target_feerate);
|
|
}
|
|
/**
|
|
* @returns a real block (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af)
|
|
* with 9 txs.
|
|
*/
|
|
CBlock getBlock13b8a()
|
|
{
|
|
CBlock block;
|
|
DataStream stream{
|
|
"0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb6800000000000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558da2fdb261b4d4c86041b1ab1bf930901000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07044c86041b0146ffffffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf254bb5c7b949450c3e997c2dc1242487a8169507b631eb3771f2b425483fb13102c4eb5d858eef260fe70fbfae0ac00000000010000000196608ccbafa16abada902780da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a493046022100dab24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd022100fe37900e0644bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb401ffffffff0100f2052a010000001976a914f15d1921f52e4007b146dfa60f369ed2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81e48b24d98ec706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad2e6d93f089cfcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21adc6c401887f2bfd1922f11d76159cbc597fbd756a23dcbb00f4d7290141042b4e8625a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c40f1f8b8d898235e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff0280969800000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388ac80d6e34c000000001976a914f0688ba1c0d1ce182c7af6741e02658c7d4dfcd388ac000000000100000002c40297f730dd7b5a99567eb8d27b78758f607507c52292d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3889e0c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2fafd60a129ed94504c4ac7bdc67b56fe67512658b3e014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cbcba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044022068010362a13c7f9919fa832b2dee4e788f61f6f5d344a7c2a0da6ae740605658022006d1af525b9a14a35c003b78b72bd59738cd676f845d1ff3fc25049e01003614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffff01001ec4110200000043410469ab4181eceb28985b9b4e895c13fa5e68d85761b7eee311db5addef76fa8621865134a221bd01f28ec9999ee3e021e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f758e91c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b48304502207ab51be6f12a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9236102210086ae728b370e5329eead9accd880d0cb070aea0c96255fae6c4f1ddcce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac002d3101000000001976a9141befba0cdc1ad56529371864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61bc3326efd90111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37f9a505c3f1a1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b25a2ee80eb59292e52b987dad92acb0c64eced92ed9ee105ad153cdb12d001410443bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6cb46cc1a4d3cf7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffffffff0280651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a12322d3788ac00aa0271000000001976a914ea4720a7a52fc166c55ff2298e07baf70ae67e1b88ac00000000010000000586c62cd602d219bb60edb14a3e204de0705176f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf062eb993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b95600db8535bbf331b19eed8d961f7a8e54159c53675d5f69df8c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e58ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b2179010000008b483045022100be12b2937179da88599e27bb31c3525097a07cdb52422d165b3ca2f2020ffcf702200971b51f853a53d644ebae9ec8f3512e442b1bcb6c315a5b491d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff2acfcab629bbc8685792603762c921580030ba144af553d271716a95089e107b010000008b483045022100fa579a840ac258871365dd48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cfbb1b659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffdcdc6023bbc9944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd2000000008b4830450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1af03c75022010e55c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d98a9ed34c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a038fd69f06b83ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261b866f86bf6867d4558165f7c8c8aca2d86022100dc6e1f53a91de3efe8f63512850811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000010000000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023df5595602aae96ed8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc87edd1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded4026e896b213d73ca4b63f014104979b82d02226b3a4597523845754d44f13639e3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90d661a3d0a33161dab29934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695a02af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93376d6b37b5c2940655a6fcaf1c8e74237988ac0000000001000000014e3f8ef2e91349a9059cb4f01e54ab2597c1387161d3da89919f7ea6acdbb371010000008c49304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb580654d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66ddb9c694e240014104976c79848e18251612f8940875b2b08d06e6dc73b9840e8860c066b7e87432c477e9a59a453e71e6d76d5fe34058b800a098fc1740ce3012e8fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb6042034aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000"_hex,
|
|
};
|
|
stream >> TX_WITH_WITNESS(block);
|
|
return block;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const arith_uint256& num)
|
|
{
|
|
return os << num.ToString();
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const uint160& num)
|
|
{
|
|
return os << num.ToString();
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const uint256& num)
|
|
{
|
|
return os << num.ToString();
|
|
}
|