From 52121506b2a37ee99162b35e2dd61d5135a8843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Tue, 15 Apr 2025 12:31:20 +0200 Subject: [PATCH] test: assert `CScript` allocation characteristics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verifies that script types are correctly allocated using prevector's direct or indirect storage based on their size: Direct allocated script types (size ≤ 28 bytes): * OP_RETURN (small) * P2WPKH * P2SH * P2PKH Indirect allocated script types (size > 28 bytes): * P2WSH * P2TR * P2PK * MULTISIG (small) This test provides a baseline for verifying changes to prevector's inline capacity. The `CHECK_SCRIPT_STATIC_SIZE` and `CHECK_SCRIPT_DYNAMIC_SIZE` macros were added to differentiate the two cases - while preserving the correct source code line in case of failure. --- src/test/script_tests.cpp | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index ca6946ea536..d5b0dfab189 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1151,6 +1151,107 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } +/** Return the TxoutType of a script without exposing Solver details. */ +static TxoutType GetTxoutType(const CScript& output_script) +{ + std::vector> unused; + return Solver(output_script, unused); +} + +#define CHECK_SCRIPT_STATIC_SIZE(script, expected_size) \ + do { \ + BOOST_CHECK_EQUAL((script).size(), (expected_size)); \ + BOOST_CHECK_EQUAL((script).capacity(), CScriptBase::STATIC_SIZE); \ + BOOST_CHECK_EQUAL((script).allocated_memory(), 0); \ + } while (0) + +#define CHECK_SCRIPT_DYNAMIC_SIZE(script, expected_size, expected_extra) \ + do { \ + BOOST_CHECK_EQUAL((script).size(), (expected_size)); \ + BOOST_CHECK_EQUAL((script).capacity(), (expected_extra)); \ + BOOST_CHECK_EQUAL((script).allocated_memory(), (expected_extra)); \ + } while (0) + +BOOST_AUTO_TEST_CASE(script_size_and_capacity_test) +{ + BOOST_CHECK_EQUAL(sizeof(CompressedScript), 40); + BOOST_CHECK_EQUAL(sizeof(CScriptBase), 32); + BOOST_CHECK_NE(sizeof(CScriptBase), sizeof(prevector)); // CScriptBase size should be set to avoid wasting space in padding + BOOST_CHECK_EQUAL(sizeof(CScript), 32); + BOOST_CHECK_EQUAL(sizeof(CTxOut), 40); + + CKey dummy_key; + dummy_key.MakeNewKey(/*fCompressed=*/true); + const CPubKey dummy_pubkey{dummy_key.GetPubKey()}; + + // Small OP_RETURN has direct allocation + { + const auto script{CScript() << OP_RETURN << std::vector(10, 0xaa)}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::NULL_DATA); + CHECK_SCRIPT_STATIC_SIZE(script, 12); + } + + // P2WPKH has direct allocation + { + const auto script{GetScriptForDestination(WitnessV0KeyHash{PKHash{dummy_pubkey}})}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::WITNESS_V0_KEYHASH); + CHECK_SCRIPT_STATIC_SIZE(script, 22); + } + + // P2SH has direct allocation + { + const auto script{GetScriptForDestination(ScriptHash{CScript{} << OP_TRUE})}; + BOOST_CHECK(script.IsPayToScriptHash()); + CHECK_SCRIPT_STATIC_SIZE(script, 23); + } + + // P2PKH has direct allocation + { + const auto script{GetScriptForDestination(PKHash{dummy_pubkey})}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::PUBKEYHASH); + CHECK_SCRIPT_STATIC_SIZE(script, 25); + } + + // P2WSH needs extra allocation + { + const auto script{GetScriptForDestination(WitnessV0ScriptHash{CScript{} << OP_TRUE})}; + BOOST_CHECK(script.IsPayToWitnessScriptHash()); + CHECK_SCRIPT_DYNAMIC_SIZE(script, 34, 34); + } + + // P2TR needs extra allocation + { + const auto script{GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey{dummy_pubkey}})}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::WITNESS_V1_TAPROOT); + CHECK_SCRIPT_DYNAMIC_SIZE(script, 34, 34); + } + + // Compressed P2PK needs extra allocation + { + const auto script{GetScriptForRawPubKey(dummy_pubkey)}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::PUBKEY); + CHECK_SCRIPT_DYNAMIC_SIZE(script, 35, 35); + } + + // Uncompressed P2PK needs extra allocation + { + CKey uncompressed_key; + uncompressed_key.MakeNewKey(/*fCompressed=*/false); + const CPubKey uncompressed_pubkey{uncompressed_key.GetPubKey()}; + + const auto script{GetScriptForRawPubKey(uncompressed_pubkey)}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::PUBKEY); + CHECK_SCRIPT_DYNAMIC_SIZE(script, 67, 67); + } + + // Bare multisig needs extra allocation + { + const auto script{GetScriptForMultisig(1, std::vector{2, dummy_pubkey})}; + BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::MULTISIG); + CHECK_SCRIPT_DYNAMIC_SIZE(script, 71, 103); + } +} + /* Wrapper around ProduceSignature to combine two scriptsigs */ SignatureData CombineSignatures(const CTxOut& txout, const CMutableTransaction& tx, const SignatureData& scriptSig1, const SignatureData& scriptSig2) {