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>
|
2018-07-23 18:05:43 +02:00
|
|
|
#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(¬hing), &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);
|
2021-05-04 16:57:55 +02:00
|
|
|
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);
|
2021-05-04 16:57:55 +02:00
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-09 19:36:48 +02:00
|
|
|
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;
|
2019-10-09 19:36:48 +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-10-09 19:36:48 +02:00
|
|
|
assert(blocks.size() == 110);
|
|
|
|
|
|
2021-11-02 10:18:24 +01:00
|
|
|
Block block1 = blocks.at(1);
|
2019-10-09 19:36:48 +02:00
|
|
|
block1.findTransactions();
|
|
|
|
|
CTransaction root = block1.transactions().at(0).createOldTransaction();
|
|
|
|
|
|
|
|
|
|
CScript scriptPubKey = CScript() << OP_1;
|
|
|
|
|
TransactionBuilder builder;
|
|
|
|
|
builder.appendInput(root.GetHash(), 0);
|
2021-05-04 16:57:55 +02:00
|
|
|
builder.pushInputSignature(myKey, root.vout[0].scriptPubKey, root.vout[0].nValue, TransactionBuilder::ECDSA);
|
2019-10-09 19:36:48 +02:00
|
|
|
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);
|
2019-10-09 19:36:48 +02:00
|
|
|
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)
|