[validation] Make script error messages uniform for parallel/single validation

This makes the debug output mostly the same for -par=1 and parallel validation runs. Of course,
parallel validation is non-deterministic in what error it may encounter first if there are
multiple issues. Also, the way certain script-related and non-script-related checks are
performed differs between the two modes still, which may result in discrepancies.
This commit is contained in:
Pieter Wuille 2024-10-18 06:01:23 -04:00
parent 1ac1c33f3f
commit 146a3d5426
9 changed files with 11 additions and 19 deletions

View file

@ -403,8 +403,7 @@ void MinerTestingSetup::TestBasicMining(const CScript& scriptPubKey, const std::
tx.vout[0].nValue -= LOWFEE;
hash = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx));
// Should throw block-validation-failed
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed"));
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("mandatory-script-verify-flag-failed"));
// Delete the dummy blocks again.
while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) {

View file

@ -2688,8 +2688,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
LogError("ConnectBlock(): CheckInputScripts on %s failed with %s\n",
tx.GetHash().ToString(), state.ToString());
LogInfo("Script validation error in block: %s\n", tx_state.GetRejectReason());
return false;
}
control.Add(std::move(vChecks));
@ -2717,8 +2716,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
auto parallel_result = control.Complete();
if (parallel_result.has_value()) {
LogPrintf("ERROR: %s: CheckQueue failed\n", __func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed");
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(*parallel_result)));
LogInfo("Script validation error in block: %s", state.GetRejectReason());
return false;
}
const auto time_4{SteadyClock::now()};
m_chainman.time_verify += time_4 - time_2;

View file

@ -88,7 +88,6 @@ class FullBlockTest(BitcoinTestFramework):
self.extra_args = [[
'-acceptnonstdtxn=1', # This is a consensus block test, we don't care about tx policy
'-testactivationheight=bip34@2',
'-par=1', # Until https://github.com/bitcoin/bitcoin/issues/30960 is fixed
]]
def run_test(self):

View file

@ -87,7 +87,6 @@ class BIP65Test(BitcoinTestFramework):
self.noban_tx_relay = True
self.extra_args = [[
f'-testactivationheight=cltv@{CLTV_HEIGHT}',
'-par=1', # Use only one script thread to get the exact reject reason for testing
'-acceptnonstdtxn=1', # cltv_invalidate is nonstandard
]]
self.setup_clean_chain = True
@ -175,7 +174,7 @@ class BIP65Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with {expected_cltv_reject_reason}']):
with self.nodes[0].assert_debug_log(expected_msgs=[f'Script validation error in block: {expected_cltv_reject_reason}']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()

View file

@ -99,7 +99,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
self.noban_tx_relay = True
self.extra_args = [[
f'-testactivationheight=csv@{CSV_ACTIVATION_HEIGHT}',
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
self.supports_cli = False

View file

@ -51,7 +51,6 @@ class BIP66Test(BitcoinTestFramework):
self.noban_tx_relay = True
self.extra_args = [[
f'-testactivationheight=dersig@{DERSIG_HEIGHT}',
'-par=1', # Use only one script thread to get the exact log msg for testing
]]
self.setup_clean_chain = True
self.rpc_timeout = 240
@ -131,7 +130,7 @@ class BIP66Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()
with self.nodes[0].assert_debug_log(expected_msgs=[f'CheckInputScripts on {block.vtx[-1].hash} failed with mandatory-script-verify-flag-failed (Non-canonical DER signature)']):
with self.nodes[0].assert_debug_log(expected_msgs=[f'Script validation error in block: mandatory-script-verify-flag-failed (Non-canonical DER signature)']):
peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping()

View file

@ -57,7 +57,6 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.extra_args = [[
f'-testactivationheight=segwit@{COINBASE_MATURITY + 5}',
'-addresstype=legacy',
'-par=1', # Use only one script thread to get the exact reject reason for testing
]]
def create_transaction(self, *, txid, input_details=None, addr, amount, privkey):

View file

@ -1285,7 +1285,6 @@ class TaprootTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.extra_args = [["-par=1"]]
def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False):

View file

@ -214,6 +214,9 @@ class SegWitTest(BitcoinTestFramework):
self.noban_tx_relay = True
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
self.extra_args = [
# -par=1 should not affect validation outcome or logging/reported failures. It is kept
# here to exercise the code path still (as it is distinct for multithread script
# validation).
["-acceptnonstdtxn=1", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}", "-par=1"],
["-acceptnonstdtxn=0", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}"],
]
@ -507,10 +510,6 @@ class SegWitTest(BitcoinTestFramework):
# When the block is serialized without witness, validation fails because the transaction is
# invalid (transactions are always validated with SCRIPT_VERIFY_WITNESS so a segwit v0 transaction
# without a witness is invalid).
# Note: The reject reason for this failure could be
# 'block-validation-failed' (if script check threads > 1) or
# 'mandatory-script-verify-flag-failed (Witness program was passed an
# empty witness)' (otherwise).
test_witness_block(self.nodes[0], self.test_node, block, accepted=False, with_witness=False,
reason='mandatory-script-verify-flag-failed (Witness program was passed an empty witness)')
@ -1015,7 +1014,7 @@ class SegWitTest(BitcoinTestFramework):
tx2.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE])))
tx2.wit.vtxinwit.extend([CTxInWitness(), CTxInWitness()])
tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), CScript([CScriptNum(1)]), witness_script]
tx2.wit.vtxinwit[1].scriptWitness.stack = [CScript([OP_TRUE])]
tx2.wit.vtxinwit[1].scriptWitness.stack = []
block = self.build_next_block()
self.update_witness_block_with_transactions(block, [tx2])