Skip to content

Commit 2935b46

Browse files
committed
Merge #10192: Cache full script execution results in addition to signatures
e3f9c05 Add CheckInputs() unit tests (Suhas Daftuar) a3543af Better document CheckInputs parameter meanings (Matt Corallo) 309ee1a Update -maxsigcachesize doc clarify init logprints for it (Matt Corallo) b014668 Add CheckInputs wrapper CCoinsViewMemPool -> non-consensus-critical (Matt Corallo) eada04e Do not print soft-fork-script warning with -promiscuousmempool (Matt Corallo) b5fea8d Cache full script execution results in addition to signatures (Matt Corallo) 6d22b2b Pull script verify flags calculation out of ConnectBlock (Matt Corallo) Tree-SHA512: 0c6c3c79c64fcb21e17ab60290c5c96d4fac11624c49f841a4201eec21cb480314c52a07d1e3abd4f9c764785cc57bfd178511f495aa0469addb204e96214fe4
2 parents 0c3542e + e3f9c05 commit 2935b46

File tree

6 files changed

+449
-43
lines changed

6 files changed

+449
-43
lines changed

src/init.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ std::string HelpMessage(HelpMessageMode mode)
447447
{
448448
strUsage += HelpMessageOpt("-logtimemicros", strprintf("Add microsecond precision to debug timestamps (default: %u)", DEFAULT_LOGTIMEMICROS));
449449
strUsage += HelpMessageOpt("-mocktime=<n>", "Replace actual time with <n> seconds since epoch (default: 0)");
450-
strUsage += HelpMessageOpt("-maxsigcachesize=<n>", strprintf("Limit size of signature cache to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE));
450+
strUsage += HelpMessageOpt("-maxsigcachesize=<n>", strprintf("Limit sum of signature cache and script execution cache sizes to <n> MiB (default: %u)", DEFAULT_MAX_SIG_CACHE_SIZE));
451451
strUsage += HelpMessageOpt("-maxtipage=<n>", strprintf("Maximum tip age in seconds to consider node in initial block download (default: %u)", DEFAULT_MAX_TIP_AGE));
452452
}
453453
strUsage += HelpMessageOpt("-maxtxfee=<amt>", strprintf(_("Maximum total fees (in %s) to use in a single wallet transaction or raw transaction; setting this too low may abort large transactions (default: %s)"),
@@ -1191,6 +1191,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
11911191
LogPrintf("Using at most %i automatic connections (%i file descriptors available)\n", nMaxConnections, nFD);
11921192

11931193
InitSignatureCache();
1194+
InitScriptExecutionCache();
11941195

11951196
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
11961197
if (nScriptCheckThreads) {

src/script/sigcache.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ void InitSignatureCache()
7474
{
7575
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
7676
// setup_bytes creates the minimum possible cache (2 elements).
77-
size_t nMaxCacheSize = std::min(std::max((int64_t)0, GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
77+
size_t nMaxCacheSize = std::min(std::max((int64_t)0, GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE) / 2), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
7878
size_t nElems = signatureCache.setup_bytes(nMaxCacheSize);
79-
LogPrintf("Using %zu MiB out of %zu requested for signature cache, able to store %zu elements\n",
80-
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
79+
LogPrintf("Using %zu MiB out of %zu/2 requested for signature cache, able to store %zu elements\n",
80+
(nElems*sizeof(uint256)) >>20, (nMaxCacheSize*2)>>20, nElems);
8181
}
8282

8383
bool CachingTransactionSignatureChecker::VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& pubkey, const uint256& sighash) const

src/test/test_bitcoin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName)
3838
SetupEnvironment();
3939
SetupNetworking();
4040
InitSignatureCache();
41+
InitScriptExecutionCache();
4142
fPrintToDebugLog = false; // don't want to write to debug.log file
4243
fCheckBlockIndex = true;
4344
SelectParams(chainName);

src/test/txvalidationcache_tests.cpp

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
#include "txmempool.h"
1111
#include "random.h"
1212
#include "script/standard.h"
13+
#include "script/sign.h"
1314
#include "test/test_bitcoin.h"
1415
#include "utiltime.h"
16+
#include "core_io.h"
17+
#include "keystore.h"
18+
#include "policy/policy.h"
1519

1620
#include <boost/test/unit_test.hpp>
1721

22+
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks);
23+
1824
BOOST_AUTO_TEST_SUITE(tx_validationcache_tests)
1925

2026
static bool
@@ -84,4 +90,282 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
8490
BOOST_CHECK_EQUAL(mempool.size(), 0);
8591
}
8692

93+
// Run CheckInputs (using pcoinsTip) on the given transaction, for all script
94+
// flags. Test that CheckInputs passes for all flags that don't overlap with
95+
// the failing_flags argument, but otherwise fails.
96+
// CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY (and future NOP codes that may
97+
// get reassigned) have an interaction with DISCOURAGE_UPGRADABLE_NOPS: if
98+
// the script flags used contain DISCOURAGE_UPGRADABLE_NOPS but don't contain
99+
// CHECKLOCKTIMEVERIFY (or CHECKSEQUENCEVERIFY), but the script does contain
100+
// OP_CHECKLOCKTIMEVERIFY (or OP_CHECKSEQUENCEVERIFY), then script execution
101+
// should fail.
102+
// Capture this interaction with the upgraded_nop argument: set it when evaluating
103+
// any script flag that is implemented as an upgraded NOP code.
104+
void ValidateCheckInputsForAllFlags(CMutableTransaction &tx, uint32_t failing_flags, bool add_to_cache, bool upgraded_nop)
105+
{
106+
PrecomputedTransactionData txdata(tx);
107+
// If we add many more flags, this loop can get too expensive, but we can
108+
// rewrite in the future to randomly pick a set of flags to evaluate.
109+
for (uint32_t test_flags=0; test_flags < (1U << 16); test_flags += 1) {
110+
CValidationState state;
111+
// Filter out incompatible flag choices
112+
if ((test_flags & SCRIPT_VERIFY_CLEANSTACK)) {
113+
// CLEANSTACK requires P2SH and WITNESS, see VerifyScript() in
114+
// script/interpreter.cpp
115+
test_flags |= SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS;
116+
}
117+
if ((test_flags & SCRIPT_VERIFY_WITNESS)) {
118+
// WITNESS requires P2SH
119+
test_flags |= SCRIPT_VERIFY_P2SH;
120+
}
121+
bool ret = CheckInputs(tx, state, pcoinsTip, true, test_flags, true, add_to_cache, txdata, nullptr);
122+
// CheckInputs should succeed iff test_flags doesn't intersect with
123+
// failing_flags
124+
bool expected_return_value = !(test_flags & failing_flags);
125+
if (expected_return_value && upgraded_nop) {
126+
// If the script flag being tested corresponds to an upgraded NOP,
127+
// then script execution should fail if DISCOURAGE_UPGRADABLE_NOPS
128+
// is set.
129+
expected_return_value = !(test_flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS);
130+
}
131+
BOOST_CHECK_EQUAL(ret, expected_return_value);
132+
133+
// Test the caching
134+
if (ret && add_to_cache) {
135+
// Check that we get a cache hit if the tx was valid
136+
std::vector<CScriptCheck> scriptchecks;
137+
BOOST_CHECK(CheckInputs(tx, state, pcoinsTip, true, test_flags, true, add_to_cache, txdata, &scriptchecks));
138+
BOOST_CHECK(scriptchecks.empty());
139+
} else {
140+
// Check that we get script executions to check, if the transaction
141+
// was invalid, or we didn't add to cache.
142+
std::vector<CScriptCheck> scriptchecks;
143+
BOOST_CHECK(CheckInputs(tx, state, pcoinsTip, true, test_flags, true, add_to_cache, txdata, &scriptchecks));
144+
BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
145+
}
146+
}
147+
}
148+
149+
BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup)
150+
{
151+
// Test that passing CheckInputs with one set of script flags doesn't imply
152+
// that we would pass again with a different set of flags.
153+
InitScriptExecutionCache();
154+
155+
CScript p2pk_scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
156+
CScript p2sh_scriptPubKey = GetScriptForDestination(CScriptID(p2pk_scriptPubKey));
157+
CScript p2pkh_scriptPubKey = GetScriptForDestination(coinbaseKey.GetPubKey().GetID());
158+
CScript p2wpkh_scriptPubKey = GetScriptForWitness(p2pkh_scriptPubKey);
159+
160+
CBasicKeyStore keystore;
161+
keystore.AddKey(coinbaseKey);
162+
keystore.AddCScript(p2pk_scriptPubKey);
163+
164+
// flags to test: SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, SCRIPT_VERIFY_CHECKSEQUENCE_VERIFY, SCRIPT_VERIFY_NULLDUMMY, uncompressed pubkey thing
165+
166+
// Create 2 outputs that match the three scripts above, spending the first
167+
// coinbase tx.
168+
CMutableTransaction spend_tx;
169+
170+
spend_tx.nVersion = 1;
171+
spend_tx.vin.resize(1);
172+
spend_tx.vin[0].prevout.hash = coinbaseTxns[0].GetHash();
173+
spend_tx.vin[0].prevout.n = 0;
174+
spend_tx.vout.resize(4);
175+
spend_tx.vout[0].nValue = 11*CENT;
176+
spend_tx.vout[0].scriptPubKey = p2sh_scriptPubKey;
177+
spend_tx.vout[1].nValue = 11*CENT;
178+
spend_tx.vout[1].scriptPubKey = p2wpkh_scriptPubKey;
179+
spend_tx.vout[2].nValue = 11*CENT;
180+
spend_tx.vout[2].scriptPubKey = CScript() << OP_CHECKLOCKTIMEVERIFY << OP_DROP << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
181+
spend_tx.vout[3].nValue = 11*CENT;
182+
spend_tx.vout[3].scriptPubKey = CScript() << OP_CHECKSEQUENCEVERIFY << OP_DROP << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
183+
184+
// Sign, with a non-DER signature
185+
{
186+
std::vector<unsigned char> vchSig;
187+
uint256 hash = SignatureHash(p2pk_scriptPubKey, spend_tx, 0, SIGHASH_ALL, 0, SIGVERSION_BASE);
188+
BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
189+
vchSig.push_back((unsigned char) 0); // padding byte makes this non-DER
190+
vchSig.push_back((unsigned char)SIGHASH_ALL);
191+
spend_tx.vin[0].scriptSig << vchSig;
192+
}
193+
194+
LOCK(cs_main);
195+
196+
// Test that invalidity under a set of flags doesn't preclude validity
197+
// under other (eg consensus) flags.
198+
// spend_tx is invalid according to DERSIG
199+
CValidationState state;
200+
{
201+
PrecomputedTransactionData ptd_spend_tx(spend_tx);
202+
203+
BOOST_CHECK(!CheckInputs(spend_tx, state, pcoinsTip, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, nullptr));
204+
205+
// If we call again asking for scriptchecks (as happens in
206+
// ConnectBlock), we should add a script check object for this -- we're
207+
// not caching invalidity (if that changes, delete this test case).
208+
std::vector<CScriptCheck> scriptchecks;
209+
BOOST_CHECK(CheckInputs(spend_tx, state, pcoinsTip, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_DERSIG, true, true, ptd_spend_tx, &scriptchecks));
210+
BOOST_CHECK_EQUAL(scriptchecks.size(), 1);
211+
212+
// Test that CheckInputs returns true iff DERSIG-enforcing flags are
213+
// not present. Don't add these checks to the cache, so that we can
214+
// test later that block validation works fine in the absence of cached
215+
// successes.
216+
ValidateCheckInputsForAllFlags(spend_tx, SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_LOW_S | SCRIPT_VERIFY_STRICTENC, false, false);
217+
218+
// And if we produce a block with this tx, it should be valid (DERSIG not
219+
// enabled yet), even though there's no cache entry.
220+
CBlock block;
221+
222+
block = CreateAndProcessBlock({spend_tx}, p2pk_scriptPubKey);
223+
BOOST_CHECK(chainActive.Tip()->GetBlockHash() == block.GetHash());
224+
BOOST_CHECK(pcoinsTip->GetBestBlock() == block.GetHash());
225+
}
226+
227+
// Test P2SH: construct a transaction that is valid without P2SH, and
228+
// then test validity with P2SH.
229+
{
230+
CMutableTransaction invalid_under_p2sh_tx;
231+
invalid_under_p2sh_tx.nVersion = 1;
232+
invalid_under_p2sh_tx.vin.resize(1);
233+
invalid_under_p2sh_tx.vin[0].prevout.hash = spend_tx.GetHash();
234+
invalid_under_p2sh_tx.vin[0].prevout.n = 0;
235+
invalid_under_p2sh_tx.vout.resize(1);
236+
invalid_under_p2sh_tx.vout[0].nValue = 11*CENT;
237+
invalid_under_p2sh_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
238+
std::vector<unsigned char> vchSig2(p2pk_scriptPubKey.begin(), p2pk_scriptPubKey.end());
239+
invalid_under_p2sh_tx.vin[0].scriptSig << vchSig2;
240+
241+
ValidateCheckInputsForAllFlags(invalid_under_p2sh_tx, SCRIPT_VERIFY_P2SH, true, false);
242+
}
243+
244+
// Test CHECKLOCKTIMEVERIFY
245+
{
246+
CMutableTransaction invalid_with_cltv_tx;
247+
invalid_with_cltv_tx.nVersion = 1;
248+
invalid_with_cltv_tx.nLockTime = 100;
249+
invalid_with_cltv_tx.vin.resize(1);
250+
invalid_with_cltv_tx.vin[0].prevout.hash = spend_tx.GetHash();
251+
invalid_with_cltv_tx.vin[0].prevout.n = 2;
252+
invalid_with_cltv_tx.vin[0].nSequence = 0;
253+
invalid_with_cltv_tx.vout.resize(1);
254+
invalid_with_cltv_tx.vout[0].nValue = 11*CENT;
255+
invalid_with_cltv_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
256+
257+
// Sign
258+
std::vector<unsigned char> vchSig;
259+
uint256 hash = SignatureHash(spend_tx.vout[2].scriptPubKey, invalid_with_cltv_tx, 0, SIGHASH_ALL, 0, SIGVERSION_BASE);
260+
BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
261+
vchSig.push_back((unsigned char)SIGHASH_ALL);
262+
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101;
263+
264+
ValidateCheckInputsForAllFlags(invalid_with_cltv_tx, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true);
265+
266+
// Make it valid, and check again
267+
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
268+
CValidationState state;
269+
PrecomputedTransactionData txdata(invalid_with_cltv_tx);
270+
BOOST_CHECK(CheckInputs(invalid_with_cltv_tx, state, pcoinsTip, true, SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY, true, true, txdata, nullptr));
271+
}
272+
273+
// TEST CHECKSEQUENCEVERIFY
274+
{
275+
CMutableTransaction invalid_with_csv_tx;
276+
invalid_with_csv_tx.nVersion = 2;
277+
invalid_with_csv_tx.vin.resize(1);
278+
invalid_with_csv_tx.vin[0].prevout.hash = spend_tx.GetHash();
279+
invalid_with_csv_tx.vin[0].prevout.n = 3;
280+
invalid_with_csv_tx.vin[0].nSequence = 100;
281+
invalid_with_csv_tx.vout.resize(1);
282+
invalid_with_csv_tx.vout[0].nValue = 11*CENT;
283+
invalid_with_csv_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
284+
285+
// Sign
286+
std::vector<unsigned char> vchSig;
287+
uint256 hash = SignatureHash(spend_tx.vout[3].scriptPubKey, invalid_with_csv_tx, 0, SIGHASH_ALL, 0, SIGVERSION_BASE);
288+
BOOST_CHECK(coinbaseKey.Sign(hash, vchSig));
289+
vchSig.push_back((unsigned char)SIGHASH_ALL);
290+
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101;
291+
292+
ValidateCheckInputsForAllFlags(invalid_with_csv_tx, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true);
293+
294+
// Make it valid, and check again
295+
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
296+
CValidationState state;
297+
PrecomputedTransactionData txdata(invalid_with_csv_tx);
298+
BOOST_CHECK(CheckInputs(invalid_with_csv_tx, state, pcoinsTip, true, SCRIPT_VERIFY_CHECKSEQUENCEVERIFY, true, true, txdata, nullptr));
299+
}
300+
301+
// TODO: add tests for remaining script flags
302+
303+
// Test that passing CheckInputs with a valid witness doesn't imply success
304+
// for the same tx with a different witness.
305+
{
306+
CMutableTransaction valid_with_witness_tx;
307+
valid_with_witness_tx.nVersion = 1;
308+
valid_with_witness_tx.vin.resize(1);
309+
valid_with_witness_tx.vin[0].prevout.hash = spend_tx.GetHash();
310+
valid_with_witness_tx.vin[0].prevout.n = 1;
311+
valid_with_witness_tx.vout.resize(1);
312+
valid_with_witness_tx.vout[0].nValue = 11*CENT;
313+
valid_with_witness_tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
314+
315+
// Sign
316+
SignatureData sigdata;
317+
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &valid_with_witness_tx, 0, 11*CENT, SIGHASH_ALL), spend_tx.vout[1].scriptPubKey, sigdata);
318+
UpdateTransaction(valid_with_witness_tx, 0, sigdata);
319+
320+
// This should be valid under all script flags.
321+
ValidateCheckInputsForAllFlags(valid_with_witness_tx, 0, true, false);
322+
323+
// Remove the witness, and check that it is now invalid.
324+
valid_with_witness_tx.vin[0].scriptWitness.SetNull();
325+
ValidateCheckInputsForAllFlags(valid_with_witness_tx, SCRIPT_VERIFY_WITNESS, true, false);
326+
}
327+
328+
{
329+
// Test a transaction with multiple inputs.
330+
CMutableTransaction tx;
331+
332+
tx.nVersion = 1;
333+
tx.vin.resize(2);
334+
tx.vin[0].prevout.hash = spend_tx.GetHash();
335+
tx.vin[0].prevout.n = 0;
336+
tx.vin[1].prevout.hash = spend_tx.GetHash();
337+
tx.vin[1].prevout.n = 1;
338+
tx.vout.resize(1);
339+
tx.vout[0].nValue = 22*CENT;
340+
tx.vout[0].scriptPubKey = p2pk_scriptPubKey;
341+
342+
// Sign
343+
for (int i=0; i<2; ++i) {
344+
SignatureData sigdata;
345+
ProduceSignature(MutableTransactionSignatureCreator(&keystore, &tx, i, 11*CENT, SIGHASH_ALL), spend_tx.vout[i].scriptPubKey, sigdata);
346+
UpdateTransaction(tx, i, sigdata);
347+
}
348+
349+
// This should be valid under all script flags
350+
ValidateCheckInputsForAllFlags(tx, 0, true, false);
351+
352+
// Check that if the second input is invalid, but the first input is
353+
// valid, the transaction is not cached.
354+
// Invalidate vin[1]
355+
tx.vin[1].scriptWitness.SetNull();
356+
357+
CValidationState state;
358+
PrecomputedTransactionData txdata(tx);
359+
// This transaction is now invalid under segwit, because of the second input.
360+
BOOST_CHECK(!CheckInputs(tx, state, pcoinsTip, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, nullptr));
361+
362+
std::vector<CScriptCheck> scriptchecks;
363+
// Make sure this transaction was not cached (ie because the first
364+
// input was valid)
365+
BOOST_CHECK(CheckInputs(tx, state, pcoinsTip, true, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, true, true, txdata, &scriptchecks));
366+
// Should get 2 script checks back -- caching is on a whole-transaction basis.
367+
BOOST_CHECK_EQUAL(scriptchecks.size(), 2);
368+
}
369+
}
370+
87371
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)