Changeset View
Changeset View
Standalone View
Standalone View
src/test/op_reversebytes_tests.cpp
// Copyright (c) 2020 The Bitcoin developers | // Copyright (c) 2020 The Bitcoin developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include <policy/policy.h> | |||||
#include <script/interpreter.h> | #include <script/interpreter.h> | ||||
#include <script/script.h> | #include <script/script.h> | ||||
#include <test/lcg.h> | #include <test/lcg.h> | ||||
#include <test/setup_common.h> | #include <test/setup_common.h> | ||||
#include <boost/test/unit_test.hpp> | #include <boost/test/unit_test.hpp> | ||||
typedef std::vector<uint8_t> valtype; | typedef std::vector<uint8_t> valtype; | ||||
typedef std::vector<valtype> stacktype; | typedef std::vector<valtype> stacktype; | ||||
BOOST_FIXTURE_TEST_SUITE(op_reversebytes_tests, BasicTestingSetup) | BOOST_FIXTURE_TEST_SUITE(op_reversebytes_tests, BasicTestingSetup) | ||||
struct ReverseTestCase { | |||||
const valtype item; | |||||
const valtype reversed_item; | |||||
}; | |||||
static void CheckErrorWithFlags(const uint32_t flags, | static void CheckErrorWithFlags(const uint32_t flags, | ||||
const stacktype &original_stack, | const stacktype &original_stack, | ||||
const CScript &script, | const CScript &script, | ||||
const ScriptError expected) { | const ScriptError expected) { | ||||
BaseSignatureChecker sigchecker; | BaseSignatureChecker sigchecker; | ||||
ScriptError err = ScriptError::OK; | ScriptError err = ScriptError::OK; | ||||
stacktype stack{original_stack}; | stacktype stack{original_stack}; | ||||
bool r = EvalScript(stack, script, flags, sigchecker, &err); | bool r = EvalScript(stack, script, flags, sigchecker, &err); | ||||
Show All 38 Lines | static void CheckPassIfEnabled(const uint32_t flags, | ||||
const stacktype &expected) { | const stacktype &expected) { | ||||
CheckPassWithFlags(flags | SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, | CheckPassWithFlags(flags | SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, | ||||
script, expected); | script, expected); | ||||
CheckErrorWithFlags(flags & ~SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, | CheckErrorWithFlags(flags & ~SCRIPT_ENABLE_OP_REVERSEBYTES, original_stack, | ||||
script, ScriptError::BAD_OPCODE); | script, ScriptError::BAD_OPCODE); | ||||
} | } | ||||
/** | /** | ||||
* Verifies a given reverse test case. | * Verifies the different combinations of a given test case. | ||||
* Checks both if <item> OP_REVERSEBYTES results in <reversed_item> and | * Checks if | ||||
* whether double-reversing <item> is a no-op. | * - <item> OP_REVERSEBYTES results in <reversed_item>, | ||||
* - <reversed_item> OP_REVERSEBYTES results in <item>, | |||||
* - <item> {OP_REVERSEBYTES} x 2 results in <item> and | |||||
* - <reversed_item> {OP_REVERSEBYTES} x 2 results in <reversed_item>. | |||||
*/ | */ | ||||
static void CheckPassReverse(const uint32_t flags, | static void CheckPassForCombinations(const uint32_t flags, const valtype &item, | ||||
const ReverseTestCase &reverse_case) { | const valtype &reversed_item) { | ||||
CheckPassIfEnabled(flags, {reverse_case.item}, CScript() << OP_REVERSEBYTES, | CheckPassIfEnabled(flags, {item}, CScript() << OP_REVERSEBYTES, | ||||
{reverse_case.reversed_item}); | {reversed_item}); | ||||
CheckPassIfEnabled(flags, {reverse_case.item}, | CheckPassIfEnabled(flags, {reversed_item}, CScript() << OP_REVERSEBYTES, | ||||
CScript() << OP_DUP << OP_REVERSEBYTES << OP_REVERSEBYTES | {item}); | ||||
<< OP_EQUALVERIFY, | CheckPassIfEnabled(flags, {item}, | ||||
{}); | CScript() << OP_REVERSEBYTES << OP_REVERSEBYTES, {item}); | ||||
} | CheckPassIfEnabled(flags, {reversed_item}, | ||||
CScript() << OP_REVERSEBYTES << OP_REVERSEBYTES, | |||||
BOOST_AUTO_TEST_CASE(op_reversebytes_tests) { | {reversed_item}); | ||||
MMIXLinearCongruentialGenerator lcg; | } | ||||
// Manual tests. | |||||
std::vector<ReverseTestCase> test_cases({ | /** | ||||
{{}, {}}, | * Get a couple of interesting script flags. | ||||
{{99}, {99}}, | */ | ||||
{{0xde, 0xad}, {0xad, 0xde}}, | static std::vector<uint32_t> GetCuratedFlagSet() { | ||||
{{0xde, 0xad, 0xa1}, {0xa1, 0xad, 0xde}}, | std::vector<uint32_t> flagset({ | ||||
{{0xde, 0xad, 0xbe, 0xef}, {0xef, 0xbe, 0xad, 0xde}}, | SCRIPT_VERIFY_NONE, | ||||
{{0x12, 0x34, 0x56}, {0x56, 0x34, 0x12}}, | STANDARD_SCRIPT_VERIFY_FLAGS, | ||||
MANDATORY_SCRIPT_VERIFY_FLAGS, | |||||
}); | }); | ||||
for (uint32_t flagindex = 0; flagindex < 32; ++flagindex) { | |||||
uint32_t flags = 1 << flagindex; | |||||
flagset.push_back(flags); | |||||
} | |||||
return flagset; | |||||
} | |||||
// Palindrome tests, they are their own reverse. | // Test a few simple manual cases with random flags (proxy for exhaustive | ||||
std::vector<valtype> palindromes; | // testing). | ||||
palindromes.reserve(MAX_SCRIPT_ELEMENT_SIZE); | BOOST_AUTO_TEST_CASE(op_reversebytes_manual_random_flags) { | ||||
MMIXLinearCongruentialGenerator lcg; | |||||
// Generated tests: | for (size_t i = 0; i < 4096; i++) { | ||||
// - for iota(n) mod 256, n = 0,..,520. | uint32_t flags = lcg.next(); | ||||
// - for random bitstrings, n = 0,..,520. | CheckPassForCombinations(flags, {}, {}); | ||||
// - for palindromes 0,..,n,..,0. | CheckPassForCombinations(flags, {99}, {99}); | ||||
for (size_t datasize = 0; datasize <= MAX_SCRIPT_ELEMENT_SIZE; ++datasize) { | CheckPassForCombinations(flags, {0xde, 0xad}, {0xad, 0xde}); | ||||
valtype iota_data, random_data, palindrome; | CheckPassForCombinations(flags, {0xde, 0xad, 0xa1}, {0xa1, 0xad, 0xde}); | ||||
CheckPassForCombinations(flags, {0xde, 0xad, 0xbe, 0xef}, | |||||
{0xef, 0xbe, 0xad, 0xde}); | |||||
CheckPassForCombinations(flags, {0x12, 0x34, 0x56}, {0x56, 0x34, 0x12}); | |||||
} | |||||
} | |||||
// Test byte strings 0..n (mod 256). | |||||
BOOST_AUTO_TEST_CASE(op_reversebytes_iota_random_flags) { | |||||
MMIXLinearCongruentialGenerator lcg; | |||||
for (uint32_t datasize : | |||||
{0, 1, 2, 10, 16, 32, 50, 128, 300, 400, 512, 519, 520}) { | |||||
valtype iota_data; | |||||
iota_data.reserve(datasize); | iota_data.reserve(datasize); | ||||
random_data.reserve(datasize); | |||||
palindrome.reserve(datasize); | |||||
for (size_t item = 0; item < datasize; ++item) { | for (size_t item = 0; item < datasize; ++item) { | ||||
iota_data.emplace_back(item % 256); | iota_data.emplace_back(item % 256); | ||||
} | |||||
valtype iota_data_reversed = {iota_data.rbegin(), iota_data.rend()}; | |||||
for (size_t i = 0; i < 4096; i++) { | |||||
uint32_t flags = lcg.next(); | |||||
CheckPassForCombinations(flags, iota_data, iota_data_reversed); | |||||
} | |||||
} | |||||
} | |||||
// Test random byte strings. | |||||
BOOST_AUTO_TEST_CASE(op_reversebytes_random_bytes) { | |||||
MMIXLinearCongruentialGenerator lcg; | |||||
const std::vector<uint32_t> curated_flagset = GetCuratedFlagSet(); | |||||
for (uint32_t datasize = 0; datasize < MAX_SCRIPT_ELEMENT_SIZE; | |||||
++datasize) { | |||||
valtype random_data; | |||||
random_data.reserve(datasize); | |||||
for (size_t item = 0; item < datasize; ++item) { | |||||
random_data.emplace_back(lcg.next() % 256); | random_data.emplace_back(lcg.next() % 256); | ||||
} | |||||
valtype random_data_reversed = {random_data.rbegin(), | |||||
random_data.rend()}; | |||||
for (const uint32_t flags : curated_flagset) { | |||||
CheckPassForCombinations(flags, random_data, random_data_reversed); | |||||
} | |||||
} | |||||
} | |||||
deadalnix: This has the same structure as `op_reversebytes_iota_random_flags` and… | |||||
/** | |||||
* Makes a palindrome of the form 0..n..0. | |||||
*/ | |||||
static valtype GetPalindrome(uint32_t datasize) { | |||||
valtype palindrome; | |||||
palindrome.reserve(datasize); | |||||
for (size_t item = 0; item < datasize; ++item) { | |||||
palindrome.emplace_back( | palindrome.emplace_back( | ||||
(item < (datasize + 1) / 2 ? item : datasize - item - 1) % 256); | (item < (datasize + 1) / 2 ? item : datasize - item - 1) % 256); | ||||
} | } | ||||
test_cases.push_back( | return palindrome; | ||||
{iota_data, {iota_data.rbegin(), iota_data.rend()}}); | |||||
test_cases.push_back( | |||||
{random_data, {random_data.rbegin(), random_data.rend()}}); | |||||
palindromes.push_back(palindrome); | |||||
} | } | ||||
deadalnixUnsubmitted Not Done Inline ActionsDon't create function if you are going to only use them in one place. deadalnix: Don't create function if you are going to only use them in one place. | |||||
for (int i = 0; i < 4096; i++) { | // Test palindromes. | ||||
// Generate random flags. | BOOST_AUTO_TEST_CASE(op_reversebytes_palindrome) { | ||||
uint32_t flags = lcg.next(); | MMIXLinearCongruentialGenerator lcg; | ||||
const std::vector<uint32_t> curated_flagset = GetCuratedFlagSet(); | |||||
// Empty stack. | for (size_t datasize = 0; datasize <= MAX_SCRIPT_ELEMENT_SIZE; ++datasize) { | ||||
CheckErrorIfEnabled(flags, {}, CScript() << OP_REVERSEBYTES, | valtype palindrome = GetPalindrome(datasize); | ||||
ScriptError::INVALID_STACK_OPERATION); | for (const uint32_t flags : curated_flagset) { | ||||
CheckPassIfEnabled(flags, {palindrome}, | |||||
for (const ReverseTestCase &test_case : test_cases) { | CScript() << OP_REVERSEBYTES, {palindrome}); | ||||
CheckPassReverse(flags, test_case); | } | ||||
} | } | ||||
for (const valtype &palindrome : palindromes) { | |||||
// Verify palindrome. | |||||
CheckPassIfEnabled( | |||||
flags, {palindrome}, | |||||
CScript() << OP_DUP << OP_REVERSEBYTES << OP_EQUALVERIFY, {}); | |||||
} | } | ||||
// Verify non-palindrome fails. | // Verify non-palindrome fails. | ||||
BOOST_AUTO_TEST_CASE(op_reversebytes_non_palindrome_random_flags) { | |||||
MMIXLinearCongruentialGenerator lcg; | |||||
// Test for random flags (proxy for exhaustive testing). | |||||
for (size_t i = 0; i < 4096; i++) { | |||||
uint32_t flags = lcg.next(); | |||||
CheckErrorIfEnabled(flags, {{0x01, 0x02, 0x03, 0x02, 0x02}}, | CheckErrorIfEnabled(flags, {{0x01, 0x02, 0x03, 0x02, 0x02}}, | ||||
CScript() | CScript() | ||||
<< OP_DUP << OP_REVERSEBYTES << OP_EQUALVERIFY, | << OP_DUP << OP_REVERSEBYTES << OP_EQUALVERIFY, | ||||
ScriptError::EQUALVERIFY); | ScriptError::EQUALVERIFY); | ||||
deadalnixUnsubmitted Not Done Inline ActionsYou can merge that into op_reversebytes_manual_random_flags with op_reversebytes_empty_stack_random_flags and maybe other pieces that I'm missing. deadalnix: You can merge that into `op_reversebytes_manual_random_flags` with… | |||||
} | } | ||||
} | } | ||||
// Test empty stack results in INVALID_STACK_OPERATION. | |||||
BOOST_AUTO_TEST_CASE(op_reversebytes_empty_stack_random_flags) { | |||||
MMIXLinearCongruentialGenerator lcg; | |||||
// Test for random flags (proxy for exhaustive testing). | |||||
for (size_t i = 0; i < 4096; i++) { | |||||
uint32_t flags = lcg.next(); | |||||
CheckErrorIfEnabled(flags, {}, CScript() << OP_REVERSEBYTES, | |||||
ScriptError::INVALID_STACK_OPERATION); | |||||
} | |||||
} | |||||
BOOST_AUTO_TEST_SUITE_END() | BOOST_AUTO_TEST_SUITE_END() |
This has the same structure as op_reversebytes_iota_random_flags and op_reversebytes_palindrome so you should probably merge them. The code is talking to you, you just got to pay attention.