Merge bitcoin/bitcoin#31576: test: Move script_assets_tests into its own suite

c40dbbbf77 test: Move `script_assets_tests` into its own suite (Hennadii Stepanov)

Pull request description:

  This PR ensures that the `script_assets_tests` test case is explicitly reported as "Skipped" when it is not run, making it clearer when running the test suite with `ctest`:

  - on the master branch @ 9355578a77:
  ```
  $ env -u DIR_UNIT_TEST_DATA ctest --test-dir build -j 16 -R "^script_"
  Internal ctest changing into directory: /home/hebasto/git/bitcoin/build
  Test project /home/hebasto/git/bitcoin/build
      Start 87: script_tests
      Start 83: script_p2sh_tests
      Start 85: script_segwit_tests
      Start 86: script_standard_tests
      Start 84: script_parse_tests
  1/5 Test #84: script_parse_tests ...............   Passed    0.11 sec
  2/5 Test #86: script_standard_tests ............   Passed    0.11 sec
  3/5 Test #85: script_segwit_tests ..............   Passed    0.12 sec
  4/5 Test #83: script_p2sh_tests ................   Passed    0.12 sec
  5/5 Test #87: script_tests .....................   Passed    0.36 sec

  100% tests passed, 0 tests failed out of 5

  Total Test time (real) =   0.37 sec
  ```
  - with this PR:
  ```
  $ env -u DIR_UNIT_TEST_DATA ctest --test-dir build -j 16 -R "^script_"
  Internal ctest changing into directory: /home/hebasto/git/bitcoin/build
  Test project /home/hebasto/git/bitcoin/build
      Start 83: script_assets_tests
      Start 88: script_tests
      Start 84: script_p2sh_tests
      Start 86: script_segwit_tests
      Start 87: script_standard_tests
      Start 85: script_parse_tests
  1/6 Test #85: script_parse_tests ...............   Passed    0.11 sec
  2/6 Test #83: script_assets_tests ..............***Skipped   0.12 sec
  3/6 Test #86: script_segwit_tests ..............   Passed    0.11 sec
  4/6 Test #87: script_standard_tests ............   Passed    0.11 sec
  5/6 Test #84: script_p2sh_tests ................   Passed    0.12 sec
  6/6 Test #88: script_tests .....................   Passed    0.36 sec

  100% tests passed, 0 tests failed out of 6

  Total Test time (real) =   0.37 sec

  The following tests did not run:
   83 - script_assets_tests (Skipped)
  $ env DIR_UNIT_TEST_DATA=/home/hebasto/git/bitcoin/qa-assets/unit_test_data ctest --test-dir build -j 16 -R "^script_"
  Internal ctest changing into directory: /home/hebasto/git/bitcoin/build
  Test project /home/hebasto/git/bitcoin/build
      Start 83: script_assets_tests
      Start 88: script_tests
      Start 84: script_p2sh_tests
      Start 86: script_segwit_tests
      Start 87: script_standard_tests
      Start 85: script_parse_tests
  1/6 Test #85: script_parse_tests ...............   Passed    0.11 sec
  2/6 Test #87: script_standard_tests ............   Passed    0.11 sec
  3/6 Test #86: script_segwit_tests ..............   Passed    0.11 sec
  4/6 Test #84: script_p2sh_tests ................   Passed    0.12 sec
  5/6 Test #88: script_tests .....................   Passed    0.35 sec
  6/6 Test #83: script_assets_tests ..............   Passed    1.58 sec

  100% tests passed, 0 tests failed out of 6

  Total Test time (real) =   1.58 sec
  ```

ACKs for top commit:
  maflcko:
    re-ACK c40dbbbf77 👈
  ajtowns:
    ACK c40dbbbf77
  achow101:
    ACK c40dbbbf77

Tree-SHA512: 25713e1c3b507b6f2a5fecc7b1ea285a6642b906c248769238a58fc0df48489ac5f7606778f9e3653b407b7f1d06563e1554d04321303b350c80eb888500cc5d
This commit is contained in:
Ava Chow
2025-07-25 14:48:14 -07:00
3 changed files with 181 additions and 141 deletions

View File

@@ -83,6 +83,7 @@ add_executable(test_bitcoin
rpc_tests.cpp
sanity_tests.cpp
scheduler_tests.cpp
script_assets_tests.cpp
script_p2sh_tests.cpp
script_parse_tests.cpp
script_segwit_tests.cpp
@@ -194,7 +195,7 @@ function(add_boost_test source_file)
COMMAND test_bitcoin --run_test=${test_suite_name} --catch_system_error=no --log_level=test_suite -- DEBUG_LOG_OUT
)
set_property(TEST ${test_suite_name} PROPERTY
SKIP_REGULAR_EXPRESSION "no test cases matching filter"
SKIP_REGULAR_EXPRESSION "no test cases matching filter" "skipping script_assets_test"
)
endforeach()
endfunction()

View File

@@ -0,0 +1,179 @@
// Copyright (c) 2011-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <script/script.h>
#include <script/sigcache.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <span.h>
#include <streams.h>
#include <test/util/json.h>
#include <util/check.h>
#include <util/fs.h>
#include <util/strencodings.h>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <utility>
#include <vector>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
unsigned int ParseScriptFlags(std::string strFlags);
BOOST_AUTO_TEST_SUITE(script_assets_tests)
template <typename T>
CScript ToScript(const T& byte_container)
{
auto span{MakeUCharSpan(byte_container)};
return {span.begin(), span.end()};
}
static CScript ScriptFromHex(const std::string& str)
{
return ToScript(*Assert(TryParseHex(str)));
}
static CMutableTransaction TxFromHex(const std::string& str)
{
CMutableTransaction tx;
SpanReader{ParseHex(str)} >> TX_NO_WITNESS(tx);
return tx;
}
static std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
{
assert(univalue.isArray());
std::vector<CTxOut> prevouts;
for (size_t i = 0; i < univalue.size(); ++i) {
CTxOut txout;
SpanReader{ParseHex(univalue[i].get_str())} >> txout;
prevouts.push_back(std::move(txout));
}
return prevouts;
}
static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
{
assert(univalue.isArray());
CScriptWitness scriptwitness;
for (size_t i = 0; i < univalue.size(); ++i) {
auto bytes = ParseHex(univalue[i].get_str());
scriptwitness.stack.push_back(std::move(bytes));
}
return scriptwitness;
}
static std::vector<unsigned int> AllConsensusFlags()
{
std::vector<unsigned int> ret;
for (unsigned int i = 0; i < 128; ++i) {
unsigned int flag = 0;
if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
// SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
// SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
ret.push_back(flag);
}
return ret;
}
/** Precomputed list of all valid combinations of consensus-relevant script validation flags. */
static const std::vector<unsigned int> ALL_CONSENSUS_FLAGS = AllConsensusFlags();
static void AssetTest(const UniValue& test, SignatureCache& signature_cache)
{
BOOST_CHECK(test.isObject());
CMutableTransaction mtx = TxFromHex(test["tx"].get_str());
const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
BOOST_CHECK(prevouts.size() == mtx.vin.size());
size_t idx = test["index"].getInt<int64_t>();
uint32_t test_flags{ParseScriptFlags(test["flags"].get_str())};
bool fin = test.exists("final") && test["final"].get_bool();
if (test.exists("success")) {
mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// "final": true tests are valid for all flags. Others are only valid with flags that are
// a subset of test_flags.
if (fin || ((flags & test_flags) == flags)) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(ret);
}
}
}
if (test.exists("failure")) {
mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
if ((flags & test_flags) == test_flags) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(!ret);
}
}
}
}
BOOST_AUTO_TEST_CASE(script_assets_test)
{
// See src/test/fuzz/script_assets_test_minimizer.cpp for information on how to generate
// the script_assets_test.json file used by this test.
SignatureCache signature_cache{DEFAULT_SIGNATURE_CACHE_BYTES};
const char* dir = std::getenv("DIR_UNIT_TEST_DATA");
BOOST_WARN_MESSAGE(dir != nullptr, "Variable DIR_UNIT_TEST_DATA unset, skipping script_assets_test");
if (dir == nullptr) return;
auto path = fs::path(dir) / "script_assets_test.json";
bool exists = fs::exists(path);
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
if (!exists) return;
std::ifstream file{path};
BOOST_CHECK(file.is_open());
file.seekg(0, std::ios::end);
size_t length = file.tellg();
file.seekg(0, std::ios::beg);
std::string data(length, '\0');
file.read(data.data(), data.size());
UniValue tests = read_json(data);
BOOST_CHECK(tests.isArray());
BOOST_CHECK(tests.size() > 0);
for (size_t i = 0; i < tests.size(); i++) {
AssetTest(tests[i], signature_cache);
}
file.close();
}
BOOST_AUTO_TEST_SUITE_END()

View File

@@ -1384,11 +1384,6 @@ CScript ToScript(const T& byte_container)
return {span.begin(), span.end()};
}
static CScript ScriptFromHex(const std::string& str)
{
return ToScript(*Assert(TryParseHex(str)));
}
BOOST_AUTO_TEST_CASE(script_byte_array_u8_vector_equivalence)
{
const CScript scriptPubKey1 = CScript() << "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"_hex_v_u8 << OP_CHECKSIG;
@@ -1519,141 +1514,6 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps)
BOOST_CHECK(!script.HasValidOps());
}
static CMutableTransaction TxFromHex(const std::string& str)
{
CMutableTransaction tx;
SpanReader{ParseHex(str)} >> TX_NO_WITNESS(tx);
return tx;
}
static std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
{
assert(univalue.isArray());
std::vector<CTxOut> prevouts;
for (size_t i = 0; i < univalue.size(); ++i) {
CTxOut txout;
SpanReader{ParseHex(univalue[i].get_str())} >> txout;
prevouts.push_back(std::move(txout));
}
return prevouts;
}
static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
{
assert(univalue.isArray());
CScriptWitness scriptwitness;
for (size_t i = 0; i < univalue.size(); ++i) {
auto bytes = ParseHex(univalue[i].get_str());
scriptwitness.stack.push_back(std::move(bytes));
}
return scriptwitness;
}
static std::vector<unsigned int> AllConsensusFlags()
{
std::vector<unsigned int> ret;
for (unsigned int i = 0; i < 128; ++i) {
unsigned int flag = 0;
if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
// SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
// SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
ret.push_back(flag);
}
return ret;
}
/** Precomputed list of all valid combinations of consensus-relevant script validation flags. */
static const std::vector<unsigned int> ALL_CONSENSUS_FLAGS = AllConsensusFlags();
static void AssetTest(const UniValue& test, SignatureCache& signature_cache)
{
BOOST_CHECK(test.isObject());
CMutableTransaction mtx = TxFromHex(test["tx"].get_str());
const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
BOOST_CHECK(prevouts.size() == mtx.vin.size());
size_t idx = test["index"].getInt<int64_t>();
uint32_t test_flags{ParseScriptFlags(test["flags"].get_str())};
bool fin = test.exists("final") && test["final"].get_bool();
if (test.exists("success")) {
mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// "final": true tests are valid for all flags. Others are only valid with flags that are
// a subset of test_flags.
if (fin || ((flags & test_flags) == flags)) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(ret);
}
}
}
if (test.exists("failure")) {
mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
CTransaction tx(mtx);
PrecomputedTransactionData txdata;
txdata.Init(tx, std::vector<CTxOut>(prevouts));
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
for (const auto flags : ALL_CONSENSUS_FLAGS) {
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
if ((flags & test_flags) == test_flags) {
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
BOOST_CHECK(!ret);
}
}
}
}
BOOST_AUTO_TEST_CASE(script_assets_test)
{
// See src/test/fuzz/script_assets_test_minimizer.cpp for information on how to generate
// the script_assets_test.json file used by this test.
SignatureCache signature_cache{DEFAULT_SIGNATURE_CACHE_BYTES};
const char* dir = std::getenv("DIR_UNIT_TEST_DATA");
BOOST_WARN_MESSAGE(dir != nullptr, "Variable DIR_UNIT_TEST_DATA unset, skipping script_assets_test");
if (dir == nullptr) return;
auto path = fs::path(dir) / "script_assets_test.json";
bool exists = fs::exists(path);
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
if (!exists) return;
std::ifstream file{path};
BOOST_CHECK(file.is_open());
file.seekg(0, std::ios::end);
size_t length = file.tellg();
file.seekg(0, std::ios::beg);
std::string data(length, '\0');
file.read(data.data(), data.size());
UniValue tests = read_json(data);
BOOST_CHECK(tests.isArray());
BOOST_CHECK(tests.size() > 0);
for (size_t i = 0; i < tests.size(); i++) {
AssetTest(tests[i], signature_cache);
}
file.close();
}
BOOST_AUTO_TEST_CASE(bip341_keypath_test_vectors)
{
UniValue tests;