Files

577 lines
24 KiB
C++
Raw Permalink Normal View History

2018-01-15 15:26:12 +00:00
/*
* This file is part of the flowee project
2021-06-20 22:44:44 +02:00
* Copyright (C) 2017-2019 Tom Zander <tom@flowee.org>
2018-01-15 15:26:12 +00:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
2019-04-21 14:39:55 +02:00
#include "blockvalidation_tests.h"
2018-01-15 15:26:12 +00:00
2019-04-21 21:42:01 +02:00
#include <TransactionBuilder.h>
2019-04-21 14:39:55 +02:00
#include <util.h>
2020-06-05 14:45:42 +02:00
#include <random.h>
2018-01-15 15:26:12 +00:00
#include <validation/BlockValidation_p.h>
#include <server/BlocksDB.h>
2021-01-21 11:56:26 +01:00
#include <script/interpreter.h>
2019-04-21 21:42:01 +02:00
#include <utxo/UnspentOutputDatabase.h>
2019-08-24 13:20:37 +02:00
#include <WaitUntilFinishedHelper.h>
2018-01-15 15:26:12 +00:00
2018-12-30 15:33:11 +01:00
#include <boost/foreach.hpp>
2018-10-22 13:08:35 +02:00
namespace {
void nothing(){
2019-04-23 13:11:10 +02:00
logInfo() << "nothing";
2018-10-22 13:08:35 +02:00
}
// as we know that headers and final block validation happen in the strand, this
// helper method may ensure we wait long enough to allow various actions to happen.
// it typically is Ok to have a higher count than required for internal details in the BV code.
void waitForStrand(MockBlockValidation &bv, int count = 10) {
for (int i = 0; i < count; ++i) {
auto d = bv.priv().lock();
WaitUntilFinishedHelper helper(std::bind(&nothing), &d->strand);
helper.run();
}
bv.waitValidationFinished();
}
2019-06-11 22:17:09 +02:00
void waitForHeight(MockBlockValidation &bv, int height) {
2019-04-23 13:11:10 +02:00
bv.waitValidationFinished();
// Validation is async, spread over many events so the best bet to get the good result is to wait a bit.
for (int i = 0; i < 20; ++i) { // max 1 sec
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
if (bv.blockchain()->Height() == height) break;
}
}
2018-10-22 13:08:35 +02:00
}
2019-04-21 14:39:55 +02:00
TestBlockValidation::TestBlockValidation()
: TestFloweeSession("regtest")
2018-01-15 15:26:12 +00:00
{
2019-04-21 14:39:55 +02:00
}
void TestBlockValidation::reorderblocks()
{
bv->appendChain(4);
QCOMPARE(bv->blockchain()->Height(), 4);
CBlockIndex *oldBlock3 = (*bv->blockchain())[3];
2018-01-15 15:26:12 +00:00
assert(oldBlock3);
2019-04-21 14:39:55 +02:00
QCOMPARE(oldBlock3->nHeight, 3);
CBlockIndex *oldBlock4 = (*bv->blockchain())[4];
2018-01-15 15:26:12 +00:00
assert(oldBlock4);
2019-04-21 14:39:55 +02:00
QCOMPARE(oldBlock4->nHeight, 4);
QVERIFY(Blocks::DB::instance()->headerChain().Contains(oldBlock3));
QVERIFY(Blocks::DB::instance()->headerChain().Contains(oldBlock4));
2018-01-15 15:26:12 +00:00
// Now, build on top of block 3 a 2 block chain. But only register them at the headersChain
// in the Blocks::DB, so I can test reorgs.
2022-07-06 22:12:33 +02:00
PrivateKey coinbaseKey;
2022-05-11 13:46:15 +02:00
coinbaseKey.makeNewKey();
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.getPubKey()) << OP_CHECKSIG;
2021-11-02 10:18:24 +01:00
Block b4 = bv->createBlock(oldBlock3, scriptPubKey);
2018-01-15 15:26:12 +00:00
// printf("B4: %s\n", b4.createHash().ToString().c_str());
2019-04-21 14:39:55 +02:00
QVERIFY(b4.previousBlockId() == *oldBlock3->phashBlock);
std::shared_ptr<BlockValidationState> state4(new BlockValidationState(bv->priv(), b4));
2018-01-15 15:26:12 +00:00
// let it create me a CBlockIndex
2019-04-21 14:39:55 +02:00
bv->priv().lock()->createBlockIndexFor(state4);
QCOMPARE(state4->m_blockIndex->nHeight, 4);
2018-01-15 15:26:12 +00:00
// work around optimization of phashblock coming from the hash table.
uint256 hash4 = state4->m_block.createHash();
state4->m_blockIndex->phashBlock = &hash4;
bool changed = Blocks::DB::instance()->appendHeader(state4->m_blockIndex);
// no reorgs yet.
2019-04-21 14:39:55 +02:00
QCOMPARE(changed, false);
QVERIFY(Blocks::DB::instance()->headerChain().Contains(oldBlock3));
QVERIFY(Blocks::DB::instance()->headerChain().Contains(oldBlock4));
QCOMPARE((int) Blocks::DB::instance()->headerChainTips().size(), 2);
2018-01-15 15:26:12 +00:00
// The method that does reorgs is the BlocksValidtionPrivate::prepareChainForBlock()
// We now have two chains as known by the headersChain.
// the tips have exactly the same POW and as such the new chain should not cause a reorg.
// (first seen principle)
2019-04-21 14:39:55 +02:00
bv->priv().lock()->prepareChain();
QCOMPARE(bv->blockchain()->Height(), 4);
QCOMPARE((*bv->blockchain())[3], oldBlock3); // unchanged.
QCOMPARE((*bv->blockchain())[4], oldBlock4);
2018-01-15 15:26:12 +00:00
2021-11-02 10:18:24 +01:00
Block b5 = bv->createBlock(state4->m_blockIndex, scriptPubKey);
2018-01-15 15:26:12 +00:00
// printf("B5: %s\n", b5.createHash().ToString().c_str());
2019-04-21 14:39:55 +02:00
QVERIFY(b5.previousBlockId() == *state4->m_blockIndex->phashBlock);
std::shared_ptr<BlockValidationState> state5(new BlockValidationState(bv->priv(), b5));
bv->priv().lock()->createBlockIndexFor(state5);
QCOMPARE(state5->m_blockIndex->pprev, state4->m_blockIndex);
2018-01-15 15:26:12 +00:00
uint256 hash5 = state5->m_block.createHash();
state5->m_blockIndex->phashBlock = &hash5;
changed = Blocks::DB::instance()->appendHeader(state5->m_blockIndex);
2019-04-21 14:39:55 +02:00
QCOMPARE(changed, true);
QCOMPARE((int) Blocks::DB::instance()->headerChainTips().size(), 2);
QVERIFY(Blocks::DB::instance()->headerChain().Contains(state4->m_blockIndex));
QVERIFY(Blocks::DB::instance()->headerChain().Contains(state5->m_blockIndex));
2018-01-15 15:26:12 +00:00
// We should now get a simple removal of block 4 from the original chain because our
// new chain has more POW.
2019-04-21 14:39:55 +02:00
auto d = bv->priv().lock(); // (make sure to call prepareChain in the strand, and avoid an assert)
2018-10-22 13:08:35 +02:00
WaitUntilFinishedHelper helper(std::bind(&ValidationEnginePrivate::prepareChain, d), &d->strand);
helper.run();
2019-04-21 14:39:55 +02:00
QCOMPARE(bv->blockchain()->Height(), 3);
QCOMPARE((*bv->blockchain())[3], oldBlock3); // unchanged.
2018-09-19 21:39:13 +02:00
CBlockIndex *null = nullptr;
2019-04-21 14:39:55 +02:00
QCOMPARE((*bv->blockchain())[4], null);
2018-01-15 15:26:12 +00:00
2019-04-21 14:39:55 +02:00
bv->shutdown(); // avoid our validation-states being deleted here causing issues.
2018-01-15 15:26:12 +00:00
}
2019-04-21 14:39:55 +02:00
void TestBlockValidation::reorderblocks2()
2018-01-15 15:26:12 +00:00
{
2019-04-21 14:39:55 +02:00
bv->appendChain(20);
QCOMPARE(bv->blockchain()->Height(), 20);
2018-01-15 15:26:12 +00:00
// create a chain of 8 blocks, forked off after 11.
2019-04-21 14:39:55 +02:00
CBlockIndex *oldBlock11 = (*bv->blockchain())[11];
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->createChain(oldBlock11, 10);
2019-04-21 14:39:55 +02:00
QCOMPARE(blocks.size(), (size_t) 10);
2021-11-02 10:18:24 +01:00
for (const Block &block : blocks) {
2019-04-21 14:39:55 +02:00
auto future = bv->addBlock(block, Validation::SaveGoodToDisk, nullptr).start();
future.waitUntilFinished();
QCOMPARE(future.error(), std::string());
2018-01-15 15:26:12 +00:00
}
2019-06-11 22:17:09 +02:00
QTRY_COMPARE(bv->blockchain()->Height(), 21);
2019-04-21 14:39:55 +02:00
QCOMPARE(oldBlock11, (*bv->blockchain())[11]);
QVERIFY(*(*bv->blockchain())[21]->phashBlock == blocks.back().createHash());
2018-01-15 15:26:12 +00:00
}
2019-04-21 14:39:55 +02:00
void TestBlockValidation::detectOrder()
2018-01-15 15:26:12 +00:00
{
// create a chain of 20 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->createChain(bv->blockchain()->Tip(), 20);
2018-01-15 15:26:12 +00:00
// add them all, in reverse order, in order to test if the code is capable of finding the proper ordering of the blocks
2021-11-02 10:18:24 +01:00
BOOST_REVERSE_FOREACH (const Block &block, blocks) {
2019-04-21 14:39:55 +02:00
bv->addBlock(block, Validation::SaveGoodToDisk, nullptr);
2018-01-15 15:26:12 +00:00
}
2019-06-11 22:17:09 +02:00
QTRY_COMPARE(bv->blockchain()->Height(), 20);
2018-01-15 15:26:12 +00:00
}
2021-11-02 10:18:24 +01:00
Block TestBlockValidation::createHeader(const Block &full) const
2019-04-21 14:39:55 +02:00
{
2021-11-02 10:18:24 +01:00
return Block(Streaming::ConstBuffer(full.data().internal_buffer(),
2018-01-15 15:26:12 +00:00
full.data().begin(), full.data().begin() + 80));
}
2019-04-21 14:39:55 +02:00
void TestBlockValidation::detectOrder2()
2018-01-15 15:26:12 +00:00
{
// create a chain of 10 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->createChain(bv->blockchain()->Tip(), 10);
2018-01-15 15:26:12 +00:00
// replace one block with a block header.
2021-11-02 10:18:24 +01:00
Block full = blocks[8];
Block header = createHeader(full);
2018-01-15 15:26:12 +00:00
blocks[8] = header;
2021-11-02 10:18:24 +01:00
for (const Block &block : blocks) {
2019-04-21 14:39:55 +02:00
bv->addBlock(block, Validation::SaveGoodToDisk, nullptr);
2018-01-15 15:26:12 +00:00
}
2019-04-29 10:25:29 +02:00
waitForHeight(*bv, 8);
2019-04-21 14:39:55 +02:00
bv->addBlock(full, Validation::SaveGoodToDisk, nullptr).start().waitUntilFinished();
2018-10-22 13:08:35 +02:00
// now we have processed 8, it will continue to process 9 in a different thread.
2019-04-23 13:11:10 +02:00
waitForHeight(*bv, 10);
2019-04-21 14:39:55 +02:00
QCOMPARE(bv->blockchain()->Height(), 10);
2018-01-15 15:26:12 +00:00
// now again, but with a bigger gap than 1
2019-04-21 14:39:55 +02:00
blocks = bv->createChain(bv->blockchain()->Tip(), 10);
2021-11-02 10:18:24 +01:00
std::vector<Block> copy(blocks);
2018-09-19 21:39:13 +02:00
for (size_t i = 3; i < 7; ++i) {
2018-01-15 15:26:12 +00:00
blocks[i] = createHeader(blocks[i]);
}
2021-11-02 10:18:24 +01:00
for (const Block &block : blocks) {
2019-04-21 14:39:55 +02:00
bv->addBlock(block, Validation::SaveGoodToDisk, nullptr);
2018-01-15 15:26:12 +00:00
}
2019-04-29 10:25:29 +02:00
waitForHeight(*bv, 13);
2019-04-21 14:39:55 +02:00
QCOMPARE(bv->blockchain()->Height(), 13);
2018-01-15 15:26:12 +00:00
// add them again, in reverse order, in order to test if the code is capable of finding the proper ordering of the blocks
2021-11-02 10:18:24 +01:00
BOOST_REVERSE_FOREACH (const Block &block, copy) {
2019-04-21 14:39:55 +02:00
bv->addBlock(block, Validation::SaveGoodToDisk, nullptr);
2018-01-15 15:26:12 +00:00
}
2019-04-23 13:11:10 +02:00
waitForHeight(*bv, 20);
2019-04-21 14:39:55 +02:00
QCOMPARE(bv->blockchain()->Height(), 20);
2018-01-15 15:26:12 +00:00
}
2019-04-21 14:39:55 +02:00
void TestBlockValidation::duplicateInput()
2018-09-19 21:39:13 +02:00
{
2022-07-06 22:12:33 +02:00
PrivateKey coinbaseKey;
2018-09-19 21:39:13 +02:00
// create a chain of 101 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(101, coinbaseKey);
2018-09-19 21:39:13 +02:00
assert(blocks.size() == 101);
CMutableTransaction newTx;
newTx.vout.resize(1);
newTx.vout[0].nValue = 11 * CENT;
2022-05-11 13:46:15 +02:00
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.getPubKey()) << OP_CHECKSIG;
2018-09-19 21:39:13 +02:00
newTx.vout[0].scriptPubKey = scriptPubKey;
CTxIn input;
input.prevout.n = 0;
input.prevout.hash = blocks.front().createHash();
newTx.vin.push_back(input);
newTx.vin.push_back(input); // duplicate input
// Sign
std::vector<unsigned char> vchSig;
uint256 hash = SignatureHash(scriptPubKey, newTx, 0, 50 * COIN, SIGHASH_ALL | SIGHASH_FORKID, SCRIPT_ENABLE_SIGHASH_FORKID);
2021-04-19 15:45:02 +02:00
QVERIFY(coinbaseKey.signECDSA(hash, vchSig));
2018-09-19 21:39:13 +02:00
vchSig.push_back((unsigned char)SIGHASH_ALL + SIGHASH_FORKID);
newTx.vin[0].scriptSig << vchSig;
newTx.vin[1].scriptSig << vchSig;
2021-11-02 10:18:24 +01:00
Block newBlock = bv->createBlock(bv->blockchain()->Tip());
2018-09-19 21:39:13 +02:00
{
2021-11-02 09:36:09 +01:00
MutableBlock block = newBlock.createOldBlock();
2018-09-19 21:39:13 +02:00
block.vtx.push_back(newTx);
2021-11-02 10:18:24 +01:00
newBlock = Block::fromOldBlock(block);
2019-04-21 14:39:55 +02:00
QCOMPARE((int) block.vtx.size(), 2);
2018-09-19 21:39:13 +02:00
}
2019-04-21 14:39:55 +02:00
auto future = bv->addBlock(newBlock, Validation::SaveGoodToDisk);
2018-09-19 21:39:13 +02:00
future.setCheckPoW(false);
future.setCheckMerkleRoot(false);
future.start();
future.waitUntilFinished();
2019-04-21 14:39:55 +02:00
QCOMPARE(future.error(), std::string("bad-txns-inputs-duplicate"));
2018-09-19 21:39:13 +02:00
}
2018-11-13 15:03:04 +01:00
// this only works if the input is a p2pkh script!
2022-07-06 22:12:33 +02:00
CTransaction TestBlockValidation::splitCoins(const Tx &inTx, int inIndex, const PrivateKey &from, const PrivateKey &to, int outputCount) const
2018-11-13 15:03:04 +01:00
{
assert(outputCount > 0);
assert(inIndex >= 0);
2019-04-21 21:42:01 +02:00
// logInfo() << inTx.createHash();
2018-11-13 15:03:04 +01:00
Tx::Output prevOut = inTx.output(inIndex);
assert(prevOut.outputValue > 0);
const uint64_t outAmount = prevOut.outputValue / outputCount;
assert(outAmount > 5);
CMutableTransaction newTx;
CTxIn input;
input.prevout.n = inIndex;
input.prevout.hash = inTx.createHash();
newTx.vin.push_back(input);
2022-05-11 13:46:15 +02:00
const CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(to.getPubKey().getKeyId())
2018-11-13 15:03:04 +01:00
<< OP_EQUALVERIFY << OP_CHECKSIG;
newTx.vout.resize(outputCount);
for (int i = 0; i < outputCount; ++i) {
newTx.vout[i].nValue = outAmount;
newTx.vout[i].scriptPubKey = scriptPubKey;
}
// Sign
const int nHashType = SIGHASH_ALL | SIGHASH_FORKID;
2026-05-13 16:43:23 +02:00
const uint256 sigHash = SignatureHash(prevOut.outputScript(), newTx, 0, prevOut.outputValue, nHashType, SCRIPT_ENABLE_SIGHASH_FORKID);
2018-11-13 15:03:04 +01:00
std::vector<unsigned char> vchSig;
2021-04-19 15:45:02 +02:00
bool ok = from.signECDSA(sigHash, vchSig);
2018-11-13 15:03:04 +01:00
assert(ok);
vchSig.push_back((unsigned char)nHashType);
newTx.vin[0].scriptSig << vchSig;
2022-05-11 13:46:15 +02:00
newTx.vin[0].scriptSig << ToByteVector(from.getPubKey());
2018-11-13 15:03:04 +01:00
return newTx;
}
2019-04-21 14:39:55 +02:00
void TestBlockValidation::CTOR()
2018-11-13 15:03:04 +01:00
{
2019-04-21 14:39:55 +02:00
auto priv = bv->priv().lock();
2018-11-13 15:03:04 +01:00
priv->tipFlags.hf201811Active = true;
2022-07-06 22:12:33 +02:00
PrivateKey myKey;
2018-11-13 15:03:04 +01:00
// create a chain of 101 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(110, myKey, MockBlockValidation::FullOutScript);
2018-11-13 15:03:04 +01:00
assert(blocks.size() == 110);
2021-11-02 10:18:24 +01:00
Block block1 = blocks.at(1);
2018-11-13 15:03:04 +01:00
block1.findTransactions();
const int OUTPUT_COUNT = 100;
std::vector<CTransaction> txs;
CTransaction root = splitCoins(block1.transactions().at(0),
0, myKey, myKey, OUTPUT_COUNT);
txs.push_back(root);
for (int i = 1; i < 5; ++i) {
txs.push_back(splitCoins(Tx::fromOldTransaction(root), i, myKey, myKey, 10));
}
for (size_t i = 0; i < txs.size(); ++i) {
// logDebug() << "tx" << i << txs.at(i).GetHash() << "in" << txs.at(i).vin.size()
// << "out" << txs.at(i).vout.size();
}
2022-07-06 22:12:33 +02:00
PrivateKey coinbaseKey;
2022-05-11 13:46:15 +02:00
coinbaseKey.makeNewKey();
2018-11-13 15:03:04 +01:00
CScript scriptPubKey;
2022-05-11 13:46:15 +02:00
scriptPubKey << ToByteVector(coinbaseKey.getPubKey()) << OP_CHECKSIG;
2021-11-02 10:18:24 +01:00
Block unsortedBlock = bv->createBlock(bv->blockchain()->Tip(), scriptPubKey, txs);
2018-11-13 15:03:04 +01:00
2019-04-21 14:39:55 +02:00
auto future = bv->addBlock(unsortedBlock, Validation::SaveGoodToDisk).start();
2018-11-13 15:03:04 +01:00
future.waitUntilFinished();
2019-04-21 14:39:55 +02:00
QCOMPARE(std::string("tx-ordering-not-CTOR"), future.error());
2018-11-13 15:03:04 +01:00
// sort the transactions and then mine it again.
2018-11-20 15:27:38 +01:00
std::sort(txs.begin(), txs.end(), &CTransaction::sortTxByTxId);
2021-11-02 10:18:24 +01:00
Block sortedBlock = bv->createBlock(bv->blockchain()->Tip(), scriptPubKey, txs);
2019-04-21 14:39:55 +02:00
future = bv->addBlock(sortedBlock, Validation::SaveGoodToDisk).start();
2018-11-13 15:03:04 +01:00
future.waitUntilFinished();
// I intended the actual validation to go fully Ok, but I get some signature failures.
2019-04-21 14:39:55 +02:00
QVERIFY("tx-ordering-not-CTOR" != future.error());
QVERIFY("missing-inputs" != future.error());
2018-11-13 15:03:04 +01:00
}
2019-04-21 21:42:01 +02:00
void TestBlockValidation::rollback()
{
auto priv = bv->priv().lock(); // enable CTOR
priv->tipFlags.hf201811Active = true;
2022-07-06 22:12:33 +02:00
PrivateKey myKey;
2019-04-21 21:42:01 +02:00
// create a chain of 101 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(110, myKey, MockBlockValidation::FullOutScript);
2019-04-21 21:42:01 +02:00
assert(blocks.size() == 110);
2021-11-02 10:18:24 +01:00
Block block1 = blocks.at(1);
2019-04-21 21:42:01 +02:00
block1.findTransactions();
// mine block to create some more inputs that are not coinbases
std::vector<CTransaction> txs;
CTransaction root = splitCoins(block1.transactions().at(0), 0, myKey, myKey, 3);
txs.push_back(root);
// dummy coinbasekey
CScript scriptPubKey;
scriptPubKey << OP_TRUE;
2021-11-02 10:18:24 +01:00
Block block = bv->createBlock(bv->blockchain()->Tip(), scriptPubKey, txs);
2019-04-21 21:42:01 +02:00
auto future = bv->addBlock(block, Validation::SaveGoodToDisk).start();
future.waitUntilFinished();
QCOMPARE(future.error(), std::string());
QCOMPARE(bv->blockchain()->Height(), 111);
// now, make a block that spends those 3 outputs just created but also spends various
// outputs created in the same block.
txs.clear();
2022-07-06 21:52:47 +02:00
const KeyId bitcoinAddress = myKey.getPubKey().getKeyId();
2019-04-21 21:42:01 +02:00
for (size_t i = 0; i < root.vout.size(); ++i) {
{
TransactionBuilder builder;
builder.appendInput(root.GetHash(), i);
builder.pushInputSignature(myKey, root.vout[i].scriptPubKey, root.vout[i].nValue, TransactionBuilder::ECDSA);
2019-04-21 21:42:01 +02:00
builder.appendOutput(root.vout[i].nValue - 1000);
builder.pushOutputPay2Address(bitcoinAddress);
txs.push_back(builder.createTransaction().createOldTransaction());
}
2020-06-05 14:45:42 +02:00
for (int x = GetRandInt(4); x > 0; --x) {
2019-04-21 21:42:01 +02:00
TransactionBuilder builder;
auto lastTx = txs.back();
builder.appendInput(lastTx.GetHash(), 0);
builder.pushInputSignature(myKey, lastTx.vout[0].scriptPubKey, lastTx.vout[0].nValue, TransactionBuilder::ECDSA);
2019-04-21 21:42:01 +02:00
builder.appendOutput(lastTx.vout[0].nValue - 1000);
builder.pushOutputPay2Address(bitcoinAddress);
txs.push_back(builder.createTransaction().createOldTransaction());
}
}
auto utxo = bv->mempool()->utxo();
// the same code we use to check at the end of the method.
QCOMPARE(bv->blockchain()->Height(), 111);
for (size_t i = 0; i < root.vout.size(); ++i) {
auto result = utxo->find(root.GetHash(), i);
QVERIFY(result.isValid());
}
for (size_t i = 0; i < txs.size(); ++i) {
auto result = utxo->find(txs[i].GetHash(), 0);
QVERIFY(!result.isValid());
}
// append tx's as block
std::sort(txs.begin(), txs.end(), &CTransaction::sortTxByTxId);
block = bv->createBlock(bv->blockchain()->Tip(), scriptPubKey, txs);
future = bv->addBlock(block, Validation::SaveGoodToDisk).start();
future.waitUntilFinished();
QCOMPARE(future.error(), std::string());
QCOMPARE(bv->blockchain()->Height(), 112);
// now, the rollback should realize which inputs come from the same block and make sure those are not
// re-added to the mempool.
block.findTransactions();
QCOMPARE(block.transactions().size(), txs.size() + 1);
bool clean = false, error = false;
2025-02-11 19:41:22 +01:00
priv->strand.post(std::bind(&ValidationEnginePrivate::disconnectTip, priv, bv->blockchain()->Tip(), &clean, &error), std::allocator<void>());
2019-04-21 21:42:01 +02:00
waitForStrand(*bv);
QCOMPARE(clean, true);
QCOMPARE(error, false);
// same code to check as above before we added the block
QCOMPARE(bv->blockchain()->Height(), 111);
for (size_t i = 0; i < root.vout.size(); ++i) {
auto result = utxo->find(root.GetHash(), i);
QVERIFY(result.isValid());
}
for (size_t i = 0; i < txs.size(); ++i) {
auto result = utxo->find(txs[i].GetHash(), 0);
QVERIFY(!result.isValid());
}
}
void TestBlockValidation::minimalPush()
{
auto priv = bv->priv().lock(); // enable minimalPush being a consensus rule
priv->tipFlags.hf201911Active = true;
2022-07-06 22:12:33 +02:00
PrivateKey myKey;
// create a chain of 101 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(110, myKey, MockBlockValidation::FullOutScript);
assert(blocks.size() == 110);
2021-11-02 10:18:24 +01:00
Block block1 = blocks.at(1);
block1.findTransactions();
CTransaction root = block1.transactions().at(0).createOldTransaction();
CScript scriptPubKey = CScript() << OP_1;
TransactionBuilder builder;
builder.appendInput(root.GetHash(), 0);
builder.pushInputSignature(myKey, root.vout[0].scriptPubKey, root.vout[0].nValue, TransactionBuilder::ECDSA);
builder.appendOutput(20 * COIN);
builder.pushOutputScript(scriptPubKey);
CMutableTransaction tx = builder.createTransaction().createOldTransaction();
// at this point the tx is perfectly Ok and should pass.
// Lets change the input script to violate the minimal push and see if the system detects this.
CScript inputScript = tx.vin[0].scriptSig;
QByteArray byteArray(reinterpret_cast<char*>(&inputScript[0]), inputScript.size());
byteArray.insert(0, OP_PUSHDATA1);
tx.vin[0].scriptSig.assign(byteArray.begin(), byteArray.end());
// mine block with this adjusted transaction to find out if its rejected by consensus rules.
std::vector<CTransaction> txs;
txs.push_back(tx);
2021-11-02 10:18:24 +01:00
Block block = bv->createBlock(bv->blockchain()->Tip(), scriptPubKey, txs);
auto future = bv->addBlock(block, Validation::SaveGoodToDisk).start();
future.waitUntilFinished();
QCOMPARE(future.error(), std::string("non-mandatory-script-verify-flag (Data push larger than necessary)"));
QCOMPARE(bv->blockchain()->Height(), 110);
}
2020-11-18 22:08:00 +01:00
void TestBlockValidation::manualAdjustments()
{
2022-07-06 22:12:33 +02:00
PrivateKey coinbaseKey;
2020-11-18 22:08:00 +01:00
// create a chain of 101 blocks.
2021-11-02 10:18:24 +01:00
std::vector<Block> blocks = bv->appendChain(15, coinbaseKey);
2020-11-18 22:08:00 +01:00
assert(blocks.size() == 15);
QCOMPARE(Blocks::DB::instance()->headerChain().Height(), 15);
// do an invalidateblock of the last block.
auto tip = Blocks::Index::get(blocks.back().createHash());
QVERIFY(tip);
QCOMPARE(tip->nHeight, 15);
QCOMPARE(Blocks::DB::instance()->headerChain().Tip(), tip);
bv->invalidateBlock(tip);
QCOMPARE(Blocks::DB::instance()->headerChain().Height(), 14);
QCOMPARE(Blocks::DB::instance()->headerChain().Tip(), tip->pprev);
auto utxo = bv->mempool()->utxo();
QVERIFY(utxo->blockIdHasFailed(tip->GetBlockHash()));
// do a reconsiderBlock of the same. Should undo the work.
Blocks::Index::reconsiderBlock(tip, utxo);
QCOMPARE(utxo->blockIdHasFailed(tip->GetBlockHash()), false);
// Do what the RPC reconsiderBlock does: try to validate it again.
Validation::Settings future = bv->addBlock(blocks.back(), 0);
future.start();
future.waitUntilFinished();
QCOMPARE(Blocks::DB::instance()->headerChain().Height(), 15);
QCOMPARE(Blocks::DB::instance()->headerChain().Tip(), tip);
auto block13 = Blocks::Index::get(blocks.at(12).createHash());
QVERIFY(block13);
QCOMPARE(block13->nHeight, 13);
QVERIFY(Blocks::DB::instance()->headerChain().Contains(block13));
bv->invalidateBlock(block13);
QCOMPARE(Blocks::DB::instance()->headerChain().Height(), 12);
QCOMPARE(Blocks::DB::instance()->headerChain().Tip(), block13->pprev);
QVERIFY(utxo->blockIdHasFailed(block13->GetBlockHash()));
QCOMPARE(utxo->blockIdHasFailed(tip->GetBlockHash()), false);
QCOMPARE(utxo->blockIdHasFailed(block13->pprev->GetBlockHash()), false);
// do a reconsiderBlock of block 14. Should still redo the work.
auto block14 = Blocks::Index::get(blocks.at(13).createHash());
QVERIFY(block14);
QCOMPARE(block14->nHeight, 14);
Blocks::Index::reconsiderBlock(block14, utxo);
QCOMPARE(utxo->blockIdHasFailed(tip->GetBlockHash()), false);
QCOMPARE(utxo->blockIdHasFailed(block13->GetBlockHash()), false);
QCOMPARE(utxo->blockIdHasFailed(block14->GetBlockHash()), false);
// Do what the RPC reconsiderBlock does: try to validate it again.
future = bv->addBlock(blocks.at(14), 0);
future.start();
future.waitUntilFinished();
QCOMPARE(Blocks::DB::instance()->headerChain().Height(), 15);
QCOMPARE(Blocks::DB::instance()->headerChain().Tip(), tip);
}
2021-02-19 13:29:08 +01:00
void TestBlockValidation::testBlockIndex()
{
CBlockIndex index;
QCOMPARE(index.nStatus, 0);
index.RaiseValidity(BLOCK_VALID_HEADER);
QCOMPARE(index.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TREE), false);
QCOMPARE(index.IsValid(BLOCK_VALID_TRANSACTIONS), false);
QCOMPARE(index.IsValid(BLOCK_VALID_CHAIN), false);
QCOMPARE(index.IsValid(BLOCK_VALID_SCRIPTS), false);
QCOMPARE(index.nStatus, BLOCK_VALID_HEADER);
index.RaiseValidity(BLOCK_VALID_TREE);
QCOMPARE(index.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TREE), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TRANSACTIONS), false);
QCOMPARE(index.IsValid(BLOCK_VALID_CHAIN), false);
QCOMPARE(index.IsValid(BLOCK_VALID_SCRIPTS), false);
QCOMPARE(index.nStatus, BLOCK_VALID_TREE);
index.nStatus |= BLOCK_HAVE_DATA;
QCOMPARE(index.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TREE), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TRANSACTIONS), false);
QCOMPARE(index.IsValid(BLOCK_VALID_CHAIN), false);
QCOMPARE(index.IsValid(BLOCK_VALID_SCRIPTS), false);
QCOMPARE(index.nStatus, BLOCK_VALID_TREE | BLOCK_HAVE_DATA);
index.nStatus |= BLOCK_HAVE_UNDO;
QCOMPARE(index.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TREE), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TRANSACTIONS), false);
QCOMPARE(index.IsValid(BLOCK_VALID_CHAIN), false);
QCOMPARE(index.IsValid(BLOCK_VALID_SCRIPTS), false);
QCOMPARE(index.nStatus, BLOCK_VALID_TREE | BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
index.RaiseValidity(BLOCK_VALID_CHAIN);
QCOMPARE(index.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TREE), true);
QCOMPARE(index.IsValid(BLOCK_VALID_TRANSACTIONS), true);
QCOMPARE(index.IsValid(BLOCK_VALID_CHAIN), true);
QCOMPARE(index.IsValid(BLOCK_VALID_SCRIPTS), false);
QCOMPARE(index.nStatus, BLOCK_VALID_CHAIN | BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO);
CBlockIndex index2;
index2.RaiseValidity(BLOCK_VALID_SCRIPTS);
QCOMPARE(index2.IsValid(BLOCK_VALID_HEADER), true);
QCOMPARE(index2.IsValid(BLOCK_VALID_TREE), true);
QCOMPARE(index2.IsValid(BLOCK_VALID_TRANSACTIONS), true);
QCOMPARE(index2.IsValid(BLOCK_VALID_CHAIN), true);
QCOMPARE(index2.IsValid(BLOCK_VALID_SCRIPTS), true);
}
2019-04-21 14:39:55 +02:00
QTEST_MAIN(TestBlockValidation)