mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
consensus: Cross-input script validation framework
Adds a framework for validation checks that persist an individual input's script validation, allowing to persist state across the evaluations of multiple inputs of a transaction. This will be used for the amount logic of OP_CHECKCONTRACTVERIFY, that performs amount checks that are aggregate across multiple inputs. It could also be used for other applications, like batch validation and cross-input Schnorr signature aggregation.
This commit is contained in:
parent
66153cca78
commit
2c1deefedc
7 changed files with 110 additions and 36 deletions
|
@ -403,7 +403,7 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
|
|||
assert(false);
|
||||
}
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror, TransactionExecutionData* tx_exec_data)
|
||||
{
|
||||
static const CScriptNum bnZero(0);
|
||||
static const CScriptNum bnOne(1);
|
||||
|
@ -1233,10 +1233,10 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
|
|||
return set_success(serror);
|
||||
}
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, TransactionExecutionData* tx_exec_data)
|
||||
{
|
||||
ScriptExecutionData execdata;
|
||||
return EvalScript(stack, script, flags, checker, sigversion, execdata, serror);
|
||||
return EvalScript(stack, script, flags, checker, sigversion, execdata, serror, tx_exec_data);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -1785,7 +1785,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSequence(const CScriptNum& nSeq
|
|||
template class GenericTransactionSignatureChecker<CTransaction>;
|
||||
template class GenericTransactionSignatureChecker<CMutableTransaction>;
|
||||
|
||||
static bool ExecuteWitnessScript(const std::span<const valtype>& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, ScriptError* serror)
|
||||
static bool ExecuteWitnessScript(const std::span<const valtype>& stack_span, const CScript& exec_script, unsigned int flags, SigVersion sigversion, const BaseSignatureChecker& checker, ScriptExecutionData& execdata, TransactionExecutionData* tx_exec_data, ScriptError* serror)
|
||||
{
|
||||
std::vector<valtype> stack{stack_span.begin(), stack_span.end()};
|
||||
|
||||
|
@ -1817,7 +1817,7 @@ static bool ExecuteWitnessScript(const std::span<const valtype>& stack_span, con
|
|||
}
|
||||
|
||||
// Run the script interpreter.
|
||||
if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror)) return false;
|
||||
if (!EvalScript(stack, exec_script, flags, checker, sigversion, execdata, serror, tx_exec_data)) return false;
|
||||
|
||||
// Scripts inside witness implicitly require cleanstack behaviour
|
||||
if (stack.size() != 1) return set_error(serror, SCRIPT_ERR_CLEANSTACK);
|
||||
|
@ -1870,7 +1870,7 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
|
|||
return q.CheckTapTweak(p, merkle_root, control[0] & 1);
|
||||
}
|
||||
|
||||
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)
|
||||
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh, TransactionExecutionData* tx_exec_data = nullptr)
|
||||
{
|
||||
CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
|
||||
std::span stack{witness.stack};
|
||||
|
@ -1889,14 +1889,14 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
|
|||
if (memcmp(hash_exec_script.begin(), program.data(), 32)) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
|
||||
}
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, tx_exec_data, serror);
|
||||
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
|
||||
// BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
|
||||
if (stack.size() != 2) {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
|
||||
}
|
||||
exec_script << OP_DUP << OP_HASH160 << program << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, serror);
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, execdata, tx_exec_data, serror);
|
||||
} else {
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
|
||||
}
|
||||
|
@ -1936,7 +1936,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
|
|||
exec_script = CScript(script.begin(), script.end());
|
||||
execdata.m_validation_weight_left = ::GetSerializeSize(witness.stack) + VALIDATION_WEIGHT_OFFSET;
|
||||
execdata.m_validation_weight_left_init = true;
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, serror);
|
||||
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::TAPSCRIPT, checker, execdata, tx_exec_data, serror);
|
||||
}
|
||||
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
|
||||
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
|
||||
|
@ -1955,7 +1955,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
|
|||
// There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above.
|
||||
}
|
||||
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, TransactionExecutionData* tx_exec_data)
|
||||
{
|
||||
static const CScriptWitness emptyWitness;
|
||||
if (witness == nullptr) {
|
||||
|
@ -1995,7 +1995,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
|
|||
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
|
||||
}
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false)) {
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/false, tx_exec_data)) {
|
||||
return false;
|
||||
}
|
||||
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
|
||||
|
@ -2040,7 +2040,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
|
|||
// reintroduce malleability.
|
||||
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
|
||||
}
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true)) {
|
||||
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /*is_p2sh=*/true, tx_exec_data)) {
|
||||
return false;
|
||||
}
|
||||
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
#include <primitives/transaction.h>
|
||||
#include <script/script_error.h> // IWYU pragma: export
|
||||
#include <span.h>
|
||||
#include <sync.h>
|
||||
#include <uint256.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class CPubKey;
|
||||
|
@ -223,6 +225,49 @@ struct ScriptExecutionData
|
|||
std::optional<uint256> m_output_hash;
|
||||
};
|
||||
|
||||
/** The state of the script interpreter that persists across inputs of a single transaction.
|
||||
*
|
||||
* As access happens across different worker threads, it is crucial that access to this struct
|
||||
* is properly synchronized. Code accessing its members must hold the m_mutex lock. */
|
||||
struct TransactionExecutionData {
|
||||
const CTransaction* m_tx;
|
||||
Mutex m_mutex;
|
||||
|
||||
// members related to the validation state of this transaction
|
||||
// that need to persist across inputs will be added here,
|
||||
// protected by m_mutex.
|
||||
|
||||
TransactionExecutionData(const CTransaction* tx)
|
||||
: m_tx(tx)
|
||||
{
|
||||
assert(tx != nullptr); // Ensure the transaction pointer is valid
|
||||
}
|
||||
};
|
||||
|
||||
/** Creates and stores TransactionExecutionData instances for each transaction. */
|
||||
class TransactionExecutionDataStore {
|
||||
private:
|
||||
Mutex m_mutex;
|
||||
std::unordered_map<const CTransaction*, std::unique_ptr<TransactionExecutionData>> m_store GUARDED_BY(m_mutex);
|
||||
|
||||
public:
|
||||
TransactionExecutionDataStore() = default;
|
||||
|
||||
// Retrieves the TransactionExecutionData for the given transaction.
|
||||
// If it does not exist, it creates a new instance.
|
||||
TransactionExecutionData* getOrCreate(const CTransaction* tx) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) {
|
||||
LOCK(m_mutex);
|
||||
auto it = m_store.find(tx);
|
||||
if (it != m_store.end()) {
|
||||
return it->second.get();
|
||||
}
|
||||
auto newData = std::make_unique<TransactionExecutionData>(tx);
|
||||
TransactionExecutionData* newDataPtr = newData.get();
|
||||
m_store[tx] = std::move(newData);
|
||||
return newDataPtr;
|
||||
}
|
||||
};
|
||||
|
||||
/** Signature hash sizes */
|
||||
static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32;
|
||||
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
|
||||
|
@ -343,9 +388,9 @@ uint256 ComputeTapbranchHash(std::span<const unsigned char> a, std::span<const u
|
|||
* Requires control block to have valid length (33 + k*32, with k in {0,1,..,128}). */
|
||||
uint256 ComputeTaprootMerkleRoot(std::span<const unsigned char> control, const uint256& tapleaf_hash);
|
||||
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr);
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr);
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr, TransactionExecutionData* tx_exec_data = nullptr);
|
||||
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr, TransactionExecutionData* tx_exec_data = nullptr);
|
||||
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr, TransactionExecutionData* tx_exec_data = nullptr);
|
||||
|
||||
size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);
|
||||
|
||||
|
|
|
@ -121,7 +121,9 @@ BOOST_AUTO_TEST_CASE(sign)
|
|||
{
|
||||
CScript sigSave = txTo[i].vin[0].scriptSig;
|
||||
txTo[i].vin[0].scriptSig = txTo[j].vin[0].scriptSig;
|
||||
bool sigOK = !CScriptCheck(txFrom.vout[txTo[i].vin[0].prevout.n], CTransaction(txTo[i]), signature_cache, 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata)().has_value();
|
||||
auto imm_txTo = CTransaction(txTo[i]);
|
||||
TransactionExecutionData tx_exec_data(&imm_txTo);
|
||||
bool sigOK = !CScriptCheck(txFrom.vout[txTo[i].vin[0].prevout.n], imm_txTo, signature_cache, 0, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, false, &txdata, &tx_exec_data)().has_value();
|
||||
if (i == j)
|
||||
BOOST_CHECK_MESSAGE(sigOK, strprintf("VerifySignature %d %d", i, j));
|
||||
else
|
||||
|
|
|
@ -567,6 +567,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction)
|
|||
|
||||
// check all inputs concurrently, with the cache
|
||||
PrecomputedTransactionData txdata(tx);
|
||||
TransactionExecutionData tx_exec_data(&tx);
|
||||
CCheckQueue<CScriptCheck> scriptcheckqueue(/*batch_size=*/128, /*worker_threads_num=*/20);
|
||||
CCheckQueueControl<CScriptCheck> control(&scriptcheckqueue);
|
||||
|
||||
|
@ -584,7 +585,7 @@ BOOST_AUTO_TEST_CASE(test_big_witness_transaction)
|
|||
|
||||
for(uint32_t i = 0; i < mtx.vin.size(); i++) {
|
||||
std::vector<CScriptCheck> vChecks;
|
||||
vChecks.emplace_back(coins[tx.vin[i].prevout.n].out, tx, signature_cache, i, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata);
|
||||
vChecks.emplace_back(coins[tx.vin[i].prevout.n].out, tx, signature_cache, i, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS, false, &txdata, &tx_exec_data);
|
||||
control.Add(std::move(vChecks));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
|
||||
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||
ValidationCache& validation_cache,
|
||||
TransactionExecutionDataStore &tx_exec_store,
|
||||
std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(txvalidationcache_tests)
|
||||
|
@ -142,7 +143,8 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
|
|||
// WITNESS requires P2SH
|
||||
test_flags |= SCRIPT_VERIFY_P2SH;
|
||||
}
|
||||
bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, nullptr);
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
bool ret = CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, tx_exec_store, nullptr);
|
||||
// CheckInputScripts should succeed iff test_flags doesn't intersect with
|
||||
// failing_flags
|
||||
bool expected_return_value = !(test_flags & failing_flags);
|
||||
|
@ -152,13 +154,15 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
|
|||
if (ret && add_to_cache) {
|
||||
// Check that we get a cache hit if the tx was valid
|
||||
std::vector<CScriptCheck> scriptchecks;
|
||||
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
|
||||
TransactionExecutionDataStore tx_exec_store2;
|
||||
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, tx_exec_store2, &scriptchecks));
|
||||
BOOST_CHECK(scriptchecks.empty());
|
||||
} else {
|
||||
// Check that we get script executions to check, if the transaction
|
||||
// was invalid, or we didn't add to cache.
|
||||
std::vector<CScriptCheck> scriptchecks;
|
||||
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, &scriptchecks));
|
||||
TransactionExecutionDataStore tx_exec_store2;
|
||||
BOOST_CHECK(CheckInputScripts(tx, state, &active_coins_tip, test_flags, true, add_to_cache, txdata, validation_cache, tx_exec_store2, &scriptchecks));
|
||||
BOOST_CHECK_EQUAL(scriptchecks.size(), tx.vin.size());
|
||||
}
|
||||
}
|
||||
|
@ -215,14 +219,16 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
|||
|
||||
TxValidationState state;
|
||||
PrecomputedTransactionData ptd_spend_tx;
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
|
||||
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));
|
||||
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, tx_exec_store, nullptr));
|
||||
|
||||
// If we call again asking for scriptchecks (as happens in
|
||||
// ConnectBlock), we should add a script check object for this -- we're
|
||||
// not caching invalidity (if that changes, delete this test case).
|
||||
std::vector<CScriptCheck> scriptchecks;
|
||||
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));
|
||||
TransactionExecutionDataStore tx_exec_store2;
|
||||
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, tx_exec_store2, &scriptchecks));
|
||||
BOOST_CHECK_EQUAL(scriptchecks.size(), 1U);
|
||||
|
||||
// Test that CheckInputScripts returns true iff DERSIG-enforcing flags are
|
||||
|
@ -284,7 +290,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
|||
invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
|
||||
TxValidationState state;
|
||||
PrecomputedTransactionData txdata;
|
||||
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));
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
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, tx_exec_store, nullptr));
|
||||
}
|
||||
|
||||
// TEST CHECKSEQUENCEVERIFY
|
||||
|
@ -312,7 +319,8 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
|||
invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 100;
|
||||
TxValidationState state;
|
||||
PrecomputedTransactionData txdata;
|
||||
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));
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
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, tx_exec_store, nullptr));
|
||||
}
|
||||
|
||||
// TODO: add tests for remaining script flags
|
||||
|
@ -373,13 +381,15 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, Dersig100Setup)
|
|||
|
||||
TxValidationState state;
|
||||
PrecomputedTransactionData txdata;
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
// This transaction is now invalid under segwit, because of the second input.
|
||||
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));
|
||||
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, tx_exec_store, nullptr));
|
||||
|
||||
std::vector<CScriptCheck> scriptchecks;
|
||||
// Make sure this transaction was not cached (ie because the first
|
||||
// input was valid)
|
||||
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));
|
||||
TransactionExecutionDataStore tx_exec_store2;
|
||||
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, tx_exec_store2, &scriptchecks));
|
||||
// Should get 2 script checks back -- caching is on a whole-transaction basis.
|
||||
BOOST_CHECK_EQUAL(scriptchecks.size(), 2U);
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
|
||||
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||
ValidationCache& validation_cache,
|
||||
TransactionExecutionDataStore& tx_exec_store,
|
||||
std::vector<CScriptCheck>* pvChecks = nullptr)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
|
@ -428,8 +429,10 @@ static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationS
|
|||
}
|
||||
}
|
||||
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
|
||||
// Call CheckInputScripts() to cache signature and script validity against current tip consensus rules.
|
||||
return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache);
|
||||
return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache, tx_exec_store);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -1235,13 +1238,16 @@ bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws)
|
|||
|
||||
// Check input scripts and signatures.
|
||||
// This is done last to help prevent CPU exhaustion denial-of-service attacks.
|
||||
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache(), tx_exec_store)) {
|
||||
// SCRIPT_VERIFY_CLEANSTACK requires SCRIPT_VERIFY_WITNESS, so we
|
||||
// need to turn both off, and compare against just turning off CLEANSTACK
|
||||
// to see if the failure is specifically due to witness validation.
|
||||
TxValidationState state_dummy; // Want reported failures to be from first CheckInputScripts
|
||||
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache()) &&
|
||||
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache())) {
|
||||
TransactionExecutionDataStore tx_exec_store2;
|
||||
TransactionExecutionDataStore tx_exec_store3;
|
||||
if (!tx.HasWitness() && CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~(SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_CLEANSTACK), true, false, ws.m_precomputed_txdata, GetValidationCache(), tx_exec_store2) &&
|
||||
!CheckInputScripts(tx, state_dummy, m_view, scriptVerifyFlags & ~SCRIPT_VERIFY_CLEANSTACK, true, false, ws.m_precomputed_txdata, GetValidationCache(), tx_exec_store3)) {
|
||||
// Only the witness is missing, so the transaction itself may be fine.
|
||||
state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED,
|
||||
state.GetRejectReason(), state.GetDebugMessage());
|
||||
|
@ -2117,7 +2123,7 @@ std::optional<std::pair<ScriptError, std::string>> CScriptCheck::operator()() {
|
|||
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
|
||||
const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
|
||||
ScriptError error{SCRIPT_ERR_UNKNOWN_ERROR};
|
||||
if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *m_signature_cache, *txdata), &error)) {
|
||||
if (VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *m_signature_cache, *txdata), &error, m_tx_execdata)) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
auto debug_str = strprintf("input %i of %s (wtxid %s), spending %s:%i", nIn, ptxTo->GetHash().ToString(), ptxTo->GetWitnessHash().ToString(), ptxTo->vin[nIn].prevout.hash.ToString(), ptxTo->vin[nIn].prevout.n);
|
||||
|
@ -2164,6 +2170,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
|
||||
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
|
||||
ValidationCache& validation_cache,
|
||||
TransactionExecutionDataStore& tx_exec_store,
|
||||
std::vector<CScriptCheck>* pvChecks)
|
||||
{
|
||||
if (tx.IsCoinBase()) return true;
|
||||
|
@ -2199,6 +2206,8 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
}
|
||||
assert(txdata.m_spent_outputs.size() == tx.vin.size());
|
||||
|
||||
TransactionExecutionData *tx_exec_data = tx_exec_store.getOrCreate(&tx);
|
||||
|
||||
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
||||
|
||||
// We very carefully only pass in things to CScriptCheck which
|
||||
|
@ -2208,7 +2217,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
// spent being checked as a part of CScriptCheck.
|
||||
|
||||
// Verify signature
|
||||
CScriptCheck check(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &txdata);
|
||||
CScriptCheck check(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i, flags, cacheSigStore, &txdata, tx_exec_data);
|
||||
if (pvChecks) {
|
||||
pvChecks->emplace_back(std::move(check));
|
||||
} else if (auto result = check(); result.has_value()) {
|
||||
|
@ -2221,8 +2230,11 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
|
|||
// splitting the network between upgraded and
|
||||
// non-upgraded nodes by banning CONSENSUS-failing
|
||||
// data providers.
|
||||
// TODO: Should we make a clone of *tx_exec_data before the first check above, or is it fine
|
||||
// to pass nullptr?
|
||||
// We cannot reuse tx_exec_data, as it might be modified
|
||||
CScriptCheck check2(txdata.m_spent_outputs[i], tx, validation_cache.m_signature_cache, i,
|
||||
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
|
||||
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata, nullptr);
|
||||
auto mandatory_result = check2();
|
||||
if (!mandatory_result.has_value()) {
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(result->first)), result->second);
|
||||
|
@ -2630,6 +2642,8 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
|||
|
||||
CBlockUndo blockundo;
|
||||
|
||||
TransactionExecutionDataStore tx_exec_store;
|
||||
|
||||
// Precomputed transaction data pointers must not be invalidated
|
||||
// until after `control` has run the script checks (potentially
|
||||
// in multiple threads). Preallocate the vector size so a new allocation
|
||||
|
@ -2698,7 +2712,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
|
|||
std::vector<CScriptCheck> vChecks;
|
||||
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
|
||||
TxValidationState tx_state;
|
||||
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, parallel_script_checks ? &vChecks : nullptr)) {
|
||||
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, tx_exec_store, parallel_script_checks ? &vChecks : nullptr)) {
|
||||
// Any transaction validation failure in ConnectBlock is a block consensus failure
|
||||
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
|
||||
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <policy/feerate.h>
|
||||
#include <policy/packages.h>
|
||||
#include <policy/policy.h>
|
||||
#include <script/interpreter.h>
|
||||
#include <script/script_error.h>
|
||||
#include <script/sigcache.h>
|
||||
#include <sync.h>
|
||||
|
@ -337,10 +338,11 @@ private:
|
|||
bool cacheStore;
|
||||
PrecomputedTransactionData *txdata;
|
||||
SignatureCache* m_signature_cache;
|
||||
TransactionExecutionData *m_tx_execdata;
|
||||
|
||||
public:
|
||||
CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, SignatureCache& signature_cache, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
|
||||
m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn), m_signature_cache(&signature_cache) { }
|
||||
CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, SignatureCache& signature_cache, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn, TransactionExecutionData* tx_execdata) :
|
||||
m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn), m_signature_cache(&signature_cache), m_tx_execdata(tx_execdata) { }
|
||||
|
||||
CScriptCheck(const CScriptCheck&) = delete;
|
||||
CScriptCheck& operator=(const CScriptCheck&) = delete;
|
||||
|
|
Loading…
Add table
Reference in a new issue