Skip to content

Commit 4567ba0

Browse files
committed
tests: add generic qa-asset-based script verification unit test
This adds a unit test that does generic script verification tests, with positive/negative witnesses/scriptsigs, under various flags. The test data is large (several MB) so it's stored in the qa-assets repo.
1 parent f06e6d0 commit 4567ba0

File tree

4 files changed

+137
-8
lines changed

4 files changed

+137
-8
lines changed

ci/test/04_install.sh

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ else
8181
fi
8282

8383
if [ ! -d ${DIR_QA_ASSETS} ]; then
84-
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
85-
DOCKER_EXEC git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
86-
fi
84+
DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
8785
fi
8886
export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/
87+
export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
8988

9089
DOCKER_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/"
9190

ci/test/06_script_b.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ fi
2323

2424
if [ "$RUN_UNIT_TESTS" = "true" ]; then
2525
BEGIN_FOLD unit-tests
26-
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
26+
DOCKER_EXEC DIR_UNIT_TEST_DATA=${DIR_UNIT_TEST_DATA} LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
2727
END_FOLD
2828
fi
2929

3030
if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
3131
BEGIN_FOLD unit-tests-seq
32-
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
32+
DOCKER_EXEC DIR_UNIT_TEST_DATA=${DIR_UNIT_TEST_DATA} LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
3333
END_FOLD
3434
fi
3535

src/test/script_tests.cpp

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
#include <test/data/script_tests.json.h>
66

77
#include <core_io.h>
8+
#include <fs.h>
89
#include <key.h>
910
#include <rpc/util.h>
1011
#include <script/script.h>
1112
#include <script/script_error.h>
13+
#include <script/sigcache.h>
1214
#include <script/sign.h>
1315
#include <script/signingprovider.h>
1416
#include <streams.h>
@@ -1339,13 +1341,41 @@ BOOST_AUTO_TEST_CASE(script_GetScriptAsm)
13391341
BOOST_CHECK_EQUAL(derSig + "83 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey));
13401342
}
13411343

1342-
static CScript
1343-
ScriptFromHex(const char* hex)
1344+
static CScript ScriptFromHex(const std::string& str)
13441345
{
1345-
std::vector<unsigned char> data = ParseHex(hex);
1346+
std::vector<unsigned char> data = ParseHex(str);
13461347
return CScript(data.begin(), data.end());
13471348
}
13481349

1350+
static CMutableTransaction TxFromHex(const std::string& str)
1351+
{
1352+
CMutableTransaction tx;
1353+
VectorReader(SER_DISK, SERIALIZE_TRANSACTION_NO_WITNESS, ParseHex(str), 0) >> tx;
1354+
return tx;
1355+
}
1356+
1357+
static std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
1358+
{
1359+
assert(univalue.isArray());
1360+
std::vector<CTxOut> prevouts;
1361+
for (size_t i = 0; i < univalue.size(); ++i) {
1362+
CTxOut txout;
1363+
VectorReader(SER_DISK, 0, ParseHex(univalue[i].get_str()), 0) >> txout;
1364+
prevouts.push_back(std::move(txout));
1365+
}
1366+
return prevouts;
1367+
}
1368+
1369+
static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
1370+
{
1371+
assert(univalue.isArray());
1372+
CScriptWitness scriptwitness;
1373+
for (size_t i = 0; i < univalue.size(); ++i) {
1374+
auto bytes = ParseHex(univalue[i].get_str());
1375+
scriptwitness.stack.push_back(std::move(bytes));
1376+
}
1377+
return scriptwitness;
1378+
}
13491379

13501380
BOOST_AUTO_TEST_CASE(script_FindAndDelete)
13511381
{
@@ -1610,5 +1640,104 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
16101640
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS);
16111641
}
16121642

1643+
static std::vector<unsigned int> AllConsensusFlags()
1644+
{
1645+
std::vector<unsigned int> ret;
1646+
1647+
for (unsigned int i = 0; i < 128; ++i) {
1648+
unsigned int flag = 0;
1649+
if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
1650+
if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
1651+
if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
1652+
if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
1653+
if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
1654+
if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
1655+
if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
1656+
1657+
// SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
1658+
if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
1659+
// SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
1660+
if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
1661+
1662+
ret.push_back(flag);
1663+
}
1664+
1665+
return ret;
1666+
}
1667+
1668+
/** Precomputed list of all valid combinations of consensus-relevant script validation flags. */
1669+
static const std::vector<unsigned int> ALL_CONSENSUS_FLAGS = AllConsensusFlags();
1670+
1671+
static void AssetTest(const UniValue& test)
1672+
{
1673+
BOOST_CHECK(test.isObject());
1674+
1675+
CMutableTransaction mtx = TxFromHex(test["tx"].get_str());
1676+
const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
1677+
BOOST_CHECK(prevouts.size() == mtx.vin.size());
1678+
size_t idx = test["index"].get_int64();
1679+
unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
1680+
bool fin = test.exists("final") && test["final"].get_bool();
1681+
1682+
if (test.exists("success")) {
1683+
mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
1684+
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
1685+
CTransaction tx(mtx);
1686+
PrecomputedTransactionData txdata;
1687+
txdata.Init(tx, std::vector<CTxOut>(prevouts));
1688+
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
1689+
for (const auto flags : ALL_CONSENSUS_FLAGS) {
1690+
// "final": true tests are valid for all flags. Others are only valid with flags that are
1691+
// a subset of test_flags.
1692+
if (fin || ((flags & test_flags) == flags)) {
1693+
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
1694+
BOOST_CHECK(ret);
1695+
}
1696+
}
1697+
}
1698+
1699+
if (test.exists("failure")) {
1700+
mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
1701+
mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
1702+
CTransaction tx(mtx);
1703+
PrecomputedTransactionData txdata;
1704+
txdata.Init(tx, std::vector<CTxOut>(prevouts));
1705+
CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, txdata);
1706+
for (const auto flags : ALL_CONSENSUS_FLAGS) {
1707+
// If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
1708+
if ((flags & test_flags) == test_flags) {
1709+
bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
1710+
BOOST_CHECK(!ret);
1711+
}
1712+
}
1713+
}
1714+
}
1715+
1716+
BOOST_AUTO_TEST_CASE(script_assets_test)
1717+
{
1718+
const char* dir = std::getenv("DIR_UNIT_TEST_DATA");
1719+
BOOST_WARN_MESSAGE(dir != nullptr, "Variable DIR_UNIT_TEST_DATA unset, skipping script_assets_test");
1720+
if (dir == nullptr) return;
1721+
auto path = fs::path(dir) / "script_assets_test.json";
1722+
bool exists = fs::exists(path);
1723+
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
1724+
if (!exists) return;
1725+
fs::ifstream file(path);
1726+
BOOST_CHECK(file.is_open());
1727+
file.seekg(0, std::ios::end);
1728+
size_t length = file.tellg();
1729+
file.seekg(0, std::ios::beg);
1730+
std::string data(length, '\0');
1731+
file.read(&data[0], data.size());
1732+
UniValue tests = read_json(data);
1733+
BOOST_CHECK(tests.isArray());
1734+
BOOST_CHECK(tests.size() > 0);
1735+
1736+
for (size_t i = 0; i < tests.size(); i++) {
1737+
AssetTest(tests[i]);
1738+
}
1739+
file.close();
1740+
}
1741+
16131742
#endif
16141743
BOOST_AUTO_TEST_SUITE_END()

src/test/transaction_tests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ static std::map<std::string, unsigned int> mapFlagNames = {
5757
{std::string("DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM},
5858
{std::string("WITNESS_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_WITNESS_PUBKEYTYPE},
5959
{std::string("CONST_SCRIPTCODE"), (unsigned int)SCRIPT_VERIFY_CONST_SCRIPTCODE},
60+
{std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
6061
};
6162

6263
unsigned int ParseScriptFlags(std::string strFlags)

0 commit comments

Comments
 (0)