txvalidationcache_tests.cpp raw
1 // Copyright (c) 2011-2022 The Limenka 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 <addresstype.h>
6 #include <consensus/validation.h>
7 #include <key.h>
8 #include <random.h>
9 #include <script/interpreter.h>
10 #include <script/sigcache.h>
11 #include <script/sign.h>
12 #include <script/signingprovider.h>
13 #include <test/util/setup_common.h>
14 #include <txmempool.h>
15 #include <util/chaintype.h>
16 #include <validation.h>
17
18 #include <boost/test/unit_test.hpp>
19
20 struct Dersig100Setup : public TestChain100Setup {
21 Dersig100Setup()
22 : TestChain100Setup{ChainType::REGTEST, {.extra_args = {"-testactivationheight=dersig@102"}}} {}
23 };
24
25 bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
26 const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
27 bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
28 ValidationCache& validation_cache,
29 std::vector<CScriptCheck>* pvChecks,
30 const std::vector<unsigned int>& flags_per_input = {}
31 ) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
32
33 BOOST_AUTO_TEST_SUITE(txvalidationcache_tests)
34
35 BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup)
36 {
37 // Make sure skipping validation of transactions that were
38 // validated going into the memory pool does not allow
39 // double-spends in blocks to pass validation when they should not.
40
41 CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
42
43 const auto ToMemPool = [this](const CMutableTransaction& tx) {
44 LOCK(cs_main);
45
46 const MempoolAcceptResult result = m_node.chainman->ProcessTransaction(MakeTransactionRef(tx));
47 return result.m_result_type == MempoolAcceptResult::ResultType::VALID;
48 };
49
50 // Create a double-spend of mature coinbase txn:
51 std::vector<CMutableTransaction> spends;
52 spends.resize(2);
53 for (int i = 0; i < 2; i++)
54 {
55 spends[i].version = 1;
56 spends[i].vin.resize(1);
57 spends[i].vin[0].prevout.hash = m_coinbase_txns[0]->GetHash();
58 spends[i].vin[0].prevout.n = 0;
59 spends[i].vout.resize(1);
60 spends[i].vout[0].nValue = 11*CENT;
61 spends[i].vout[0].scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
62
63 // Sign:
64 std::vector<unsigned char> vchSig;
65 uint256 hash = SignatureHash(scriptPubKey, spends[i], 0, SIGHASH_ALL, 0, SigVersion::BASE);
66 BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
67 vchSig.push_back((unsigned char)SIGHASH_ALL);
68 spends[i].vin[0].scriptSig << vchSig;
69 }
70
71 CBlock block;
72
73 // Test 1: block with both of those transactions should be rejected.
74 block = CreateAndProcessBlock(spends, scriptPubKey);
75 {
76 LOCK(cs_main);
77 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash());
78 }
79
80 // Test 2: ... and should be rejected if spend1 is in the memory pool
81 BOOST_CHECK(ToMemPool(spends[0]));
82 block = CreateAndProcessBlock(spends, scriptPubKey);
83 {
84 LOCK(cs_main);
85 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash());
86 }
87 BOOST_CHECK_EQUAL(m_node.mempool->size(), 1U);
88 WITH_LOCK(m_node.mempool->cs, m_node.mempool->removeRecursive(CTransaction{spends[0]}, MemPoolRemovalReason::CONFLICT));
89 BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);
90
91 // Test 3: ... and should be rejected if spend2 is in the memory pool
92 BOOST_CHECK(ToMemPool(spends[1]));
93 block = CreateAndProcessBlock(spends, scriptPubKey);
94 {
95 LOCK(cs_main);
96 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() != block.GetHash());
97 }
98 BOOST_CHECK_EQUAL(m_node.mempool->size(), 1U);
99 WITH_LOCK(m_node.mempool->cs, m_node.mempool->removeRecursive(CTransaction{spends[1]}, MemPoolRemovalReason::CONFLICT));
100 BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);
101
102 // Final sanity test: first spend in *m_node.mempool, second in block, that's OK:
103 std::vector<CMutableTransaction> oneSpend;
104 oneSpend.push_back(spends[0]);
105 BOOST_CHECK(ToMemPool(spends[1]));
106 block = CreateAndProcessBlock(oneSpend, scriptPubKey);
107 {
108 LOCK(cs_main);
109 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash());
110 }
111 // spends[1] should have been removed from the mempool when the
112 // block with spends[0] is accepted:
113 BOOST_CHECK_EQUAL(m_node.mempool->size(), 0U);
114 }
115
116 // Run CheckInputScripts (using CoinsTip()) on the given transaction, for all script
117 // flags. Test that CheckInputScripts passes for all flags that don't overlap with
118 // the failing_flags argument, but otherwise fails.
119 // CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY (and future NOP codes that may
120 // get reassigned) have an interaction with DISCOURAGE_UPGRADABLE_NOPS: if
121 // the script flags used contain DISCOURAGE_UPGRADABLE_NOPS but don't contain
122 // CHECKLOCKTIMEVERIFY (or CHECKSEQUENCEVERIFY), but the script does contain
123 // OP_CHECKLOCKTIMEVERIFY (or OP_CHECKSEQUENCEVERIFY), then script execution
124 // should fail.
125 // Capture this interaction with the upgraded_nop argument: set it when evaluating
126 // any script flag that is implemented as an upgraded NOP code.
127 static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t failing_flags, bool add_to_cache, CCoinsViewCache& active_coins_tip, ValidationCache& validation_cache) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
128 {
129 PrecomputedTransactionData txdata;
130
131 FastRandomContext insecure_rand(true);
132
133 for (int count = 0; count < 10000; ++count) {
134 TxValidationState state;
135
136 // Randomly selects flag combinations
137 uint32_t test_flags = (uint32_t) insecure_rand.randrange((SCRIPT_VERIFY_END_MARKER - 1) << 1);
138
139 // Fork-chain flags are excluded: FORKID fails any transaction whose
140 // signature is actually evaluated (via CheckSignatureEncoding), so it
141 // does not fit the simple independent-failing-flags model below.
142 // These flags are exercised directly in fork_block_tests.
143 test_flags &= ~(SCRIPT_VERIFY_P2SPKH | SCRIPT_VERIFY_P2BPCT);
144
145 // Filter out incompatible flag choices
146 if ((test_flags & SCRIPT_VERIFY_CLEANSTACK)) {
147 // CLEANSTACK requires P2SH and WITNESS, see VerifyScript() in
148 // script/interpreter.cpp
149 test_flags |= SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS;
150 }
151 if ((test_flags & SCRIPT_VERIFY_WITNESS)) {
152 // WITNESS requires P2SH
153 test_flags |= SCRIPT_VERIFY_P2SH;
154 }
155 bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);
156 // CheckInputScripts should succeed iff test_flags doesn't intersect with
157 // failing_flags
158 bool expected_return_value = !(test_flags & failing_flags);
159 BOOST_CHECK_EQUAL(ret, expected_return_value);
160
161 // Test the caching
162 if (ret && add_to_cache) {
163 // Check that we get a cache hit if the tx was valid
164 std::vector<CScriptCheck> scriptchecks;
165 BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
166 BOOST_CHECK(scriptchecks.empty());
167 } else {
168 // Check that we get script executions to check, if the transaction
169 // was invalid, or we didn't add to cache.
170 std::vector<CScriptCheck> scriptchecks;
171 BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
172 BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
173 }
174 }
175 }
176
177 BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
178 {
179 // Test that passing CheckInputScripts with one set of script flags doesn't imply
180 // that we would pass again with a different set of flags.
181 CScript p2pk_scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
182 CScript p2sh_scriptPubKey = GetScriptForDestination(ScriptHash(p2pk_scriptPubKey));
183 CScript p2pkh_scriptPubKey = GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()));
184 CScript p2wpkh_scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(coinbaseKey.GetPubKey()));
185
186 FillableSigningProvider keystore;
187 BOOST_CHECK(keystore.AddKey(coinbaseKey));
188 BOOST_CHECK(keystore.AddCScript(p2pk_scriptPubKey));
189
190 // flags to test: SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, SCRIPT_VERIFY_CHECKSEQUENCE_VERIFY, SCRIPT_VERIFY_NULLDUMMY, uncompressed pubkey thing
191
192 // Create 2 outputs that match the three scripts above, spending the first
193 // coinbase tx.
194 CMutableTransaction spend_tx;
195
196 spend_tx.version = 1;
197 spend_tx.vin.resize(1);
198 spend_tx.vin[0].prevout.hash = m_coinbase_txns[0]->GetHash();
199 spend_tx.vin[0].prevout.n = 0;
200 spend_tx.vout.resize(4);
201 spend_tx.vout[0].nValue = 11*CENT;
202 spend_tx.vout[0].scriptPubKey = p2sh_scriptPubKey;
203 spend_tx.vout[1].nValue = 11*CENT;
204 spend_tx.vout[1].scriptPubKey = p2wpkh_scriptPubKey;
205 spend_tx.vout[2].nValue = 11*CENT;
206 spend_tx.vout[2].scriptPubKey = CScript() << OP_CHECKLOCKTIMEVERIFY << OP_DROP << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
207 spend_tx.vout[3].nValue = 11*CENT;
208 spend_tx.vout[3].scriptPubKey = CScript() << OP_CHECKSEQUENCEVERIFY << OP_DROP << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
209
210 // Sign, with a non-DER signature
211 {
212 std::vector<unsigned char> vchSig;
213 uint256 hash = SignatureHash(p2pk_scriptPubKey, spend_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE);
214 BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
215 vchSig.push_back((unsigned char) 0); // padding byte makes this non-DER
216 vchSig.push_back((unsigned char)SIGHASH_ALL);
217 spend_tx.vin[0].scriptSig << vchSig;
218 }
219
220 // Test that invalidity under a set of flags doesn't preclude validity
221 // under other (eg consensus) flags.
222 // spend_tx is invalid according to DERSIG
223 {
224 LOCK(cs_main);
225
226 TxValidationState state;
227 PrecomputedTransactionData ptd_spend_tx;
228
229 BOOST_CHECK(!CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, nullptr));
230
231 // If we call again asking for scriptchecks (as happens in
232 // ConnectBlock), we should add a script check object for this -- we're
233 // not caching invalidity (if that changes, delete this test case).
234 std::vector<CScriptCheck> scriptchecks;
235 BOOST_CHECK(CheckInputScripts(CTransaction(spend_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, m_node.chainman->m_validation_cache, &scriptchecks));
236 BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);
237
238 // Test that CheckInputScripts returns true iff DERSIG-enforcing flags are
239 // not present. Don't add these checks to the cache, so that we can
240 // test later that block validation works fine in the absence of cached
241 // successes.
242 ValidateCheckInputsForAllFlags(CTransaction(spend_tx), SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
243 }
244
245 // And if we produce a block with this tx, it should be valid (DERSIG not
246 // enabled yet), even though there's no cache entry.
247 CBlock block;
248
249 block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
250 LOCK(cs_main);
251 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash());
252 BOOST_CHECK(m_node.chainman->ActiveChainstate().CoinsTip().GetBestBlock() == block.GetHash());
253
254 // Test P2SH: construct a transaction that is valid without P2SH, and
255 // then test validity with P2SH.
256 {
257 CMutableTransaction invalid_under_p2sh_tx;
258 invalid_under_p2sh_tx.version = 1;
259 invalid_under_p2sh_tx.vin.resize(1);
260 invalid_under_p2sh_tx.vin[0].prevout.hash = spend_tx.GetHash();
261 invalid_under_p2sh_tx.vin[0].prevout.n = 0;
262 invalid_under_p2sh_tx.vout.resize(1);
263 invalid_under_p2sh_tx.vout[0].nValue = 11*CENT;
264 invalid_under_p2sh_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
265 std::vector<unsigned char> vchSig2(p2pk_scriptPubKey.begin(), p2pk_scriptPubKey.end());
266 invalid_under_p2sh_tx.vin[0].scriptSig << vchSig2;
267
268 ValidateCheckInputsForAllFlags(CTransaction(invalid_under_p2sh_tx), SCRIPT_VERIFY_P2SH, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
269 }
270
271 // Test CHECKLOCKTIMEVERIFY
272 {
273 CMutableTransaction invalid_with_cltv_tx;
274 invalid_with_cltv_tx.version = 1;
275 invalid_with_cltv_tx.nLockTime = 100;
276 invalid_with_cltv_tx.vin.resize(1);
277 invalid_with_cltv_tx.vin[0].prevout.hash = spend_tx.GetHash();
278 invalid_with_cltv_tx.vin[0].prevout.n = 2;
279 invalid_with_cltv_tx.vin[0].nSequence = 0;
280 invalid_with_cltv_tx.vout.resize(1);
281 invalid_with_cltv_tx.vout[0].nValue = 11*CENT;
282 invalid_with_cltv_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
283
284 // Sign
285 std::vector<unsigned char> vchSig;
286 uint256 hash = SignatureHash(spend_tx.vout[2].scriptPubKey, invalid_with_cltv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE);
287 BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
288 vchSig.push_back((unsigned char)SIGHASH_ALL);
289 invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101;
290
291 ValidateCheckInputsForAllFlags(CTransaction(invalid_with_cltv_tx), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
292
293 // Make it valid, and check again
294 invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
295 TxValidationState state;
296 PrecomputedTransactionData txdata;
297 BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_cltv_tx), state, m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
298 }
299
300 // TEST CHECKSEQUENCEVERIFY
301 {
302 CMutableTransaction invalid_with_csv_tx;
303 invalid_with_csv_tx.version = 2;
304 invalid_with_csv_tx.vin.resize(1);
305 invalid_with_csv_tx.vin[0].prevout.hash = spend_tx.GetHash();
306 invalid_with_csv_tx.vin[0].prevout.n = 3;
307 invalid_with_csv_tx.vin[0].nSequence = 100;
308 invalid_with_csv_tx.vout.resize(1);
309 invalid_with_csv_tx.vout[0].nValue = 11*CENT;
310 invalid_with_csv_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
311
312 // Sign
313 std::vector<unsigned char> vchSig;
314 uint256 hash = SignatureHash(spend_tx.vout[3].scriptPubKey, invalid_with_csv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE);
315 BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
316 vchSig.push_back((unsigned char)SIGHASH_ALL);
317 invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101;
318
319 ValidateCheckInputsForAllFlags(CTransaction(invalid_with_csv_tx), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
320
321 // Make it valid, and check again
322 invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
323 TxValidationState state;
324 PrecomputedTransactionData txdata;
325 BOOST_CHECK(CheckInputScripts(CTransaction(invalid_with_csv_tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
326 }
327
328 // TODO: add tests for remaining script flags
329
330 // Test that passing CheckInputScripts with a valid witness doesn't imply success
331 // for the same tx with a different witness.
332 {
333 CMutableTransaction valid_with_witness_tx;
334 valid_with_witness_tx.version = 1;
335 valid_with_witness_tx.vin.resize(1);
336 valid_with_witness_tx.vin[0].prevout.hash = spend_tx.GetHash();
337 valid_with_witness_tx.vin[0].prevout.n = 1;
338 valid_with_witness_tx.vout.resize(1);
339 valid_with_witness_tx.vout[0].nValue = 11*CENT;
340 valid_with_witness_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
341
342 // Sign
343 SignatureData sigdata;
344 BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(valid_with_witness_tx, 0, 11 * CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata));
345 UpdateInput(valid_with_witness_tx.vin[0], sigdata);
346
347 // This should be valid under all script flags.
348 ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
349
350 // Remove the witness, and check that it is now invalid.
351 valid_with_witness_tx.vin[0].scriptWitness.SetNull();
352 ValidateCheckInputsForAllFlags(CTransaction(valid_with_witness_tx), SCRIPT_VERIFY_WITNESS, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
353 }
354
355 {
356 // Test a transaction with multiple inputs.
357 CMutableTransaction tx;
358
359 tx.version = 1;
360 tx.vin.resize(2);
361 tx.vin[0].prevout.hash = spend_tx.GetHash();
362 tx.vin[0].prevout.n = 0;
363 tx.vin[1].prevout.hash = spend_tx.GetHash();
364 tx.vin[1].prevout.n = 1;
365 tx.vout.resize(1);
366 tx.vout[0].nValue = 22*CENT;
367 tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
368
369 // Sign
370 for (int i = 0; i < 2; ++i) {
371 SignatureData sigdata;
372 BOOST_CHECK(ProduceSignature(keystore, MutableTransactionSignatureCreator(tx, i, 11 * CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata));
373 UpdateInput(tx.vin[i], sigdata);
374 }
375
376 // This should be valid under all script flags
377 ValidateCheckInputsForAllFlags(CTransaction(tx), 0, true, m_node.chainman->ActiveChainstate().CoinsTip(), m_node.chainman->m_validation_cache);
378
379 // Check that if the second input is invalid, but the first input is
380 // valid, the transaction is not cached.
381 // Invalidate vin[1]
382 tx.vin[1].scriptWitness.SetNull();
383
384 TxValidationState state;
385 PrecomputedTransactionData txdata;
386 // This transaction is now invalid under segwit, because of the second input.
387 BOOST_CHECK(!CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, nullptr));
388
389 std::vector<CScriptCheck> scriptchecks;
390 // Make sure this transaction was not cached (ie because the first
391 // input was valid)
392 BOOST_CHECK(CheckInputScripts(CTransaction(tx), state, &m_node.chainman->ActiveChainstate().CoinsTip(), SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, m_node.chainman->m_validation_cache, &scriptchecks));
393 // Should get 2 script checks back -- caching is on a whole-transaction basis.
394 BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
395 }
396 }
397
398 BOOST_FIXTURE_TEST_CASE(checkinputs_flags_per_input_cache_safety, Dersig100Setup)
399 {
400 // Reproducer for cache poisoning via per-input flag relaxation.
401 //
402 // A 300-byte witness push passes only when SCRIPT_VERIFY_REDUCED_DATA is
403 // relaxed (as it would be for pre-activation UTXOs). The cache key is
404 // computed from the strict global flags, so if the result is cached after
405 // passing with relaxed per-input flags, a subsequent strict-flags lookup
406 // will incorrectly return a cache hit.
407
408 const auto& coinbase_script{m_coinbase_txns[0]->vout[0].scriptPubKey};
409 const unsigned int strict_flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_REDUCED_DATA};
410 const unsigned int relaxed_flags{SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS};
411
412 // P2WSH witness script: OP_DROP OP_TRUE (accepts any single witness element)
413 const CScript witness_script = CScript() << OP_DROP << OP_TRUE;
414 const std::vector<unsigned char> big_witness_elem(300, 0x42);
415 const CScript p2wsh_script = GetScriptForDestination(WitnessV0ScriptHash(witness_script));
416
417 // Mine a funding tx that pays to the P2WSH output.
418 const auto mine_funding_tx{[&]
419 {
420 CMutableTransaction tx;
421 tx.vin = {CTxIn{m_coinbase_txns[0]->GetHash(), 0}};
422 tx.vout = {CTxOut{11 * CENT, p2wsh_script}};
423
424 std::vector<unsigned char> vchSig;
425 const uint256 hash = SignatureHash(coinbase_script, tx, 0, SIGHASH_ALL, 0, SigVersion::BASE);
426 BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
427 vchSig.push_back(SIGHASH_ALL);
428 tx.vin[0].scriptSig << vchSig;
429
430 const CBlock block = CreateAndProcessBlock({tx}, coinbase_script);
431 LOCK(cs_main);
432 BOOST_CHECK(m_node.chainman->ActiveChain().Tip()->GetBlockHash() == block.GetHash());
433 return CTransaction{tx};
434 }};
435 const CTransaction funding_tx{mine_funding_tx()};
436
437 // Build spending tx with a 300-byte witness element (violates REDUCED_DATA).
438 CMutableTransaction spend_tx;
439 spend_tx.vin = {CTxIn{funding_tx.GetHash(), 0}};
440 spend_tx.vout = {CTxOut{10 * CENT, GetScriptForDestination(PKHash(coinbaseKey.GetPubKey()))}};
441 spend_tx.vin[0].scriptWitness.stack = {big_witness_elem, {witness_script.begin(), witness_script.end()}};
442 const CTransaction spend{spend_tx};
443 BOOST_CHECK_EQUAL(spend.vin[0].scriptWitness.stack[0].size(), 300U);
444
445 LOCK(cs_main);
446 auto& coins_tip = m_node.chainman->ActiveChainstate().CoinsTip();
447
448 // Use a fresh validation cache to isolate this test.
449 ValidationCache validation_cache{/*script_execution_cache_bytes=*/1 << 20, /*signature_cache_bytes=*/1 << 20};
450
451 const auto run_check{[&](const std::vector<unsigned int>& flags_per_input) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
452 TxValidationState state;
453 PrecomputedTransactionData txdata;
454 return CheckInputScripts(spend, state, &coins_tip, strict_flags,
455 /*cacheSigStore=*/true, /*cacheFullScriptStore=*/true,
456 txdata, validation_cache, /*pvChecks=*/nullptr, flags_per_input);
457 }};
458
459 // Step 1: strict validation (REDUCED_DATA enforced) must fail.
460 BOOST_CHECK(!run_check({}));
461
462 // Step 2: relaxed per-input flags (no REDUCED_DATA) must pass.
463 BOOST_CHECK(run_check({relaxed_flags}));
464
465 // Step 3: strict validation must STILL fail.
466 // If the cache was poisoned in step 2, this incorrectly passes.
467 BOOST_CHECK(!run_check({}));
468 }
469
470 BOOST_AUTO_TEST_SUITE_END()
471