#include <boost/test/unit_test.hpp>
#include <chainparams.h>
#include <consensus/merkle.h>
#include <consensus/validation.h>
#include <node/miner.h>
#include <pow.h>
#include <random.h>
#include <test/util/random.h>
#include <test/util/script.h>
#include <test/util/setup_common.h>
#include <util/time.h>
#include <validation.h>
#include <validationinterface.h>
#include <thread>
using node::BlockAssembler;
namespace validation_block_tests {
struct MinerTestingSetup : public RegTestingSetup {
std::shared_ptr<CBlock> Block(const uint256& prev_hash);
std::shared_ptr<const CBlock> GoodBlock(const uint256& prev_hash);
std::shared_ptr<const CBlock> BadBlock(const uint256& prev_hash);
std::shared_ptr<CBlock> FinalizeBlock(std::shared_ptr<CBlock> pblock);
void BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks);
};
}
BOOST_FIXTURE_TEST_SUITE(validation_block_tests, MinerTestingSetup)
struct TestSubscriber final : public CValidationInterface {
uint256 m_expected_tip;
explicit TestSubscriber(uint256 tip) : m_expected_tip(tip) {}
void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override
{
BOOST_CHECK_EQUAL(m_expected_tip, pindexNew->GetBlockHash());
}
void BlockConnected(ChainstateRole role, const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
{
BOOST_CHECK_EQUAL(m_expected_tip, block->hashPrevBlock);
BOOST_CHECK_EQUAL(m_expected_tip, pindex->pprev->GetBlockHash());
m_expected_tip = block->GetHash();
}
void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override
{
BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash());
BOOST_CHECK_EQUAL(m_expected_tip, pindex->GetBlockHash());
m_expected_tip = block->hashPrevBlock;
}
};
std::shared_ptr<CBlock> MinerTestingSetup::Block(const uint256& prev_hash)
{
static int i = 0;
static uint64_t time = Params().GenesisBlock().nTime;
BlockAssembler::Options options;
options.coinbase_output_script = CScript{} << i++ << OP_TRUE;
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock();
auto pblock = std::make_shared<CBlock>(ptemplate->block);
pblock->hashPrevBlock = prev_hash;
pblock->nTime = ++time;
CMutableTransaction txCoinbase(*pblock->vtx[0]);
txCoinbase.vout.resize(2);
txCoinbase.vout[1].scriptPubKey = P2WSH_OP_TRUE;
txCoinbase.vout[1].nValue = txCoinbase.vout[0].nValue;
txCoinbase.vout[0].nValue = 0;
txCoinbase.vin[0].scriptWitness.SetNull();
const int prev_height{WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(prev_hash)->nHeight)};
txCoinbase.vin[0].scriptSig = CScript{} << prev_height + 1 << OP_0;
txCoinbase.nLockTime = static_cast<uint32_t>(prev_height);
pblock->vtx[0] = MakeTransactionRef(std::move(txCoinbase));
return pblock;
}
std::shared_ptr<CBlock> MinerTestingSetup::FinalizeBlock(std::shared_ptr<CBlock> pblock)
{
const CBlockIndex* prev_block{WITH_LOCK(::cs_main, return m_node.chainman->m_blockman.LookupBlockIndex(pblock->hashPrevBlock))};
m_node.chainman->GenerateCoinbaseCommitment(*pblock, prev_block);
pblock->hashMerkleRoot = BlockMerkleRoot(*pblock);
while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits, Params().GetConsensus())) {
++(pblock->nNonce);
}
BlockValidationState ignored;
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlockHeaders({{pblock->GetBlockHeader()}}, true, ignored));
return pblock;
}
std::shared_ptr<const CBlock> MinerTestingSetup::GoodBlock(const uint256& prev_hash)
{
return FinalizeBlock(Block(prev_hash));
}
std::shared_ptr<const CBlock> MinerTestingSetup::BadBlock(const uint256& prev_hash)
{
auto pblock = Block(prev_hash);
CMutableTransaction coinbase_spend;
coinbase_spend.vin.emplace_back(COutPoint(pblock->vtx[0]->GetHash(), 0), CScript(), 0);
coinbase_spend.vout.push_back(pblock->vtx[0]->vout[0]);
CTransactionRef tx = MakeTransactionRef(coinbase_spend);
pblock->vtx.push_back(tx);
auto ret = FinalizeBlock(pblock);
return ret;
}
void MinerTestingSetup::BuildChain(const uint256& root, int height, const unsigned int invalid_rate, const unsigned int branch_rate, const unsigned int max_size, std::vector<std::shared_ptr<const CBlock>>& blocks)
{
if (height <= 0 || blocks.size() >= max_size) return;
bool gen_invalid = m_rng.randrange(100U) < invalid_rate;
bool gen_fork = m_rng.randrange(100U) < branch_rate;
const std::shared_ptr<const CBlock> pblock = gen_invalid ? BadBlock(root) : GoodBlock(root);
blocks.push_back(pblock);
if (!gen_invalid) {
BuildChain(pblock->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
}
if (gen_fork) {
blocks.push_back(GoodBlock(root));
BuildChain(blocks.back()->GetHash(), height - 1, invalid_rate, branch_rate, max_size, blocks);
}
}
BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering)
{
std::vector<std::shared_ptr<const CBlock>> blocks;
while (blocks.size() < 50) {
blocks.clear();
BuildChain(Params().GenesisBlock().GetHash(), 100, 15, 10, 500, blocks);
}
bool ignored;
BOOST_CHECK(Assert(m_node.chainman)->ProcessNewBlock(std::make_shared<CBlock>(Params().GenesisBlock()), true, true, &ignored));
m_node.validation_signals->SyncWithValidationInterfaceQueue();
const CBlockIndex* initial_tip = nullptr;
{
LOCK(cs_main);
initial_tip = m_node.chainman->ActiveChain().Tip();
}
auto sub = std::make_shared<TestSubscriber>(initial_tip->GetBlockHash());
m_node.validation_signals->RegisterSharedValidationInterface(sub);
std::vector<std::thread> threads;
threads.reserve(10);
for (int i = 0; i < 10; i++) {
threads.emplace_back([&]() {
bool ignored;
FastRandomContext insecure;
for (int i = 0; i < 1000; i++) {
const auto& block = blocks[insecure.randrange(blocks.size() - 1)];
Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
}
for (const auto& block : blocks) {
if (block->vtx.size() == 1) {
bool processed = Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
assert(processed);
}
}
});
}
for (auto& t : threads) {
t.join();
}
m_node.validation_signals->SyncWithValidationInterfaceQueue();
m_node.validation_signals->UnregisterSharedValidationInterface(sub);
LOCK(cs_main);
BOOST_CHECK_EQUAL(sub->m_expected_tip, m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}
BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
{
bool ignored;
auto ProcessBlock = [&](std::shared_ptr<const CBlock> block) -> bool {
return Assert(m_node.chainman)->ProcessNewBlock(block, true, true, &ignored);
};
BOOST_REQUIRE(ProcessBlock(std::make_shared<CBlock>(Params().GenesisBlock())));
auto last_mined = GoodBlock(Params().GenesisBlock().GetHash());
BOOST_REQUIRE(ProcessBlock(last_mined));
for (int test_runs = 3; test_runs > 0; --test_runs) {
BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash()));
const uint256 split_hash{last_mined->hashPrevBlock};
std::vector<CTransactionRef> txs;
for (int num_txs = 22; num_txs > 0; --num_txs) {
CMutableTransaction mtx;
mtx.vin.emplace_back(COutPoint{last_mined->vtx[0]->GetHash(), 1}, CScript{});
mtx.vin[0].scriptWitness.stack.push_back(WITNESS_STACK_ELEM_OP_TRUE);
mtx.vout.push_back(last_mined->vtx[0]->vout[1]);
mtx.vout[0].nValue -= 1000;
txs.push_back(MakeTransactionRef(mtx));
last_mined = GoodBlock(last_mined->GetHash());
BOOST_REQUIRE(ProcessBlock(last_mined));
}
for (int j = COINBASE_MATURITY; j > 0; --j) {
last_mined = GoodBlock(last_mined->GetHash());
BOOST_REQUIRE(ProcessBlock(last_mined));
}
const uint256 tip_init{last_mined->GetHash()};
std::vector<std::shared_ptr<const CBlock>> reorg;
last_mined = GoodBlock(split_hash);
reorg.push_back(last_mined);
for (size_t j = COINBASE_MATURITY + txs.size() + 1; j > 0; --j) {
last_mined = GoodBlock(last_mined->GetHash());
reorg.push_back(last_mined);
}
{
LOCK(cs_main);
for (const auto& tx : txs) {
const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(tx);
BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}
{
BOOST_CHECK_EQUAL(m_node.mempool->size(), txs.size());
}
std::thread rpc_thread{[&]() {
while (true) {
LOCK(m_node.mempool->cs);
if (m_node.mempool->size() == 0) {
break;
}
assert(m_node.mempool->size() == txs.size());
continue;
}
LOCK(cs_main);
assert(tip_init != m_node.chainman->ActiveChain().Tip()->GetBlockHash());
}};
for (const auto& b : reorg) {
ProcessBlock(b);
}
BOOST_CHECK_EQUAL(last_mined->GetHash(), WITH_LOCK(Assert(m_node.chainman)->GetMutex(), return m_node.chainman->ActiveChain().Tip()->GetBlockHash()));
rpc_thread.join();
}
}
BOOST_AUTO_TEST_CASE(witness_commitment_index)
{
LOCK(Assert(m_node.chainman)->GetMutex());
CScript pubKey;
pubKey << 1 << OP_TRUE;
BlockAssembler::Options options;
options.coinbase_output_script = pubKey;
auto ptemplate = BlockAssembler{m_node.chainman->ActiveChainstate(), m_node.mempool.get(), options}.CreateNewBlock();
CBlock pblock = ptemplate->block;
CTxOut witness;
witness.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT);
witness.scriptPubKey[0] = OP_RETURN;
witness.scriptPubKey[1] = 0x24;
witness.scriptPubKey[2] = 0xaa;
witness.scriptPubKey[3] = 0x21;
witness.scriptPubKey[4] = 0xa9;
witness.scriptPubKey[5] = 0xed;
CTxOut min_plus_one = witness;
min_plus_one.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT + 1);
CTxOut invalid = witness;
invalid.scriptPubKey[0] = OP_VERIFY;
CMutableTransaction txCoinbase(*pblock.vtx[0]);
txCoinbase.vout.resize(4);
txCoinbase.vout[0] = witness;
txCoinbase.vout[1] = witness;
txCoinbase.vout[2] = min_plus_one;
txCoinbase.vout[3] = invalid;
pblock.vtx[0] = MakeTransactionRef(std::move(txCoinbase));
BOOST_CHECK_EQUAL(GetWitnessCommitmentIndex(pblock), 2);
}
BOOST_AUTO_TEST_SUITE_END()