[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; tx.vout[0].nValue -= LOWFEE;
hash = tx.GetHash(); hash = tx.GetHash();
AddToMempool(tx_mempool, entry.Fee(LOWFEE).Time(Now<NodeSeconds>()).SpendsCoinbase(false).FromTx(tx)); 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("mandatory-script-verify-flag-failed"));
BOOST_CHECK_EXCEPTION(AssemblerForTest(tx_mempool).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed"));
// Delete the dummy blocks again. // Delete the dummy blocks again.
while (m_node.chainman->ActiveChain().Tip()->nHeight > nHeight) { 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 // Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage()); tx_state.GetRejectReason(), tx_state.GetDebugMessage());
LogError("ConnectBlock(): CheckInputScripts on %s failed with %s\n", LogInfo("Script validation error in block: %s\n", tx_state.GetRejectReason());
tx.GetHash().ToString(), state.ToString());
return false; return false;
} }
control.Add(std::move(vChecks)); control.Add(std::move(vChecks));
@ -2717,8 +2716,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
auto parallel_result = control.Complete(); auto parallel_result = control.Complete();
if (parallel_result.has_value()) { if (parallel_result.has_value()) {
LogPrintf("ERROR: %s: CheckQueue failed\n", __func__); state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(*parallel_result)));
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed"); LogInfo("Script validation error in block: %s", state.GetRejectReason());
return false;
} }
const auto time_4{SteadyClock::now()}; const auto time_4{SteadyClock::now()};
m_chainman.time_verify += time_4 - time_2; m_chainman.time_verify += time_4 - time_2;

View file

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

View file

@ -87,7 +87,6 @@ class BIP65Test(BitcoinTestFramework):
self.noban_tx_relay = True self.noban_tx_relay = True
self.extra_args = [[ self.extra_args = [[
f'-testactivationheight=cltv@{CLTV_HEIGHT}', 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 '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard
]] ]]
self.setup_clean_chain = True self.setup_clean_chain = True
@ -175,7 +174,7 @@ class BIP65Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root() block.hashMerkleRoot = block.calc_merkle_root()
block.solve() 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)) peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping() peer.sync_with_ping()

View file

@ -99,7 +99,6 @@ class BIP68_112_113Test(BitcoinTestFramework):
self.noban_tx_relay = True self.noban_tx_relay = True
self.extra_args = [[ self.extra_args = [[
f'-testactivationheight=csv@{CSV_ACTIVATION_HEIGHT}', 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 self.supports_cli = False

View file

@ -51,7 +51,6 @@ class BIP66Test(BitcoinTestFramework):
self.noban_tx_relay = True self.noban_tx_relay = True
self.extra_args = [[ self.extra_args = [[
f'-testactivationheight=dersig@{DERSIG_HEIGHT}', 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.setup_clean_chain = True
self.rpc_timeout = 240 self.rpc_timeout = 240
@ -131,7 +130,7 @@ class BIP66Test(BitcoinTestFramework):
block.hashMerkleRoot = block.calc_merkle_root() block.hashMerkleRoot = block.calc_merkle_root()
block.solve() 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)) peer.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
peer.sync_with_ping() peer.sync_with_ping()

View file

@ -57,7 +57,6 @@ class NULLDUMMYTest(BitcoinTestFramework):
self.extra_args = [[ self.extra_args = [[
f'-testactivationheight=segwit@{COINBASE_MATURITY + 5}', f'-testactivationheight=segwit@{COINBASE_MATURITY + 5}',
'-addresstype=legacy', '-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): 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): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
self.setup_clean_chain = True 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): 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 self.noban_tx_relay = True
# This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation.
self.extra_args = [ 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=1", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}", "-par=1"],
["-acceptnonstdtxn=0", f"-testactivationheight=segwit@{SEGWIT_HEIGHT}"], ["-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 # 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 # invalid (transactions are always validated with SCRIPT_VERIFY_WITNESS so a segwit v0 transaction
# without a witness is invalid). # 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, 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)') 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.vout.append(CTxOut(tx.vout[0].nValue, CScript([OP_TRUE])))
tx2.wit.vtxinwit.extend([CTxInWitness(), CTxInWitness()]) tx2.wit.vtxinwit.extend([CTxInWitness(), CTxInWitness()])
tx2.wit.vtxinwit[0].scriptWitness.stack = [CScript([CScriptNum(1)]), CScript([CScriptNum(1)]), witness_script] 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() block = self.build_next_block()
self.update_witness_block_with_transactions(block, [tx2]) self.update_witness_block_with_transactions(block, [tx2])