From a164f16e8e6ad5e928f8232ad44d1bdd9b783831 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Thu, 10 Aug 2023 08:16:35 -0500 Subject: [PATCH 1/7] WIP create test file --- unittests/dry_run_trx_tests.cpp | 337 ++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 unittests/dry_run_trx_tests.cpp diff --git a/unittests/dry_run_trx_tests.cpp b/unittests/dry_run_trx_tests.cpp new file mode 100644 index 0000000000..9a49d3d15a --- /dev/null +++ b/unittests/dry_run_trx_tests.cpp @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +using mvo = fc::mutable_variant_object; + +struct dry_run_trx_tester : validating_tester { + dry_run_trx_tester() { + produce_block(); + }; + + void set_up_test_contract() { + create_accounts( {"noauthtable"_n, "alice"_n} ); + set_code( "noauthtable"_n, test_contracts::no_auth_table_wasm() ); + set_abi( "noauthtable"_n, test_contracts::no_auth_table_abi() ); + produce_block(); + + insert_data = abi_ser.variant_to_binary( "insert", mutable_variant_object() + ("user", "alice") ("id", 1) ("age", 10), + abi_serializer::create_yield_function( abi_serializer_max_time ) ); + getage_data = abi_ser.variant_to_binary("getage", mutable_variant_object() + ("user", "alice"), + abi_serializer::create_yield_function( abi_serializer_max_time )); + produce_block(); + } + + void send_action(const action& act) { + signed_transaction trx; + trx.actions.push_back( act ); + set_transaction_headers( trx ); + //trx.sign(get_private_key(config::system_account_name, "active"), control->get_chain_id()); + + push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run ); + } + + auto send_db_api_transaction( action_name name, bytes data, const vector& auth={{"alice"_n, config::active_name}}, transaction_metadata::trx_type type=transaction_metadata::trx_type::input, uint32_t delay_sec=0 ) { + action act; + signed_transaction trx; + + act.account = "noauthtable"_n; + act.name = name; + act.authorization = auth; + act.data = data; + + trx.actions.push_back( act ); + set_transaction_headers( trx ); + trx.delay_sec = delay_sec; + if ( type == transaction_metadata::trx_type::input ) { + trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id()); + } + + return push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, type ); + } + + void insert_a_record() { + auto res = send_db_api_transaction("insert"_n, insert_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + } + + abi_serializer abi_ser{ json::from_string(test_contracts::no_auth_table_abi()).as(), abi_serializer::create_yield_function(abi_serializer_max_time )}; + bytes insert_data; + bytes getage_data; +}; + +BOOST_AUTO_TEST_SUITE(dry_run_trx_tests) + +BOOST_FIXTURE_TEST_CASE(newaccount_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + action act = { + {}, //vector{{config::system_account_name,config::active_name}}, // todo remove + newaccount{ + .creator = config::system_account_name, + .name = "alice"_n, + .owner = authority( get_public_key( "alice"_n, "owner" ) ), + .active = authority( get_public_key( "alice"_n, "active" ) ) + } + }; + + send_action(act); // should not throw + send_action(act); // should not throw + BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created + +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(setcode_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + std::vector code(10); + action act = { + {}, setcode { "eosio"_n, 0, 0, bytes(code.begin(), code.end()) } + }; + + BOOST_CHECK_THROW( send_action(act), action_validate_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(setabi_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + std::vector abi(10); + action act = { + {}, + setabi { + .account = "alice"_n, .abi = bytes(abi.begin(), abi.end()) + } + }; + + BOOST_CHECK_THROW( send_action(act), action_validate_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(updateauth_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + auto auth = authority( get_public_key( "alice"_n, "test" ) ); + action act = { + vector{{config::system_account_name,config::active_name}}, + updateauth { + .account = "alice"_n, .permission = "active"_n, .parent = "owner"_n, .auth = auth + } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + + +BOOST_FIXTURE_TEST_CASE(deleteauth_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name permission = "active"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + deleteauth { account, permission } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(linkauth_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name code = "eosio_token"_n; + name type = "transfer"_n; + name requirement = "first"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + linkauth { account, code, type, requirement } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(unlinkauth_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name code = "eosio_token"_n; + name type = "transfer"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + unlinkauth { account, code, type } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(canceldelay_test, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + permission_level canceling_auth { config::system_account_name,config::active_name }; + transaction_id_type trx_id { "0718886aa8a3895510218b523d3d694280d1dbc1f6d30e173a10b2039fc894f1" }; + action act = { + vector{{config::system_account_name,config::active_name}}, + canceldelay { canceling_auth, trx_id } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_dry_run_mode_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + //control->set_db_dry_run_mode(); + // verify no write is allowed in read-only mode + BOOST_CHECK_THROW( create_account("bob"_n), std::exception ); + + // verify a read-only transaction in read-only mode + auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); + //control->unset_db_dry_run_mode(); + + // verify db write is allowed in regular mode + BOOST_REQUIRE_NO_THROW( create_account("bob"_n) ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_insert_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + // verify DB insert is not allowed by read-only transaction + BOOST_CHECK_THROW(send_db_api_transaction("insert"_n, insert_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + + // verify DB insert still works with non-read-only transaction after read-only + insert_a_record(); + + // do a read-only transaction and verify the return value (age) is the same as inserted + auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); + BOOST_CHECK_GT(res->net_usage, 0u); + BOOST_CHECK_GT(res->elapsed.count(), 0u); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(auth_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + // verify read-only transaction does not allow authorizations. + BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run), transaction_exception); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(delay_sec_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + // verify read-only transaction does not allow non-zero delay_sec. + BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run, 3), transaction_exception); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_modify_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + // verify DB update is not allowed by read-only transaction + auto modify_data = abi_ser.variant_to_binary("modify", mutable_variant_object() + ("user", "alice") ("age", 25), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("modify"_n, modify_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + + // verify DB update still works in by non-read-only transaction + auto res = send_db_api_transaction("modify"_n, modify_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + + // verify the value was successfully updated + res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 25); + + // verify DB update by secondary key is not allowed by read-only transaction + auto modifybyid_data = abi_ser.variant_to_binary("modifybyid", mutable_variant_object() + ("id", 1) ("age", 50), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("modifybyid"_n, modifybyid_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + + // verify DB update by secondary key still works in by non-read-only transaction + res = send_db_api_transaction("modifybyid"_n, modifybyid_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + + // verify the value was successfully updated + res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 50); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_erase_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + // verify DB erase is not allowed by read-only transaction + auto erase_data = abi_ser.variant_to_binary("erase", mutable_variant_object() + ("user", "alice"), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("erase"_n, erase_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + + // verify DB erase by secondary key is not allowed by read-only transaction + auto erasebyid_data = abi_ser.variant_to_binary("erasebyid", mutable_variant_object() + ("id", 1), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("erasebyid"_n, erasebyid_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + + // verify DB erase still works in by non-read-only transaction + auto res = send_db_api_transaction("erase"_n, erase_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(sequence_numbers_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + const auto& p = control->get_dynamic_global_properties(); + auto receiver_account = control->db().find("noauthtable"_n); + auto amo = control->db().find("alice"_n); + + // verify sequence numbers in state increment for non-read-only transactions + auto prev_global_action_sequence = p.global_action_sequence; + auto prev_recv_sequence = receiver_account->recv_sequence; + auto prev_auth_sequence = amo->auth_sequence; + + auto res = send_db_api_transaction("insert"_n, insert_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + + BOOST_CHECK_EQUAL( prev_global_action_sequence + 1, p.global_action_sequence ); + BOOST_CHECK_EQUAL( prev_recv_sequence + 1, receiver_account->recv_sequence ); + BOOST_CHECK_EQUAL( prev_auth_sequence + 1, amo->auth_sequence ); + + produce_block(); + + // verify sequence numbers in state do not change for read-only transactions + prev_global_action_sequence = p.global_action_sequence; + prev_recv_sequence = receiver_account->recv_sequence; + prev_auth_sequence = amo->auth_sequence; + + send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + + BOOST_CHECK_EQUAL( prev_global_action_sequence, p.global_action_sequence ); + BOOST_CHECK_EQUAL( prev_recv_sequence, receiver_account->recv_sequence ); + BOOST_CHECK_EQUAL( prev_auth_sequence, amo->auth_sequence ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() From 064d8310a7b24767bae429f231e7de1f99f7ca3b Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Sun, 13 Aug 2023 18:04:51 -0400 Subject: [PATCH 2/7] set max mirroring pages for read-only threads to 10 to save virtual memory --- libraries/chain/webassembly/runtimes/eos-vm-oc.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp index 6c00754059..ad650d3a95 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp @@ -60,6 +60,9 @@ void eosvmoc_runtime::init_thread_local_data() { } thread_local std::unique_ptr eosvmoc_runtime::exec_thread_local {}; -thread_local eosvmoc::memory eosvmoc_runtime::mem_thread_local{ wasm_constraints::maximum_linear_memory/wasm_constraints::wasm_page_size }; +// Set mirroring_pages_for_ro_thread to a small number to save upfront virtual memory +// consumption. Usage beyond this limit will be handled by mprotect. +constexpr uint32_t mirroring_pages_for_ro_thread = 10; +thread_local eosvmoc::memory eosvmoc_runtime::mem_thread_local{mirroring_pages_for_ro_thread}; }}}} From 571fccfcf8a30e68cbd40e3eeca0c5e51d233fc2 Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Sun, 13 Aug 2023 18:07:08 -0400 Subject: [PATCH 3/7] delete unneeded complicated code for calculating number of threads allowed for EOS VM OC --- plugins/producer_plugin/producer_plugin.cpp | 42 --------------------- 1 file changed, 42 deletions(-) diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index dd0b944b0a..afeca0d840 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -1256,48 +1256,6 @@ void producer_plugin_impl::plugin_initialize(const boost::program_options::varia // only initialize other read-only options when read-only thread pool is enabled if (_ro_thread_pool_size > 0) { -#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED - if (chain.is_eos_vm_oc_enabled()) { - // EOS VM OC requires 4.2TB Virtual for each executing thread. Make sure the memory - // required by configured read-only threads does not exceed the total system virtual memory. - std::string attr_name; - size_t vm_total_kb{0}; - size_t vm_used_kb{0}; - std::ifstream meminfo_file("/proc/meminfo"); - while (meminfo_file >> attr_name) { - if (attr_name == "VmallocTotal:") { - if (!(meminfo_file >> vm_total_kb)) - break; - } else if (attr_name == "VmallocUsed:") { - if (!(meminfo_file >> vm_used_kb)) - break; - } - meminfo_file.ignore(std::numeric_limits::max(), '\n'); - } - - EOS_ASSERT(vm_total_kb > 0, plugin_config_exception, - "Unable to get system virtual memory size (not a Linux?), therefore cannot determine if the system has enough " - "virtual memory for multi-threaded read-only transactions on EOS VM OC"); - EOS_ASSERT(vm_total_kb > vm_used_kb, plugin_config_exception, - "vm total (${t}) must be greater than vm used (${u})", - ("t", vm_total_kb)("u", vm_used_kb)); - uint32_t num_threads_supported = (vm_total_kb - vm_used_kb) / 4200000000; - // reserve 1 for the app thread, 1 for anything else which might use VM - EOS_ASSERT(num_threads_supported > 2, plugin_config_exception, - "With the EOS VM OC configured, there is not enough system virtual memory to support the required minimum of " - "3 threads (1 for main thread, 1 for read-only, and 1 for anything else), vm total: ${t}, vm used: ${u}", - ("t", vm_total_kb)("u", vm_used_kb)); - num_threads_supported -= 2; - auto actual_threads_allowed = std::min(_ro_max_threads_allowed, num_threads_supported); - ilog("vm total in kb: ${total}, vm used in kb: ${used}, number of EOS VM OC threads supported " - "((vm total - vm used)/4.2 TB - 2): ${supp}, max allowed: ${max}, actual allowed: ${actual}", - ("total", vm_total_kb)("used", vm_used_kb)("supp", num_threads_supported)("max", _ro_max_threads_allowed) - ("actual", actual_threads_allowed)); - EOS_ASSERT(_ro_thread_pool_size <= actual_threads_allowed, plugin_config_exception, - "read-only-threads (${th}) greater than number of threads allowed for EOS VM OC (${allowed})", - ("th", _ro_thread_pool_size)("allowed", actual_threads_allowed)); - } -#endif EOS_ASSERT(_ro_thread_pool_size <= _ro_max_threads_allowed, plugin_config_exception, "read-only-threads (${th}) greater than the number of threads allowed (${allowed})", From 8b308b6ac0d7ffc24ebcf1b8036ed9d773fac36c Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 14 Aug 2023 08:43:57 -0500 Subject: [PATCH 4/7] GH-1083 Cleanup #includes --- libraries/chain/controller.cpp | 8 +++---- .../include/eosio/chain/apply_context.hpp | 1 + .../chain/include/eosio/chain/controller.hpp | 4 +--- libraries/chain/transaction_context.cpp | 13 ++---------- libraries/chain/webassembly/authorization.cpp | 4 +++- libraries/chain/webassembly/crypto.cpp | 9 ++++---- libraries/chain/webassembly/permission.cpp | 1 + libraries/chain/webassembly/privileged.cpp | 21 +++++++++++-------- .../chain/webassembly/runtimes/eos-vm.cpp | 1 + 9 files changed, 30 insertions(+), 32 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 3825d839d4..a528960790 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1014,8 +1014,8 @@ struct controller_impl { ); } - sha256 calculate_integrity_hash() { - sha256::encoder enc; + fc::sha256 calculate_integrity_hash() { + fc::sha256::encoder enc; auto hash_writer = std::make_shared(enc); add_to_snapshot(hash_writer); hash_writer->finalize(); @@ -2091,7 +2091,7 @@ struct controller_impl { } else { packed_transaction_ptr ptrx( b, &pt ); // alias signed_block_ptr auto fut = transaction_metadata::start_recover_keys( - std::move( ptrx ), thread_pool.get_executor(), chain_id, microseconds::maximum(), transaction_metadata::trx_type::input ); + std::move( ptrx ), thread_pool.get_executor(), chain_id, fc::microseconds::maximum(), transaction_metadata::trx_type::input ); trx_metas.emplace_back( transaction_metadata_ptr{}, std::move( fut ) ); } } @@ -3218,7 +3218,7 @@ block_id_type controller::get_block_id_for_num( uint32_t block_num )const { try return id; } FC_CAPTURE_AND_RETHROW( (block_num) ) } -sha256 controller::calculate_integrity_hash() { try { +fc::sha256 controller::calculate_integrity_hash() { try { return my->calculate_integrity_hash(); } FC_LOG_AND_RETHROW() } diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 090531bfcb..da6e17cfa6 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -14,6 +14,7 @@ namespace chainbase { class database; } namespace eosio { namespace chain { class controller; +class account_metadata_object; class apply_context { private: diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index e0d40bf21c..5960853786 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -6,8 +6,6 @@ #include #include -#include -#include #include #include #include @@ -259,7 +257,7 @@ namespace eosio { namespace chain { // thread-safe block_id_type get_block_id_for_num( uint32_t block_num )const; - sha256 calculate_integrity_hash(); + fc::sha256 calculate_integrity_hash(); void write_snapshot( const snapshot_writer_ptr& snapshot ); bool sender_avoids_whitelist_blacklist_enforcement( account_name sender )const; diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index 3ee699c496..8c9e434d8f 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -8,16 +9,6 @@ #include #include -#pragma push_macro("N") -#undef N -#include -#include -#include -#include -#include -#include -#pragma pop_macro("N") - #include namespace eosio { namespace chain { @@ -828,7 +819,7 @@ namespace eosio { namespace chain { actors.insert( auth.actor ); } } - EOS_ASSERT( one_auth || is_transient(), tx_no_auths, "transaction must have at least one authorization" ); + EOS_ASSERT( one_auth || is_read_only(), tx_no_auths, "transaction must have at least one authorization" ); if( enforce_actor_whitelist_blacklist ) { control.check_actor_list( actors ); diff --git a/libraries/chain/webassembly/authorization.cpp b/libraries/chain/webassembly/authorization.cpp index 43af5cdce6..8f446591ba 100644 --- a/libraries/chain/webassembly/authorization.cpp +++ b/libraries/chain/webassembly/authorization.cpp @@ -1,6 +1,8 @@ #include #include +#include + namespace eosio { namespace chain { namespace webassembly { void interface::require_auth( account_name account ) const { context.require_authorization( account ); @@ -42,7 +44,7 @@ namespace eosio { namespace chain { namespace webassembly { auto s = fc::raw::pack_size(result); if (s <= packed_result.size()) { - datastream ds(packed_result.data(), s); + fc::datastream ds(packed_result.data(), s); fc::raw::pack(ds, result); } return s; diff --git a/libraries/chain/webassembly/crypto.cpp b/libraries/chain/webassembly/crypto.cpp index 095ee4c1d9..d169123d7a 100644 --- a/libraries/chain/webassembly/crypto.cpp +++ b/libraries/chain/webassembly/crypto.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -25,8 +26,8 @@ namespace eosio { namespace chain { namespace webassembly { legacy_span pub ) const { fc::crypto::signature s; fc::crypto::public_key p; - datastream ds( sig.data(), sig.size() ); - datastream pubds ( pub.data(), pub.size() ); + fc::datastream ds( sig.data(), sig.size() ); + fc::datastream pubds ( pub.data(), pub.size() ); fc::raw::unpack( ds, s ); fc::raw::unpack( pubds, p ); @@ -48,7 +49,7 @@ namespace eosio { namespace chain { namespace webassembly { legacy_span sig, legacy_span pub ) const { fc::crypto::signature s; - datastream ds( sig.data(), sig.size() ); + fc::datastream ds( sig.data(), sig.size() ); fc::raw::unpack(ds, s); EOS_ASSERT(s.which() < context.db.get().num_supported_key_types, unactivated_signature_type, @@ -74,7 +75,7 @@ namespace eosio { namespace chain { namespace webassembly { // this will do one less copy for those keys while maintaining the rules of // [0..33) dest sizes: assert (asserts in fc::raw::pack) // [33..inf) dest sizes: return packed size (always 33) - datastream out_ds( pub.data(), pub.size() ); + fc::datastream out_ds( pub.data(), pub.size() ); fc::raw::pack(out_ds, recovered); return out_ds.tellp(); } diff --git a/libraries/chain/webassembly/permission.cpp b/libraries/chain/webassembly/permission.cpp index c5b521b851..a65ad82fcd 100644 --- a/libraries/chain/webassembly/permission.cpp +++ b/libraries/chain/webassembly/permission.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 1e8ce2382c..f9a8456745 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,6 +6,8 @@ #include #include +#include + #include #include @@ -94,7 +97,7 @@ namespace eosio { namespace chain { namespace webassembly { return s; if ( s <= packed_parameters.size() ) { - datastream ds( packed_parameters.data(), s ); + fc::datastream ds( packed_parameters.data(), s ); fc::raw::pack(ds, version); fc::raw::pack(ds, params); } @@ -102,7 +105,7 @@ namespace eosio { namespace chain { namespace webassembly { } void interface::set_wasm_parameters_packed( span packed_parameters ) { EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_wasm_parameters_packed not allowed in a readonly transaction"); - datastream ds( packed_parameters.data(), packed_parameters.size() ); + fc::datastream ds( packed_parameters.data(), packed_parameters.size() ); uint32_t version; chain::wasm_config cfg; fc::raw::unpack(ds, version); @@ -117,7 +120,7 @@ namespace eosio { namespace chain { namespace webassembly { } int64_t interface::set_proposed_producers( legacy_span packed_producer_schedule) { EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_proposed_producers not allowed in a readonly transaction"); - datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); + fc::datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); std::vector producers; std::vector old_version; fc::raw::unpack(ds, old_version); @@ -137,7 +140,7 @@ namespace eosio { namespace chain { namespace webassembly { if (packed_producer_format == 0) { return set_proposed_producers(std::move(packed_producer_schedule)); } else if (packed_producer_format == 1) { - datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); + fc::datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); vector producers; fc::raw::unpack(ds, producers); @@ -154,7 +157,7 @@ namespace eosio { namespace chain { namespace webassembly { if( packed_blockchain_parameters.size() == 0 ) return s; if ( s <= packed_blockchain_parameters.size() ) { - datastream ds( packed_blockchain_parameters.data(), s ); + fc::datastream ds( packed_blockchain_parameters.data(), s ); fc::raw::pack(ds, gpo.configuration.v0()); return s; } @@ -163,7 +166,7 @@ namespace eosio { namespace chain { namespace webassembly { void interface::set_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) { EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_blockchain_parameters_packed not allowed in a readonly transaction"); - datastream ds( packed_blockchain_parameters.data(), packed_blockchain_parameters.size() ); + fc::datastream ds( packed_blockchain_parameters.data(), packed_blockchain_parameters.size() ); chain::chain_config_v0 cfg; fc::raw::unpack(ds, cfg); cfg.validate(); @@ -174,7 +177,7 @@ namespace eosio { namespace chain { namespace webassembly { } uint32_t interface::get_parameters_packed( span packed_parameter_ids, span packed_parameters) const{ - datastream ds_ids( packed_parameter_ids.data(), packed_parameter_ids.size() ); + fc::datastream ds_ids( packed_parameter_ids.data(), packed_parameter_ids.size() ); chain::chain_config cfg = context.control.get_global_properties().configuration; std::vector ids; @@ -188,14 +191,14 @@ namespace eosio { namespace chain { namespace webassembly { chain::config_parse_error, "get_parameters_packed: buffer size is smaller than ${size}", ("size", size)); - datastream ds( packed_parameters.data(), size ); + fc::datastream ds( packed_parameters.data(), size ); fc::raw::pack( ds, config_range ); return size; } void interface::set_parameters_packed( span packed_parameters ){ EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_parameters_packed not allowed in a readonly transaction"); - datastream ds( packed_parameters.data(), packed_parameters.size() ); + fc::datastream ds( packed_parameters.data(), packed_parameters.size() ); chain::chain_config cfg = context.control.get_global_properties().configuration; config_range config_range(cfg, {context.control}); diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index b0c81c3c6a..033cf978a3 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include From 1fa2c1756a4e1ba313fb8626bb44645b7d6fc018 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 14 Aug 2023 08:44:23 -0500 Subject: [PATCH 5/7] GH-1083 Add dry-run tests --- unittests/delay_tests.cpp | 6 +- unittests/dry_run_trx_tests.cpp | 270 ++++++++++++++++---------------- 2 files changed, 136 insertions(+), 140 deletions(-) diff --git a/unittests/delay_tests.cpp b/unittests/delay_tests.cpp index 52a22451e8..9dc3f710e5 100644 --- a/unittests/delay_tests.cpp +++ b/unittests/delay_tests.cpp @@ -1901,9 +1901,11 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { chain.set_transaction_headers(trx); trx.sign(chain.get_private_key("tester"_n, "active"), chain.control->get_chain_id()); - trace = chain.push_transaction(trx); - //wdump((fc::json::to_pretty_string(trace))); + // first push as a dry_run trx + trace = chain.push_transaction(trx, fc::time_point::maximum(), base_tester::DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run); BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); + // now push for real + trace = chain.push_transaction(trx); gen_size = chain.control->db().get_index().size(); BOOST_CHECK_EQUAL(2u, gen_size); diff --git a/unittests/dry_run_trx_tests.cpp b/unittests/dry_run_trx_tests.cpp index 9a49d3d15a..366ffc6773 100644 --- a/unittests/dry_run_trx_tests.cpp +++ b/unittests/dry_run_trx_tests.cpp @@ -32,16 +32,18 @@ struct dry_run_trx_tester : validating_tester { produce_block(); } - void send_action(const action& act) { + void send_action(const action& act, bool sign = false) { signed_transaction trx; trx.actions.push_back( act ); set_transaction_headers( trx ); - //trx.sign(get_private_key(config::system_account_name, "active"), control->get_chain_id()); + if (sign) // dry-run can contain signature, but not required + trx.sign(get_private_key(act.authorization.at(0).actor, act.authorization.at(0).permission.to_string()), control->get_chain_id()); push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run ); } - auto send_db_api_transaction( action_name name, bytes data, const vector& auth={{"alice"_n, config::active_name}}, transaction_metadata::trx_type type=transaction_metadata::trx_type::input, uint32_t delay_sec=0 ) { + auto send_db_api_transaction(action_name name, bytes data, const vector& auth={{"alice"_n, config::active_name}}, + transaction_metadata::trx_type type=transaction_metadata::trx_type::input, uint32_t delay_sec=0) { action act; signed_transaction trx; @@ -73,11 +75,35 @@ struct dry_run_trx_tester : validating_tester { BOOST_AUTO_TEST_SUITE(dry_run_trx_tests) +BOOST_FIXTURE_TEST_CASE(require_authorization, dry_run_trx_tester) { try { + produce_blocks( 1 ); + + action act = { + {}, // no authorization provided: vector{{config::system_account_name,config::active_name}}, + newaccount{ + .creator = config::system_account_name, + .name = "alice"_n, + .owner = authority( get_public_key( "alice"_n, "owner" ) ), + .active = authority( get_public_key( "alice"_n, "active" ) ) + } + }; + + // dry-run requires authorization + BOOST_REQUIRE_THROW(send_action(act, false), tx_no_auths); + + // sign trx with no authorization + signed_transaction trx; + trx.actions.push_back( act ); + set_transaction_headers( trx ); + trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id()); + BOOST_REQUIRE_THROW(push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::dry_run ), tx_no_auths); +} FC_LOG_AND_RETHROW() } + BOOST_FIXTURE_TEST_CASE(newaccount_test, dry_run_trx_tester) { try { produce_blocks( 1 ); action act = { - {}, //vector{{config::system_account_name,config::active_name}}, // todo remove + vector{{config::system_account_name,config::active_name}}, newaccount{ .creator = config::system_account_name, .name = "alice"_n, @@ -86,219 +112,187 @@ BOOST_FIXTURE_TEST_CASE(newaccount_test, dry_run_trx_tester) { try { } }; - send_action(act); // should not throw - send_action(act); // should not throw + send_action(act, false); // should not throw + send_action(act, false); // should not throw + send_action(act, true); // should not throw BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created - } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(setcode_test, dry_run_trx_tester) { try { produce_blocks( 1 ); - std::vector code(10); + create_accounts( {"setcodetest"_n} ); + + auto wasm = test_contracts::no_auth_table_wasm(); action act = { - {}, setcode { "eosio"_n, 0, 0, bytes(code.begin(), code.end()) } + vector{{"setcodetest"_n,config::active_name}}, + setcode{ + .account = "setcodetest"_n, + .vmtype = 0, + .vmversion = 0, + .code = bytes(wasm.begin(), wasm.end()) + } }; - BOOST_CHECK_THROW( send_action(act), action_validate_exception ); + send_action(act, false); // should not throw + send_action(act, true); // should not throw + BOOST_TEST(!is_code_cached("setcodetest"_n)); } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(setabi_test, dry_run_trx_tester) { try { produce_blocks( 1 ); - std::vector abi(10); + create_accounts( {"setabitest"_n} ); + + auto abi = test_contracts::no_auth_table_abi(); action act = { - {}, + vector{{"setabitest"_n,config::active_name}}, setabi { - .account = "alice"_n, .abi = bytes(abi.begin(), abi.end()) + .account = "setabitest"_n, .abi = bytes(abi.begin(), abi.end()) } }; - BOOST_CHECK_THROW( send_action(act), action_validate_exception ); + send_action(act, false); // should not throw + send_action(act, true); // should not throw + const auto* accnt = control->db().template find( "setabitest"_n ); + BOOST_REQUIRE(accnt); + BOOST_TEST(accnt->abi.size() == 0); // no abi actually set } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(updateauth_test, dry_run_trx_tester) { try { produce_blocks( 1 ); + create_accounts( {"alice"_n} ); + auto auth = authority( get_public_key( "alice"_n, "test" ) ); action act = { - vector{{config::system_account_name,config::active_name}}, + vector{{"alice"_n, config::active_name}}, updateauth { .account = "alice"_n, .permission = "active"_n, .parent = "owner"_n, .auth = auth } }; - BOOST_CHECK_THROW( send_action(act), transaction_exception ); + send_action(act, false); // should not throw + send_action(act, true); // should not throw } FC_LOG_AND_RETHROW() } - BOOST_FIXTURE_TEST_CASE(deleteauth_test, dry_run_trx_tester) { try { produce_blocks( 1 ); + create_accounts( {"alice"_n} ); + + // update auth + push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object() + ("account", "alice") + ("permission", "first") + ("parent", "active") + ("auth", authority(get_public_key("alice"_n, "first"))) + ); + name account = "alice"_n; - name permission = "active"_n; + name permission = "first"_n; action act = { - vector{{config::system_account_name,config::active_name}}, + vector{{"alice"_n, config::active_name}}, deleteauth { account, permission } }; - BOOST_CHECK_THROW( send_action(act), transaction_exception ); + send_action(act, false); // should not throw + send_action(act, true); // should not throw } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(linkauth_test, dry_run_trx_tester) { try { produce_blocks( 1 ); + create_account("eosio.token"_n); + set_code("eosio.token"_n, test_contracts::eosio_token_wasm()); + set_abi("eosio.token"_n, test_contracts::eosio_token_abi()); + + create_accounts( {"alice"_n} ); + + // update auth + push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object() + ("account", "alice") + ("permission", "first") + ("parent", "active") + ("auth", authority(get_public_key("alice"_n, "first"))) + ); + name account = "alice"_n; name code = "eosio_token"_n; name type = "transfer"_n; name requirement = "first"_n; action act = { - vector{{config::system_account_name,config::active_name}}, + vector{{"alice"_n, config::active_name}}, linkauth { account, code, type, requirement } }; - BOOST_CHECK_THROW( send_action(act), transaction_exception ); + send_action(act, false); // should not throw + send_action(act, true); // should not throw } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(unlinkauth_test, dry_run_trx_tester) { try { produce_blocks( 1 ); + create_account("eosio.token"_n); + set_code("eosio.token"_n, test_contracts::eosio_token_wasm()); + set_abi("eosio.token"_n, test_contracts::eosio_token_abi()); + + create_accounts( {"alice"_n} ); + + // update auth + push_action(config::system_account_name, updateauth::get_name(), "alice"_n, fc::mutable_variant_object() + ("account", "alice") + ("permission", "first") + ("parent", "active") + ("auth", authority(get_public_key("alice"_n, "first"))) + ); + + // link auth + push_action(config::system_account_name, linkauth::get_name(), "alice"_n, fc::mutable_variant_object() + ("account", "alice") + ("code", "eosio.token") + ("type", "transfer") + ("requirement", "first")); + name account = "alice"_n; name code = "eosio_token"_n; name type = "transfer"_n; action act = { - vector{{config::system_account_name,config::active_name}}, + vector{{"alice"_n, config::active_name}}, unlinkauth { account, code, type } }; - BOOST_CHECK_THROW( send_action(act), transaction_exception ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(canceldelay_test, dry_run_trx_tester) { try { - produce_blocks( 1 ); - - permission_level canceling_auth { config::system_account_name,config::active_name }; - transaction_id_type trx_id { "0718886aa8a3895510218b523d3d694280d1dbc1f6d30e173a10b2039fc894f1" }; - action act = { - vector{{config::system_account_name,config::active_name}}, - canceldelay { canceling_auth, trx_id } - }; - - BOOST_CHECK_THROW( send_action(act), transaction_exception ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(db_dry_run_mode_test, dry_run_trx_tester) { try { - set_up_test_contract(); - - insert_a_record(); - - //control->set_db_dry_run_mode(); - // verify no write is allowed in read-only mode - BOOST_CHECK_THROW( create_account("bob"_n), std::exception ); - - // verify a read-only transaction in read-only mode - auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); - //control->unset_db_dry_run_mode(); - - // verify db write is allowed in regular mode - BOOST_REQUIRE_NO_THROW( create_account("bob"_n) ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(db_insert_test, dry_run_trx_tester) { try { - set_up_test_contract(); - - // verify DB insert is not allowed by read-only transaction - BOOST_CHECK_THROW(send_db_api_transaction("insert"_n, insert_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); - - // verify DB insert still works with non-read-only transaction after read-only - insert_a_record(); - - // do a read-only transaction and verify the return value (age) is the same as inserted - auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); - BOOST_CHECK_GT(res->net_usage, 0u); - BOOST_CHECK_GT(res->elapsed.count(), 0u); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(auth_test, dry_run_trx_tester) { try { - set_up_test_contract(); - - // verify read-only transaction does not allow authorizations. - BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run), transaction_exception); + send_action(act, false); // should not throw + send_action(act, true); // should not throw } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(delay_sec_test, dry_run_trx_tester) { try { set_up_test_contract(); - // verify read-only transaction does not allow non-zero delay_sec. + // verify dry-run transaction does not allow non-zero delay_sec. BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run, 3), transaction_exception); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(db_modify_test, dry_run_trx_tester) { try { +BOOST_FIXTURE_TEST_CASE(db_insert_test, dry_run_trx_tester) { try { set_up_test_contract(); - insert_a_record(); - - // verify DB update is not allowed by read-only transaction - auto modify_data = abi_ser.variant_to_binary("modify", mutable_variant_object() - ("user", "alice") ("age", 25), - abi_serializer::create_yield_function( abi_serializer_max_time ) - ); - BOOST_CHECK_THROW(send_db_api_transaction("modify"_n, modify_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); - - // verify DB update still works in by non-read-only transaction - auto res = send_db_api_transaction("modify"_n, modify_data); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - produce_block(); - - // verify the value was successfully updated - res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 25); - - // verify DB update by secondary key is not allowed by read-only transaction - auto modifybyid_data = abi_ser.variant_to_binary("modifybyid", mutable_variant_object() - ("id", 1) ("age", 50), - abi_serializer::create_yield_function( abi_serializer_max_time ) - ); - BOOST_CHECK_THROW(send_db_api_transaction("modifybyid"_n, modifybyid_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); + // verify DB operation is allowed by dry-run transaction + send_db_api_transaction("insert"_n, insert_data, vector{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run); - // verify DB update by secondary key still works in by non-read-only transaction - res = send_db_api_transaction("modifybyid"_n, modifybyid_data); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - produce_block(); - - // verify the value was successfully updated - res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); - BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); - BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 50); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(db_erase_test, dry_run_trx_tester) { try { - set_up_test_contract(); + // verify the dry-run insert was rolled back, use a read-only trx to query + BOOST_CHECK_EXCEPTION(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only), fc::exception, + [](const fc::exception& e) { + return expect_assert_message(e, "Record does not exist"); + }); insert_a_record(); - // verify DB erase is not allowed by read-only transaction - auto erase_data = abi_ser.variant_to_binary("erase", mutable_variant_object() - ("user", "alice"), - abi_serializer::create_yield_function( abi_serializer_max_time ) - ); - BOOST_CHECK_THROW(send_db_api_transaction("erase"_n, erase_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); - - // verify DB erase by secondary key is not allowed by read-only transaction - auto erasebyid_data = abi_ser.variant_to_binary("erasebyid", mutable_variant_object() - ("id", 1), - abi_serializer::create_yield_function( abi_serializer_max_time ) - ); - BOOST_CHECK_THROW(send_db_api_transaction("erasebyid"_n, erasebyid_data, {}, transaction_metadata::trx_type::dry_run), table_operation_not_permitted); - - // verify DB erase still works in by non-read-only transaction - auto res = send_db_api_transaction("erase"_n, erase_data); + // do a dry-run transaction and verify the return value (age) is the same as inserted + auto res = send_db_api_transaction("getage"_n, getage_data, vector{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run); BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); + BOOST_CHECK_GT(res->net_usage, 0u); + BOOST_CHECK_GT(res->elapsed.count(), 0u); } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(sequence_numbers_test, dry_run_trx_tester) { try { @@ -322,12 +316,12 @@ BOOST_FIXTURE_TEST_CASE(sequence_numbers_test, dry_run_trx_tester) { try { produce_block(); - // verify sequence numbers in state do not change for read-only transactions + // verify sequence numbers in state do not change for dry-run transactions prev_global_action_sequence = p.global_action_sequence; prev_recv_sequence = receiver_account->recv_sequence; prev_auth_sequence = amo->auth_sequence; - send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::dry_run); + send_db_api_transaction("getage"_n, getage_data, vector{{"alice"_n, config::active_name}}, transaction_metadata::trx_type::dry_run); BOOST_CHECK_EQUAL( prev_global_action_sequence, p.global_action_sequence ); BOOST_CHECK_EQUAL( prev_recv_sequence, receiver_account->recv_sequence ); From 74c672b47ed6ff2cc9e076b78a9e422475f0052f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 14 Aug 2023 12:05:05 -0500 Subject: [PATCH 6/7] GH-1083 Minor cleanup --- unittests/delay_tests.cpp | 1 + unittests/dry_run_trx_tests.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/unittests/delay_tests.cpp b/unittests/delay_tests.cpp index 9dc3f710e5..5b229e48d2 100644 --- a/unittests/delay_tests.cpp +++ b/unittests/delay_tests.cpp @@ -1906,6 +1906,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try { BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); // now push for real trace = chain.push_transaction(trx); + BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace->receipt->status); gen_size = chain.control->db().get_index().size(); BOOST_CHECK_EQUAL(2u, gen_size); diff --git a/unittests/dry_run_trx_tests.cpp b/unittests/dry_run_trx_tests.cpp index 366ffc6773..6da0f4e75e 100644 --- a/unittests/dry_run_trx_tests.cpp +++ b/unittests/dry_run_trx_tests.cpp @@ -29,7 +29,6 @@ struct dry_run_trx_tester : validating_tester { getage_data = abi_ser.variant_to_binary("getage", mutable_variant_object() ("user", "alice"), abi_serializer::create_yield_function( abi_serializer_max_time )); - produce_block(); } void send_action(const action& act, bool sign = false) { @@ -116,6 +115,8 @@ BOOST_FIXTURE_TEST_CASE(newaccount_test, dry_run_trx_tester) { try { send_action(act, false); // should not throw send_action(act, true); // should not throw BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created + produce_blocks( 1 ); + BOOST_CHECK_THROW(control->get_account("alice"_n), fc::exception); // not actually created } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(setcode_test, dry_run_trx_tester) { try { From e8533b97d1a4c4bee144299156b629bc23d99fb3 Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Mon, 14 Aug 2023 13:51:20 -0400 Subject: [PATCH 7/7] change memory::memory(uint64_t max_pages) parameter max_pages to sliced_pages and delete unused memory::reset(uint64_t max_pages) --- .../eosio/chain/webassembly/eos-vm-oc/memory.hpp | 3 +-- .../chain/webassembly/runtimes/eos-vm-oc.cpp | 6 +++--- .../webassembly/runtimes/eos-vm-oc/memory.cpp | 16 +++------------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp index 60b2619ca1..cce12a2703 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/memory.hpp @@ -24,11 +24,10 @@ class memory { static constexpr uint64_t total_memory_per_slice = memory_prologue_size + UINT64_C(0x200000000) + UINT64_C(4096); public: - explicit memory(uint64_t max_pages); + explicit memory(uint64_t sliced_pages); ~memory(); memory(const memory&) = delete; memory& operator=(const memory&) = delete; - void reset(uint64_t max_pages); uint8_t* const zero_page_memory_base() const { return zeropage_base; } uint8_t* const full_page_memory_base() const { return fullpage_base; } diff --git a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp index ad650d3a95..9c7fd4ca21 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp @@ -60,9 +60,9 @@ void eosvmoc_runtime::init_thread_local_data() { } thread_local std::unique_ptr eosvmoc_runtime::exec_thread_local {}; -// Set mirroring_pages_for_ro_thread to a small number to save upfront virtual memory +// Set sliced_pages_for_ro_thread to a small number to save upfront virtual memory // consumption. Usage beyond this limit will be handled by mprotect. -constexpr uint32_t mirroring_pages_for_ro_thread = 10; -thread_local eosvmoc::memory eosvmoc_runtime::mem_thread_local{mirroring_pages_for_ro_thread}; +constexpr uint32_t sliced_pages_for_ro_thread = 10; +thread_local eosvmoc::memory eosvmoc_runtime::mem_thread_local{sliced_pages_for_ro_thread}; }}}} diff --git a/libraries/chain/webassembly/runtimes/eos-vm-oc/memory.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc/memory.cpp index db17b9d5d7..dd46a761af 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm-oc/memory.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm-oc/memory.cpp @@ -11,9 +11,9 @@ namespace eosio { namespace chain { namespace eosvmoc { -memory::memory(uint64_t max_pages) { - uint64_t number_slices = max_pages + 1; - uint64_t wasm_memory_size = max_pages * wasm_constraints::wasm_page_size; +memory::memory(uint64_t sliced_pages) { + uint64_t number_slices = sliced_pages + 1; + uint64_t wasm_memory_size = sliced_pages * wasm_constraints::wasm_page_size; int fd = syscall(SYS_memfd_create, "eosvmoc_mem", MFD_CLOEXEC); FC_ASSERT(fd >= 0, "Failed to create memory memfd"); auto cleanup_fd = fc::make_scoped_exit([&fd](){close(fd);}); @@ -44,16 +44,6 @@ memory::memory(uint64_t max_pages) { intrinsic_jump_table[-intrinsic.second.ordinal] = (uintptr_t)intrinsic.second.function_ptr; } -void memory::reset(uint64_t max_pages) { - uint64_t old_max_pages = mapsize / memory::total_memory_per_slice - 1; - if(max_pages == old_max_pages) return; - memory new_memory{max_pages}; - std::swap(mapbase, new_memory.mapbase); - std::swap(mapsize, new_memory.mapsize); - std::swap(zeropage_base, new_memory.zeropage_base); - std::swap(fullpage_base, new_memory.fullpage_base); -} - memory::~memory() { munmap(mapbase, mapsize); }