1 // Copyright (c) 2011-2019 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 <blockencodings.h>
6 #include <chainparams.h>
7 #include <consensus/merkle.h>
8 #include <pow.h>
9 #include <primitives/pureheader.h>
10 #include <streams.h>
11 
12 #include <test/util/setup_common.h>
13 
14 #include <boost/test/unit_test.hpp>
15 
16 std::vector<std::pair<uint256, CTransactionRef>> extra_txn;
17 
BOOST_FIXTURE_TEST_SUITE(blockencodings_tests,RegTestingSetup)18 BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup)
19 
20 static void SetBlockVersion(CPureBlockHeader& header, int32_t baseVersion) {
21   const int32_t nChainId = Params().GetConsensus().nAuxpowChainId;
22   header.SetBaseVersion(baseVersion, nChainId);
23 }
24 
BuildBlockTestCase()25 static CBlock BuildBlockTestCase() {
26     CBlock block;
27     CMutableTransaction tx;
28     tx.vin.resize(1);
29     tx.vin[0].scriptSig.resize(10);
30     tx.vout.resize(1);
31     tx.vout[0].nValue = 42;
32 
33     block.vtx.resize(3);
34     block.vtx[0] = MakeTransactionRef(tx);
35     SetBlockVersion(block, 42);
36     block.hashPrevBlock = InsecureRand256();
37     block.nBits = 0x207fffff;
38 
39     tx.vin[0].prevout.hash = InsecureRand256();
40     tx.vin[0].prevout.n = 0;
41     block.vtx[1] = MakeTransactionRef(tx);
42 
43     tx.vin.resize(10);
44     for (size_t i = 0; i < tx.vin.size(); i++) {
45         tx.vin[i].prevout.hash = InsecureRand256();
46         tx.vin[i].prevout.n = 0;
47     }
48     block.vtx[2] = MakeTransactionRef(tx);
49 
50     bool mutated;
51     block.hashMerkleRoot = BlockMerkleRoot(block, &mutated);
52     assert(!mutated);
53     while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;
54     return block;
55 }
56 
57 // Number of shared use_counts we expect for a tx we haven't touched
58 // (block + mempool + our copy from the GetSharedTx call)
59 constexpr long SHARED_TX_OFFSET{3};
60 
BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)61 BOOST_AUTO_TEST_CASE(SimpleRoundTripTest)
62 {
63     CTxMemPool pool;
64     TestMemPoolEntryHelper entry;
65     CBlock block(BuildBlockTestCase());
66 
67     LOCK2(cs_main, pool.cs);
68     pool.addUnchecked(entry.FromTx(block.vtx[2]));
69     BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
70 
71     // Do a simple ShortTxIDs RT
72     {
73         CBlockHeaderAndShortTxIDs shortIDs(block, true);
74 
75         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
76         stream << shortIDs;
77 
78         CBlockHeaderAndShortTxIDs shortIDs2;
79         stream >> shortIDs2;
80 
81         PartiallyDownloadedBlock partialBlock(&pool);
82         BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
83         BOOST_CHECK( partialBlock.IsTxAvailable(0));
84         BOOST_CHECK(!partialBlock.IsTxAvailable(1));
85         BOOST_CHECK( partialBlock.IsTxAvailable(2));
86 
87         BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1);
88 
89         size_t poolSize = pool.size();
90         pool.removeRecursive(*block.vtx[2], MemPoolRemovalReason::REPLACED);
91         BOOST_CHECK_EQUAL(pool.size(), poolSize - 1);
92 
93         CBlock block2;
94         {
95             PartiallyDownloadedBlock tmp = partialBlock;
96             BOOST_CHECK(partialBlock.FillBlock(block2, {}) == READ_STATUS_INVALID); // No transactions
97             partialBlock = tmp;
98         }
99 
100         // Wrong transaction
101         {
102             PartiallyDownloadedBlock tmp = partialBlock;
103             partialBlock.FillBlock(block2, {block.vtx[2]}); // Current implementation doesn't check txn here, but don't require that
104             partialBlock = tmp;
105         }
106         bool mutated;
107         BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated));
108 
109         CBlock block3;
110         BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[1]}) == READ_STATUS_OK);
111         BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString());
112         BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString());
113         BOOST_CHECK(!mutated);
114     }
115 }
116 
117 class TestHeaderAndShortIDs {
118     // Utility to encode custom CBlockHeaderAndShortTxIDs
119 public:
120     CBlockHeader header;
121     uint64_t nonce;
122     std::vector<uint64_t> shorttxids;
123     std::vector<PrefilledTransaction> prefilledtxn;
124 
TestHeaderAndShortIDs(const CBlockHeaderAndShortTxIDs & orig)125     explicit TestHeaderAndShortIDs(const CBlockHeaderAndShortTxIDs& orig) {
126         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
127         stream << orig;
128         stream >> *this;
129     }
TestHeaderAndShortIDs(const CBlock & block)130     explicit TestHeaderAndShortIDs(const CBlock& block) :
131         TestHeaderAndShortIDs(CBlockHeaderAndShortTxIDs(block, true)) {}
132 
GetShortID(const uint256 & txhash) const133     uint64_t GetShortID(const uint256& txhash) const {
134         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
135         stream << *this;
136         CBlockHeaderAndShortTxIDs base;
137         stream >> base;
138         return base.GetShortID(txhash);
139     }
140 
SERIALIZE_METHODS(TestHeaderAndShortIDs,obj)141     SERIALIZE_METHODS(TestHeaderAndShortIDs, obj) { READWRITE(obj.header, obj.nonce, Using<VectorFormatter<CustomUintFormatter<CBlockHeaderAndShortTxIDs::SHORTTXIDS_LENGTH>>>(obj.shorttxids), obj.prefilledtxn); }
142 };
143 
BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)144 BOOST_AUTO_TEST_CASE(NonCoinbasePreforwardRTTest)
145 {
146     CTxMemPool pool;
147     TestMemPoolEntryHelper entry;
148     CBlock block(BuildBlockTestCase());
149 
150     LOCK2(cs_main, pool.cs);
151     pool.addUnchecked(entry.FromTx(block.vtx[2]));
152     BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
153 
154     uint256 txhash;
155 
156     // Test with pre-forwarding tx 1, but not coinbase
157     {
158         TestHeaderAndShortIDs shortIDs(block);
159         shortIDs.prefilledtxn.resize(1);
160         shortIDs.prefilledtxn[0] = {1, block.vtx[1]};
161         shortIDs.shorttxids.resize(2);
162         shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[0]->GetHash());
163         shortIDs.shorttxids[1] = shortIDs.GetShortID(block.vtx[2]->GetHash());
164 
165         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
166         stream << shortIDs;
167 
168         CBlockHeaderAndShortTxIDs shortIDs2;
169         stream >> shortIDs2;
170 
171         PartiallyDownloadedBlock partialBlock(&pool);
172         BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
173         BOOST_CHECK(!partialBlock.IsTxAvailable(0));
174         BOOST_CHECK( partialBlock.IsTxAvailable(1));
175         BOOST_CHECK( partialBlock.IsTxAvailable(2));
176 
177         BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1); // +1 because of partialBlock
178 
179         CBlock block2;
180         {
181             PartiallyDownloadedBlock tmp = partialBlock;
182             BOOST_CHECK(partialBlock.FillBlock(block2, {}) == READ_STATUS_INVALID); // No transactions
183             partialBlock = tmp;
184         }
185 
186         // Wrong transaction
187         {
188             PartiallyDownloadedBlock tmp = partialBlock;
189             partialBlock.FillBlock(block2, {block.vtx[1]}); // Current implementation doesn't check txn here, but don't require that
190             partialBlock = tmp;
191         }
192         BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 2); // +2 because of partialBlock and block2
193         bool mutated;
194         BOOST_CHECK(block.hashMerkleRoot != BlockMerkleRoot(block2, &mutated));
195 
196         CBlock block3;
197         PartiallyDownloadedBlock partialBlockCopy = partialBlock;
198         BOOST_CHECK(partialBlock.FillBlock(block3, {block.vtx[0]}) == READ_STATUS_OK);
199         BOOST_CHECK_EQUAL(block.GetHash().ToString(), block3.GetHash().ToString());
200         BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block3, &mutated).ToString());
201         BOOST_CHECK(!mutated);
202 
203         BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[2]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 3); // +2 because of partialBlock and block2 and block3
204 
205         txhash = block.vtx[2]->GetHash();
206         block.vtx.clear();
207         block2.vtx.clear();
208         block3.vtx.clear();
209         BOOST_CHECK_EQUAL(pool.mapTx.find(txhash)->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1 - 1); // + 1 because of partialBlock; -1 because of block.
210     }
211     BOOST_CHECK_EQUAL(pool.mapTx.find(txhash)->GetSharedTx().use_count(), SHARED_TX_OFFSET - 1); // -1 because of block
212 }
213 
BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)214 BOOST_AUTO_TEST_CASE(SufficientPreforwardRTTest)
215 {
216     CTxMemPool pool;
217     TestMemPoolEntryHelper entry;
218     CBlock block(BuildBlockTestCase());
219 
220     LOCK2(cs_main, pool.cs);
221     pool.addUnchecked(entry.FromTx(block.vtx[1]));
222     BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 0);
223 
224     uint256 txhash;
225 
226     // Test with pre-forwarding coinbase + tx 2 with tx 1 in mempool
227     {
228         TestHeaderAndShortIDs shortIDs(block);
229         shortIDs.prefilledtxn.resize(2);
230         shortIDs.prefilledtxn[0] = {0, block.vtx[0]};
231         shortIDs.prefilledtxn[1] = {1, block.vtx[2]}; // id == 1 as it is 1 after index 1
232         shortIDs.shorttxids.resize(1);
233         shortIDs.shorttxids[0] = shortIDs.GetShortID(block.vtx[1]->GetHash());
234 
235         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
236         stream << shortIDs;
237 
238         CBlockHeaderAndShortTxIDs shortIDs2;
239         stream >> shortIDs2;
240 
241         PartiallyDownloadedBlock partialBlock(&pool);
242         BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
243         BOOST_CHECK( partialBlock.IsTxAvailable(0));
244         BOOST_CHECK( partialBlock.IsTxAvailable(1));
245         BOOST_CHECK( partialBlock.IsTxAvailable(2));
246 
247         BOOST_CHECK_EQUAL(pool.mapTx.find(block.vtx[1]->GetHash())->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1);
248 
249         CBlock block2;
250         PartiallyDownloadedBlock partialBlockCopy = partialBlock;
251         BOOST_CHECK(partialBlock.FillBlock(block2, {}) == READ_STATUS_OK);
252         BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString());
253         bool mutated;
254         BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString());
255         BOOST_CHECK(!mutated);
256 
257         txhash = block.vtx[1]->GetHash();
258         block.vtx.clear();
259         block2.vtx.clear();
260         BOOST_CHECK_EQUAL(pool.mapTx.find(txhash)->GetSharedTx().use_count(), SHARED_TX_OFFSET + 1 - 1); // + 1 because of partialBlock; -1 because of block.
261     }
262     BOOST_CHECK_EQUAL(pool.mapTx.find(txhash)->GetSharedTx().use_count(), SHARED_TX_OFFSET - 1); // -1 because of block
263 }
264 
BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)265 BOOST_AUTO_TEST_CASE(EmptyBlockRoundTripTest)
266 {
267     CTxMemPool pool;
268     CMutableTransaction coinbase;
269     coinbase.vin.resize(1);
270     coinbase.vin[0].scriptSig.resize(10);
271     coinbase.vout.resize(1);
272     coinbase.vout[0].nValue = 42;
273 
274     CBlock block;
275     block.vtx.resize(1);
276     block.vtx[0] = MakeTransactionRef(std::move(coinbase));
277     SetBlockVersion(block, 42);
278     block.hashPrevBlock = InsecureRand256();
279     block.nBits = 0x207fffff;
280 
281     bool mutated;
282     block.hashMerkleRoot = BlockMerkleRoot(block, &mutated);
283     assert(!mutated);
284     while (!CheckProofOfWork(block.GetHash(), block.nBits, Params().GetConsensus())) ++block.nNonce;
285 
286     // Test simple header round-trip with only coinbase
287     {
288         CBlockHeaderAndShortTxIDs shortIDs(block, false);
289 
290         CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
291         stream << shortIDs;
292 
293         CBlockHeaderAndShortTxIDs shortIDs2;
294         stream >> shortIDs2;
295 
296         PartiallyDownloadedBlock partialBlock(&pool);
297         BOOST_CHECK(partialBlock.InitData(shortIDs2, extra_txn) == READ_STATUS_OK);
298         BOOST_CHECK(partialBlock.IsTxAvailable(0));
299 
300         CBlock block2;
301         std::vector<CTransactionRef> vtx_missing;
302         BOOST_CHECK(partialBlock.FillBlock(block2, vtx_missing) == READ_STATUS_OK);
303         BOOST_CHECK_EQUAL(block.GetHash().ToString(), block2.GetHash().ToString());
304         BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), BlockMerkleRoot(block2, &mutated).ToString());
305         BOOST_CHECK(!mutated);
306     }
307 }
308 
BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest)309 BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) {
310     BlockTransactionsRequest req1;
311     req1.blockhash = InsecureRand256();
312     req1.indexes.resize(4);
313     req1.indexes[0] = 0;
314     req1.indexes[1] = 1;
315     req1.indexes[2] = 3;
316     req1.indexes[3] = 4;
317 
318     CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
319     stream << req1;
320 
321     BlockTransactionsRequest req2;
322     stream >> req2;
323 
324     BOOST_CHECK_EQUAL(req1.blockhash.ToString(), req2.blockhash.ToString());
325     BOOST_CHECK_EQUAL(req1.indexes.size(), req2.indexes.size());
326     BOOST_CHECK_EQUAL(req1.indexes[0], req2.indexes[0]);
327     BOOST_CHECK_EQUAL(req1.indexes[1], req2.indexes[1]);
328     BOOST_CHECK_EQUAL(req1.indexes[2], req2.indexes[2]);
329     BOOST_CHECK_EQUAL(req1.indexes[3], req2.indexes[3]);
330 }
331 
BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest)332 BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest) {
333     // Check that the highest legal index is decoded correctly
334     BlockTransactionsRequest req0;
335     req0.blockhash = InsecureRand256();
336     req0.indexes.resize(1);
337     req0.indexes[0] = 0xffff;
338     CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
339     stream << req0;
340 
341     BlockTransactionsRequest req1;
342     stream >> req1;
343     BOOST_CHECK_EQUAL(req0.indexes.size(), req1.indexes.size());
344     BOOST_CHECK_EQUAL(req0.indexes[0], req1.indexes[0]);
345 }
346 
BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest)347 BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) {
348     // Any set of index deltas that starts with N values that sum to (0x10000 - N)
349     // causes the edge-case overflow that was originally not checked for. Such
350     // a request cannot be created by serializing a real BlockTransactionsRequest
351     // due to the overflow, so here we'll serialize from raw deltas.
352     BlockTransactionsRequest req0;
353     req0.blockhash = InsecureRand256();
354     req0.indexes.resize(3);
355     req0.indexes[0] = 0x7000;
356     req0.indexes[1] = 0x10000 - 0x7000 - 2;
357     req0.indexes[2] = 0;
358     CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
359     stream << req0.blockhash;
360     WriteCompactSize(stream, req0.indexes.size());
361     WriteCompactSize(stream, req0.indexes[0]);
362     WriteCompactSize(stream, req0.indexes[1]);
363     WriteCompactSize(stream, req0.indexes[2]);
364 
365     BlockTransactionsRequest req1;
366     try {
367         stream >> req1;
368         // before patch: deserialize above succeeds and this check fails, demonstrating the overflow
369         BOOST_CHECK(req1.indexes[1] < req1.indexes[2]);
370         // this shouldn't be reachable before or after patch
371         BOOST_CHECK(0);
372     } catch(std::ios_base::failure &) {
373         // deserialize should fail
374         BOOST_CHECK(true); // Needed to suppress "Test case [...] did not check any assertions"
375     }
376 }
377 
378 BOOST_AUTO_TEST_SUITE_END()
379