From 105cd06d7e28e79dd1c0dfd9b463e3a339eec8b4 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Fri, 7 Oct 2022 11:41:33 +0000 Subject: [PATCH] add more hook unit tests, and test wasm checker --- src/test/app/SetHook_test.cpp | 196 ++++++++++++++++++++++++------- src/test/app/build_test_hooks.sh | 4 +- src/test/app/check_test_hooks.sh | 12 ++ src/test/jtx/hook.h | 2 +- src/test/jtx/impl/hook.cpp | 5 +- 5 files changed, 172 insertions(+), 47 deletions(-) create mode 100755 src/test/app/check_test_hooks.sh diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index cc9f90093..0d8f5fcba 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -31,6 +31,107 @@ using TestHook = std::vector const&; class SetHook_test : public beast::unit_test::suite { +public: + + void + testHooksDisabled() + { + testcase("Check for disabled amendment"); + using namespace jtx; + Env env{*this, supported_amendments() - featureHooks}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + // RH TODO: does it matter that passing malformed txn here gives back temMALFORMED (and not disabled)? + env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0), ter(temDISABLED)); + } + +//Json::Value +//hook(Account const& account, std::optional> hooks, std::uint32_t flags); + +//Json::Value +//hso(std::vector wasmBytes, uint64_t hookOn = 0, uint256 ns = beast::zero, uint8_t apiversion = 0); + + void + testMalformedTxStructure() + { + testcase("Checks malformed transactions"); + using namespace jtx; + Env env{*this, supported_amendments()}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + // Must have a "Hooks" field + env(ripple::test::jtx::hook(alice, {}, 0), ter(temMALFORMED)); + + // Must have at least one non-empty subfield + env(ripple::test::jtx::hook(alice, {{}}, 0), ter(temMALFORMED)); + + // Must have fewer than 5 entries + env(ripple::test::jtx::hook(alice, {{ + hso(accept_wasm), + hso(accept_wasm), + hso(accept_wasm), + hso(accept_wasm), + hso(accept_wasm)}}, 0), ter(temMALFORMED)); + + // If createcode present must be less than 64kib + { + std::vector longbin(0x10000U, 0xFFU); + env(ripple::test::jtx::hook(alice, {{hso(longbin)}}, 0), ter(temMALFORMED)); + } + + } + +/* + Json::Value jv; + + jv[jss::Account] = alice.human(); + jv[jss::TransactionType] = jss::SetHook; + jv[jss::Flags] = 0; + jv[jss::Hooks] = + Json::Value{Json::arrayValue}; + + Json::Value iv; + + iv[jss::CreateCode] = std::string(65536, 'F'); + iv[jss::HookOn] = "0000000000000000"; + iv[jss::HookNamespace] = to_string(uint256{beast::zero}); + iv[jss::HookApiVersion] = Json::Value{0}; + + jv[jss::Hooks][i][jss::Hook] = iv; + env(jv, ter(temMALFORMED)); +*/ + void + testMalformedWasm() + { + testcase("Checks malformed hook binaries"); + using namespace jtx; + Env env{*this, supported_amendments()}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + + // Must import guard + env(ripple::test::jtx::hook(alice, {{hso(noguard_wasm)}}, 0), ter(temMALFORMED)); + + // Must only contain hook and cbak + env(ripple::test::jtx::hook(alice, {{hso(illegalfunc_wasm)}}, 0), ter(temMALFORMED)); + } + + // Trivial single hook + //env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0)); + + // RH TODO + void + run() override + { + //testTicketSetHook(); // RH TODO + testHooksDisabled(); + testMalformedTxStructure(); + testMalformedWasm(); + } + private: TestHook accept_wasm = @@ -41,55 +142,64 @@ private: extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); int64_t hook(uint32_t reserved ) { - accept(0,0,0); + return accept(0,0,0); _g(1,1); } )[test.hook]" ]; -public: + + TestHook + noguard_wasm = + wasm[ + R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + int64_t hook(uint32_t reserved ) + { + return accept(0,0,0); + } + )[test.hook]" + ]; + + TestHook + illegalfunc_wasm = + wasm[ + R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + int64_t hook(uint32_t reserved ) + { + return accept(0,0,0); + } + void otherfunc() + { + _g(1,1); + } + )[test.hook]" + ]; + + TestHook + long_wasm = + wasm[ + R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + #define SBUF(x) (uint32_t)(x), sizeof(x) + #define M_REPEAT_10(X) X X X X X X X X X X + #define M_REPEAT_100(X) M_REPEAT_10(M_REPEAT_10(X)) + #define M_REPEAT_1000(X) M_REPEAT_100(M_REPEAT_10(X)) + int64_t hook(uint32_t reserved ) + { + char ret[] = M_REPEAT_1000("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz01234567890123"); + return accept(SBUF(ret), 0); + } + )[test.hook]" + ]; - void - testHooksDisabled() - { - testcase("SetHook checks for disabled amendment"); - using namespace jtx; - Env env{*this, supported_amendments() - featureHooks}; - auto const alice = Account{"alice"}; - env.fund(XRP(10000), alice); - env(ripple::test::jtx::hook(alice, {}, 0), ter(temDISABLED)); - } - - void - testMalformedTransaction() - { - testcase("SetHook checks for malformed transactions"); - using namespace jtx; - Env env{*this, supported_amendments()}; - - auto const alice = Account{"alice"}; - env.fund(XRP(10000), alice); - - - // Must have a "Hooks" field - env(ripple::test::jtx::hook(alice, {}, 0), ter(temMALFORMED)); - - // Must have at least one non-empty subfield - env(ripple::test::jtx::hook(alice, {{}}, 0), ter(temMALFORMED)); - - // Trivial single hook - env(ripple::test::jtx::hook(alice, {{hso(accept_wasm)}}, 0)); - - // RH TODO - } - - void - run() override - { - //testTicketSetHook(); // RH TODO - testHooksDisabled(); - testMalformedTransaction(); - } }; BEAST_DEFINE_TESTSUITE(SetHook, tx, ripple); } // namespace test diff --git a/src/test/app/build_test_hooks.sh b/src/test/app/build_test_hooks.sh index b89e03c06..a77ea58a3 100755 --- a/src/test/app/build_test_hooks.sh +++ b/src/test/app/build_test_hooks.sh @@ -14,12 +14,12 @@ std::map> wasm = {' > SetHook_wasm.h cat SetHook_test.cpp | tr '\n' '\f' | grep -Po 'R"\[test\.hook\](.*?)\[test\.hook\]"' | sed -E 's/R"\[test\.hook\]\(//g' | - sed -E 's/\)\[test\.hook\]"[\f \t]*//g' | + sed -E 's/\)\[test\.hook\]"[\f \t]*/\/*end*\//g' | while read -r line do echo -n '{ R"[test.hook](' >> SetHook_wasm.h # tr '\f' '\n' <<< $line >> SetHook_wasm.h - cat <<< $line | tr -d '\n' | tr '\f' '\n' >> SetHook_wasm.h + cat <<< $line | sed -E 's/.{7}$//g' | tr -d '\n' | tr '\f' '\n' >> SetHook_wasm.h echo ')[test.hook]",' >> SetHook_wasm.h echo "{" >> SetHook_wasm.h wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined <<< `tr '\f' '\n' <<< $line` | diff --git a/src/test/app/check_test_hooks.sh b/src/test/app/check_test_hooks.sh new file mode 100755 index 000000000..efdc0db34 --- /dev/null +++ b/src/test/app/check_test_hooks.sh @@ -0,0 +1,12 @@ +#!/bin/bash +ERRORS="0" +cat SetHook_wasm.h | tr -d '\n' | grep -Po '{[0-9A-FUx, ]*}' | tr -d ' ,{}U' | sed -E 's/0x//g' | + while read -r line + do + xxd -r -p <<< $line | wasm-objdump -d - + if [ "$?" -gt "0" ] + then + ERRORS=`echo $ERRORS + 1 | bc` + fi + done +echo "Errors decompiling: $ERRORS" diff --git a/src/test/jtx/hook.h b/src/test/jtx/hook.h index 5d27a1610..ca516c523 100644 --- a/src/test/jtx/hook.h +++ b/src/test/jtx/hook.h @@ -32,7 +32,7 @@ Json::Value hook(Account const& account, std::optional> hooks, std::uint32_t flags); Json::Value -hso(std::vector wasmBytes, uint64_t hookOn = 0, uint256 ns = beast::zero, uint8_t apiversion = 0); +hso(std::vector const& wasmBytes, uint64_t hookOn = 0, uint256 ns = beast::zero, uint8_t apiversion = 0); } // namespace jtx } // namespace test diff --git a/src/test/jtx/impl/hook.cpp b/src/test/jtx/impl/hook.cpp index f62c21441..0df56d973 100644 --- a/src/test/jtx/impl/hook.cpp +++ b/src/test/jtx/impl/hook.cpp @@ -65,8 +65,11 @@ inline std::string uint64_hex(uint64_t x) } Json::Value -hso(std::vector wasmBytes, uint64_t hookOn, uint256 ns, uint8_t apiversion) +hso(std::vector const& wasmBytes, uint64_t hookOn, uint256 ns, uint8_t apiversion) { + if (wasmBytes.size() == 0) + throw std::runtime_error("empty hook wasm passed to hso()"); + Json::Value jv; jv[jss::CreateCode] = strHex(wasmBytes); jv[jss::HookOn] = uint64_hex(hookOn);