mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-10 23:58:17 +02:00
Merge bitcoin/bitcoin#29060: Policy: Report debug message why inputs are non standard
d8f4e7caf0doc: add release notes (ismaelsadeeq)248c175e3dtest: ensure `ValidateInputsStandardness` optionally returns debug string (ismaelsadeeq)d2716e9e5bpolicy: update `AreInputsStandard` to return error string (ismaelsadeeq) Pull request description: This PR is another attempt at #13525. Transactions that fail `PreChecks` Validation due to non-standard inputs now returns invalid validation state`TxValidationResult::TX_INPUTS_NOT_STANDARD` along with a debug error message. Previously, the debug error message for non-standard inputs do not specify why the inputs were considered non-standard. Instead, the same error string, `bad-txns-nonstandard-inputs`, used for all types of non-standard input scriptSigs. This PR updates the `AreInputsStandard` to include the reason why inputs are non-standard in the debug message. This improves the `Precheck` debug message to be more descriptive. Furthermore, I have addressed all remaining comments from #13525 in this PR. ACKs for top commit: instagibbs: ACKd8f4e7caf0achow101: ACKd8f4e7caf0sedited: Re-ACKd8f4e7caf0Tree-SHA512: 19b1a73c68584522f863b9ee2c8d3a735348667f3628dc51e36be3ba59158509509fcc1ffc5683555112c09c8b14da3ad140bb879eac629b6f60b8313cfd8b91
This commit is contained in:
@@ -267,7 +267,7 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
|
||||
AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
|
||||
},
|
||||
[&] {
|
||||
(void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||
(void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||
},
|
||||
[&] {
|
||||
TxValidationState state;
|
||||
|
||||
@@ -88,7 +88,7 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
|
||||
|
||||
CCoinsView coins_view;
|
||||
const CCoinsViewCache coins_view_cache(&coins_view);
|
||||
(void)AreInputsStandard(tx, coins_view_cache);
|
||||
(void)ValidateInputsStandardness(tx, coins_view_cache);
|
||||
(void)IsWitnessStandard(tx, coins_view_cache);
|
||||
|
||||
if (tx.ComputeTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding
|
||||
|
||||
@@ -275,7 +275,7 @@ BOOST_AUTO_TEST_CASE(switchover)
|
||||
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EQUALVERIFY, ScriptErrorString(err));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
BOOST_AUTO_TEST_CASE(ValidateInputsStandardness)
|
||||
{
|
||||
CCoinsView coinsDummy;
|
||||
CCoinsViewCache coins(&coinsDummy);
|
||||
@@ -292,7 +292,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
keys.push_back(key[i].GetPubKey());
|
||||
|
||||
CMutableTransaction txFrom;
|
||||
txFrom.vout.resize(7);
|
||||
txFrom.vout.resize(10);
|
||||
|
||||
// First three are standard:
|
||||
CScript pay1 = GetScriptForDestination(PKHash(key[0].GetPubKey()));
|
||||
@@ -334,7 +334,24 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
CScript twentySigops; twentySigops << OP_CHECKMULTISIG;
|
||||
BOOST_CHECK(keystore.AddCScript(twentySigops));
|
||||
txFrom.vout[6].scriptPubKey = GetScriptForDestination(ScriptHash(twentySigops));
|
||||
txFrom.vout[6].nValue = 6000;
|
||||
txFrom.vout[6].nValue = 3000;
|
||||
|
||||
// vout[7] is non-standard because it lacks sigops
|
||||
CScript no_sigops;
|
||||
txFrom.vout[7].scriptPubKey = no_sigops;
|
||||
txFrom.vout[7].nValue = 1000;
|
||||
|
||||
// vout [8] is non-standard because it contains OP_RETURN in its redeemScript.
|
||||
static const unsigned char op_return[] = {OP_RETURN};
|
||||
const auto op_return_script = CScript(op_return, op_return + sizeof(op_return));
|
||||
txFrom.vout[8].scriptPubKey = GetScriptForDestination(ScriptHash(op_return_script));
|
||||
txFrom.vout[8].nValue = 1000;
|
||||
|
||||
// vout[9] is non-standard because its witness is unknown
|
||||
CScript witnessUnknown;
|
||||
witnessUnknown << OP_16 << ToByteVector(uint256::ONE);
|
||||
txFrom.vout[9].scriptPubKey = witnessUnknown;
|
||||
txFrom.vout[9].nValue = 1000;
|
||||
|
||||
AddCoins(coins, CTransaction(txFrom), 0);
|
||||
|
||||
@@ -360,7 +377,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
|
||||
txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end());
|
||||
|
||||
BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
|
||||
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(txTo), coins).IsValid());
|
||||
// 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U);
|
||||
|
||||
@@ -370,6 +387,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
BOOST_CHECK(coinbase_tx.IsCoinBase());
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(coinbase_tx, coins), 0U);
|
||||
|
||||
// TxoutType::SCRIPTHASH
|
||||
CMutableTransaction txToNonStd1;
|
||||
txToNonStd1.vout.resize(1);
|
||||
txToNonStd1.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
|
||||
@@ -379,7 +397,11 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
txToNonStd1.vin[0].prevout.hash = txFrom.GetHash();
|
||||
txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
|
||||
|
||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins));
|
||||
const auto txToNonStd1_res = ::ValidateInputsStandardness(CTransaction(txToNonStd1), coins);
|
||||
BOOST_CHECK(txToNonStd1_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txToNonStd1_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txToNonStd1_res.GetDebugMessage(), "p2sh redeemscript sigops exceed limit (input 0: 16 > 15)");
|
||||
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U);
|
||||
|
||||
CMutableTransaction txToNonStd2;
|
||||
@@ -391,8 +413,67 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
|
||||
txToNonStd2.vin[0].prevout.hash = txFrom.GetHash();
|
||||
txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
|
||||
|
||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins));
|
||||
const auto txToNonStd2_res = ::ValidateInputsStandardness(CTransaction(txToNonStd2), coins);
|
||||
BOOST_CHECK(txToNonStd2_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txToNonStd2_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txToNonStd2_res.GetDebugMessage(), "p2sh redeemscript sigops exceed limit (input 0: 20 > 15)");
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
|
||||
|
||||
CMutableTransaction txToNonStd2_no_scriptSig;
|
||||
txToNonStd2_no_scriptSig.vout.resize(1);
|
||||
txToNonStd2_no_scriptSig.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
|
||||
txToNonStd2_no_scriptSig.vout[0].nValue = 1000;
|
||||
txToNonStd2_no_scriptSig.vin.resize(1);
|
||||
txToNonStd2_no_scriptSig.vin[0].prevout.n = 6;
|
||||
txToNonStd2_no_scriptSig.vin[0].prevout.hash = txFrom.GetHash();
|
||||
|
||||
const auto txToNonStd2_no_scriptSig_res = ::ValidateInputsStandardness(CTransaction(txToNonStd2_no_scriptSig), coins);
|
||||
BOOST_CHECK(txToNonStd2_no_scriptSig_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res.GetDebugMessage(), "input 0 P2SH redeemscript missing");
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
|
||||
|
||||
// TxoutType::NONSTANDARD
|
||||
CMutableTransaction txToNonStd3;
|
||||
txToNonStd3.vout.resize(1);
|
||||
txToNonStd3.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
|
||||
txToNonStd3.vout[0].nValue = 1000;
|
||||
txToNonStd3.vin.resize(1);
|
||||
txToNonStd3.vin[0].prevout.n = 7;
|
||||
txToNonStd3.vin[0].prevout.hash = txFrom.GetHash();
|
||||
|
||||
const auto txToNonStd3_res = ::ValidateInputsStandardness(CTransaction(txToNonStd3), coins);
|
||||
BOOST_CHECK(txToNonStd3_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txToNonStd3_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txToNonStd3_res.GetDebugMessage(), "input 0 script unknown");
|
||||
|
||||
// TxoutType::INCORRECT_SCRIPTSIG
|
||||
CMutableTransaction txToNonStd4;
|
||||
txToNonStd4.vout.resize(1);
|
||||
txToNonStd4.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
|
||||
txToNonStd4.vout[0].nValue = 1000;
|
||||
txToNonStd4.vin.resize(1);
|
||||
txToNonStd4.vin[0].prevout.n = 8;
|
||||
txToNonStd4.vin[0].prevout.hash = txFrom.GetHash();
|
||||
txToNonStd4.vin[0].scriptSig = op_return_script;
|
||||
|
||||
const auto txToNonStd4_res = ::ValidateInputsStandardness(CTransaction(txToNonStd4), coins);
|
||||
BOOST_CHECK(txToNonStd4_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txToNonStd4_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txToNonStd4_res.GetDebugMessage(), "p2sh scriptsig malformed (input 0: OP_RETURN was encountered)");
|
||||
|
||||
// TxoutType::WITNESS_UNKNOWN
|
||||
CMutableTransaction txWitnessUnknown;
|
||||
txWitnessUnknown.vout.resize(1);
|
||||
txWitnessUnknown.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
|
||||
txWitnessUnknown.vout[0].nValue = 1000;
|
||||
txWitnessUnknown.vin.resize(1);
|
||||
txWitnessUnknown.vin[0].prevout.n = 9;
|
||||
txWitnessUnknown.vin[0].prevout.hash = txFrom.GetHash();
|
||||
const auto txWitnessUnknown_res = ::ValidateInputsStandardness(CTransaction(txWitnessUnknown), coins);
|
||||
BOOST_CHECK(txWitnessUnknown_res.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(txWitnessUnknown_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
|
||||
BOOST_CHECK_EQUAL(txWitnessUnknown_res.GetDebugMessage(), "input 0 witness program is undefined");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
@@ -412,7 +412,7 @@ BOOST_AUTO_TEST_CASE(test_Get)
|
||||
t1.vout[0].nValue = 90*CENT;
|
||||
t1.vout[0].scriptPubKey << OP_1;
|
||||
|
||||
BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins));
|
||||
BOOST_CHECK(ValidateInputsStandardness(CTransaction(t1), coins).IsValid());
|
||||
}
|
||||
|
||||
static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true)
|
||||
@@ -1053,7 +1053,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
|
||||
// 2490 sigops is below the limit.
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2490);
|
||||
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
|
||||
|
||||
// Adding one more input will bump this to 2505, hitting the limit.
|
||||
tx_create.vout.emplace_back(424242, max_sigops_p2sh);
|
||||
@@ -1064,8 +1064,17 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
tx_max_sigops.vin.emplace_back(prev_txid, p2sh_inputs_count, CScript() << ToByteVector(max_sigops_redeem_script));
|
||||
AddCoins(coins, CTransaction(tx_create), 0, false);
|
||||
BOOST_CHECK_GT((p2sh_inputs_count + 1) * MAX_P2SH_SIGOPS, MAX_TX_LEGACY_SIGOPS);
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2505);
|
||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
auto legacy_sigops_count = GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins);
|
||||
BOOST_CHECK_EQUAL(legacy_sigops_count, 2505);
|
||||
std::string reject_reason("bad-txns-nonstandard-inputs");
|
||||
std::string sigop_limit_reject_debug_message("non-witness sigops exceed bip54 limit");
|
||||
{
|
||||
auto validation_state = ValidateInputsStandardness(CTransaction(tx_max_sigops), coins);
|
||||
BOOST_CHECK(validation_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(validation_state.GetRejectReason(), reject_reason);
|
||||
BOOST_CHECK_EQUAL(validation_state.GetDebugMessage(), sigop_limit_reject_debug_message);
|
||||
}
|
||||
|
||||
|
||||
// Now, check the limit can be reached with regular P2PK outputs too. Use a separate
|
||||
// preparation transaction, to demonstrate spending coins from a single tx is irrelevant.
|
||||
@@ -1083,8 +1092,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
|
||||
|
||||
// The transaction now contains exactly 2500 sigops, the check should pass.
|
||||
BOOST_CHECK_EQUAL(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
|
||||
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
|
||||
|
||||
// Now, add some Segwit inputs. We add one for each defined Segwit output type. The limit
|
||||
// is exclusively on non-witness sigops and therefore those should not be counted.
|
||||
@@ -1100,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
|
||||
// The transaction now still contains exactly 2500 sigops, the check should pass.
|
||||
AddCoins(coins, CTransaction(tx_create_segwit), 0, false);
|
||||
BOOST_REQUIRE(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
BOOST_REQUIRE(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
|
||||
|
||||
// Add one more P2PK input. We'll reach the limit.
|
||||
tx_create_p2pk.vout.emplace_back(212121, p2pk_script);
|
||||
@@ -1111,8 +1119,14 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
|
||||
tx_max_sigops.vin.emplace_back(prev_txid, i);
|
||||
}
|
||||
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
|
||||
BOOST_CHECK_GT(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
|
||||
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
|
||||
auto legacy_sigop_count_p2pk = p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1;
|
||||
BOOST_CHECK_GT(legacy_sigop_count_p2pk, MAX_TX_LEGACY_SIGOPS);
|
||||
{
|
||||
auto validation_state = ValidateInputsStandardness(CTransaction(tx_max_sigops), coins);
|
||||
BOOST_CHECK(validation_state.IsInvalid());
|
||||
BOOST_CHECK_EQUAL(validation_state.GetRejectReason(), reject_reason);
|
||||
BOOST_CHECK_EQUAL(validation_state.GetDebugMessage(), sigop_limit_reject_debug_message);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(checktxinputs_invalid_transactions_test)
|
||||
|
||||
Reference in New Issue
Block a user