From cafceaa6d7c8aba6bb36d12e0a02c53263fbd952 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 28 Dec 2022 15:04:53 -0600 Subject: [PATCH 1/3] GH-260 Add is_code_cached method to wasm_interface and use in api_tests. --- .../include/eosio/chain/wasm_interface.hpp | 3 +++ .../eosio/chain/wasm_interface_private.hpp | 5 +++++ libraries/chain/wasm_interface.cpp | 4 ++++ .../testing/include/eosio/testing/tester.hpp | 2 ++ libraries/testing/tester.cpp | 7 +++++++ unittests/api_tests.cpp | 18 +++++++++--------- 6 files changed, 30 insertions(+), 9 deletions(-) diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index 85a959a70b..d0294b75b5 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -63,6 +63,9 @@ namespace eosio { namespace chain { //Immediately exits currently running wasm. UB is called when no wasm running void exit(); + //Returns true if the code is cached + size_t is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const; + // If substitute_apply is set, then apply calls it before doing anything else. If substitute_apply returns true, // then apply returns immediately. std::function parse_initial_memory(const Module& module) { std::vector mem_image; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 49d5e26d12..a9ccf7a478 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -108,6 +108,10 @@ namespace eosio { namespace chain { my->runtime_interface->immediately_exit_currently_running_module(); } + size_t wasm_interface::is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const { + return my->is_code_cached(code_hash, vm_type, vm_version); + } + wasm_instantiated_module_interface::~wasm_instantiated_module_interface() {} wasm_runtime_interface::~wasm_runtime_interface() {} diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index fa588124e0..3d3b4e9dbe 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -307,6 +307,8 @@ namespace eosio { namespace testing { void set_code( account_name name, const vector wasm, const private_key_type* signer = nullptr ); void set_abi( account_name name, const char* abi_json, const private_key_type* signer = nullptr ); + bool is_code_cached( account_name name ) const; + bool chain_has_transaction( const transaction_id_type& txid ) const; const transaction_receipt& get_transaction_receipt( const transaction_id_type& txid ) const; diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index 8befa1e5ad..c04ea7ccd4 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -950,6 +950,13 @@ namespace eosio { namespace testing { push_transaction( trx ); } + bool base_tester::is_code_cached( eosio::chain::account_name name ) const { + const auto& db = control->db(); + const account_metadata_object* receiver_account = &db.template get( name ); + if ( receiver_account->code_hash == digest_type() ) return false; + return control->get_wasm_interface().is_code_cached( receiver_account->code_hash, receiver_account->vm_type, receiver_account->vm_version ); + } + bool base_tester::chain_has_transaction( const transaction_id_type& txid ) const { return chain_transactions.count(txid) != 0; diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 9e370306ea..7fa3cb2997 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1264,14 +1264,14 @@ BOOST_FIXTURE_TEST_CASE(checktime_intrinsic, TESTER) { try { set_code( "testapi"_n, ss.str().c_str() ); produce_blocks(1); + BOOST_TEST( !is_code_cached("testapi"_n) ); + //initialize cache BOOST_CHECK_EXCEPTION( call_test( *this, test_api_action{}, 5000, 10, 10 ), deadline_exception, is_deadline_exception ); -// https://github.com/AntelopeIO/leap/issues/260 was created to track this TODO. -// Remove those comments after the issue is resolved. -// #warning TODO validate that the contract was successfully cached + BOOST_TEST( is_code_cached("testapi"_n) ); //it will always call BOOST_CHECK_EXCEPTION( call_test( *this, test_api_action{}, @@ -1303,14 +1303,14 @@ BOOST_FIXTURE_TEST_CASE(checktime_grow_memory, TESTER) { try { set_code( "testapi"_n, ss.str().c_str() ); produce_blocks(1); + BOOST_TEST( !is_code_cached("testapi"_n) ); + //initialize cache BOOST_CHECK_EXCEPTION( call_test( *this, test_api_action{}, 5000, 10, 10 ), deadline_exception, is_deadline_exception ); -// https://github.com/AntelopeIO/leap/issues/260 was created to track this TODO. -// Remove those comments after the issue is resolved. -//#warning TODO validate that the contract was successfully cached + BOOST_TEST( is_code_cached("testapi"_n) ); //it will always call BOOST_CHECK_EXCEPTION( call_test( *this, test_api_action{}, @@ -1325,14 +1325,14 @@ BOOST_FIXTURE_TEST_CASE(checktime_hashing_fail, TESTER) { try { set_code( "testapi"_n, contracts::test_api_wasm() ); produce_blocks(1); + BOOST_TEST( !is_code_cached("testapi"_n) ); + //hit deadline exception, but cache the contract BOOST_CHECK_EXCEPTION( call_test( *this, test_api_action{}, 5000, 3, 3 ), deadline_exception, is_deadline_exception ); -// https://github.com/AntelopeIO/leap/issues/260 was created to track this TODO. -// Remove those comments after the issue is resolved. -//#warning TODO validate that the contract was successfully cached + BOOST_TEST( 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( *this, test_api_action{}, From b4db5ce4ab72ab63f4494e748e0f34976c04918f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Fri, 30 Dec 2022 12:14:16 -0600 Subject: [PATCH 2/3] GH-260 Additional uses of is_code_cached --- unittests/api_tests.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index 7fa3cb2997..127bb127ab 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1051,6 +1051,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_max_trx_cpu_extended_test) { try { // 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. @@ -1063,7 +1065,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_max_trx_cpu_extended_test) { try { auto dur = (after - before).count(); dlog("elapsed ${e}us", ("e", dur) ); BOOST_CHECK( dur >= 24'999 ); // should never fail - // This assumes that loading the WASM takes at least 1.5 ms + BOOST_TEST( t.is_code_cached("pause"_n) ); + // This assumes that loading the WASM takes at least 1.5 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 1.5 ms to load. BOOST_CHECK_MESSAGE( dur > 26'500, "elapsed " << dur << "us" ); @@ -1106,6 +1109,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_max_trx_extended_test) { try { // 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. @@ -1118,6 +1123,7 @@ BOOST_AUTO_TEST_CASE(checktime_pause_max_trx_extended_test) { try { 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 1.5 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 1.5 ms to load. @@ -1152,6 +1158,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_block_deadline_not_extended_test) { try { // 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{}, @@ -1162,6 +1170,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_block_deadline_not_extended_test) { try { 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 10 ms for checktime to trigger, trace to be created, and to get to the now() call. BOOST_CHECK_MESSAGE( dur < 85'000, "elapsed " << dur << "us" ); @@ -1198,6 +1208,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_block_deadline_not_extended_while_loading_t // 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{}, @@ -1209,6 +1221,8 @@ BOOST_AUTO_TEST_CASE(checktime_pause_block_deadline_not_extended_while_loading_t auto dur = (after - before).count(); dlog("elapsed ${e}us", ("e", dur) ); BOOST_CHECK( dur >= 5'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 // considerably lower than the 150ms max_transaction_time From 4ff794875b298adde2a5734e76e63026abcc1ad4 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Wed, 4 Jan 2023 19:44:59 -0600 Subject: [PATCH 3/3] GH-260 Fix return type --- libraries/chain/include/eosio/chain/wasm_interface.hpp | 2 +- libraries/chain/include/eosio/chain/wasm_interface_private.hpp | 2 +- libraries/chain/wasm_interface.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index d0294b75b5..51f91c9ff1 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -64,7 +64,7 @@ namespace eosio { namespace chain { void exit(); //Returns true if the code is cached - size_t is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const; + bool is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const; // If substitute_apply is set, then apply calls it before doing anything else. If substitute_apply returns true, // then apply returns immediately. diff --git a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp index 255144b729..e0c89483cf 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp @@ -94,7 +94,7 @@ namespace eosio { namespace chain { }); } - size_t is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const { + bool is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const { wasm_cache_index::iterator it = wasm_instantiation_cache.find( boost::make_tuple(code_hash, vm_type, vm_version) ); return it != wasm_instantiation_cache.end(); } diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index a9ccf7a478..5b9f5c48b9 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -108,7 +108,7 @@ namespace eosio { namespace chain { my->runtime_interface->immediately_exit_currently_running_module(); } - size_t wasm_interface::is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const { + bool wasm_interface::is_code_cached(const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) const { return my->is_code_cached(code_hash, vm_type, vm_version); }