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