Skip to content

Commit 517e6dd

Browse files
gavinandresensipa
authored andcommitted
Unit test doublespends in new blocks
As suggested by Greg Maxwell-- unit test to make sure a block with a double-spend in it doesn't pass validation if half of the double-spend is already in the memory pool (so full-blown transaction validation is skipped) when the block is received.
1 parent 17b1142 commit 517e6dd

File tree

5 files changed

+180
-6
lines changed

5 files changed

+180
-6
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ BITCOIN_TESTS =\
7373
test/test_bitcoin.h \
7474
test/timedata_tests.cpp \
7575
test/transaction_tests.cpp \
76+
test/txvalidationcache_tests.cpp \
7677
test/uint256_tests.cpp \
7778
test/univalue_tests.cpp \
7879
test/util_tests.cpp

src/test/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,16 @@ uint256_tests.cpp.
1818

1919
For further reading, I found the following website to be helpful in
2020
explaining how the boost unit test framework works:
21-
[http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/](http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/).
21+
[http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/](http://www.alittlemadness.com/2009/03/31/c-unit-testing-with-boosttest/).
22+
23+
test_bitcoin has some built-in command-line arguments; for
24+
example, to run just the getarg_tests verbosely:
25+
26+
test_bitcoin --log_level=all --run_test=getarg_tests
27+
28+
... or to run just the doubledash test:
29+
30+
test_bitcoin --run_test=getarg_tests/doubledash
31+
32+
Run test_bitcoin --help for the full list.
33+

src/test/test_bitcoin.cpp

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#include "test_bitcoin.h"
88

99
#include "chainparams.h"
10+
#include "consensus/consensus.h"
11+
#include "consensus/validation.h"
1012
#include "key.h"
1113
#include "main.h"
14+
#include "miner.h"
15+
#include "pubkey.h"
1216
#include "random.h"
1317
#include "txdb.h"
1418
#include "ui_interface.h"
@@ -28,20 +32,22 @@ CWallet* pwalletMain;
2832
extern bool fPrintToConsole;
2933
extern void noui_connect();
3034

31-
BasicTestingSetup::BasicTestingSetup()
35+
BasicTestingSetup::BasicTestingSetup(CBaseChainParams::Network network)
3236
{
3337
ECC_Start();
3438
SetupEnvironment();
3539
fPrintToDebugLog = false; // don't want to write to debug.log file
3640
fCheckBlockIndex = true;
37-
SelectParams(CBaseChainParams::MAIN);
41+
SelectParams(network);
42+
noui_connect();
3843
}
44+
3945
BasicTestingSetup::~BasicTestingSetup()
4046
{
4147
ECC_Stop();
4248
}
4349

44-
TestingSetup::TestingSetup()
50+
TestingSetup::TestingSetup(CBaseChainParams::Network network) : BasicTestingSetup(network)
4551
{
4652
#ifdef ENABLE_WALLET
4753
bitdb.MakeMock();
@@ -87,6 +93,51 @@ TestingSetup::~TestingSetup()
8793
boost::filesystem::remove_all(pathTemp);
8894
}
8995

96+
TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST)
97+
{
98+
// Generate a 100-block chain:
99+
coinbaseKey.MakeNewKey(true);
100+
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
101+
for (int i = 0; i < COINBASE_MATURITY; i++)
102+
{
103+
std::vector<CMutableTransaction> noTxns;
104+
CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey);
105+
coinbaseTxns.push_back(b.vtx[0]);
106+
}
107+
}
108+
109+
//
110+
// Create a new block with just given transactions, coinbase paying to
111+
// scriptPubKey, and try to add it to the current chain.
112+
//
113+
CBlock
114+
TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
115+
{
116+
CBlockTemplate *pblocktemplate = CreateNewBlock(scriptPubKey);
117+
CBlock& block = pblocktemplate->block;
118+
119+
// Replace mempool-selected txns with just coinbase plus passed-in txns:
120+
block.vtx.resize(1);
121+
BOOST_FOREACH(const CMutableTransaction& tx, txns)
122+
block.vtx.push_back(tx);
123+
// IncrementExtraNonce creates a valid coinbase and merkleRoot
124+
unsigned int extraNonce = 0;
125+
IncrementExtraNonce(&block, chainActive.Tip(), extraNonce);
126+
127+
while (!CheckProofOfWork(block.GetHash(), block.nBits, Params(CBaseChainParams::REGTEST).GetConsensus())) ++block.nNonce;
128+
129+
CValidationState state;
130+
ProcessNewBlock(state, NULL, &block, true, NULL);
131+
132+
CBlock result = block;
133+
delete pblocktemplate;
134+
return result;
135+
}
136+
137+
TestChain100Setup::~TestChain100Setup()
138+
{
139+
}
140+
90141
void Shutdown(void* parg)
91142
{
92143
exit(0);

src/test/test_bitcoin.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef BITCOIN_TEST_TEST_BITCOIN_H
22
#define BITCOIN_TEST_TEST_BITCOIN_H
33

4+
#include "chainparamsbase.h"
5+
#include "key.h"
46
#include "txdb.h"
57

68
#include <boost/filesystem.hpp>
@@ -10,7 +12,7 @@
1012
* This just configures logging and chain parameters.
1113
*/
1214
struct BasicTestingSetup {
13-
BasicTestingSetup();
15+
BasicTestingSetup(CBaseChainParams::Network network = CBaseChainParams::MAIN);
1416
~BasicTestingSetup();
1517
};
1618

@@ -23,8 +25,30 @@ struct TestingSetup: public BasicTestingSetup {
2325
boost::filesystem::path pathTemp;
2426
boost::thread_group threadGroup;
2527

26-
TestingSetup();
28+
TestingSetup(CBaseChainParams::Network network = CBaseChainParams::MAIN);
2729
~TestingSetup();
2830
};
2931

32+
class CBlock;
33+
struct CMutableTransaction;
34+
class CScript;
35+
36+
//
37+
// Testing fixture that pre-creates a
38+
// 100-block REGTEST-mode block chain
39+
//
40+
struct TestChain100Setup : public TestingSetup {
41+
TestChain100Setup();
42+
43+
// Create a new block with just given transactions, coinbase paying to
44+
// scriptPubKey, and try to add it to the current chain.
45+
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
46+
const CScript& scriptPubKey);
47+
48+
~TestChain100Setup();
49+
50+
std::vector<CTransaction> coinbaseTxns; // For convenience, coinbase transactions
51+
CKey coinbaseKey; // private/public key needed to spend coinbase transactions
52+
};
53+
3054
#endif

src/test/txvalidationcache_tests.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) 2011-2014 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "consensus/validation.h"
6+
#include "key.h"
7+
#include "main.h"
8+
#include "miner.h"
9+
#include "pubkey.h"
10+
#include "txmempool.h"
11+
#include "random.h"
12+
#include "script/standard.h"
13+
#include "test/test_bitcoin.h"
14+
#include "utiltime.h"
15+
16+
#include <boost/test/unit_test.hpp>
17+
18+
BOOST_AUTO_TEST_SUITE(tx_validationcache_tests)
19+
20+
static bool
21+
ToMemPool(CMutableTransaction& tx)
22+
{
23+
LOCK(cs_main);
24+
25+
CValidationState state;
26+
return AcceptToMemoryPool(mempool, state, tx, false, NULL, false);
27+
}
28+
29+
BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
30+
{
31+
// Make sure skipping validation of transctions that were
32+
// validated going into the memory pool does not allow
33+
// double-spends in blocks to pass validation when they should not.
34+
35+
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
36+
37+
// Create a double-spend of mature coinbase txn:
38+
std::vector<CMutableTransaction> spends;
39+
spends.resize(2);
40+
for (int i = 0; i < 2; i++)
41+
{
42+
spends[i].vin.resize(1);
43+
spends[i].vin[0].prevout.hash = coinbaseTxns[0].GetHash();
44+
spends[i].vin[0].prevout.n = 0;
45+
spends[i].vout.resize(1);
46+
spends[i].vout[0].nValue = 11*CENT;
47+
spends[i].vout[0].scriptPubKey = scriptPubKey;
48+
49+
// Sign:
50+
std::vector<unsigned char> vchSig;
51+
uint256 hash = SignatureHash(scriptPubKey, spends[i], 0, SIGHASH_ALL);
52+
BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
53+
vchSig.push_back((unsigned char)SIGHASH_ALL);
54+
spends[i].vin[0].scriptSig << vchSig;
55+
}
56+
57+
CBlock block;
58+
59+
// Test 1: block with both of those transactions should be rejected.
60+
block = CreateAndProcessBlock(spends, scriptPubKey);
61+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash());
62+
63+
// Test 2: ... and should be rejected if spend1 is in the memory pool
64+
BOOST_CHECK(ToMemPool(spends[0]));
65+
block = CreateAndProcessBlock(spends, scriptPubKey);
66+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash());
67+
mempool.clear();
68+
69+
// Test 3: ... and should be rejected if spend2 is in the memory pool
70+
BOOST_CHECK(ToMemPool(spends[1]));
71+
block = CreateAndProcessBlock(spends, scriptPubKey);
72+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash());
73+
mempool.clear();
74+
75+
// Final sanity test: first spend in mempool, second in block, that's OK:
76+
std::vector<CMutableTransaction> oneSpend;
77+
oneSpend.push_back(spends[0]);
78+
BOOST_CHECK(ToMemPool(spends[1]));
79+
block = CreateAndProcessBlock(oneSpend, scriptPubKey);
80+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
81+
// spends[1] should have been removed from the mempool when the
82+
// block with spends[0] is accepted:
83+
BOOST_CHECK_EQUAL(mempool.size(), 0);
84+
}
85+
86+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)