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