Skip to content

Commit 3671479

Browse files
committed
qa: unit test standardness of inputs packed with legacy sigops
Check bounds and different output types.
1 parent 5863315 commit 3671479

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed

src/test/transaction_tests.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <clientversion.h>
1111
#include <consensus/amount.h>
1212
#include <consensus/tx_check.h>
13+
#include <consensus/tx_verify.h>
1314
#include <consensus/validation.h>
1415
#include <core_io.h>
1516
#include <key.h>
@@ -1053,4 +1054,99 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
10531054
CheckIsNotStandard(t, "dust");
10541055
}
10551056

1057+
BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
1058+
{
1059+
CCoinsView coins_dummy;
1060+
CCoinsViewCache coins(&coins_dummy);
1061+
CKey key;
1062+
key.MakeNewKey(true);
1063+
1064+
// Create a pathological P2SH script padded with as many sigops as is standard.
1065+
CScript max_sigops_redeem_script{CScript() << std::vector<unsigned char>{} << key.GetPubKey()};
1066+
for (unsigned i{0}; i < MAX_P2SH_SIGOPS - 1; ++i) max_sigops_redeem_script << OP_2DUP << OP_CHECKSIG << OP_DROP;
1067+
max_sigops_redeem_script << OP_CHECKSIG << OP_NOT;
1068+
const CScript max_sigops_p2sh{GetScriptForDestination(ScriptHash(max_sigops_redeem_script))};
1069+
1070+
// Create a transaction fanning out as many such P2SH outputs as is standard to spend in a
1071+
// single transaction, and a transaction spending them.
1072+
CMutableTransaction tx_create, tx_max_sigops;
1073+
const unsigned p2sh_inputs_count{MAX_TX_LEGACY_SIGOPS / MAX_P2SH_SIGOPS};
1074+
tx_create.vout.reserve(p2sh_inputs_count);
1075+
for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
1076+
tx_create.vout.emplace_back(424242 + i, max_sigops_p2sh);
1077+
}
1078+
auto prev_txid{tx_create.GetHash()};
1079+
tx_max_sigops.vin.reserve(p2sh_inputs_count);
1080+
for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
1081+
tx_max_sigops.vin.emplace_back(prev_txid, i, CScript() << ToByteVector(max_sigops_redeem_script));
1082+
}
1083+
1084+
// p2sh_inputs_count is truncated to 166 (from 166.6666..)
1085+
BOOST_CHECK_LT(p2sh_inputs_count * MAX_P2SH_SIGOPS, MAX_TX_LEGACY_SIGOPS);
1086+
AddCoins(coins, CTransaction(tx_create), 0, false);
1087+
1088+
// 2490 sigops is below the limit.
1089+
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2490);
1090+
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
1091+
1092+
// Adding one more input will bump this to 2505, hitting the limit.
1093+
tx_create.vout.emplace_back(424242, max_sigops_p2sh);
1094+
prev_txid = tx_create.GetHash();
1095+
for (unsigned i{0}; i < p2sh_inputs_count; ++i) {
1096+
tx_max_sigops.vin[i] = CTxIn(COutPoint(prev_txid, i), CScript() << ToByteVector(max_sigops_redeem_script));
1097+
}
1098+
tx_max_sigops.vin.emplace_back(prev_txid, p2sh_inputs_count, CScript() << ToByteVector(max_sigops_redeem_script));
1099+
AddCoins(coins, CTransaction(tx_create), 0, false);
1100+
BOOST_CHECK_GT((p2sh_inputs_count + 1) * MAX_P2SH_SIGOPS, MAX_TX_LEGACY_SIGOPS);
1101+
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2505);
1102+
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
1103+
1104+
// Now, check the limit can be reached with regular P2PK outputs too. Use a separate
1105+
// preparation transaction, to demonstrate spending coins from a single tx is irrelevant.
1106+
CMutableTransaction tx_create_p2pk;
1107+
const auto p2pk_script{CScript() << key.GetPubKey() << OP_CHECKSIG};
1108+
unsigned p2pk_inputs_count{10}; // From 2490 to 2500.
1109+
for (unsigned i{0}; i < p2pk_inputs_count; ++i) {
1110+
tx_create_p2pk.vout.emplace_back(212121 + i, p2pk_script);
1111+
}
1112+
prev_txid = tx_create_p2pk.GetHash();
1113+
tx_max_sigops.vin.resize(p2sh_inputs_count); // Drop the extra input.
1114+
for (unsigned i{0}; i < p2pk_inputs_count; ++i) {
1115+
tx_max_sigops.vin.emplace_back(prev_txid, i);
1116+
}
1117+
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
1118+
1119+
// The transaction now contains exactly 2500 sigops, the check should pass.
1120+
BOOST_CHECK_EQUAL(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
1121+
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
1122+
1123+
// Now, add some Segwit inputs. We add one for each defined Segwit output type. The limit
1124+
// is exclusively on non-witness sigops and therefore those should not be counted.
1125+
CMutableTransaction tx_create_segwit;
1126+
const auto witness_script{CScript() << key.GetPubKey() << OP_CHECKSIG};
1127+
tx_create_segwit.vout.emplace_back(121212, GetScriptForDestination(WitnessV0KeyHash(key.GetPubKey())));
1128+
tx_create_segwit.vout.emplace_back(131313, GetScriptForDestination(WitnessV0ScriptHash(witness_script)));
1129+
tx_create_segwit.vout.emplace_back(141414, GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey(key.GetPubKey())}));
1130+
prev_txid = tx_create_segwit.GetHash();
1131+
for (unsigned i{0}; i < tx_create_segwit.vout.size(); ++i) {
1132+
tx_max_sigops.vin.emplace_back(prev_txid, i);
1133+
}
1134+
1135+
// The transaction now still contains exactly 2500 sigops, the check should pass.
1136+
AddCoins(coins, CTransaction(tx_create_segwit), 0, false);
1137+
BOOST_REQUIRE(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
1138+
1139+
// Add one more P2PK input. We'll reach the limit.
1140+
tx_create_p2pk.vout.emplace_back(212121, p2pk_script);
1141+
prev_txid = tx_create_p2pk.GetHash();
1142+
tx_max_sigops.vin.resize(p2sh_inputs_count);
1143+
++p2pk_inputs_count;
1144+
for (unsigned i{0}; i < p2pk_inputs_count; ++i) {
1145+
tx_max_sigops.vin.emplace_back(prev_txid, i);
1146+
}
1147+
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
1148+
BOOST_CHECK_GT(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
1149+
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
1150+
}
1151+
10561152
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)