mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-02 09:46:52 -05:00
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.
This commit is contained in:
parent
f06e6d0345
commit
4567ba034c
4 changed files with 137 additions and 8 deletions
|
@ -81,11 +81,10 @@ else
|
|||
fi
|
||||
|
||||
if [ ! -d ${DIR_QA_ASSETS} ]; then
|
||||
if [ "$RUN_FUZZ_TESTS" = "true" ]; then
|
||||
DOCKER_EXEC git clone https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
|
||||
fi
|
||||
DOCKER_EXEC git clone --depth=1 https://github.com/bitcoin-core/qa-assets ${DIR_QA_ASSETS}
|
||||
fi
|
||||
export DIR_FUZZ_IN=${DIR_QA_ASSETS}/fuzz_seed_corpus/
|
||||
export DIR_UNIT_TEST_DATA=${DIR_QA_ASSETS}/unit_test_data/
|
||||
|
||||
DOCKER_EXEC mkdir -p "${BASE_SCRATCH_DIR}/sanitizer-output/"
|
||||
|
||||
|
|
|
@ -23,13 +23,13 @@ fi
|
|||
|
||||
if [ "$RUN_UNIT_TESTS" = "true" ]; then
|
||||
BEGIN_FOLD unit-tests
|
||||
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
|
||||
DOCKER_EXEC DIR_UNIT_TEST_DATA=${DIR_UNIT_TEST_DATA} LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib make $MAKEJOBS check VERBOSE=1
|
||||
END_FOLD
|
||||
fi
|
||||
|
||||
if [ "$RUN_UNIT_TESTS_SEQUENTIAL" = "true" ]; then
|
||||
BEGIN_FOLD unit-tests-seq
|
||||
DOCKER_EXEC LD_LIBRARY_PATH=$DEPENDS_DIR/$HOST/lib "${BASE_BUILD_DIR}/bitcoin-*/src/test/test_bitcoin*" --catch_system_errors=no -l test_suite
|
||||
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
|
||||
END_FOLD
|
||||
fi
|
||||
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
#include <test/data/script_tests.json.h>
|
||||
|
||||
#include <core_io.h>
|
||||
#include <fs.h>
|
||||
#include <key.h>
|
||||
#include <rpc/util.h>
|
||||
#include <script/script.h>
|
||||
#include <script/script_error.h>
|
||||
#include <script/sigcache.h>
|
||||
#include <script/sign.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <streams.h>
|
||||
|
@ -1339,13 +1341,41 @@ BOOST_AUTO_TEST_CASE(script_GetScriptAsm)
|
|||
BOOST_CHECK_EQUAL(derSig + "83 " + pubKey, ScriptToAsmStr(CScript() << ToByteVector(ParseHex(derSig + "83")) << vchPubKey));
|
||||
}
|
||||
|
||||
static CScript
|
||||
ScriptFromHex(const char* hex)
|
||||
static CScript ScriptFromHex(const std::string& str)
|
||||
{
|
||||
std::vector<unsigned char> data = ParseHex(hex);
|
||||
std::vector<unsigned char> data = ParseHex(str);
|
||||
return CScript(data.begin(), data.end());
|
||||
}
|
||||
|
||||
static CMutableTransaction TxFromHex(const std::string& str)
|
||||
{
|
||||
CMutableTransaction tx;
|
||||
VectorReader(SER_DISK, SERIALIZE_TRANSACTION_NO_WITNESS, ParseHex(str), 0) >> 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;
|
||||
VectorReader(SER_DISK, 0, ParseHex(univalue[i].get_str()), 0) >> 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;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(script_FindAndDelete)
|
||||
{
|
||||
|
@ -1610,5 +1640,104 @@ BOOST_AUTO_TEST_CASE(bitcoinconsensus_verify_script_invalid_flags)
|
|||
BOOST_CHECK_EQUAL(err, bitcoinconsensus_ERR_INVALID_FLAGS);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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"].get_int64();
|
||||
unsigned int 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, 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, 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)
|
||||
{
|
||||
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;
|
||||
fs::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[0], 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]);
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
||||
#endif
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
@ -57,6 +57,7 @@ static std::map<std::string, unsigned int> mapFlagNames = {
|
|||
{std::string("DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM},
|
||||
{std::string("WITNESS_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_WITNESS_PUBKEYTYPE},
|
||||
{std::string("CONST_SCRIPTCODE"), (unsigned int)SCRIPT_VERIFY_CONST_SCRIPTCODE},
|
||||
{std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
|
||||
};
|
||||
|
||||
unsigned int ParseScriptFlags(std::string strFlags)
|
||||
|
|
Loading…
Add table
Reference in a new issue