From 67614182b40ae54837e2554b422d559985d3b0d2 Mon Sep 17 00:00:00 2001 From: Richard Holland Date: Fri, 18 Nov 2022 12:09:09 +0000 Subject: [PATCH] unit tests for hook_account, hook_hash --- src/ripple/app/hook/impl/applyHook.cpp | 4 + src/test/app/SetHook_test.cpp | 287 +++++++++++++++++++++++++ src/test/app/build_test_hooks.sh | 2 + src/test/jtx/impl/Env.cpp | 11 +- 4 files changed, 301 insertions(+), 3 deletions(-) diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index b65e05fe1..6cd7f696f 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -3206,6 +3206,10 @@ DEFINE_HOOK_FUNCTION( uint32_t write_ptr, uint32_t ptr_len ) { HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, hookCtx on current stack + + if (ptr_len < 20) + return TOO_SMALL; + if (NOT_IN_BOUNDS(write_ptr, 20, memory_length)) return OUT_OF_BOUNDS; diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index f323c4297..68a827869 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -3731,6 +3731,127 @@ public: void test_hook_account() { + testcase("Test hook_account"); + using namespace jtx; + + + auto const test = [&](Account alice)->void + { + Env env{*this, supported_amendments()}; + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + TestHook + hook = + wasm[R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t hook_account (uint32_t, uint32_t); + #define TOO_SMALL -4 + #define OUT_OF_BOUNDS -1 + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + int64_t hook(uint32_t reserved ) + { + _g(1,1); + uint8_t acc[20]; + + // Test out of bounds check + ASSERT(hook_account(1000000, 20) == OUT_OF_BOUNDS); + ASSERT(hook_account((uint32_t)acc, 19) == TOO_SMALL); + ASSERT(hook_account((uint32_t)acc, 20) == 20); + + // return the accid as the return string + accept((uint32_t)acc, 20, 0); + } + )[test.hook]"]; + + // install the hook on alice + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set hook_account"), + HSFEE); + env.close(); + + // invoke the hook + env(pay(bob, alice, XRP(1)), + M("test hook_account"), + fee(XRP(1))); + + { + auto meta = env.meta(); + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + // ensure there was only one hook execution + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 1); + + // get the data in the return string of the extention + auto const retStr = hookExecutions[0].getFieldVL(sfHookReturnString); + + // check that it matches the account id + BEAST_EXPECT(retStr.size() == 20); + auto const a = alice.id(); + BEAST_EXPECT(memcmp(retStr.data(), a.data(), 20) == 0); + } + + // install the same hook bob + env(ripple::test::jtx::hook(bob, {{hso(hook, overrideFlag)}}, 0), + M("set hook_account 2"), + HSFEE); + env.close(); + + // invoke the hook + env(pay(bob, alice, XRP(1)), + M("test hook_account 2"), + fee(XRP(1))); + + // there should be two hook executions, the first should be bob's address + // the second should be alice's + { + auto meta = env.meta(); + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + // ensure there were two hook executions + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 2); + + { + // get the data in the return string of the extention + auto const retStr = hookExecutions[0].getFieldVL(sfHookReturnString); + + // check that it matches the account id + BEAST_EXPECT(retStr.size() == 20); + auto const b = bob.id(); + BEAST_EXPECT(memcmp(retStr.data(), b.data(), 20) == 0); + } + + { + // get the data in the return string of the extention + auto const retStr = hookExecutions[1].getFieldVL(sfHookReturnString); + + // check that it matches the account id + BEAST_EXPECT(retStr.size() == 20); + auto const a = alice.id(); + BEAST_EXPECT(memcmp(retStr.data(), a.data(), 20) == 0); + } + + } + }; + + test(Account{"alice"}); + test(Account{"cho"}); } void @@ -3741,6 +3862,172 @@ public: void test_hook_hash() { + testcase("Test hook_hash"); + using namespace jtx; + + + auto const test = [&](Account alice)->void + { + Env env{*this, supported_amendments()}; + + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + TestHook + hook = + wasm[R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t hook_hash (uint32_t, uint32_t, int32_t); + #define TOO_SMALL -4 + #define OUT_OF_BOUNDS -1 + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + int64_t hook(uint32_t reserved ) + { + _g(1,1); + uint8_t hash[32]; + + // Test out of bounds check + ASSERT(hook_hash(1000000, 32, -1) == OUT_OF_BOUNDS); + ASSERT(hook_hash((uint32_t)hash, 31, -1) == TOO_SMALL); + ASSERT(hook_hash((uint32_t)hash, 32, -1) == 32); + + // return the hash as the return string + accept((uint32_t)hash, 32, 0); + } + )[test.hook]"]; + + // install the hook on alice + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set hook_hash"), + HSFEE); + env.close(); + + // invoke the hook + env(pay(bob, alice, XRP(1)), + M("test hook_hash"), + fee(XRP(1))); + + { + auto meta = env.meta(); + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + // ensure there was only one hook execution + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 1); + + // get the data in the return string of the extention + auto const retStr = hookExecutions[0].getFieldVL(sfHookReturnString); + + // check that it matches the hook hash + BEAST_EXPECT(retStr.size() == 32); + + auto const hash = hookExecutions[0].getFieldH256(sfHookHash); + BEAST_EXPECT(memcmp(hash.data(), retStr.data(), 32) == 0); + } + + TestHook + hook2 = + wasm[R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t hook_hash (uint32_t, uint32_t, int32_t); + #define TOO_SMALL -4 + #define OUT_OF_BOUNDS -1 + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + int64_t hook(uint32_t reserved ) + { + _g(1,2); + uint8_t hash[32]; + + // Test out of bounds check + ASSERT(hook_hash(1000000, 32, -1) == OUT_OF_BOUNDS); + ASSERT(hook_hash((uint32_t)hash, 31, -1) == TOO_SMALL); + ASSERT(hook_hash((uint32_t)hash, 32, -1) == 32); + + // return the hash as the return string + accept((uint32_t)hash, 32, 0); + } + )[test.hook]"]; + + // install a slightly different hook on bob + env(ripple::test::jtx::hook(bob, {{hso(hook2, overrideFlag)}}, 0), + M("set hook_hash 2"), + HSFEE); + env.close(); + + // invoke the hook + env(pay(bob, alice, XRP(1)), + M("test hook_hash 2"), + fee(XRP(1))); + + + // there should be two hook executions, the first should have bob's hook hash + // the second should have alice's hook hash + { + auto meta = env.meta(); + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + // ensure there was only one hook execution + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 2); + + // get the data in the return string of the extention + auto const retStr1 = hookExecutions[0].getFieldVL(sfHookReturnString); + + // check that it matches the hook hash + BEAST_EXPECT(retStr1.size() == 32); + + auto const hash1 = hookExecutions[0].getFieldH256(sfHookHash); + BEAST_EXPECT(memcmp(hash1.data(), retStr1.data(), 32) == 0); + + // get the data in the return string of the extention + auto const retStr2 = hookExecutions[1].getFieldVL(sfHookReturnString); + + // check that it matches the hook hash + BEAST_EXPECT(retStr2.size() == 32); + + auto const hash2 = hookExecutions[1].getFieldH256(sfHookHash); + BEAST_EXPECT(memcmp(hash2.data(), retStr2.data(), 32) == 0); + + // make sure they're not the same + BEAST_EXPECT(memcmp(hash1.data(), hash2.data(), 32) != 0); + + // compute the hashes + auto computedHash2 = + ripple::sha512Half_s( + ripple::Slice(hook.data(), hook.size()) + ); + + auto computedHash1 = + ripple::sha512Half_s( + ripple::Slice(hook2.data(), hook2.size()) + ); + + // ensure the computed hashes match + BEAST_EXPECT(computedHash1 == hash1); + BEAST_EXPECT(computedHash2 == hash2); + } + }; + + test(Account{"alice"}); } void diff --git a/src/test/app/build_test_hooks.sh b/src/test/app/build_test_hooks.sh index 8cdfbf5be..837eaf0b4 100755 --- a/src/test/app/build_test_hooks.sh +++ b/src/test/app/build_test_hooks.sh @@ -25,6 +25,8 @@ cat SetHook_test.cpp | tr '\n' '\f' | WAT=`grep -Eo '\(module' <<< $line | wc -l` if [ "$WAT" -eq "0" ] then + echo '#include "api.h"' > /root/xrpld-hooks/hook/tests/hookapi/wasm/test-$COUNTER.c + tr '\f' '\n' <<< $line >> /root/xrpld-hooks/hook/tests/hookapi/wasm/test-$COUNTER.c wasmcc -x c /dev/stdin -o /dev/stdout -O2 -Wl,--allow-undefined <<< `tr '\f' '\n' <<< $line` | hook-cleaner - - 2>/dev/null | xxd -p -u -c 19 | diff --git a/src/test/jtx/impl/Env.cpp b/src/test/jtx/impl/Env.cpp index 3140f9afc..b7bfb88cc 100644 --- a/src/test/jtx/impl/Env.cpp +++ b/src/test/jtx/impl/Env.cpp @@ -376,9 +376,14 @@ Env::postconditions(JTx const& jt, TER ter, bool didApply) std::shared_ptr Env::meta() { - close(); - auto const item = closed()->txRead(txid_); - return item.second; + auto cur = current()->txRead(txid_); + if (cur.second) + return cur.second; + else + if (cur.first) + close(); + + return closed()->txRead(txid_).second; } std::shared_ptr