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/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/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-oc.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc.cpp index 6c00754059..9c7fd4ca21 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 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 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); } 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 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})", diff --git a/unittests/delay_tests.cpp b/unittests/delay_tests.cpp index 52a22451e8..5b229e48d2 100644 --- a/unittests/delay_tests.cpp +++ b/unittests/delay_tests.cpp @@ -1901,8 +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()); + // 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); - //wdump((fc::json::to_pretty_string(trace))); 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 new file mode 100644 index 0000000000..6da0f4e75e --- /dev/null +++ b/unittests/dry_run_trx_tests.cpp @@ -0,0 +1,332 @@ +#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 )); + } + + void send_action(const action& act, bool sign = false) { + signed_transaction trx; + trx.actions.push_back( act ); + set_transaction_headers( trx ); + 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) { + 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(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}}, + 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, 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 + 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 { + produce_blocks( 1 ); + + create_accounts( {"setcodetest"_n} ); + + auto wasm = test_contracts::no_auth_table_wasm(); + action act = { + vector{{"setcodetest"_n,config::active_name}}, + setcode{ + .account = "setcodetest"_n, + .vmtype = 0, + .vmversion = 0, + .code = bytes(wasm.begin(), wasm.end()) + } + }; + + 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 ); + + create_accounts( {"setabitest"_n} ); + + auto abi = test_contracts::no_auth_table_abi(); + action act = { + vector{{"setabitest"_n,config::active_name}}, + setabi { + .account = "setabitest"_n, .abi = bytes(abi.begin(), abi.end()) + } + }; + + 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{{"alice"_n, config::active_name}}, + updateauth { + .account = "alice"_n, .permission = "active"_n, .parent = "owner"_n, .auth = auth + } + }; + + 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 = "first"_n; + action act = { + vector{{"alice"_n, config::active_name}}, + deleteauth { account, permission } + }; + + 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{{"alice"_n, config::active_name}}, + linkauth { account, code, type, requirement } + }; + + 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{{"alice"_n, config::active_name}}, + unlinkauth { account, code, type } + }; + + 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 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_insert_test, dry_run_trx_tester) { try { + set_up_test_contract(); + + // 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 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(); + + // 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 { + 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 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, 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 ); + BOOST_CHECK_EQUAL( prev_auth_sequence, amo->auth_sequence ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END()