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