From 8169da96e5771d820a3e80f3ade60b4054005538 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 28 Oct 2024 09:33:57 -0500 Subject: [PATCH 1/2] Pull checktime tests out of api_tests into its own file. Also moved some test_utils code out of unittests into chain_test_utils.hpp for tests/* --- tests/chain_test_utils.hpp | 186 ++++++++++ tests/test_read_only_trx.cpp | 1 + unittests/CMakeLists.txt | 2 +- unittests/api_tests.cpp | 682 +--------------------------------- unittests/checktime_tests.cpp | 519 ++++++++++++++++++++++++++ unittests/test_utils.hpp | 267 ++++++------- 6 files changed, 842 insertions(+), 815 deletions(-) create mode 100644 tests/chain_test_utils.hpp create mode 100644 unittests/checktime_tests.cpp diff --git a/tests/chain_test_utils.hpp b/tests/chain_test_utils.hpp new file mode 100644 index 0000000000..caa69f75fb --- /dev/null +++ b/tests/chain_test_utils.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace eosio::test_utils { + +using namespace eosio::chain; +using namespace eosio::chain::literals; + +struct testit { + uint64_t id; + explicit testit(uint64_t id = 0) + :id(id){} + static account_name get_account() { + return chain::config::system_account_name; + } + static action_name get_name() { + return "testit"_n; + } +}; + +// Corresponds to the reqactivated action of the bios contract. +// See libraries/testing/contracts/eosio.bios/eosio.bios.hpp +struct reqactivated { + chain::digest_type feature_digest; + + explicit reqactivated(const chain::digest_type& fd) + :feature_digest(fd){}; + + static account_name get_account() { + return chain::config::system_account_name; + } + static action_name get_name() { + return "reqactivated"_n; + } +}; + +// Create a read-only trx that works with bios reqactivated action +auto make_bios_ro_trx(eosio::chain::controller& control) { + const auto& pfm = control.get_protocol_feature_manager(); + static auto feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::replace_deferred); + + signed_transaction trx; + trx.expiration = fc::time_point_sec{fc::time_point::now() + fc::seconds(30)}; + vector no_auth{}; + trx.actions.emplace_back( no_auth, reqactivated{*feature_digest} ); + return std::make_shared( std::move(trx) ); +} + +// Push an input transaction to controller and return trx trace +// If account is eosio then signs with the default private key +auto push_input_trx(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, signed_transaction& trx) { + trx.expiration = fc::time_point_sec{fc::time_point::now() + fc::seconds(30)}; + trx.set_reference_block( control.head().id() ); + if (account == config::system_account_name) { + auto default_priv_key = private_key_type::regenerate(fc::sha256::hash(std::string("nathan"))); + trx.sign(default_priv_key, control.get_chain_id()); + } else { + trx.sign(testing::tester::get_private_key(account, "active"), control.get_chain_id()); + } + auto ptrx = std::make_shared( trx, packed_transaction::compression_type::zlib ); + + auto trx_promise = std::make_shared>(); + std::future trx_future = trx_promise->get_future(); + + app->executor().post( priority::low, exec_queue::read_write, [&ptrx, &app, trx_promise]() { + app->get_method()(ptrx, + false, // api_trx + transaction_metadata::trx_type::input, // trx_type + true, // return_failure_traces + [trx_promise](const next_function_variant& result) { + if( std::holds_alternative( result ) ) { + try { + std::get(result)->dynamic_rethrow_exception(); + } catch(...) { + trx_promise->set_exception(std::current_exception()); + } + } else if ( std::get( result )->except ) { + try { + std::get(result)->except->dynamic_rethrow_exception(); + } catch(...) { + trx_promise->set_exception(std::current_exception()); + } + } else { + trx_promise->set_value(std::get(result)); + } + }); + }); + + if (trx_future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) + throw std::runtime_error("failed to execute trx: " + ptrx->get_transaction().actions.at(0).name.to_string() + " to account: " + account.to_string()); + + return trx_future.get(); +} + +// Push setcode trx to controller and return trx trace +auto set_code(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, const vector& wasm) { + signed_transaction trx; + trx.actions.emplace_back(std::vector{{account, config::active_name}}, + chain::setcode{ + .account = account, + .vmtype = 0, + .vmversion = 0, + .code = bytes(wasm.begin(), wasm.end()) + }); + return push_input_trx(app, control, account, trx); +} + +void activate_protocol_features_set_bios_contract(appbase::scoped_app& app, chain_plugin* chain_plug) { + using namespace appbase; + + auto feature_set = std::make_shared>(false); + // has to execute when pending block is not null + for (int tries = 0; tries < 100; ++tries) { + app->executor().post( priority::high, exec_queue::read_write, [&chain_plug=chain_plug, feature_set](){ + try { + if (!chain_plug->chain().is_building_block() || *feature_set) + return; + const auto& pfm = chain_plug->chain().get_protocol_feature_manager(); + auto preactivate_feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::preactivate_feature); + BOOST_CHECK( preactivate_feature_digest ); + chain_plug->chain().preactivate_feature( *preactivate_feature_digest, false ); + + vector feature_digests; + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::only_link_to_existing_permission)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::replace_deferred)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::no_duplicate_deferred_id)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::fix_linkauth_restriction)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disallow_empty_producer_schedule)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::restrict_action_to_self)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::only_bill_first_authorizer)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::forward_setcode)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_sender)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::ram_restrictions)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::webauthn_key)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::action_return_value)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::configurable_wasm_limits)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::blockchain_parameters)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_code_hash)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::crypto_primitives)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_block_num)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::bls_primitives)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disable_deferred_trxs_stage_1)); + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disable_deferred_trxs_stage_2)); + // savanna + feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::savanna)); + + for (const auto feature_digest : feature_digests) { + chain_plug->chain().preactivate_feature( feature_digest, false ); + } + *feature_set = true; + return; + } FC_LOG_AND_DROP() + BOOST_CHECK(!"exception setting protocol features"); + }); + if (*feature_set) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + // Wait for next block + std::this_thread::sleep_for( std::chrono::milliseconds(config::block_interval_ms) ); + + auto r = set_code(app, chain_plug->chain(), config::system_account_name, testing::contracts::eosio_bios_wasm()); + BOOST_CHECK(r->receipt && r->receipt->status == transaction_receipt_header::executed); +} + +} // namespace eosio::test_utils + +FC_REFLECT( eosio::test_utils::testit, (id) ) +FC_REFLECT( eosio::test_utils::reqactivated, (feature_digest) ) diff --git a/tests/test_read_only_trx.cpp b/tests/test_read_only_trx.cpp index 56b684da86..66a141cfb0 100644 --- a/tests/test_read_only_trx.cpp +++ b/tests/test_read_only_trx.cpp @@ -16,6 +16,7 @@ #include #include +#include "chain_test_utils.hpp" #include #include diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 96ca538029..0238bdde60 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -114,7 +114,7 @@ foreach(RUNTIME ${EOSIO_WASM_RUNTIMES}) set_tests_properties(api_part1_unit_test_${RUNTIME} PROPERTIES COST 5000) set_tests_properties(api_part2_unit_test_${RUNTIME} PROPERTIES COST 5000) set_tests_properties(api_part3_unit_test_${RUNTIME} PROPERTIES COST 5000) - set_tests_properties(api_part4_unit_test_${RUNTIME} PROPERTIES COST 5000) + set_tests_properties(checktime_unit_test_${RUNTIME} PROPERTIES COST 5000) set_tests_properties(wasm_part1_unit_test_${RUNTIME} PROPERTIES COST 5000) set_tests_properties(wasm_part2_unit_test_${RUNTIME} PROPERTIES COST 5000) set_tests_properties(wasm_part3_unit_test_${RUNTIME} PROPERTIES COST 5000) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 7b804804cd..fd88bd45ad 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1,12 +1,3 @@ -#include -#include -#include -#include -#include -#include -#include -#include - #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-compare" #include @@ -31,36 +22,32 @@ #include #include -#include -#include -#include -#include -#include - #include #include #include #include "test_cfd_transaction.hpp" +#include "test_utils.hpp" + +#include +#include +#include +#include +#include +#include #define DUMMY_ACTION_DEFAULT_A 0x45 #define DUMMY_ACTION_DEFAULT_B 0xab11cd1244556677 #define DUMMY_ACTION_DEFAULT_C 0x7451ae12 -static constexpr unsigned int DJBH(const char* cp) -{ - unsigned int hash = 5381; - while (*cp) - hash = 33 * hash ^ (unsigned char) *cp++; - return hash; -} +using namespace eosio; +using namespace eosio::chain::literals; +using namespace eosio::test_utils; +using namespace eosio::testing; +using namespace fc; -static constexpr unsigned long long WASM_TEST_ACTION(const char* cls, const char* method) -{ - return static_cast(DJBH(cls)) << 32 | static_cast(DJBH(method)); -} +namespace bio = boost::iostreams; -using namespace eosio::chain::literals; struct u128_action { unsigned __int128 values[3]; //16*3 @@ -93,52 +80,6 @@ FC_REFLECT( u128_action, (values) ) FC_REFLECT( dtt_action, (payer)(deferred_account)(deferred_action)(permission_name)(delay_sec) ) FC_REFLECT( invalid_access_action, (code)(val)(index)(store) ) -using namespace eosio; -using namespace eosio::testing; -using namespace chain; -using namespace fc; - -namespace bio = boost::iostreams; - -template -struct test_api_action { - static account_name get_account() { - return "testapi"_n; - } - - static action_name get_name() { - return action_name(NAME); - } -}; - -FC_REFLECT_TEMPLATE((uint64_t T), test_api_action, BOOST_PP_SEQ_NIL) - -template -struct test_pause_action { - static account_name get_account() { - return "pause"_n; - } - - static action_name get_name() { - return action_name(NAME); - } -}; - -FC_REFLECT_TEMPLATE((uint64_t T), test_pause_action, BOOST_PP_SEQ_NIL) - -template -struct test_chain_action { - static account_name get_account() { - return account_name(config::system_account_name); - } - - static action_name get_name() { - return action_name(NAME); - } -}; - -FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action, BOOST_PP_SEQ_NIL) - struct check_auth { account_name account; permission_name permission; @@ -178,102 +119,6 @@ string U128Str(unsigned __int128 i) return fc::variant(fc::uint128(i)).get_string(); } -template -transaction_trace_ptr CallAction(validating_tester& test, T ac, const vector& scope = {"testapi"_n}) { - signed_transaction trx; - - - auto pl = vector{{scope[0], config::active_name}}; - if (scope.size() > 1) - for (size_t i = 1; i < scope.size(); i++) - pl.push_back({scope[i], config::active_name}); - - action act(pl, ac); - trx.actions.push_back(act); - - test.set_transaction_headers(trx); - auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.get_chain_id()); - flat_set keys; - trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); - auto res = test.push_transaction(trx); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - test.produce_block(); - return res; -} - -template -std::pair _CallFunction(Tester& test, T ac, const vector& data, const vector& scope = {"testapi"_n}, bool no_throw = false) { - { - signed_transaction trx; - - auto pl = vector{{scope[0], config::active_name}}; - if (scope.size() > 1) - for (unsigned int i = 1; i < scope.size(); i++) - pl.push_back({scope[i], config::active_name}); - - action act(pl, ac); - act.data = data; - act.authorization = {{"testapi"_n, config::active_name}}; - trx.actions.push_back(act); - - test.set_transaction_headers(trx, test.DEFAULT_EXPIRATION_DELTA); - auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.get_chain_id()); - - flat_set keys; - trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); - - auto res = test.push_transaction(trx, fc::time_point::maximum(), Tester::DEFAULT_BILLED_CPU_TIME_US, no_throw); - if (!no_throw) { - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - } - auto block = test.produce_block(); - return { res, block }; - } -} - -template -transaction_trace_ptr CallFunction(Tester& test, T ac, const vector& data, const vector& scope = {"testapi"_n}, bool no_throw = false) { - { - return _CallFunction(test, ac, data, scope, no_throw).first; - } -} - -#define CALL_TEST_FUNCTION(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_api_action{}, DATA) -#define CALL_TEST_FUNCTION_WITH_BLOCK(_TESTER, CLS, MTH, DATA) _CallFunction(_TESTER, test_api_action{}, DATA) -#define CALL_TEST_FUNCTION_SYSTEM(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_chain_action{}, DATA, {config::system_account_name} ) -#define CALL_TEST_FUNCTION_SCOPE(_TESTER, CLS, MTH, DATA, ACCOUNT) CallFunction(_TESTER, test_api_action{}, DATA, ACCOUNT) -#define CALL_TEST_FUNCTION_NO_THROW(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_api_action{}, DATA, {"testapi"_n}, true) -#define CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION(_TESTER, CLS, MTH, DATA, EXC, EXC_MESSAGE) \ -BOOST_CHECK_EXCEPTION( \ - CALL_TEST_FUNCTION( _TESTER, CLS, MTH, DATA), \ - EXC, \ - [](const EXC& e) { \ - return expect_assert_message(e, EXC_MESSAGE); \ - } \ -); - -bool is_access_violation(fc::unhandled_exception const & e) { - try { - std::rethrow_exception(e.get_inner_exception()); - } - catch (const eosio::chain::wasm_execution_error& e) { - return true; - } catch (...) { - - } - return false; -} - -bool is_assert_exception(fc::assert_exception const & e) { return true; } -bool is_page_memory_error(page_memory_error const &e) { return true; } -bool is_unsatisfied_authorization(unsatisfied_authorization const & e) { return true;} -bool is_wasm_execution_error(eosio::chain::wasm_execution_error const& e) {return true;} -bool is_tx_net_usage_exceeded(const tx_net_usage_exceeded& e) { return true; } -bool is_block_net_usage_exceeded(const block_net_usage_exceeded& e) { return true; } -bool is_tx_cpu_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; } -bool is_block_cpu_usage_exceeded(const block_cpu_usage_exceeded& e) { return true; } -bool is_deadline_exception(const deadline_exception& e) { return true; } - /* * Split the tests into multiple parts so that they can be finished within CICD time limit. * Register test suite `api_part1_tests` @@ -936,501 +781,6 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(light_validation_skip_cfa, T, testers) try { } FC_LOG_AND_RETHROW() -/************************************************************************************* - * checktime_tests test case - *************************************************************************************/ -BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_pass_tests, T, validating_testers) { try { - T chain; - - chain.produce_block(); - chain.create_account( "testapi"_n ); - chain.produce_block(); - chain.set_code( "testapi"_n, test_contracts::test_api_wasm() ); - chain.produce_block(); - - // test checktime_pass - CALL_TEST_FUNCTION( chain, "test_checktime", "checktime_pass", {}); - - BOOST_REQUIRE_EQUAL( chain.validate(), true ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_SUITE_END() - -BOOST_AUTO_TEST_SUITE(api_part2_tests) - -template -void push_trx(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, - bool explicit_bill, std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { - signed_transaction trx; - - action act; - act.account = ac.get_account(); - act.name = ac.get_name(); - if ( trx_type != transaction_metadata::trx_type::read_only ) { - auto pl = vector{{account, config::active_name}}; - act.authorization = pl; - } - act.data = payload; - - trx.actions.push_back(act); - test.set_transaction_headers(trx); - if ( trx_type != transaction_metadata::trx_type::read_only ) { - auto sigs = trx.sign(test.get_private_key(account, "active"), test.get_chain_id()); - } - flat_set keys; - trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); - auto ptrx = std::make_shared( std::move(trx) ); - - auto fut = transaction_metadata::start_recover_keys( std::move( ptrx ), test.control->get_thread_pool(), - test.get_chain_id(), fc::microseconds::maximum(), - trx_type ); - auto res = test.control->push_transaction( fut.get(), fc::time_point::now() + fc::milliseconds(max_block_cpu_ms), - fc::milliseconds(max_cpu_usage_ms), billed_cpu_time_us, explicit_bill, 0 ); - if( res->except_ptr ) std::rethrow_exception( res->except_ptr ); - if( res->except ) throw *res->except; -}; - -template -void call_test(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, - std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { - push_trx(test, ac, billed_cpu_time_us, max_cpu_usage_ms, max_block_cpu_ms, billed_cpu_time_us > 0, payload, account, trx_type); - test.produce_block(); -} - -BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_fail_tests, T, validating_testers ) { try { - T t; - t.produce_block(); - - ilog( "create account" ); - t.create_account( "testapi"_n ); - ilog( "set code" ); - t.set_code( "testapi"_n, test_contracts::test_api_wasm() ); - ilog( "produce block" ); - t.produce_block(); - - int64_t x; int64_t net; int64_t cpu; - t.control->get_resource_limits_manager().get_account_limits( "testapi"_n, x, net, cpu ); - wdump((net)(cpu)); - - BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, - 5000, 200, 200, fc::raw::pack(10000000000000000000ULL) ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, - 0, 200, 200, fc::raw::pack(10000000000000000000ULL) ), - tx_cpu_usage_exceeded, fc_exception_message_contains("reached on chain max_transaction_cpu_usage") ); - - BOOST_CHECK_EXCEPTION( push_trx( t, test_api_action{}, - 5000, 10, 200, false, fc::raw::pack(10000000000000000000ULL) ), - tx_cpu_usage_exceeded, fc_exception_message_contains("reached speculative executed adjusted trx max time") ); - - uint32_t time_left_in_block_us = config::default_max_block_cpu_usage - config::default_min_transaction_cpu_usage; - std::string dummy_string = "nonce"; - uint32_t increment = config::default_max_transaction_cpu_usage / 3; - for( auto i = 0; time_left_in_block_us > 2*increment; ++i ) { - t.push_dummy( "testapi"_n, dummy_string + std::to_string(i), increment ); - time_left_in_block_us -= increment; - } - BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, - 0, 200, 200, fc::raw::pack(10000000000000000000ULL) ), - block_cpu_usage_exceeded, is_block_cpu_usage_exceeded ); - - BOOST_REQUIRE_EQUAL( t.validate(), true ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_max_trx_cpu_extended_test, T, testers ) { try { - fc::temp_directory tempdir; - auto conf_genesis = tester::default_config( tempdir ); - auto& cfg = conf_genesis.second.initial_configuration; - - cfg.max_block_cpu_usage = 150'000; - cfg.max_transaction_cpu_usage = 24'999; // needs to be large enough for create_account and set_code - cfg.min_transaction_cpu_usage = 1; - - T t( conf_genesis.first, conf_genesis.second ); - if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { - // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. - // A completely different test with different constraints would be needed to test with eos_vm_oc. - // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. - return; - } - t.execute_setup_policy( setup_policy::full ); - t.produce_block(); - t.create_account( "pause"_n ); - t.set_code( "pause"_n, test_contracts::test_api_wasm() ); - t.produce_block(); - - int64_t ram_bytes; int64_t net; int64_t cpu; - auto& rl = t.control->get_resource_limits_manager(); - rl.get_account_limits( "pause"_n, ram_bytes, net, cpu ); - BOOST_CHECK_EQUAL( cpu, -1 ); - auto cpu_limit = rl.get_block_cpu_limit(); - idump(("cpu_limit")(cpu_limit)); - BOOST_CHECK( cpu_limit <= 150'000 ); - - // Test deadline is extended when max_transaction_cpu_time is the limiting factor - - BOOST_TEST( !t.is_code_cached("pause"_n) ); - - // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. - // Verify that the restriction on the transaction of 24'999 is honored even though there is wall clock time to - // load the wasm. If this test fails it is possible that the wasm loaded faster or slower than expected. - auto before = fc::time_point::now(); - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 9999, 500, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - tx_cpu_usage_exceeded, fc_exception_message_contains("reached on chain max_transaction_cpu_usage") ); - auto after = fc::time_point::now(); - // Test that it runs longer than specified limit of 24'999 to allow for wasm load time. - auto dur = (after - before).count(); - dlog("elapsed ${e}us", ("e", dur) ); - BOOST_CHECK( dur >= 24'999 ); // should never fail - BOOST_TEST( t.is_code_cached("pause"_n) ); - // This assumes that loading the WASM takes at least 0.750 ms - // If this check fails but duration is >= 24'999 (previous check did not fail), then the check here is likely - // because WASM took less than 0.750 ms to load. - BOOST_CHECK_MESSAGE( dur > 25'750, "elapsed " << dur << "us" ); - BOOST_CHECK_MESSAGE( dur < 150'000, "elapsed " << dur << "us" ); // Should not run to block_cpu_usage deadline - - // Test hitting max_transaction_time throws tx_cpu_usage_exceeded - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 5, 50, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-transaction-time") ); - - // Test hitting block deadline throws deadline_exception - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 50, 5, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - deadline_exception, is_deadline_exception ); - - BOOST_REQUIRE_EQUAL( t.validate(), true ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_max_trx_extended_test, T, testers ) { try { - fc::temp_directory tempdir; - auto conf_genesis = tester::default_config( tempdir ); - auto& cfg = conf_genesis.second.initial_configuration; - - cfg.max_block_cpu_usage = 350'000; - cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code - cfg.min_transaction_cpu_usage = 1; - - T t( conf_genesis.first, conf_genesis.second ); - if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { - // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. - // A completely different test with different constraints would be needed to test with eos_vm_oc. - // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. - return; - } - t.execute_setup_policy( setup_policy::full ); - t.produce_block(); - t.create_account( "pause"_n ); - t.set_code( "pause"_n, test_contracts::test_api_wasm() ); - t.produce_block(); - - // Test deadline is extended when max_transaction_time is the limiting factor - - BOOST_TEST( !t.is_code_cached("pause"_n) ); - - // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. - // Verify that the restriction on the max_transaction_time of 25ms is honored even though there is wall clock time to - // load the wasm. If this test fails it is possible that the wasm loaded faster or slower than expected. - auto before = fc::time_point::now(); - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 25, 500, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-transaction-time") ); - auto after = fc::time_point::now(); - // Test that it runs longer than specified limit of 24'999 to allow for wasm load time. - auto dur = (after - before).count(); - dlog("elapsed ${e}us", ("e", dur) ); - BOOST_CHECK( dur >= 25'000 ); // should never fail - BOOST_TEST( t.is_code_cached("pause"_n) ); - // This assumes that loading the WASM takes at least 0.750 ms - // If this check fails but duration is >= 25'000 (previous check did not fail), then the check here is likely - // because WASM took less than 0.750 ms to load. - BOOST_CHECK_MESSAGE( dur > 25'750, "elapsed " << dur << "us" ); - BOOST_CHECK_MESSAGE( dur < 250'000, "elapsed " << dur << "us" ); // Should not run to max_transaction_cpu_usage deadline - - BOOST_REQUIRE_EQUAL( t.validate(), true ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_block_deadline_not_extended_test, T, testers ) { try { - fc::temp_directory tempdir; - auto conf_genesis = tester::default_config( tempdir ); - auto& cfg = conf_genesis.second.initial_configuration; - - cfg.max_block_cpu_usage = 350'000; - cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code - cfg.min_transaction_cpu_usage = 1; - - T t( conf_genesis.first, conf_genesis.second ); - if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { - // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. - // A completely different test with different constraints would be needed to test with eos_vm_oc. - // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. - return; - } - t.execute_setup_policy( setup_policy::full ); - t.produce_block(); - t.create_account( "pause"_n ); - t.set_code( "pause"_n, test_contracts::test_api_wasm() ); - t.produce_block(); - - // Test block deadline is not extended when it is the limiting factor - // Specify large enough time so that WASM is completely loaded. - - BOOST_TEST( !t.is_code_cached("pause"_n) ); - - // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. - auto before = fc::time_point::now(); - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 150, 75, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - deadline_exception, is_deadline_exception ); - auto after = fc::time_point::now(); - // WASM load times on my machine are around 35ms - auto dur = (after - before).count(); - dlog("elapsed ${e}us", ("e", dur) ); - BOOST_CHECK( dur >= 75'000 ); // should never fail - BOOST_TEST( t.is_code_cached("pause"_n) ); - - // If this check fails but duration is >= 75'000 (previous check did not fail), then the check here is likely - // because it took longer than 50 ms for checktime to trigger, trace to be created, and to get to the now() call. - BOOST_CHECK_MESSAGE( dur < 125'000, "elapsed " << dur << "us" ); - - BOOST_REQUIRE_EQUAL( t.validate(), true ); -} FC_LOG_AND_RETHROW() } - - -BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_block_deadline_not_extended_while_loading_test, T, testers ) { try { - fc::temp_directory tempdir; - auto conf_genesis = tester::default_config( tempdir ); - auto& cfg = conf_genesis.second.initial_configuration; - - cfg.max_block_cpu_usage = 350'000; - cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code - cfg.min_transaction_cpu_usage = 1; - - T t( conf_genesis.first, conf_genesis.second ); - if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { - // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. - // A completely different test with different constraints would be needed to test with eos_vm_oc. - // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. - return; - } - t.execute_setup_policy( setup_policy::full ); - t.produce_block(); - t.create_account( "pause"_n ); - t.set_code( "pause"_n, test_contracts::test_api_wasm() ); - t.produce_block(); - - // Test block deadline is not extended when it is the limiting factor - // This test is different from the previous in that not enough time is provided to load the WASM. - // The block deadline will kick in once the timer is unpaused after loading the WASM. - // This is difficult to determine as checktime is not checked until WASM has completed loading. - // We want to test that blocktime is enforced immediately after timer is unpaused. - - BOOST_TEST( !t.is_code_cached("pause"_n) ); - - // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. - auto before = fc::time_point::now(); - BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, - 0, 150, 15, fc::raw::pack(10000000000000000000ULL), "pause"_n ), - deadline_exception, is_deadline_exception ); - auto after = fc::time_point::now(); - // Test that it runs longer than specified limit of 15ms to allow for wasm load time. - // WASM load times on my machine are around 35ms - auto dur = (after - before).count(); - dlog("elapsed ${e}us", ("e", dur) ); - BOOST_CHECK( dur >= 15'000 ); // should never fail - BOOST_TEST( t.is_code_cached("pause"_n) ); - - // WASM load times on my machine was 35ms. - // Since checktime only kicks in after WASM is loaded this needs to be large enough to load the WASM, but should be - // lower than the 150ms max_transaction_time - BOOST_CHECK_MESSAGE( dur < 125'000, "elapsed " << dur << "us" ); - BOOST_REQUIRE_MESSAGE( dur < 150'000, "elapsed " << dur << "us" ); // should never fail - - BOOST_REQUIRE_EQUAL( t.validate(), true ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_intrinsic, T, validating_testers) { try { - T chain; - - chain.produce_block(); - chain.create_account("testapi"_n); - chain.produce_block(); - - std::stringstream ss; - ss << R"CONTRACT( -(module - (type $FUNCSIG$vij (func (param i32 i64))) - (type $FUNCSIG$j (func (result i64))) - (type $FUNCSIG$vjj (func (param i64 i64))) - (type $FUNCSIG$vii (func (param i32 i32))) - (type $FUNCSIG$i (func (result i32))) - (type $FUNCSIG$iii (func (param i32 i32) (result i32))) - (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) - (type $FUNCSIG$vi (func (param i32))) - (type $FUNCSIG$v (func )) - (type $_1 (func (param i64 i64 i64))) - (export "apply" (func $apply)) - (import "env" "memmove" (func $memmove (param i32 i32 i32) (result i32))) - (import "env" "printui" (func $printui (param i64))) - (memory $0 1) - - (func $apply (type $_1) - (param $0 i64) - (param $1 i64) - (param $2 i64) - (drop (grow_memory (i32.const 527))) - - (call $printui (i64.const 11)) -)CONTRACT"; - - for(unsigned int i = 0; i < 5000; ++i) { - ss << R"CONTRACT( -(drop (call $memmove - (i32.const 1) - (i32.const 9) - (i32.const 33554432) - )) - -)CONTRACT"; - } - ss<< "))"; - chain.set_code( "testapi"_n, ss.str().c_str() ); - chain.produce_block(); - - BOOST_TEST( !chain.is_code_cached("testapi"_n) ); - - //initialize cache - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 10, 10 ), - deadline_exception, is_deadline_exception ); - - BOOST_TEST( chain.is_code_cached("testapi"_n) ); - - //it will always call - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 10, 10 ), - deadline_exception, is_deadline_exception ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_grow_memory, T, validating_testers) { try { - T chain; - - chain.produce_block(); - chain.create_account("testapi"_n); - chain.produce_block(); - - std::stringstream ss; - ss << R"CONTRACT( -(module - (memory 1) - - (func (export "apply") (param i64 i64 i64) -)CONTRACT"; - - for(unsigned int i = 0; i < 5000; ++i) { - ss << R"CONTRACT( - (drop (grow_memory (i32.const 527))) - (drop (grow_memory (i32.const -527))) - -)CONTRACT"; - } - ss<< "))"; - chain.set_code( "testapi"_n, ss.str().c_str() ); - chain.produce_block(); - - BOOST_TEST( !chain.is_code_cached("testapi"_n) ); - - //initialize cache - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 10, 10 ), - deadline_exception, is_deadline_exception ); - - BOOST_TEST( chain.is_code_cached("testapi"_n) ); - - //it will always call - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 10, 10 ), - deadline_exception, is_deadline_exception ); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_hashing_fail, T, validating_testers) { try { - T chain; - - chain.produce_block(); - chain.create_account( "testapi"_n ); - chain.produce_block(); - chain.set_code( "testapi"_n, test_contracts::test_api_wasm() ); - chain.produce_block(); - - BOOST_TEST( !chain.is_code_cached("testapi"_n) ); - - //hit deadline exception, but cache the contract - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 8, 8 ), - deadline_exception, is_deadline_exception ); - - BOOST_TEST( chain.is_code_cached("testapi"_n) ); - - //the contract should be cached, now we should get deadline_exception because of calls to checktime() from hashing function - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); - - BOOST_REQUIRE_EQUAL( chain.validate(), true ); -} FC_LOG_AND_RETHROW() } - - -BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_start, T, validating_testers) try { - T chain; - - const char checktime_start_wast[] = R"=====( -(module - (func $start (loop (br 0))) - (func (export "apply") (param i64 i64 i64)) - (start $start) -) -)====="; - chain.produce_block(); - chain.create_account( "testapi"_n ); - chain.produce_block(); - chain.set_code( "testapi"_n, checktime_start_wast ); - chain.produce_block(); - - BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, - 5000, 3, 3 ), - deadline_exception, is_deadline_exception ); -} FC_LOG_AND_RETHROW() - /************************************************************************************* * compiler_builtins_tests test case *************************************************************************************/ @@ -1773,7 +1123,7 @@ BOOST_AUTO_TEST_CASE(deferred_inline_action_limit) { try { BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE(api_part3_tests) +BOOST_AUTO_TEST_SUITE(api_part2_tests) BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, validating_tester_no_disable_deferred_trx) { try { produce_block(); @@ -3036,7 +2386,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(resource_limits_tests, T, validating_testers) { BOOST_AUTO_TEST_SUITE_END() -BOOST_AUTO_TEST_SUITE(api_part4_tests) +BOOST_AUTO_TEST_SUITE(api_part3_tests) BOOST_AUTO_TEST_CASE( set_producers_legacy ) { try { fc::temp_directory tempdir; diff --git a/unittests/checktime_tests.cpp b/unittests/checktime_tests.cpp new file mode 100644 index 0000000000..37da24b8d7 --- /dev/null +++ b/unittests/checktime_tests.cpp @@ -0,0 +1,519 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-compare" +#include +#pragma GCC diagnostic pop + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain::literals; +using namespace eosio::testing; +using namespace eosio::test_utils; +using namespace fc; + +BOOST_AUTO_TEST_SUITE(checktime_tests) + +/************************************************************************************* + * checktime_tests test case + *************************************************************************************/ +BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_pass_tests, T, validating_testers) { try { + T chain; + + chain.produce_block(); + chain.create_account( "testapi"_n ); + chain.produce_block(); + chain.set_code( "testapi"_n, test_contracts::test_api_wasm() ); + chain.produce_block(); + + // test checktime_pass + CALL_TEST_FUNCTION( chain, "test_checktime", "checktime_pass", {}); + + BOOST_REQUIRE_EQUAL( chain.validate(), true ); +} FC_LOG_AND_RETHROW() } + +template +void push_trx(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, + bool explicit_bill, std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { + signed_transaction trx; + + action act; + act.account = ac.get_account(); + act.name = ac.get_name(); + if ( trx_type != transaction_metadata::trx_type::read_only ) { + auto pl = vector{{account, config::active_name}}; + act.authorization = pl; + } + act.data = payload; + + trx.actions.push_back(act); + test.set_transaction_headers(trx); + if ( trx_type != transaction_metadata::trx_type::read_only ) { + auto sigs = trx.sign(test.get_private_key(account, "active"), test.get_chain_id()); + } + flat_set keys; + trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); + auto ptrx = std::make_shared( std::move(trx) ); + + auto fut = transaction_metadata::start_recover_keys( std::move( ptrx ), test.control->get_thread_pool(), + test.get_chain_id(), fc::microseconds::maximum(), + trx_type ); + auto res = test.control->push_transaction( fut.get(), fc::time_point::now() + fc::milliseconds(max_block_cpu_ms), + fc::milliseconds(max_cpu_usage_ms), billed_cpu_time_us, explicit_bill, 0 ); + if( res->except_ptr ) std::rethrow_exception( res->except_ptr ); + if( res->except ) throw *res->except; +}; + +template +void call_test(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, + std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { + push_trx(test, ac, billed_cpu_time_us, max_cpu_usage_ms, max_block_cpu_ms, billed_cpu_time_us > 0, payload, account, trx_type); + test.produce_block(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_fail_tests, T, validating_testers ) { try { + T t; + t.produce_block(); + + ilog( "create account" ); + t.create_account( "testapi"_n ); + ilog( "set code" ); + t.set_code( "testapi"_n, test_contracts::test_api_wasm() ); + ilog( "produce block" ); + t.produce_block(); + + int64_t x; int64_t net; int64_t cpu; + t.control->get_resource_limits_manager().get_account_limits( "testapi"_n, x, net, cpu ); + wdump((net)(cpu)); + + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 5000, 200, 200, fc::raw::pack(10000000000000000000ULL) ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 0, 200, 200, fc::raw::pack(10000000000000000000ULL) ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached on chain max_transaction_cpu_usage") ); + + BOOST_CHECK_EXCEPTION( push_trx( t, test_api_action{}, + 5000, 10, 200, false, fc::raw::pack(10000000000000000000ULL) ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached speculative executed adjusted trx max time") ); + + uint32_t time_left_in_block_us = config::default_max_block_cpu_usage - config::default_min_transaction_cpu_usage; + std::string dummy_string = "nonce"; + uint32_t increment = config::default_max_transaction_cpu_usage / 3; + for( auto i = 0; time_left_in_block_us > 2*increment; ++i ) { + t.push_dummy( "testapi"_n, dummy_string + std::to_string(i), increment ); + time_left_in_block_us -= increment; + } + BOOST_CHECK_EXCEPTION( call_test( t, test_api_action{}, + 0, 200, 200, fc::raw::pack(10000000000000000000ULL) ), + block_cpu_usage_exceeded, is_block_cpu_usage_exceeded ); + + BOOST_REQUIRE_EQUAL( t.validate(), true ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_max_trx_cpu_extended_test, T, testers ) { try { + fc::temp_directory tempdir; + auto conf_genesis = tester::default_config( tempdir ); + auto& cfg = conf_genesis.second.initial_configuration; + + cfg.max_block_cpu_usage = 150'000; + cfg.max_transaction_cpu_usage = 24'999; // needs to be large enough for create_account and set_code + cfg.min_transaction_cpu_usage = 1; + + T t( conf_genesis.first, conf_genesis.second ); + if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { + // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. + // A completely different test with different constraints would be needed to test with eos_vm_oc. + // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. + return; + } + t.execute_setup_policy( setup_policy::full ); + t.produce_block(); + t.create_account( "pause"_n ); + t.set_code( "pause"_n, test_contracts::test_api_wasm() ); + t.produce_block(); + + int64_t ram_bytes; int64_t net; int64_t cpu; + auto& rl = t.control->get_resource_limits_manager(); + rl.get_account_limits( "pause"_n, ram_bytes, net, cpu ); + BOOST_CHECK_EQUAL( cpu, -1 ); + auto cpu_limit = rl.get_block_cpu_limit(); + idump(("cpu_limit")(cpu_limit)); + BOOST_CHECK( cpu_limit <= 150'000 ); + + // Test deadline is extended when max_transaction_cpu_time is the limiting factor + + BOOST_TEST( !t.is_code_cached("pause"_n) ); + + // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. + // Verify that the restriction on the transaction of 24'999 is honored even though there is wall clock time to + // load the wasm. If this test fails it is possible that the wasm loaded faster or slower than expected. + auto before = fc::time_point::now(); + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 9999, 500, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached on chain max_transaction_cpu_usage") ); + auto after = fc::time_point::now(); + // Test that it runs longer than specified limit of 24'999 to allow for wasm load time. + auto dur = (after - before).count(); + dlog("elapsed ${e}us", ("e", dur) ); + BOOST_CHECK( dur >= 24'999 ); // should never fail + BOOST_TEST( t.is_code_cached("pause"_n) ); + // This assumes that loading the WASM takes at least 0.750 ms + // If this check fails but duration is >= 24'999 (previous check did not fail), then the check here is likely + // because WASM took less than 0.750 ms to load. + BOOST_CHECK_MESSAGE( dur > 25'750, "elapsed " << dur << "us" ); + BOOST_CHECK_MESSAGE( dur < 150'000, "elapsed " << dur << "us" ); // Should not run to block_cpu_usage deadline + + // Test hitting max_transaction_time throws tx_cpu_usage_exceeded + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 5, 50, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-transaction-time") ); + + // Test hitting block deadline throws deadline_exception + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 50, 5, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + deadline_exception, is_deadline_exception ); + + BOOST_REQUIRE_EQUAL( t.validate(), true ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_max_trx_extended_test, T, testers ) { try { + fc::temp_directory tempdir; + auto conf_genesis = tester::default_config( tempdir ); + auto& cfg = conf_genesis.second.initial_configuration; + + cfg.max_block_cpu_usage = 350'000; + cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code + cfg.min_transaction_cpu_usage = 1; + + T t( conf_genesis.first, conf_genesis.second ); + if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { + // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. + // A completely different test with different constraints would be needed to test with eos_vm_oc. + // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. + return; + } + t.execute_setup_policy( setup_policy::full ); + t.produce_block(); + t.create_account( "pause"_n ); + t.set_code( "pause"_n, test_contracts::test_api_wasm() ); + t.produce_block(); + + // Test deadline is extended when max_transaction_time is the limiting factor + + BOOST_TEST( !t.is_code_cached("pause"_n) ); + + // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. + // Verify that the restriction on the max_transaction_time of 25ms is honored even though there is wall clock time to + // load the wasm. If this test fails it is possible that the wasm loaded faster or slower than expected. + auto before = fc::time_point::now(); + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 25, 500, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-transaction-time") ); + auto after = fc::time_point::now(); + // Test that it runs longer than specified limit of 24'999 to allow for wasm load time. + auto dur = (after - before).count(); + dlog("elapsed ${e}us", ("e", dur) ); + BOOST_CHECK( dur >= 25'000 ); // should never fail + BOOST_TEST( t.is_code_cached("pause"_n) ); + // This assumes that loading the WASM takes at least 0.750 ms + // If this check fails but duration is >= 25'000 (previous check did not fail), then the check here is likely + // because WASM took less than 0.750 ms to load. + BOOST_CHECK_MESSAGE( dur > 25'750, "elapsed " << dur << "us" ); + BOOST_CHECK_MESSAGE( dur < 250'000, "elapsed " << dur << "us" ); // Should not run to max_transaction_cpu_usage deadline + + BOOST_REQUIRE_EQUAL( t.validate(), true ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_block_deadline_not_extended_test, T, testers ) { try { + fc::temp_directory tempdir; + auto conf_genesis = tester::default_config( tempdir ); + auto& cfg = conf_genesis.second.initial_configuration; + + cfg.max_block_cpu_usage = 350'000; + cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code + cfg.min_transaction_cpu_usage = 1; + + T t( conf_genesis.first, conf_genesis.second ); + if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { + // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. + // A completely different test with different constraints would be needed to test with eos_vm_oc. + // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. + return; + } + t.execute_setup_policy( setup_policy::full ); + t.produce_block(); + t.create_account( "pause"_n ); + t.set_code( "pause"_n, test_contracts::test_api_wasm() ); + t.produce_block(); + + // Test block deadline is not extended when it is the limiting factor + // Specify large enough time so that WASM is completely loaded. + + BOOST_TEST( !t.is_code_cached("pause"_n) ); + + // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. + auto before = fc::time_point::now(); + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 150, 75, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + deadline_exception, is_deadline_exception ); + auto after = fc::time_point::now(); + // WASM load times on my machine are around 35ms + auto dur = (after - before).count(); + dlog("elapsed ${e}us", ("e", dur) ); + BOOST_CHECK( dur >= 75'000 ); // should never fail + BOOST_TEST( t.is_code_cached("pause"_n) ); + + // If this check fails but duration is >= 75'000 (previous check did not fail), then the check here is likely + // because it took longer than 50 ms for checktime to trigger, trace to be created, and to get to the now() call. + BOOST_CHECK_MESSAGE( dur < 125'000, "elapsed " << dur << "us" ); + + BOOST_REQUIRE_EQUAL( t.validate(), true ); +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE_TEMPLATE( checktime_pause_block_deadline_not_extended_while_loading_test, T, testers ) { try { + fc::temp_directory tempdir; + auto conf_genesis = tester::default_config( tempdir ); + auto& cfg = conf_genesis.second.initial_configuration; + + cfg.max_block_cpu_usage = 350'000; + cfg.max_transaction_cpu_usage = 250'000; // needs to be large enough for create_account and set_code + cfg.min_transaction_cpu_usage = 1; + + T t( conf_genesis.first, conf_genesis.second ); + if( t.get_config().wasm_runtime == wasm_interface::vm_type::eos_vm_oc ) { + // eos_vm_oc wasm_runtime does not tier-up and completes compile before continuing execution. + // A completely different test with different constraints would be needed to test with eos_vm_oc. + // Since non-tier-up is not a normal valid nodeos runtime, just skip this test for eos_vm_oc. + return; + } + t.execute_setup_policy( setup_policy::full ); + t.produce_block(); + t.create_account( "pause"_n ); + t.set_code( "pause"_n, test_contracts::test_api_wasm() ); + t.produce_block(); + + // Test block deadline is not extended when it is the limiting factor + // This test is different from the previous in that not enough time is provided to load the WASM. + // The block deadline will kick in once the timer is unpaused after loading the WASM. + // This is difficult to determine as checktime is not checked until WASM has completed loading. + // We want to test that blocktime is enforced immediately after timer is unpaused. + + BOOST_TEST( !t.is_code_cached("pause"_n) ); + + // First call to contract which should cause the WASM to load and trx_context.pause_billing_timer() to be called. + auto before = fc::time_point::now(); + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 150, 15, fc::raw::pack(10000000000000000000ULL), "pause"_n ), + deadline_exception, is_deadline_exception ); + auto after = fc::time_point::now(); + // Test that it runs longer than specified limit of 15ms to allow for wasm load time. + // WASM load times on my machine are around 35ms + auto dur = (after - before).count(); + dlog("elapsed ${e}us", ("e", dur) ); + BOOST_CHECK( dur >= 15'000 ); // should never fail + BOOST_TEST( t.is_code_cached("pause"_n) ); + + // WASM load times on my machine was 35ms. + // Since checktime only kicks in after WASM is loaded this needs to be large enough to load the WASM, but should be + // lower than the 150ms max_transaction_time + BOOST_CHECK_MESSAGE( dur < 125'000, "elapsed " << dur << "us" ); + BOOST_REQUIRE_MESSAGE( dur < 150'000, "elapsed " << dur << "us" ); // should never fail + + BOOST_REQUIRE_EQUAL( t.validate(), true ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_intrinsic, T, validating_testers) { try { + T chain; + + chain.produce_block(); + chain.create_account("testapi"_n); + chain.produce_block(); + + std::stringstream ss; + ss << R"CONTRACT( +(module + (type $FUNCSIG$vij (func (param i32 i64))) + (type $FUNCSIG$j (func (result i64))) + (type $FUNCSIG$vjj (func (param i64 i64))) + (type $FUNCSIG$vii (func (param i32 i32))) + (type $FUNCSIG$i (func (result i32))) + (type $FUNCSIG$iii (func (param i32 i32) (result i32))) + (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$v (func )) + (type $_1 (func (param i64 i64 i64))) + (export "apply" (func $apply)) + (import "env" "memmove" (func $memmove (param i32 i32 i32) (result i32))) + (import "env" "printui" (func $printui (param i64))) + (memory $0 1) + + (func $apply (type $_1) + (param $0 i64) + (param $1 i64) + (param $2 i64) + (drop (grow_memory (i32.const 527))) + + (call $printui (i64.const 11)) +)CONTRACT"; + + for(unsigned int i = 0; i < 5000; ++i) { + ss << R"CONTRACT( +(drop (call $memmove + (i32.const 1) + (i32.const 9) + (i32.const 33554432) + )) + +)CONTRACT"; + } + ss<< "))"; + chain.set_code( "testapi"_n, ss.str().c_str() ); + chain.produce_block(); + + BOOST_TEST( !chain.is_code_cached("testapi"_n) ); + + //initialize cache + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 10, 10 ), + deadline_exception, is_deadline_exception ); + + BOOST_TEST( chain.is_code_cached("testapi"_n) ); + + //it will always call + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 10, 10 ), + deadline_exception, is_deadline_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_grow_memory, T, validating_testers) { try { + T chain; + + chain.produce_block(); + chain.create_account("testapi"_n); + chain.produce_block(); + + std::stringstream ss; + ss << R"CONTRACT( +(module + (memory 1) + + (func (export "apply") (param i64 i64 i64) +)CONTRACT"; + + for(unsigned int i = 0; i < 5000; ++i) { + ss << R"CONTRACT( + (drop (grow_memory (i32.const 527))) + (drop (grow_memory (i32.const -527))) + +)CONTRACT"; + } + ss<< "))"; + chain.set_code( "testapi"_n, ss.str().c_str() ); + chain.produce_block(); + + BOOST_TEST( !chain.is_code_cached("testapi"_n) ); + + //initialize cache + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 10, 10 ), + deadline_exception, is_deadline_exception ); + + BOOST_TEST( chain.is_code_cached("testapi"_n) ); + + //it will always call + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 10, 10 ), + deadline_exception, is_deadline_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_hashing_fail, T, validating_testers) { try { + T chain; + + chain.produce_block(); + chain.create_account( "testapi"_n ); + chain.produce_block(); + chain.set_code( "testapi"_n, test_contracts::test_api_wasm() ); + chain.produce_block(); + + BOOST_TEST( !chain.is_code_cached("testapi"_n) ); + + //hit deadline exception, but cache the contract + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 8, 8 ), + deadline_exception, is_deadline_exception ); + + BOOST_TEST( chain.is_code_cached("testapi"_n) ); + + //the contract should be cached, now we should get deadline_exception because of calls to checktime() from hashing function + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); + + BOOST_REQUIRE_EQUAL( chain.validate(), true ); +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE_TEMPLATE(checktime_start, T, validating_testers) try { + T chain; + + const char checktime_start_wast[] = R"=====( +(module + (func $start (loop (br 0))) + (func (export "apply") (param i64 i64 i64)) + (start $start) +) +)====="; + chain.produce_block(); + chain.create_account( "testapi"_n ); + chain.produce_block(); + chain.set_code( "testapi"_n, checktime_start_wast ); + chain.produce_block(); + + BOOST_CHECK_EXCEPTION( call_test( chain, test_api_action{}, + 5000, 3, 3 ), + deadline_exception, is_deadline_exception ); +} FC_LOG_AND_RETHROW() + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/test_utils.hpp b/unittests/test_utils.hpp index 0998f25dbc..1f218b2c9e 100644 --- a/unittests/test_utils.hpp +++ b/unittests/test_utils.hpp @@ -1,188 +1,159 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include -#include namespace eosio::test_utils { using namespace eosio::chain; -using namespace eosio::chain::literals; -struct testit { - uint64_t id; - explicit testit(uint64_t id = 0) - :id(id){} +inline bool is_access_violation(const fc::unhandled_exception& e) { + try { + std::rethrow_exception(e.get_inner_exception()); + } + catch (const wasm_execution_error& e) { + return true; + } catch (...) { + + } + return false; +} + +inline bool is_assert_exception(const fc::assert_exception& e) { return true; } +inline bool is_page_memory_error(const page_memory_error& e) { return true; } +inline bool is_unsatisfied_authorization(const unsatisfied_authorization& e) { return true;} +inline bool is_wasm_execution_error(const wasm_execution_error& e) {return true;} +inline bool is_tx_net_usage_exceeded(const tx_net_usage_exceeded& e) { return true; } +inline bool is_block_net_usage_exceeded(const block_net_usage_exceeded& e) { return true; } +inline bool is_tx_cpu_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; } +inline bool is_block_cpu_usage_exceeded(const block_cpu_usage_exceeded& e) { return true; } +inline bool is_deadline_exception(const deadline_exception& e) { return true; } + +template +struct test_api_action { static account_name get_account() { - return chain::config::system_account_name; + return "testapi"_n; } + static action_name get_name() { - return "testit"_n; + return action_name(NAME); } }; -// Corresponds to the reqactivated action of the bios contract. -// See libraries/testing/contracts/eosio.bios/eosio.bios.hpp -struct reqactivated { - chain::digest_type feature_digest; - - explicit reqactivated(const chain::digest_type& fd) - :feature_digest(fd){}; +template +struct test_pause_action { static account_name get_account() { - return chain::config::system_account_name; + return "pause"_n; } + static action_name get_name() { - return "reqactivated"_n; + return action_name(NAME); } }; -// Create a read-only trx that works with bios reqactivated action -auto make_bios_ro_trx(eosio::chain::controller& control) { - const auto& pfm = control.get_protocol_feature_manager(); - static auto feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::replace_deferred); - signed_transaction trx; - trx.expiration = fc::time_point_sec{fc::time_point::now() + fc::seconds(30)}; - vector no_auth{}; - trx.actions.emplace_back( no_auth, reqactivated{*feature_digest} ); - return std::make_shared( std::move(trx) ); -} +template +struct test_chain_action { + static account_name get_account() { + return account_name(config::system_account_name); + } -// Push an input transaction to controller and return trx trace -// If account is eosio then signs with the default private key -auto push_input_trx(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, signed_transaction& trx) { - trx.expiration = fc::time_point_sec{fc::time_point::now() + fc::seconds(30)}; - trx.set_reference_block( control.head().id() ); - if (account == config::system_account_name) { - auto default_priv_key = private_key_type::regenerate(fc::sha256::hash(std::string("nathan"))); - trx.sign(default_priv_key, control.get_chain_id()); - } else { - trx.sign(testing::tester::get_private_key(account, "active"), control.get_chain_id()); + static action_name get_name() { + return action_name(NAME); } - auto ptrx = std::make_shared( trx, packed_transaction::compression_type::zlib ); - - auto trx_promise = std::make_shared>(); - std::future trx_future = trx_promise->get_future(); - - app->executor().post( priority::low, exec_queue::read_write, [&ptrx, &app, trx_promise]() { - app->get_method()(ptrx, - false, // api_trx - transaction_metadata::trx_type::input, // trx_type - true, // return_failure_traces - [trx_promise](const next_function_variant& result) { - if( std::holds_alternative( result ) ) { - try { - std::get(result)->dynamic_rethrow_exception(); - } catch(...) { - trx_promise->set_exception(std::current_exception()); - } - } else if ( std::get( result )->except ) { - try { - std::get(result)->except->dynamic_rethrow_exception(); - } catch(...) { - trx_promise->set_exception(std::current_exception()); - } - } else { - trx_promise->set_value(std::get(result)); - } - }); - }); - - if (trx_future.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) - throw std::runtime_error("failed to execute trx: " + ptrx->get_transaction().actions.at(0).name.to_string() + " to account: " + account.to_string()); - - return trx_future.get(); +}; + +static constexpr unsigned int DJBH(const char* cp) { + unsigned int hash = 5381; + while (*cp) + hash = 33 * hash ^ (unsigned char) *cp++; + return hash; +} + +static constexpr unsigned long long WASM_TEST_ACTION(const char* cls, const char* method) { + return static_cast(DJBH(cls)) << 32 | static_cast(DJBH(method)); } -// Push setcode trx to controller and return trx trace -auto set_code(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, const vector& wasm) { +template +transaction_trace_ptr CallAction(testing::validating_tester& test, T ac, const vector& scope = {"testapi"_n}) { signed_transaction trx; - trx.actions.emplace_back(std::vector{{account, config::active_name}}, - chain::setcode{ - .account = account, - .vmtype = 0, - .vmversion = 0, - .code = bytes(wasm.begin(), wasm.end()) - }); - return push_input_trx(app, control, account, trx); + + + auto pl = vector{{scope[0], config::active_name}}; + if (scope.size() > 1) + for (size_t i = 1; i < scope.size(); i++) + pl.push_back({scope[i], config::active_name}); + + action act(pl, ac); + trx.actions.push_back(act); + + test.set_transaction_headers(trx); + auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.get_chain_id()); + flat_set keys; + trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); + auto res = test.push_transaction(trx); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + test.produce_block(); + return res; } -void activate_protocol_features_set_bios_contract(appbase::scoped_app& app, chain_plugin* chain_plug) { - using namespace appbase; - - auto feature_set = std::make_shared>(false); - // has to execute when pending block is not null - for (int tries = 0; tries < 100; ++tries) { - app->executor().post( priority::high, exec_queue::read_write, [&chain_plug=chain_plug, feature_set](){ - try { - if (!chain_plug->chain().is_building_block() || *feature_set) - return; - const auto& pfm = chain_plug->chain().get_protocol_feature_manager(); - auto preactivate_feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::preactivate_feature); - BOOST_CHECK( preactivate_feature_digest ); - chain_plug->chain().preactivate_feature( *preactivate_feature_digest, false ); - - vector feature_digests; - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::only_link_to_existing_permission)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::replace_deferred)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::no_duplicate_deferred_id)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::fix_linkauth_restriction)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disallow_empty_producer_schedule)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::restrict_action_to_self)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::only_bill_first_authorizer)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::forward_setcode)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_sender)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::ram_restrictions)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::webauthn_key)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::wtmsig_block_signatures)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::action_return_value)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::configurable_wasm_limits)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::blockchain_parameters)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_code_hash)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::crypto_primitives)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::get_block_num)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::bls_primitives)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disable_deferred_trxs_stage_1)); - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::disable_deferred_trxs_stage_2)); - // savanna - feature_digests.push_back(*pfm.get_builtin_digest(builtin_protocol_feature_t::savanna)); - - for (const auto feature_digest : feature_digests) { - chain_plug->chain().preactivate_feature( feature_digest, false ); - } - *feature_set = true; - return; - } FC_LOG_AND_DROP() - BOOST_CHECK(!"exception setting protocol features"); - }); - if (*feature_set) - break; - std::this_thread::sleep_for(std::chrono::milliseconds(50)); +template +std::pair _CallFunction(Tester& test, T ac, const vector& data, const vector& scope = {"testapi"_n}, bool no_throw = false) { + { + signed_transaction trx; + + auto pl = vector{{scope[0], config::active_name}}; + if (scope.size() > 1) + for (unsigned int i = 1; i < scope.size(); i++) + pl.push_back({scope[i], config::active_name}); + + action act(pl, ac); + act.data = data; + act.authorization = {{"testapi"_n, config::active_name}}; + trx.actions.push_back(act); + + test.set_transaction_headers(trx, test.DEFAULT_EXPIRATION_DELTA); + auto sigs = trx.sign(test.get_private_key(scope[0], "active"), test.get_chain_id()); + + flat_set keys; + trx.get_signature_keys(test.get_chain_id(), fc::time_point::maximum(), keys); + + auto res = test.push_transaction(trx, fc::time_point::maximum(), Tester::DEFAULT_BILLED_CPU_TIME_US, no_throw); + if (!no_throw) { + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + } + auto block = test.produce_block(); + return { res, block }; } +} - // Wait for next block - std::this_thread::sleep_for( std::chrono::milliseconds(config::block_interval_ms) ); - - auto r = set_code(app, chain_plug->chain(), config::system_account_name, testing::contracts::eosio_bios_wasm()); - BOOST_CHECK(r->receipt && r->receipt->status == transaction_receipt_header::executed); +template +transaction_trace_ptr CallFunction(Tester& test, T ac, const vector& data, const vector& scope = {"testapi"_n}, bool no_throw = false) { + { + return _CallFunction(test, ac, data, scope, no_throw).first; + } } +#define CALL_TEST_FUNCTION(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_api_action{}, DATA) +#define CALL_TEST_FUNCTION_WITH_BLOCK(_TESTER, CLS, MTH, DATA) _CallFunction(_TESTER, test_api_action{}, DATA) +#define CALL_TEST_FUNCTION_SYSTEM(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_chain_action{}, DATA, {config::system_account_name} ) +#define CALL_TEST_FUNCTION_SCOPE(_TESTER, CLS, MTH, DATA, ACCOUNT) CallFunction(_TESTER, test_api_action{}, DATA, ACCOUNT) +#define CALL_TEST_FUNCTION_NO_THROW(_TESTER, CLS, MTH, DATA) CallFunction(_TESTER, test_api_action{}, DATA, {"testapi"_n}, true) +#define CALL_TEST_FUNCTION_AND_CHECK_EXCEPTION(_TESTER, CLS, MTH, DATA, EXC, EXC_MESSAGE) \ +BOOST_CHECK_EXCEPTION( \ + CALL_TEST_FUNCTION( _TESTER, CLS, MTH, DATA), \ + EXC, \ + [](const EXC& e) { \ + return expect_assert_message(e, EXC_MESSAGE); \ + } \ +); } // namespace eosio::test_utils -FC_REFLECT( eosio::test_utils::testit, (id) ) -FC_REFLECT( eosio::test_utils::reqactivated, (feature_digest) ) +FC_REFLECT_TEMPLATE((uint64_t T), eosio::test_utils::test_api_action, BOOST_PP_SEQ_NIL) +FC_REFLECT_TEMPLATE((uint64_t T), eosio::test_utils::test_pause_action, BOOST_PP_SEQ_NIL) +FC_REFLECT_TEMPLATE((uint64_t T), eosio::test_utils::test_chain_action, BOOST_PP_SEQ_NIL) From 5ed992259066455fa4517d49e0c9675ea07a54b8 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 29 Oct 2024 11:09:33 -0500 Subject: [PATCH 2/2] Add missing inline --- tests/chain_test_utils.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/chain_test_utils.hpp b/tests/chain_test_utils.hpp index caa69f75fb..67f4e5f30c 100644 --- a/tests/chain_test_utils.hpp +++ b/tests/chain_test_utils.hpp @@ -50,7 +50,7 @@ struct reqactivated { }; // Create a read-only trx that works with bios reqactivated action -auto make_bios_ro_trx(eosio::chain::controller& control) { +inline auto make_bios_ro_trx(eosio::chain::controller& control) { const auto& pfm = control.get_protocol_feature_manager(); static auto feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::replace_deferred); @@ -63,7 +63,7 @@ auto make_bios_ro_trx(eosio::chain::controller& control) { // Push an input transaction to controller and return trx trace // If account is eosio then signs with the default private key -auto push_input_trx(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, signed_transaction& trx) { +inline auto push_input_trx(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, signed_transaction& trx) { trx.expiration = fc::time_point_sec{fc::time_point::now() + fc::seconds(30)}; trx.set_reference_block( control.head().id() ); if (account == config::system_account_name) { @@ -108,7 +108,7 @@ auto push_input_trx(appbase::scoped_app& app, eosio::chain::controller& control, } // Push setcode trx to controller and return trx trace -auto set_code(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, const vector& wasm) { +inline auto set_code(appbase::scoped_app& app, eosio::chain::controller& control, account_name account, const vector& wasm) { signed_transaction trx; trx.actions.emplace_back(std::vector{{account, config::active_name}}, chain::setcode{ @@ -120,7 +120,7 @@ auto set_code(appbase::scoped_app& app, eosio::chain::controller& control, accou return push_input_trx(app, control, account, trx); } -void activate_protocol_features_set_bios_contract(appbase::scoped_app& app, chain_plugin* chain_plug) { +inline void activate_protocol_features_set_bios_contract(appbase::scoped_app& app, chain_plugin* chain_plug) { using namespace appbase; auto feature_set = std::make_shared>(false);