From e44cf2e37984819aebf7142ef11259ce8379c76b Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Thu, 13 Jan 2022 13:16:31 -0500 Subject: [PATCH 1/6] Backport CONFIGURABLE_WASM_LIMITS from 2.1 --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/controller.cpp | 62 +- .../include/eosio/chain/chain_config.hpp | 81 +- .../include/eosio/chain/chain_id_type.hpp | 7 + .../include/eosio/chain/chain_snapshot.hpp | 8 +- .../chain/include/eosio/chain/config.hpp | 12 + .../chain/include/eosio/chain/exceptions.hpp | 2 + .../include/eosio/chain/genesis_state.hpp | 19 +- .../eosio/chain/global_property_object.hpp | 68 +- .../chain/include/eosio/chain/kv_config.hpp | 30 + .../eosio/chain/protocol_feature_manager.hpp | 1 + .../chain/include/eosio/chain/wasm_config.hpp | 55 + .../eos-vm-oc/intrinsic_mapping.hpp | 6 +- .../eosio/chain/webassembly/eos-vm.hpp | 3 + .../eosio/chain/webassembly/interface.hpp | 63 ++ libraries/chain/protocol_feature_manager.cpp | 11 + libraries/chain/wasm_config.cpp | 15 + libraries/chain/wasm_interface.cpp | 9 +- libraries/chain/webassembly/privileged.cpp | 36 +- .../runtimes/eos-vm-oc/executor.cpp | 5 + .../chain/webassembly/runtimes/eos-vm.cpp | 32 + libraries/testing/contracts.hpp.in | 1 + .../testing/include/eosio/testing/tester.hpp | 2 + libraries/testing/tester.cpp | 35 + unittests/contracts/test_wasts.hpp | 85 ++ unittests/test-contracts/CMakeLists.txt | 1 + .../wasm_config_bios/CMakeLists.txt | 6 + .../wasm_config_bios/wasm_config_bios.abi | 77 ++ .../wasm_config_bios/wasm_config_bios.cpp | 35 + .../wasm_config_bios/wasm_config_bios.wasm | Bin 0 -> 13135 bytes unittests/wasm_config_tests.cpp | 951 ++++++++++++++++++ unittests/wasm_tests.cpp | 333 +++--- 32 files changed, 1799 insertions(+), 253 deletions(-) create mode 100644 libraries/chain/include/eosio/chain/kv_config.hpp create mode 100644 libraries/chain/include/eosio/chain/wasm_config.hpp create mode 100644 libraries/chain/wasm_config.cpp create mode 100644 unittests/test-contracts/wasm_config_bios/CMakeLists.txt create mode 100644 unittests/test-contracts/wasm_config_bios/wasm_config_bios.abi create mode 100644 unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp create mode 100755 unittests/test-contracts/wasm_config_bios/wasm_config_bios.wasm create mode 100644 unittests/wasm_config_tests.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index e34283c4dfb..9e7087da039 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -99,6 +99,7 @@ add_library( eosio_chain wasm_interface.cpp wasm_eosio_validation.cpp wasm_eosio_injection.cpp + wasm_config.cpp apply_context.cpp abi_serializer.cpp asset.cpp diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4db2e63217f..e60b737906d 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -328,6 +328,7 @@ struct controller_impl { set_activation_handler(); set_activation_handler(); set_activation_handler(); + set_activation_handler(); self.irreversible_block.connect([this](const block_state_ptr& bsp) { wasmif.current_lib(bsp->block_num); @@ -943,6 +944,8 @@ struct controller_impl { // special case for in-place upgrade of global_property_object if (std::is_same::value) { using v2 = legacy::snapshot_global_property_object_v2; + using v3 = legacy::snapshot_global_property_object_v3; + using v4 = legacy::snapshot_global_property_object_v4; if (std::clamp(header.version, v2::minimum_version, v2::maximum_version) == header.version ) { fc::optional genesis = extract_legacy_genesis_state(*snapshot, header.version); @@ -954,7 +957,33 @@ struct controller_impl { section.read_row(legacy_global_properties, db); db.create([&legacy_global_properties,&gs_chain_id](auto& gpo ){ - gpo.initalize_from(legacy_global_properties, gs_chain_id); + gpo.initalize_from(legacy_global_properties, gs_chain_id, kv_database_config{}, + genesis_state::default_initial_wasm_configuration); + }); + }); + return; // early out to avoid default processing + } + + if (std::clamp(header.version, v3::minimum_version, v3::maximum_version) == header.version ) { + snapshot->read_section([&db=this->db]( auto §ion ) { + v3 legacy_global_properties; + section.read_row(legacy_global_properties, db); + + db.create([&legacy_global_properties](auto& gpo ){ + gpo.initalize_from(legacy_global_properties, kv_database_config{}, + genesis_state::default_initial_wasm_configuration); + }); + }); + return; // early out to avoid default processing + } + + if (std::clamp(header.version, v4::minimum_version, v4::maximum_version) == header.version) { + snapshot->read_section([&db = this->db](auto& section) { + v4 legacy_global_properties; + section.read_row(legacy_global_properties, db); + + db.create([&legacy_global_properties](auto& gpo) { + gpo.initalize_from(legacy_global_properties); }); }); return; // early out to avoid default processing @@ -1047,6 +1076,8 @@ struct controller_impl { genesis.initial_configuration.validate(); db.create([&genesis,&chain_id=this->chain_id](auto& gpo ){ gpo.configuration = genesis.initial_configuration; + // TODO: Update this when genesis protocol features are enabled. + gpo.wasm_configuration = genesis_state::default_initial_wasm_configuration; gpo.chain_id = chain_id; }); @@ -3291,11 +3322,23 @@ chain_id_type controller::extract_chain_id(snapshot_reader& snapshot) { } chain_id_type chain_id; - snapshot.read_section([&chain_id]( auto §ion ){ - snapshot_global_property_object global_properties; - section.read_row(global_properties); - chain_id = global_properties.chain_id; - }); + + using v4 = legacy::snapshot_global_property_object_v4; + if (header.version <= v4::maximum_version) { + snapshot.read_section([&chain_id]( auto §ion ){ + v4 global_properties; + section.read_row(global_properties); + chain_id = global_properties.chain_id; + }); + } + else { + snapshot.read_section([&chain_id]( auto §ion ){ + snapshot_global_property_object global_properties; + section.read_row(global_properties); + chain_id = global_properties.chain_id; + }); + } + return chain_id; } @@ -3367,6 +3410,13 @@ void controller_impl::on_activation +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "set_wasm_parameters_packed" ); + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "get_wasm_parameters_packed" ); + } ); +} /// End of protocol feature activation handlers diff --git a/libraries/chain/include/eosio/chain/chain_config.hpp b/libraries/chain/include/eosio/chain/chain_config.hpp index 9e83b09779f..d8f7b990fae 100644 --- a/libraries/chain/include/eosio/chain/chain_config.hpp +++ b/libraries/chain/include/eosio/chain/chain_config.hpp @@ -12,7 +12,30 @@ namespace eosio { namespace chain { * their preference for each of the parameters in this object, and the blockchain runs according to the median of the * values specified by the producers. */ -struct chain_config { +struct chain_config_v0 { + + //order must match parameters as ids are used in serialization + enum { + max_block_net_usage_id, + target_block_net_usage_pct_id, + max_transaction_net_usage_id, + base_per_transaction_net_usage_id, + net_usage_leeway_id, + context_free_discount_net_usage_num_id, + context_free_discount_net_usage_den_id, + max_block_cpu_usage_id, + target_block_cpu_usage_pct_id, + max_transaction_cpu_usage_id, + min_transaction_cpu_usage_id, + max_transaction_lifetime_id, + deferred_trx_expiration_window_id, + max_transaction_delay_id, + max_inline_action_size_id, + max_inline_action_depth_id, + max_authority_depth_id, + PARAMS_COUNT + }; + uint64_t max_block_net_usage; ///< the maxiumum net usage in instructions for a block uint32_t target_block_net_usage_pct; ///< the target percent (1% == 100, 100%= 10,000) of maximum net usage; exceeding this triggers congestion handling uint32_t max_transaction_net_usage; ///< the maximum objectively measured net usage that the chain will allow regardless of account limits @@ -35,29 +58,16 @@ struct chain_config { void validate()const; + inline const chain_config_v0& v0() const { + return *this; + } + template - friend Stream& operator << ( Stream& out, const chain_config& c ) { - return out << "Max Block Net Usage: " << c.max_block_net_usage << ", " - << "Target Block Net Usage Percent: " << ((double)c.target_block_net_usage_pct / (double)config::percent_1) << "%, " - << "Max Transaction Net Usage: " << c.max_transaction_net_usage << ", " - << "Base Per-Transaction Net Usage: " << c.base_per_transaction_net_usage << ", " - << "Net Usage Leeway: " << c.net_usage_leeway << ", " - << "Context-Free Data Net Usage Discount: " << (double)c.context_free_discount_net_usage_num * 100.0 / (double)c.context_free_discount_net_usage_den << "% , " - - << "Max Block CPU Usage: " << c.max_block_cpu_usage << ", " - << "Target Block CPU Usage Percent: " << ((double)c.target_block_cpu_usage_pct / (double)config::percent_1) << "%, " - << "Max Transaction CPU Usage: " << c.max_transaction_cpu_usage << ", " - << "Min Transaction CPU Usage: " << c.min_transaction_cpu_usage << ", " - - << "Max Transaction Lifetime: " << c.max_transaction_lifetime << ", " - << "Deferred Transaction Expiration Window: " << c.deferred_trx_expiration_window << ", " - << "Max Transaction Delay: " << c.max_transaction_delay << ", " - << "Max Inline Action Size: " << c.max_inline_action_size << ", " - << "Max Inline Action Depth: " << c.max_inline_action_depth << ", " - << "Max Authority Depth: " << c.max_authority_depth << "\n"; + friend Stream& operator << ( Stream& out, const chain_config_v0& c ) { + return c.log(out) << "\n"; } - friend inline bool operator ==( const chain_config& lhs, const chain_config& rhs ) { + friend inline bool operator ==( const chain_config_v0& lhs, const chain_config_v0& rhs ) { return std::tie( lhs.max_block_net_usage, lhs.target_block_net_usage_pct, lhs.max_transaction_net_usage, @@ -97,13 +107,38 @@ struct chain_config { ); }; - friend inline bool operator !=( const chain_config& lhs, const chain_config& rhs ) { return !(lhs == rhs); } + friend inline bool operator !=( const chain_config_v0& lhs, const chain_config_v0& rhs ) { return !(lhs == rhs); } +protected: + template + Stream& log(Stream& out) const{ + return out << "Max Block Net Usage: " << max_block_net_usage << ", " + << "Target Block Net Usage Percent: " << ((double)target_block_net_usage_pct / (double)config::percent_1) << "%, " + << "Max Transaction Net Usage: " << max_transaction_net_usage << ", " + << "Base Per-Transaction Net Usage: " << base_per_transaction_net_usage << ", " + << "Net Usage Leeway: " << net_usage_leeway << ", " + << "Context-Free Data Net Usage Discount: " << (double)context_free_discount_net_usage_num * 100.0 / (double)context_free_discount_net_usage_den << "% , " + + << "Max Block CPU Usage: " << max_block_cpu_usage << ", " + << "Target Block CPU Usage Percent: " << ((double)target_block_cpu_usage_pct / (double)config::percent_1) << "%, " + << "Max Transaction CPU Usage: " << max_transaction_cpu_usage << ", " + << "Min Transaction CPU Usage: " << min_transaction_cpu_usage << ", " + + << "Max Transaction Lifetime: " << max_transaction_lifetime << ", " + << "Deferred Transaction Expiration Window: " << deferred_trx_expiration_window << ", " + << "Max Transaction Delay: " << max_transaction_delay << ", " + << "Max Inline Action Size: " << max_inline_action_size << ", " + << "Max Inline Action Depth: " << max_inline_action_depth << ", " + << "Max Authority Depth: " << max_authority_depth; + } }; +//after adding 1st value to chain_config_v1 change this using to point to v1 +using chain_config = chain_config_v0; + } } // namespace eosio::chain -FC_REFLECT(eosio::chain::chain_config, +FC_REFLECT(eosio::chain::chain_config_v0, (max_block_net_usage)(target_block_net_usage_pct) (max_transaction_net_usage)(base_per_transaction_net_usage)(net_usage_leeway) (context_free_discount_net_usage_num)(context_free_discount_net_usage_den) diff --git a/libraries/chain/include/eosio/chain/chain_id_type.hpp b/libraries/chain/include/eosio/chain/chain_id_type.hpp index 5a1cc52de86..be0dcd780ec 100644 --- a/libraries/chain/include/eosio/chain/chain_id_type.hpp +++ b/libraries/chain/include/eosio/chain/chain_id_type.hpp @@ -17,6 +17,11 @@ namespace eosio { namespace chain { + namespace legacy { + struct snapshot_global_property_object_v3; + struct snapshot_global_property_object_v4; + } + struct chain_id_type : public fc::sha256 { using fc::sha256::sha256; @@ -51,6 +56,8 @@ namespace chain { friend struct controller_impl; friend class global_property_object; friend struct snapshot_global_property_object; + friend struct legacy::snapshot_global_property_object_v3; + friend struct legacy::snapshot_global_property_object_v4; }; } } // namespace eosio::chain diff --git a/libraries/chain/include/eosio/chain/chain_snapshot.hpp b/libraries/chain/include/eosio/chain/chain_snapshot.hpp index a92e9be8695..0e44b654ed5 100644 --- a/libraries/chain/include/eosio/chain/chain_snapshot.hpp +++ b/libraries/chain/include/eosio/chain/chain_snapshot.hpp @@ -16,10 +16,16 @@ struct chain_snapshot_header { * - WebAuthn keys * - wtmsig block siganatures: the block header state changed to include producer authorities and additional signatures * - removed genesis_state and added chain ID to global_property_object + * 4: Updated for v3.0.0 protocol features: + * - forwards compatible with versions 2 and 3 + * - kv database + * - Configurable wasm limits + * 5: Updated for v3.0.0 eos features: + * - chain_config update */ static constexpr uint32_t minimum_compatible_version = 2; - static constexpr uint32_t current_version = 3; + static constexpr uint32_t current_version = 4; uint32_t version = current_version; diff --git a/libraries/chain/include/eosio/chain/config.hpp b/libraries/chain/include/eosio/chain/config.hpp index 6461f7c8368..bde0c11362f 100644 --- a/libraries/chain/include/eosio/chain/config.hpp +++ b/libraries/chain/include/eosio/chain/config.hpp @@ -84,6 +84,18 @@ const static uint16_t default_controller_thread_pool_size = 2; const static uint32_t default_max_variable_signature_length = 16384u; const static uint32_t default_max_nonprivileged_inline_action_size = 4 * 1024; // 4 KB +const static uint32_t default_max_wasm_mutable_global_bytes = 1024; +const static uint32_t default_max_wasm_table_elements = 1024; +const static uint32_t default_max_wasm_section_elements = 8192; +const static uint32_t default_max_wasm_linear_memory_init = 64*1024; +const static uint32_t default_max_wasm_func_local_bytes = 8192; +const static uint32_t default_max_wasm_nested_structures = 1024; +const static uint32_t default_max_wasm_symbol_bytes = 8192; +const static uint32_t default_max_wasm_module_bytes = 20*1024*1024; +const static uint32_t default_max_wasm_code_bytes = 20*1024*1024; +const static uint32_t default_max_wasm_pages = 528; +const static uint32_t default_max_wasm_call_depth = 251; + const static uint32_t min_net_usage_delta_between_base_and_max_for_trx = 10*1024; // Should be large enough to allow recovery from badly set blockchain parameters without a hard fork // (unless net_usage_leeway is set to 0 and so are the net limits of all accounts that can help with resetting blockchain parameters). diff --git a/libraries/chain/include/eosio/chain/exceptions.hpp b/libraries/chain/include/eosio/chain/exceptions.hpp index c19cc97858e..6b5b3593d86 100644 --- a/libraries/chain/include/eosio/chain/exceptions.hpp +++ b/libraries/chain/include/eosio/chain/exceptions.hpp @@ -541,6 +541,8 @@ namespace eosio { namespace chain { 3160009, "No wasm file found" ) FC_DECLARE_DERIVED_EXCEPTION( abi_file_not_found, contract_exception, 3160010, "No abi file found" ) + FC_DECLARE_DERIVED_EXCEPTION( wasm_config_unknown_version, contract_exception, + 3160015, "Unknown wasm_config version" ) FC_DECLARE_DERIVED_EXCEPTION( producer_exception, chain_exception, 3170000, "Producer exception" ) diff --git a/libraries/chain/include/eosio/chain/genesis_state.hpp b/libraries/chain/include/eosio/chain/genesis_state.hpp index 5e5b643a1b5..66cb1f3a3a9 100644 --- a/libraries/chain/include/eosio/chain/genesis_state.hpp +++ b/libraries/chain/include/eosio/chain/genesis_state.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -15,7 +16,7 @@ struct genesis_state { static const string eosio_root_key; - chain_config initial_configuration = { + chain_config_v0 initial_configuration = { .max_block_net_usage = config::default_max_block_net_usage, .target_block_net_usage_pct = config::default_target_block_net_usage_pct, .max_transaction_net_usage = config::default_max_transaction_net_usage, @@ -37,6 +38,20 @@ struct genesis_state { .max_authority_depth = config::default_max_auth_depth, }; + static constexpr wasm_config default_initial_wasm_configuration { + .max_mutable_global_bytes = config::default_max_wasm_mutable_global_bytes, + .max_table_elements = config::default_max_wasm_table_elements, + .max_section_elements = config::default_max_wasm_section_elements, + .max_linear_memory_init = config::default_max_wasm_linear_memory_init, + .max_func_local_bytes = config::default_max_wasm_func_local_bytes, + .max_nested_structures = config::default_max_wasm_nested_structures, + .max_symbol_bytes = config::default_max_wasm_symbol_bytes, + .max_module_bytes = config::default_max_wasm_module_bytes, + .max_code_bytes = config::default_max_wasm_code_bytes, + .max_pages = config::default_max_wasm_pages, + .max_call_depth = config::default_max_wasm_call_depth + }; + time_point initial_timestamp; public_key_type initial_key; @@ -58,6 +73,6 @@ struct genesis_state { } } // namespace eosio::chain - +// @swap initial_timestamp initial_key initial_configuration FC_REFLECT(eosio::chain::genesis_state, (initial_timestamp)(initial_key)(initial_configuration)) diff --git a/libraries/chain/include/eosio/chain/global_property_object.hpp b/libraries/chain/include/eosio/chain/global_property_object.hpp index 57d6c745fe2..326604ea489 100644 --- a/libraries/chain/include/eosio/chain/global_property_object.hpp +++ b/libraries/chain/include/eosio/chain/global_property_object.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -26,7 +28,29 @@ namespace eosio { namespace chain { optional proposed_schedule_block_num; producer_schedule_type proposed_schedule; - chain_config configuration; + chain_config_v0 configuration; + }; + struct snapshot_global_property_object_v3 { + static constexpr uint32_t minimum_version = 3; + static constexpr uint32_t maximum_version = 3; + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_global_property_object_v3 is no longer needed"); + + optional proposed_schedule_block_num; + producer_authority_schedule proposed_schedule; + chain_config_v0 configuration; + chain_id_type chain_id; + }; + struct snapshot_global_property_object_v4 { + static constexpr uint32_t minimum_version = 4; + static constexpr uint32_t maximum_version = 4; + static_assert(chain_snapshot_header::minimum_compatible_version <= maximum_version, "snapshot_global_property_object_v4 is no longer needed"); + + optional proposed_schedule_block_num; + producer_authority_schedule proposed_schedule; + chain_config_v0 configuration; + chain_id_type chain_id; + kv_database_config kv_configuration; + wasm_config wasm_configuration; }; } @@ -46,12 +70,34 @@ namespace eosio { namespace chain { shared_producer_authority_schedule proposed_schedule; chain_config configuration; chain_id_type chain_id; + kv_database_config kv_configuration; + wasm_config wasm_configuration; - void initalize_from( const legacy::snapshot_global_property_object_v2& legacy, const chain_id_type& chain_id_val ) { + void initalize_from( const legacy::snapshot_global_property_object_v2& legacy, const chain_id_type& chain_id_val, const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { proposed_schedule_block_num = legacy.proposed_schedule_block_num; proposed_schedule = producer_authority_schedule(legacy.proposed_schedule).to_shared(proposed_schedule.producers.get_allocator()); configuration = legacy.configuration; chain_id = chain_id_val; + kv_configuration = kv_config_val; + wasm_configuration = wasm_config_val; + } + + void initalize_from( const legacy::snapshot_global_property_object_v3& legacy, const kv_database_config& kv_config_val, const wasm_config& wasm_config_val ) { + proposed_schedule_block_num = legacy.proposed_schedule_block_num; + proposed_schedule = legacy.proposed_schedule.to_shared(proposed_schedule.producers.get_allocator()); + configuration = legacy.configuration; + chain_id = legacy.chain_id; + kv_configuration = kv_config_val; + wasm_configuration = wasm_config_val; + } + + void initalize_from( const legacy::snapshot_global_property_object_v4& legacy ) { + proposed_schedule_block_num = legacy.proposed_schedule_block_num; + proposed_schedule = legacy.proposed_schedule.to_shared(proposed_schedule.producers.get_allocator()); + configuration = legacy.configuration; + chain_id = legacy.chain_id; + kv_configuration = legacy.kv_configuration; + wasm_configuration = legacy.wasm_configuration; } }; @@ -70,6 +116,8 @@ namespace eosio { namespace chain { producer_authority_schedule proposed_schedule; chain_config configuration; chain_id_type chain_id; + kv_database_config kv_configuration; + wasm_config wasm_configuration; }; namespace detail { @@ -79,7 +127,7 @@ namespace eosio { namespace chain { using snapshot_type = snapshot_global_property_object; static snapshot_global_property_object to_snapshot_row( const global_property_object& value, const chainbase::database& ) { - return {value.proposed_schedule_block_num, producer_authority_schedule::from_shared(value.proposed_schedule), value.configuration, value.chain_id}; + return {value.proposed_schedule_block_num, producer_authority_schedule::from_shared(value.proposed_schedule), value.configuration, value.chain_id, value.kv_configuration, value.wasm_configuration}; } static void from_snapshot_row( snapshot_global_property_object&& row, global_property_object& value, chainbase::database& ) { @@ -87,6 +135,8 @@ namespace eosio { namespace chain { value.proposed_schedule = row.proposed_schedule.to_shared(value.proposed_schedule.producers.get_allocator()); value.configuration = row.configuration; value.chain_id = row.chain_id; + value.kv_configuration = row.kv_configuration; + value.wasm_configuration = row.wasm_configuration; } }; } @@ -121,17 +171,25 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::dynamic_global_property_object, eosio::chain::dynamic_global_property_multi_index) FC_REFLECT(eosio::chain::global_property_object, - (proposed_schedule_block_num)(proposed_schedule)(configuration)(chain_id) + (proposed_schedule_block_num)(proposed_schedule)(configuration)(chain_id)(kv_configuration)(wasm_configuration) ) FC_REFLECT(eosio::chain::legacy::snapshot_global_property_object_v2, (proposed_schedule_block_num)(proposed_schedule)(configuration) ) -FC_REFLECT(eosio::chain::snapshot_global_property_object, +FC_REFLECT(eosio::chain::legacy::snapshot_global_property_object_v3, (proposed_schedule_block_num)(proposed_schedule)(configuration)(chain_id) ) +FC_REFLECT(eosio::chain::legacy::snapshot_global_property_object_v4, + (proposed_schedule_block_num)(proposed_schedule)(configuration)(chain_id)(kv_configuration)(wasm_configuration) + ) + +FC_REFLECT(eosio::chain::snapshot_global_property_object, + (proposed_schedule_block_num)(proposed_schedule)(configuration)(chain_id)(kv_configuration)(wasm_configuration) + ) + FC_REFLECT(eosio::chain::dynamic_global_property_object, (global_action_sequence) ) diff --git a/libraries/chain/include/eosio/chain/kv_config.hpp b/libraries/chain/include/eosio/chain/kv_config.hpp new file mode 100644 index 00000000000..7330fa09acb --- /dev/null +++ b/libraries/chain/include/eosio/chain/kv_config.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace eosio { namespace chain { + + /** + * @brief limits for a kv database. + * + * Each database (ram or disk, currently) has its own limits for these parameters. + * The key and value limits apply when adding or modifying elements. They may be reduced + * below existing database entries. + */ + struct kv_database_config { + std::uint32_t max_key_size = 0; ///< the maximum size in bytes of a key + std::uint32_t max_value_size = 0; ///< the maximum size in bytes of a value + std::uint32_t max_iterators = 0; ///< the maximum number of iterators that a contract can have simultaneously. + }; + inline bool operator==(const kv_database_config& lhs, const kv_database_config& rhs) { + return std::tie(lhs.max_key_size, lhs.max_value_size, lhs.max_iterators) + == std::tie(rhs.max_key_size, rhs.max_value_size, rhs.max_iterators); + } + inline bool operator!=(const kv_database_config& lhs, const kv_database_config& rhs) { + return !(lhs == rhs); + } +}} + +FC_REFLECT(eosio::chain::kv_database_config, (max_key_size)(max_value_size)(max_iterators)) + diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index 81d22c15b0e..80341530dda 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -23,6 +23,7 @@ enum class builtin_protocol_feature_t : uint32_t { ram_restrictions, webauthn_key, wtmsig_block_signatures, + configurable_wasm_limits, }; struct protocol_feature_subjective_restrictions { diff --git a/libraries/chain/include/eosio/chain/wasm_config.hpp b/libraries/chain/include/eosio/chain/wasm_config.hpp new file mode 100644 index 00000000000..bcfdd9014d3 --- /dev/null +++ b/libraries/chain/include/eosio/chain/wasm_config.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +namespace eosio { namespace chain { + +struct wasm_config { + std::uint32_t max_mutable_global_bytes; + std::uint32_t max_table_elements; + std::uint32_t max_section_elements; + std::uint32_t max_linear_memory_init; + std::uint32_t max_func_local_bytes; + std::uint32_t max_nested_structures; + std::uint32_t max_symbol_bytes; + std::uint32_t max_module_bytes; + std::uint32_t max_code_bytes; + std::uint32_t max_pages; + std::uint32_t max_call_depth; + void validate() const; +}; + +inline constexpr bool operator==(const wasm_config& lhs, const wasm_config& rhs) { + return lhs.max_mutable_global_bytes == rhs.max_mutable_global_bytes && + lhs.max_table_elements == rhs.max_table_elements && + lhs.max_section_elements == rhs.max_section_elements && + lhs.max_linear_memory_init == rhs.max_linear_memory_init && + lhs.max_func_local_bytes == rhs.max_func_local_bytes && + lhs.max_nested_structures == rhs.max_nested_structures && + lhs.max_symbol_bytes == rhs.max_symbol_bytes && + lhs.max_module_bytes == rhs.max_module_bytes && + lhs.max_code_bytes == rhs.max_code_bytes && + lhs.max_pages == rhs.max_pages && + lhs.max_call_depth == rhs.max_call_depth; +} +inline constexpr bool operator!=(const wasm_config& lhs, const wasm_config& rhs) { + return !(lhs == rhs); +} + +}} + +FC_REFLECT(eosio::chain::wasm_config, + (max_mutable_global_bytes) + (max_table_elements) + (max_section_elements) + (max_linear_memory_init) + (max_func_local_bytes) + (max_nested_structures) + (max_symbol_bytes) + (max_module_bytes) + (max_code_bytes) + (max_pages) + (max_call_depth) +) diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp index 73d344caf83..f43d2ca632f 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm-oc/intrinsic_mapping.hpp @@ -253,7 +253,9 @@ inline constexpr auto get_intrinsic_table() { "eosio_injection._eosio_i32_to_f64", "eosio_injection._eosio_i64_to_f64", "eosio_injection._eosio_ui32_to_f64", - "eosio_injection._eosio_ui64_to_f64" + "eosio_injection._eosio_ui64_to_f64", + "env.get_wasm_parameters_packed", + "env.set_wasm_parameters_packed" ); } inline constexpr std::size_t find_intrinsic_index(std::string_view hf) { @@ -267,4 +269,4 @@ inline constexpr std::size_t find_intrinsic_index(std::string_view hf) { inline constexpr std::size_t intrinsic_table_size() { return std::tuple_size::value; } -}}} \ No newline at end of file +}}} diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp index 8f468c57a32..6f4bf2ceccd 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,8 @@ using namespace eosio::vm; void validate(const bytes& code, const whitelisted_intrinsics_type& intrinsics ); +void validate(const bytes& code, const wasm_config& cfg, const whitelisted_intrinsics_type& intrinsics ); + struct apply_options; template diff --git a/libraries/chain/include/eosio/chain/webassembly/interface.hpp b/libraries/chain/include/eosio/chain/webassembly/interface.hpp index f20e5541a39..cdc20bbde6f 100644 --- a/libraries/chain/include/eosio/chain/webassembly/interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/interface.hpp @@ -82,6 +82,69 @@ namespace webassembly { */ void get_resource_limits(account_name account, legacy_ptr ram_bytes, legacy_ptr net_weight, legacy_ptr cpu_weight) const; + /** + * Get the current wasm limits configuration. + * + * The structure of the parameters is as follows: + * + * - max_mutable_global_bytes + * The maximum total size (in bytes) used for mutable globals. + * i32 and f32 consume 4 bytes and i64 and f64 consume 8 bytes. + * Const globals are not included in this count. + * + * - max_table_elements + * The maximum number of elements of a table. + * + * - max_section_elements + * The maximum number of elements in each section. + * + * - max_linear_memory_init + * The size (in bytes) of the range of memory that may be initialized. + * Data segments may use the range [0, max_linear_memory_init). + * + * - max_func_local_bytes + * The maximum total size (in bytes) used by parameters and local variables in a function. + * + * - max_nested_structures + * The maximum nesting depth of structured control instructions. + * The function itself is included in this count. + * + * - max_symbol_bytes + * The maximum size (in bytes) of names used for import and export. + * + * - max_module_bytes + * The maximum total size (in bytes) of a wasm module. + * + * - max_code_bytes + * The maximum size (in bytes) of each function body. + * + * - max_pages + * The maximum number of 64 KiB pages of linear memory that a contract can use. + * Enforced when an action is executed. The initial size of linear memory is also checked at setcode. + * + * - max_call_depth + * The maximum number of functions that may be on the stack. Enforced when an action is executed. + * + * @ingroup privileged + * + * @param[out] packed_parameters the ouput for the parameters. + * @param max_version has no effect, but should be 0. + * + * @return the size of the packed parameters if packed_parameters is empty, otherwise it returns the amount of data written in packed_parameters. + */ + uint32_t get_wasm_parameters_packed( span packed_parameters, uint32_t max_version ) const; + + /** + * Set the configuration for wasm limits. + * + * See get_wasm_parameters_packed documentation for more details on the structure of the packed_parameters. + * + * @ingroup privileged + * + * @param packed_parameters - a span containing the packed configuration to set. + */ + void set_wasm_parameters_packed( span packed_parameters ); + /** * Proposes a schedule change using the legacy producer key format. * diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index d61a7fe25c4..452283b19e6 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -183,6 +183,17 @@ and may have additional signatures in a block extension with ID `2`. Privileged Contracts: may continue to use `set_proposed_producers` as they have; may use a new `set_proposed_producers_ex` intrinsic to access extended features. +*/ + {} + } ) + ( builtin_protocol_feature_t::configurable_wasm_limits, builtin_protocol_feature_spec{ + "CONFIGURABLE_WASM_LIMITS", + fc::variant("67f5f1e92cbf6f7276e7b3fc8c2ad23e63448e657641a1e5de69bccd114542d6").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: CONFIGURABLE_WASM_LIMITS + +Allows privileged contracts to set the constraints on WebAssembly code. */ {} } ) diff --git a/libraries/chain/wasm_config.cpp b/libraries/chain/wasm_config.cpp new file mode 100644 index 00000000000..2968b0f1337 --- /dev/null +++ b/libraries/chain/wasm_config.cpp @@ -0,0 +1,15 @@ +#include +#include + +using namespace eosio::chain; + +void wasm_config::validate() const { + EOS_ASSERT(max_section_elements >= 4, action_validate_exception, "max_section_elements cannot be less than 4"); + EOS_ASSERT(max_func_local_bytes >= 8, action_validate_exception, "max_func_local_bytes cannot be less than 8"); + EOS_ASSERT(max_nested_structures >= 1, action_validate_exception, "max_nested_structures cannot be less than 1"); + EOS_ASSERT(max_symbol_bytes >= 32, action_validate_exception, "max_symbol_bytes cannot be less than 32"); + EOS_ASSERT(max_module_bytes >= 256, action_validate_exception, "max_module_bytes cannot be less than 256"); + EOS_ASSERT(max_code_bytes >= 32, action_validate_exception, "max_code_bytes cannot be less than 32"); + EOS_ASSERT(max_pages >= 1, action_validate_exception, "max_pages cannot be less than 1"); + EOS_ASSERT(max_call_depth >= 2, action_validate_exception, "max_call_depth cannot be less than 2"); +} diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index fc340024591..5a53207bc3a 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -39,6 +39,13 @@ namespace eosio { namespace chain { wasm_interface::~wasm_interface() {} void wasm_interface::validate(const controller& control, const bytes& code) { + const auto& pso = control.db().get(); + + if (control.is_builtin_activated(builtin_protocol_feature_t::configurable_wasm_limits)) { + const auto& gpo = control.get_global_properties(); + webassembly::eos_vm_runtime::validate( code, gpo.wasm_configuration, pso.whitelisted_intrinsics ); + return; + } Module module; try { Serialization::MemoryInputStream stream((U8*)code.data(), code.size()); @@ -52,8 +59,6 @@ namespace eosio { namespace chain { wasm_validations::wasm_binary_validation validator(control, module); validator.validate(); - const auto& pso = control.db().get(); - webassembly::eos_vm_runtime::validate( code, pso.whitelisted_intrinsics ); //there are a couple opportunties for improvement here-- diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 1bd040f49cf..418a7ee1691 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -82,6 +82,36 @@ namespace eosio { namespace chain { namespace webassembly { return context.control.set_proposed_producers( std::move(producers) ); } + uint32_t interface::get_wasm_parameters_packed( span packed_parameters, uint32_t max_version ) const { + auto& gpo = context.control.get_global_properties(); + auto& params = gpo.wasm_configuration; + uint32_t version = std::min( max_version, uint32_t(0) ); + + auto s = fc::raw::pack_size( params ); + if ( packed_parameters.size() == 0 ) + return s; + + if ( s <= packed_parameters.size() ) { + datastream ds( packed_parameters.data(), s ); + fc::raw::pack(ds, version); + fc::raw::pack(ds, params); + } + return s; + } + void interface::set_wasm_parameters_packed( span packed_parameters ) { + datastream ds( packed_parameters.data(), packed_parameters.size() ); + uint32_t version; + chain::wasm_config cfg; + fc::raw::unpack(ds, version); + EOS_ASSERT(version == 0, wasm_config_unknown_version, "set_wasm_parameters_packed: Unknown version: ${version}", ("version", version)); + fc::raw::unpack(ds, cfg); + cfg.validate(); + context.db.modify( context.control.get_global_properties(), + [&]( auto& gprops ) { + gprops.wasm_configuration = cfg; + } + ); + } int64_t interface::set_proposed_producers( legacy_span packed_producer_schedule) { datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); std::vector producers; @@ -115,12 +145,12 @@ namespace eosio { namespace chain { namespace webassembly { uint32_t interface::get_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) const { auto& gpo = context.control.get_global_properties(); - auto s = fc::raw::pack_size( gpo.configuration ); + auto s = fc::raw::pack_size( gpo.configuration.v0() ); if( packed_blockchain_parameters.size() == 0 ) return s; if ( s <= packed_blockchain_parameters.size() ) { datastream ds( packed_blockchain_parameters.data(), s ); - fc::raw::pack(ds, gpo.configuration); + fc::raw::pack(ds, gpo.configuration.v0()); return s; } return 0; @@ -128,7 +158,7 @@ namespace eosio { namespace chain { namespace webassembly { void interface::set_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) { datastream ds( packed_blockchain_parameters.data(), packed_blockchain_parameters.size() ); - chain::chain_config cfg; + chain::chain_config_v0 cfg; fc::raw::unpack(ds, cfg); cfg.validate(); context.db.modify( context.control.get_global_properties(), diff --git a/libraries/chain/webassembly/runtimes/eos-vm-oc/executor.cpp b/libraries/chain/webassembly/runtimes/eos-vm-oc/executor.cpp index 466841eb144..ca32f8cd0e4 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm-oc/executor.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm-oc/executor.cpp @@ -157,6 +157,11 @@ void executor::execute(const code_descriptor& code, memory& mem, apply_context& uint64_t max_call_depth = eosio::chain::wasm_constraints::maximum_call_depth+1; uint64_t max_pages = eosio::chain::wasm_constraints::maximum_linear_memory/eosio::chain::wasm_constraints::wasm_page_size; + if(context.control.is_builtin_activated(builtin_protocol_feature_t::configurable_wasm_limits)) { + const wasm_config& config = context.control.get_global_properties().wasm_configuration; + max_call_depth = config.max_call_depth; + max_pages = config.max_pages; + } stack.reset(max_call_depth); EOS_ASSERT(code.starting_memory_pages <= (int)max_pages, wasm_execution_error, "Initial memory out of range"); diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index 6febf979223..f89f42b16b4 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -81,6 +81,32 @@ void validate(const bytes& code, const whitelisted_intrinsics_type& intrinsics) } } +void validate( const bytes& code, const wasm_config& cfg, const whitelisted_intrinsics_type& intrinsics ) { + EOS_ASSERT(code.size() <= cfg.max_module_bytes, wasm_serialization_error, "Code too large"); + wasm_code_ptr code_ptr((uint8_t*)code.data(), code.size()); + try { + eos_vm_null_backend_t bkend(code_ptr, code.size(), nullptr, cfg); + // check import signatures + eos_vm_host_functions_t::resolve(bkend.get_module()); + // check that the imports are all currently enabled + const auto& imports = bkend.get_module().imports; + for(std::uint32_t i = 0; i < imports.size(); ++i) { + EOS_ASSERT(std::string_view((char*)imports[i].module_str.raw(), imports[i].module_str.size()) == "env" && + is_intrinsic_whitelisted(intrinsics, std::string_view((char*)imports[i].field_str.raw(), imports[i].field_str.size())), + wasm_serialization_error, "${module}.${fn} unresolveable", + ("module", std::string((char*)imports[i].module_str.raw(), imports[i].module_str.size())) + ("fn", std::string((char*)imports[i].field_str.raw(), imports[i].field_str.size()))); + } + // check apply + uint32_t apply_idx = bkend.get_module().get_exported_function("apply"); + EOS_ASSERT(apply_idx < std::numeric_limits::max(), wasm_serialization_error, "apply not exported"); + const vm::func_type& apply_type = bkend.get_module().get_function_type(apply_idx); + EOS_ASSERT((apply_type == vm::host_function{{vm::i64, vm::i64, vm::i64}, {}}), wasm_serialization_error, "apply has wrong type"); + } catch(vm::exception& e) { + EOS_THROW(wasm_serialization_error, e.detail()); + } +} + // Be permissive on apply. struct apply_options { std::uint32_t max_pages = wasm_constraints::maximum_linear_memory/wasm_constraints::wasm_page_size; @@ -105,6 +131,10 @@ class eos_vm_instantiated_module : public wasm_instantiated_module_interface { _instantiated_module->set_wasm_allocator(&context.control.get_wasm_allocator()); _runtime->_bkend = _instantiated_module.get(); apply_options opts; + if(context.control.is_builtin_activated(builtin_protocol_feature_t::configurable_wasm_limits)) { + const wasm_config& config = context.control.get_global_properties().wasm_configuration; + opts = {config.max_pages, config.max_call_depth}; + } auto fn = [&]() { eosio::chain::webassembly::interface iface(context); _runtime->_bkend->initialize(&iface, opts); @@ -230,6 +260,8 @@ REGISTER_HOST_FUNCTION(activate_feature, privileged_check); REGISTER_LEGACY_HOST_FUNCTION(preactivate_feature, privileged_check); REGISTER_HOST_FUNCTION(set_resource_limits, privileged_check); REGISTER_LEGACY_HOST_FUNCTION(get_resource_limits, privileged_check); +REGISTER_HOST_FUNCTION(get_wasm_parameters_packed, privileged_check); +REGISTER_HOST_FUNCTION(set_wasm_parameters_packed, privileged_check); REGISTER_LEGACY_HOST_FUNCTION(set_proposed_producers, privileged_check); REGISTER_LEGACY_HOST_FUNCTION(set_proposed_producers_ex, privileged_check); REGISTER_LEGACY_HOST_FUNCTION(get_blockchain_parameters_packed, privileged_check); diff --git a/libraries/testing/contracts.hpp.in b/libraries/testing/contracts.hpp.in index 8c835359de1..0c816c1342c 100644 --- a/libraries/testing/contracts.hpp.in +++ b/libraries/testing/contracts.hpp.in @@ -62,6 +62,7 @@ namespace eosio { MAKE_READ_WASM_ABI(test_api_db, test_api_db, test-contracts) MAKE_READ_WASM_ABI(test_api_multi_index, test_api_multi_index, test-contracts) MAKE_READ_WASM_ABI(test_ram_limit, test_ram_limit, test-contracts) + MAKE_READ_WASM_ABI(wasm_config_bios, wasm_config_bios, test-contracts) }; } /// eosio::testing } /// eosio diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 46b868e4a5e..1b21019c028 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -62,6 +62,7 @@ namespace eosio { namespace testing { old_bios_only, preactivate_feature_only, preactivate_feature_and_new_bios, + old_wasm_parser, full }; @@ -380,6 +381,7 @@ namespace eosio { namespace testing { void schedule_protocol_features_wo_preactivation(const vector feature_digests); void preactivate_protocol_features(const vector feature_digests); + void preactivate_builtin_protocol_features(const std::vector& features); void preactivate_all_builtin_protocol_features(); static genesis_state default_genesis() { diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index ae88d56b737..62704ef6b9f 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -214,6 +214,28 @@ namespace eosio { namespace testing { set_before_producer_authority_bios_contract(); break; } + case setup_policy::old_wasm_parser: { + schedule_preactivate_protocol_feature(); + produce_block(); + set_before_producer_authority_bios_contract(); + preactivate_builtin_protocol_features({ + builtin_protocol_feature_t::only_link_to_existing_permission, + builtin_protocol_feature_t::replace_deferred, + builtin_protocol_feature_t::no_duplicate_deferred_id, + builtin_protocol_feature_t::fix_linkauth_restriction, + builtin_protocol_feature_t::disallow_empty_producer_schedule, + builtin_protocol_feature_t::restrict_action_to_self, + builtin_protocol_feature_t::only_bill_first_authorizer, + builtin_protocol_feature_t::forward_setcode, + builtin_protocol_feature_t::get_sender, + builtin_protocol_feature_t::ram_restrictions, + builtin_protocol_feature_t::webauthn_key, + builtin_protocol_feature_t::wtmsig_block_signatures + }); + produce_block(); + set_bios_contract(); + break; + } case setup_policy::full: { schedule_preactivate_protocol_feature(); produce_block(); @@ -1119,6 +1141,19 @@ namespace eosio { namespace testing { } } + void base_tester::preactivate_builtin_protocol_features(const std::vector& builtin_features) { + const auto& pfs = control->get_protocol_feature_manager().get_protocol_feature_set(); + + // This behavior is disabled by configurable_wasm_limits + std::vector features; + for(builtin_protocol_feature_t feature : builtin_features ) { + if( auto digest = pfs.get_builtin_digest( feature ) ) { + features.push_back( *digest ); + } + } + preactivate_protocol_features(features); + } + void base_tester::preactivate_all_builtin_protocol_features() { const auto& pfm = control->get_protocol_feature_manager(); const auto& pfs = pfm.get_protocol_feature_set(); diff --git a/unittests/contracts/test_wasts.hpp b/unittests/contracts/test_wasts.hpp index 935874bbba5..248d4bf3ed5 100644 --- a/unittests/contracts/test_wasts.hpp +++ b/unittests/contracts/test_wasts.hpp @@ -252,6 +252,35 @@ static const char biggest_memory_wast[] = R"=====( ) )====="; +static const char biggest_memory_variable_wast[] = R"=====( +(module + (import "env" "eosio_assert" (func $$eosio_assert (param i32 i32))) + (import "env" "require_auth" (func $$require_auth (param i64))) + (table 0 anyfunc) + (memory $$0 ${MAX_WASM_PAGES}) + (export "memory" (memory $$0)) + (export "apply" (func $$apply)) + (func $$apply (param $$0 i64) (param $$1 i64) (param $$2 i64) + (call $$eosio_assert + (i32.eq + (grow_memory (i32.wrap/i64 (get_local 2))) + (i32.const ${MAX_WASM_PAGES}) + ) + (i32.const 0) + ) + (call $$eosio_assert + (i32.eq + (grow_memory (i32.const 1)) + (i32.const -1) + ) + (i32.const 32) + ) + ) + (data (i32.const 0) "failed grow_memory") + (data (i32.const 32) "grow_memory unexpected success") +) +)====="; + static const char too_big_memory_wast[] = R"=====( (module (table 0 anyfunc) @@ -262,6 +291,35 @@ static const char too_big_memory_wast[] = R"=====( ) )====="; +static const char max_memory_wast[] = R"=====( +(module + (import "env" "eosio_assert" (func $$eosio_assert (param i32 i32))) + (import "env" "require_auth" (func $$require_auth (param i64))) + (table 0 anyfunc) + (memory $$0 ${INIT_WASM_PAGES} ${MAX_WASM_PAGES}) + (export "memory" (memory $$0)) + (export "apply" (func $$apply)) + (func $$apply (param $$0 i64) (param $$1 i64) (param $$2 i64) + (call $$eosio_assert + (i32.eq + (grow_memory (i32.wrap/i64 (get_local 2))) + (i32.const ${INIT_WASM_PAGES}) + ) + (i32.const 0) + ) + (call $$eosio_assert + (i32.eq + (grow_memory (i32.const 1)) + (i32.const -1) + ) + (i32.const 32) + ) + ) + (data (i32.const 0) "failed grow_memory") + (data (i32.const 32) "grow_memory unexpected success") +) +)====="; + static const char valid_sparse_table[] = R"=====( (module (table 1024 anyfunc) @@ -282,6 +340,15 @@ static const char too_big_table[] = R"=====( ) )====="; +static const char variable_table[] = R"=====( +(module + (table ${TABLE_SIZE} anyfunc) + (func (export "apply") (param i64 i64 i64)) + (elem (i32.const 0) 0) + (elem (i32.const ${TABLE_OFFSET}) 0 0) +) +)====="; + static const char memory_init_borderline[] = R"=====( (module (memory $0 16) @@ -471,6 +538,17 @@ static const char table_init_oob_no_table_wast[] = R"=====( ) )====="; +static const char table_init_oob_empty_wast[] = R"=====( +(module + (type $mahsig (func (param i64) (param i64) (param i64))) + (table 1024 anyfunc) + (export "apply" (func $apply)) + (func $apply (param $0 i64) (param $1 i64) (param $2 i64) + ) + (elem (i32.const 1025)) +) +)====="; + static const char global_protection_none_get_wast[] = R"=====( (module (export "apply" (func $apply)) @@ -638,6 +716,13 @@ static const char import_injected_wast[] = " (func $apply (param $0 i64) (param $1 i64) (param $2 i64))" \ ")"; +static const char import_wrong_signature_wast[] = R"=====( +(module + (import "env" "eosio_assert" (func (param i32 i64))) + (func (export "apply") (param i64 i64 i64)) +) +)====="; + static const char memory_growth_memset_store[] = R"=====( (module (export "apply" (func $apply)) diff --git a/unittests/test-contracts/CMakeLists.txt b/unittests/test-contracts/CMakeLists.txt index 7c48729a524..ccccd88b360 100644 --- a/unittests/test-contracts/CMakeLists.txt +++ b/unittests/test-contracts/CMakeLists.txt @@ -23,3 +23,4 @@ add_subdirectory( test_api ) add_subdirectory( test_api_db ) add_subdirectory( test_api_multi_index ) add_subdirectory( test_ram_limit ) +add_subdirectory( wasm_config_bios ) diff --git a/unittests/test-contracts/wasm_config_bios/CMakeLists.txt b/unittests/test-contracts/wasm_config_bios/CMakeLists.txt new file mode 100644 index 00000000000..e22bc811cf4 --- /dev/null +++ b/unittests/test-contracts/wasm_config_bios/CMakeLists.txt @@ -0,0 +1,6 @@ +if( EOSIO_COMPILE_TEST_CONTRACTS ) + add_contract( wasm_config_bios wasm_config_bios wasm_config_bios.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/wasm_config_bios.wasm ${CMAKE_CURRENT_BINARY_DIR}/wasm_config_bios.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/wasm_config_bios.abi ${CMAKE_CURRENT_BINARY_DIR}/wasm_config_bios.abi COPYONLY ) +endif() diff --git a/unittests/test-contracts/wasm_config_bios/wasm_config_bios.abi b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.abi new file mode 100644 index 00000000000..5907801e317 --- /dev/null +++ b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.abi @@ -0,0 +1,77 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.1", + "types": [], + "structs": [ + { + "name": "setwparams", + "base": "", + "fields": [ + { + "name": "cfg", + "type": "wasm_config" + } + ] + }, + { + "name": "wasm_config", + "base": "", + "fields": [ + { + "name": "max_mutable_global_bytes", + "type": "uint32" + }, + { + "name": "max_table_elements", + "type": "uint32" + }, + { + "name": "max_section_elements", + "type": "uint32" + }, + { + "name": "max_linear_memory_init", + "type": "uint32" + }, + { + "name": "max_func_local_bytes", + "type": "uint32" + }, + { + "name": "max_nested_structures", + "type": "uint32" + }, + { + "name": "max_symbol_bytes", + "type": "uint32" + }, + { + "name": "max_module_bytes", + "type": "uint32" + }, + { + "name": "max_code_bytes", + "type": "uint32" + }, + { + "name": "max_pages", + "type": "uint32" + }, + { + "name": "max_call_depth", + "type": "uint32" + } + ] + } + ], + "actions": [ + { + "name": "setwparams", + "type": "setwparams", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [] +} \ No newline at end of file diff --git a/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp new file mode 100644 index 00000000000..f3a9c82ca83 --- /dev/null +++ b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp @@ -0,0 +1,35 @@ +#include + +extern "C" __attribute__((eosio_wasm_import)) void set_wasm_parameters_packed(const void*, std::size_t); +#ifdef USE_EOSIO_CDT_1_7_X +extern "C" __attribute__((eosio_wasm_import)) uint32_t read_action_data( void* msg, uint32_t len ); +extern "C" __attribute__((eosio_wasm_import)) uint32_t action_data_size(); +#endif + +struct wasm_config { + std::uint32_t max_mutable_global_bytes; + std::uint32_t max_table_elements; + std::uint32_t max_section_elements; + std::uint32_t max_linear_memory_init; + std::uint32_t max_func_local_bytes; + std::uint32_t max_nested_structures; + std::uint32_t max_symbol_bytes; + std::uint32_t max_module_bytes; + std::uint32_t max_code_bytes; + std::uint32_t max_pages; + std::uint32_t max_call_depth; +}; + +struct internal_config { + uint32_t version; + wasm_config config; +}; + +class [[eosio::contract]] wasm_config_bios : public eosio::contract { + public: + using contract::contract; + [[eosio::action]] void setwparams(const wasm_config& cfg) { + internal_config config{0, cfg}; + set_wasm_parameters_packed(&config, sizeof(config)); + } +}; diff --git a/unittests/test-contracts/wasm_config_bios/wasm_config_bios.wasm b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.wasm new file mode 100755 index 0000000000000000000000000000000000000000..e844249fd3c7ab79c0e60e0b103b693fed6ca28a GIT binary patch literal 13135 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 xo5#0+ZxLUi-n +#include +#include + +#include +#include + +#include +#include +#include + +#include "test_wasts.hpp" + +#include + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; +namespace data = boost::unit_test::data; + +#ifdef NON_VALIDATING_TEST +#define TESTER tester +#else +#define TESTER validating_tester +#endif + +namespace { +struct wasm_config_tester : TESTER { + wasm_config_tester() { + set_abi(config::system_account_name, contracts::wasm_config_bios_abi().data()); + set_code(config::system_account_name, contracts::wasm_config_bios_wasm()); + bios_abi_ser = *get_resolver()(config::system_account_name); + } + void set_wasm_params(const wasm_config& params) { + signed_transaction trx; + trx.actions.emplace_back(vector{{"eosio"_n,config::active_name}}, "eosio"_n, "setwparams"_n, + bios_abi_ser.variant_to_binary("setwparams", fc::mutable_variant_object()("cfg", params), + abi_serializer::create_yield_function( abi_serializer_max_time ))); + trx.actions[0].authorization = vector{{"eosio"_n,config::active_name}}; + set_transaction_headers(trx); + trx.sign(get_private_key("eosio"_n, "active"), control->get_chain_id()); + push_transaction(trx); + } + // Pushes an empty action + void push_action(account_name account) { + signed_transaction trx; + trx.actions.push_back({{{account,config::active_name}}, account, name(), {}}); + set_transaction_headers(trx); + trx.sign(get_private_key( account, "active" ), control->get_chain_id()); + push_transaction(trx); + } + chain::abi_serializer bios_abi_ser; +}; + + +std::string make_locals_wasm(int n_params, int n_locals, int n_stack) +{ + std::stringstream ss; + ss << "(module "; + ss << " (func (export \"apply\") (param i64 i64 i64))"; + ss << " (func "; + for(int i = 0; i < n_params; i+=8) + ss << "(param i64)"; + for(int i = 0; i < n_locals; i+=8) + ss << "(local i64)"; + for(int i = 0; i < n_stack; i+=8) + ss << "(i64.const 0)"; + for(int i = 0; i < n_stack; i+=8) + ss << "(drop)"; + ss << " )"; + ss << ")"; + return ss.str(); +} + +} + +BOOST_AUTO_TEST_SUITE(wasm_config_tests) + +struct old_wasm_tester : tester { + old_wasm_tester() : tester{setup_policy::old_wasm_parser} {} +}; + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_mutable_global_bytes, data::make({ 4096, 8192 , 16384 }) * data::make({0, 1}), n_globals, oversize) { + produce_block(); + create_accounts({"globals"_n}); + produce_block(); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_mutable_global_bytes = n_globals; + set_wasm_params(params); + + std::string code = [&] { + std::ostringstream ss; + ss << "(module "; + ss << " (func $eosio_assert (import \"env\" \"eosio_assert\") (param i32 i32))"; + ss << " (memory 1)"; + for(int i = 0; i < n_globals + oversize; i += 4) + ss << "(global (mut i32) (i32.const " << i << "))"; + ss << " (func (export \"apply\") (param i64 i64 i64)"; + for(int i = 0; i < n_globals + oversize; i += 4) + ss << "(call $eosio_assert (i32.eq (get_global " << i/4 << ") (i32.const " << i << ")) (i32.const 0))"; + ss << " )"; + ss << ")"; + return ss.str(); + }(); + + if(oversize) { + BOOST_CHECK_THROW(set_code("globals"_n, code.c_str()), wasm_exception); + produce_block(); + } else { + set_code("globals"_n, code.c_str()); + push_action("globals"_n); + produce_block(); + --params.max_mutable_global_bytes; + set_wasm_params(params); + push_action("globals"_n); + } +} + + +static const char many_funcs_wast[] = R"=====( +(module + (export "apply" (func 0)) + ${SECTION} +) +)====="; +static const char one_func[] = "(func (param i64 i64 i64))"; + +static const char many_types_wast[] = R"=====( +(module + ${SECTION} + (export "apply" (func 0)) + (func (type 0)) +) +)====="; +static const char one_type[] = "(type (func (param i64 i64 i64)))"; + +static const char many_imports_wast[] = R"=====( +(module + ${SECTION} + (func (export "apply") (param i64 i64 i64)) +) +)====="; +static const char one_import[] = "(func (import \"env\" \"abort\"))"; + +static const char many_globals_wast[] = R"=====( +(module + ${SECTION} + (func (export "apply") (param i64 i64 i64)) +) +)====="; +static const char one_global[] = "(global i32 (i32.const 0))"; + +static const char many_exports_wast[] = R"=====( +(module + ${SECTION} + (func (export "apply") (param i64 i64 i64)) +) +)====="; +static const char one_export[] = "(export \"fn${N}\" (func 0))"; + +static const char many_elem_wast[] = R"=====( +(module + (table 0 anyfunc) + ${SECTION} + (func (export "apply") (param i64 i64 i64)) +) +)====="; +static const char one_elem[] = "(elem (i32.const 0))"; + +static const char many_data_wast[] = R"=====( +(module + (memory 1) + ${SECTION} + (func (export "apply") (param i64 i64 i64)) +) +)====="; +static const char one_data[] = "(data (i32.const 0))"; + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_section_elements, + data::make({1024, 8192, 16384}) * data::make({0, 1}) * + (data::make({many_funcs_wast, many_types_wast, many_imports_wast, many_globals_wast, many_elem_wast, many_data_wast}) ^ + data::make({one_func , one_type , one_import, one_global , one_elem , one_data})), + n_elements, oversize, wast, one_element) { + produce_blocks(2); + create_accounts({"section"_n}); + produce_block(); + + std::string buf; + for(int i = 0; i < n_elements + oversize; ++i) { + buf += one_element; + } + std::string code = fc::format_string(wast, fc::mutable_variant_object("SECTION", buf)); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_section_elements = n_elements; + set_wasm_params(params); + + if(oversize) { + BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception); + } else { + set_code("section"_n, code.c_str()); + push_action("section"_n); + --params.max_section_elements; + set_wasm_params(params); + produce_block(); + push_action("section"_n); + produce_block(); + set_code("section"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception); + } +} + +// export has to be formatted slightly differently because export names +// must be unique and apply must be one of the exports. +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_section_elements_export, + data::make({1024, 8192, 16384}) * data::make({0, 1}), + n_elements, oversize) { + produce_blocks(2); + create_accounts({"section"_n}); + produce_block(); + + std::string buf; + for(int i = 0; i < n_elements + oversize - 1; ++i) { + buf += "(export \"fn$"; + buf += std::to_string(i); + buf += "\" (func 0))"; + } + std::string code = fc::format_string(many_exports_wast, fc::mutable_variant_object("SECTION", buf)); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_section_elements = n_elements; + set_wasm_params(params); + + if(oversize) { + BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception); + } else { + set_code("section"_n, code.c_str()); + push_action("section"_n); + --params.max_section_elements; + set_wasm_params(params); + produce_block(); + push_action("section"_n); + produce_block(); + set_code("section"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("section"_n, code.c_str()), wasm_exception); + } +} + +static const char max_linear_memory_wast[] = R"=====( +(module + (import "env" "eosio_assert" (func $$eosio_assert (param i32 i32))) + (memory 4) + (data (i32.const ${OFFSET}) "\11\22\33\44") + (func (export "apply") (param i64 i64 i64) + (call $$eosio_assert (i32.eq (i32.load (i32.const ${OFFSET})) (i32.const 0x44332211)) (i32.const 0)) + ) +) +)====="; + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_linear_memory_init, + data::make({32768, 65536, 86513, 131072}) * data::make({0, 1}), + n_init, oversize) { + produce_blocks(2); + create_accounts({"initdata"_n}); + produce_block(); + + std::string code = fc::format_string(max_linear_memory_wast, fc::mutable_variant_object("OFFSET", n_init + oversize - 4)); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_linear_memory_init = n_init; + set_wasm_params(params); + + if(oversize) { + BOOST_CHECK_THROW(set_code("initdata"_n, code.c_str()), wasm_exception); + } else { + set_code("initdata"_n, code.c_str()); + push_action("initdata"_n); + --params.max_linear_memory_init; + set_wasm_params(params); + produce_block(); + push_action("initdata"_n); + produce_block(); + set_code("initdata"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("initdata"_n, code.c_str()), wasm_exception); + } +} + +static const std::vector> func_local_params = { + // Default value of max_func_local_bytes + {8192, 0, false, true}, {4096, 4096, false, true}, {0, 8192, false, true}, + {8192 + 1, 0, false, false}, {4096 + 1, 4096, false, false}, {0, 8192 + 1, false, false}, + // Larger than the default + {16384, 0, true, true}, {8192, 8192, true, true}, {0, 16384, true, true}, + {16384 + 1, 0, true, false}, {8192 + 1, 8192, true, false}, {0, 16384 + 1, true, false} +}; + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_func_local_bytes, data::make({0, 8192, 16384}) * data::make(func_local_params), n_params, n_locals, n_stack, set_high, expect_success) { + produce_blocks(2); + create_accounts({"stackz"_n}); + produce_block(); + + auto def_params = genesis_state::default_initial_wasm_configuration; + auto high_params = def_params; + high_params.max_func_local_bytes = 16384; + auto low_params = def_params; + low_params.max_func_local_bytes = 4096; + + if(set_high) { + set_wasm_params(high_params); + produce_block(); + } + + auto pushit = [&]() { + action act; + act.account = "stackz"_n; + act.name = name(); + act.authorization = vector{{"stackz"_n,config::active_name}}; + signed_transaction trx; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id()); + push_transaction(trx); + }; + + std::string code = make_locals_wasm(n_params, n_locals, n_stack); + + if(expect_success) { + set_code("stackz"_n, code.c_str()); + produce_block(); + pushit(); + set_wasm_params(low_params); + produce_block(); + pushit(); // Only checked at set_code. + set_code("stackz"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception); + produce_block(); + } else { + BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception); + produce_block(); + } +} + +BOOST_FIXTURE_TEST_CASE(max_func_local_bytes_mixed, wasm_config_tester) { + produce_blocks(2); + create_accounts({"stackz"_n}); + produce_block(); + + std::string code; + { + std::stringstream ss; + ss << "(module "; + ss << " (func (export \"apply\") (param i64 i64 i64))"; + ss << " (func "; + ss << " (local i32 i64 i64 f32 f32 f32 f32 f64 f64 f64 f64 f64 f64 f64 f64)"; + for(int i = 0; i < 16; ++i) + ss << "(i32.const 0)"; + for(int i = 0; i < 32; ++i) + ss << "(i64.const 0)"; + for(int i = 0; i < 64; ++i) + ss << "(f32.const 0)"; + for(int i = 0; i < 128; ++i) + ss << "(f64.const 0)"; + ss << "(drop)(f64.const 0)"; + ss << "(return)"; + for(int i = 0; i < 8192; ++i) + ss << "(i64.const 0)"; + ss << "(return)"; + ss << " )"; + ss << ")"; + code = ss.str(); + } + auto params = genesis_state::default_initial_wasm_configuration; + params.max_func_local_bytes = 4 + 16 + 16 + 64 + 64 + 256 + 256 + 1024; + set_code("stackz"_n, code.c_str()); + set_code("stackz"_n, std::vector{}); + produce_block(); + --params.max_func_local_bytes; + set_wasm_params(params); + BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception); +} + +static const std::vector> old_func_local_params = { + {8192, 0, true}, {4096, 4096, true}, {0, 8192, true}, + {8192 + 1, 0, false}, {4096 + 1, 4096, false}, {0, 8192 + 1, false}, +}; + +BOOST_DATA_TEST_CASE_F(old_wasm_tester, max_func_local_bytes_old, data::make({0, 8192, 16384}) * data::make(old_func_local_params), n_stack, n_params, n_locals, expect_success) { + produce_blocks(2); + create_accounts({"stackz"_n}); + produce_block(); + + auto pushit = [&]() { + action act; + act.account = "stackz"_n; + act.name = name(); + act.authorization = vector{{"stackz"_n,config::active_name}}; + signed_transaction trx; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( "stackz"_n, "active" ), control->get_chain_id()); + push_transaction(trx); + }; + + std::string code = make_locals_wasm(n_params, n_locals, n_stack); + + if(expect_success) { + set_code("stackz"_n, code.c_str()); + produce_block(); + pushit(); + produce_block(); + } else { + BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception); + produce_block(); + } +} + +BOOST_FIXTURE_TEST_CASE(max_func_local_bytes_mixed_old, old_wasm_tester) { + produce_blocks(2); + create_accounts({"stackz"_n}); + produce_block(); + + std::string code; + { + std::stringstream ss; + ss << "(module "; + ss << " (func (export \"apply\") (param i64 i64 i64))"; + ss << " (func "; + ss << " (param i32 i64 i64 f32 f32 f32 f32 f64 f64 f64 f64 f64 f64 f64 f64)"; + for(int i = 0; i < 16; ++i) + ss << "(local i32)"; + for(int i = 0; i < 32; ++i) + ss << "(local i64)"; + for(int i = 0; i < 64; ++i) + ss << "(local f32)"; + for(int i = 0; i < 128; ++i) + ss << "(local f64)"; + // pad to 8192 bytes + for(int i = 0; i < 1623; ++i) + ss << "(local i32)"; + ss << " )"; + ss << ")"; + code = ss.str(); + } + set_code("stackz"_n, code.c_str()); + produce_block(); + // add one more parameter + code.replace(code.find("param i32"), 5, "param f32"); + BOOST_CHECK_THROW(set_code("stackz"_n, code.c_str()), wasm_exception); +} + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_table_elements, data::make({512, 2048}) * data::make({0, 1}), max_table_elements, oversize) { + produce_block(); + create_accounts( { "table"_n } ); + produce_block(); + + auto pushit = [&]{ + signed_transaction trx; + trx.actions.push_back({{{"table"_n,config::active_name}}, "table"_n, name(), {}}); + set_transaction_headers(trx); + trx.sign(get_private_key( "table"_n, "active" ), control->get_chain_id()); + push_transaction(trx); + }; + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_table_elements = max_table_elements; + set_wasm_params(params); + + std::string code = fc::format_string(variable_table, fc::mutable_variant_object()("TABLE_SIZE", max_table_elements + oversize)("TABLE_OFFSET", max_table_elements - 2)); + if(!oversize) { + set_code("table"_n, code.c_str()); + pushit(); + produce_block(); + + --params.max_table_elements; + set_wasm_params(params); + pushit(); + } else { + BOOST_CHECK_THROW(set_code("table"_n, code.c_str()), wasm_exception); + } +} + +BOOST_DATA_TEST_CASE_F(wasm_config_tester, max_nested_structures, + data::make({512, 1024, 2048}) * data::make({0, 1}), + n_nesting, oversize) { + produce_block(); + create_accounts( { "nested"_n } ); + produce_block(); + + std::string code = [&]{ + std::ostringstream ss; + ss << "(module "; + ss << " (func (export \"apply\") (param i64 i64 i64) "; + for(int i = 0; i < n_nesting + oversize - 1; ++i) + ss << "(block "; + for(int i = 0; i < n_nesting + oversize - 1; ++i) + ss << ")"; + ss << " )"; + ss << ")"; + return ss.str(); + }(); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_nested_structures = n_nesting; + set_wasm_params(params); + + if(oversize) { + BOOST_CHECK_THROW(set_code("nested"_n, code.c_str()), wasm_exception); + } else { + set_code("nested"_n, code.c_str()); + push_action("nested"_n); + --params.max_nested_structures; + set_wasm_params(params); + produce_block(); + push_action("nested"_n); + produce_block(); + set_code("nested"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("nested"_n, code.c_str()), wasm_exception); + } +} + +static const char max_symbol_func_wast[] = R"=====( +(module + (func (export "apply") (param i64 i64 i64)) + (func (export "${NAME}")) +) +)====="; + +static const char max_symbol_global_wast[] = R"=====( +(module + (global (export "${NAME}") i32 (i32.const 0)) + (func (export "apply") (param i64 i64 i64)) +) +)====="; + +static const char max_symbol_memory_wast[] = R"=====( +(module + (memory (export "${NAME}") 0) + (func (export "apply") (param i64 i64 i64)) +) +)====="; + +static const char max_symbol_table_wast[] = R"=====( +(module + (table (export "${NAME}") 0 anyfunc) + (func (export "apply") (param i64 i64 i64)) +) +)====="; + +BOOST_DATA_TEST_CASE_F( wasm_config_tester, max_symbol_bytes_export, data::make({4096, 8192, 16384}) * data::make({0, 1}) * + data::make({max_symbol_func_wast, max_symbol_global_wast, max_symbol_memory_wast, max_symbol_table_wast}), + n_symbol, oversize, wast ) { + produce_blocks(2); + + create_accounts({"bigname"_n}); + + std::string name(n_symbol + oversize, 'x'); + std::string code = fc::format_string(wast, fc::mutable_variant_object("NAME", name)); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_symbol_bytes = n_symbol; + set_wasm_params(params); + + if(oversize) { + BOOST_CHECK_THROW(set_code("bigname"_n, code.c_str()), wasm_exception); + } else { + set_code("bigname"_n, code.c_str()); + push_action("bigname"_n); + --params.max_symbol_bytes; + set_wasm_params(params); + produce_block(); + push_action("bigname"_n); + produce_block(); + set_code("bigname"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("bigname"_n, code.c_str()), wasm_exception); + } +} + +static const char max_symbol_import_wast[] = R"=====( +(module + (func (import "env" "db_idx_long_double_find_secondary") (param i64 i64 i64 i32 i32) (result i32)) + (func (export "apply") (param i64 i64 i64)) +) +)====="; + +BOOST_FIXTURE_TEST_CASE( max_symbol_bytes_import, wasm_config_tester ) { + produce_blocks(2); + create_accounts({"bigname"_n}); + + constexpr int n_symbol = 33; + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_symbol_bytes = n_symbol; + set_wasm_params(params); + + set_code("bigname"_n, max_symbol_import_wast); + push_action("bigname"_n); + --params.max_symbol_bytes; + set_wasm_params(params); + produce_block(); + push_action("bigname"_n); + produce_block(); + set_code("bigname"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("bigname"_n, max_symbol_import_wast), wasm_exception); +} + +static const std::vector small_contract_wasm{ + 0x00, 'a', 's', 'm', 0x01, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x01, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, + 0x03, 0x02, 0x01, 0x00, + 0x07, 0x09, 0x01, 0x05, 'a', 'p', 'p', 'l', 'y', 0x00, 0x00, + 0x0a, 0xE3, 0x01, 0x01, 0xE0, 0x01, + 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0b +}; + +BOOST_FIXTURE_TEST_CASE( max_module_bytes, wasm_config_tester ) { + produce_blocks(2); + create_accounts({"bigmodule"_n}); + + const int n_module = small_contract_wasm.size(); + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_module_bytes = n_module; + set_wasm_params(params); + + set_code("bigmodule"_n, small_contract_wasm); + push_action("bigmodule"_n); + --params.max_module_bytes; + set_wasm_params(params); + produce_block(); + push_action("bigmodule"_n); + produce_block(); + set_code("bigmodule"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("bigmodule"_n, small_contract_wasm), wasm_exception); +} + +BOOST_FIXTURE_TEST_CASE( max_code_bytes, wasm_config_tester ) { + produce_blocks(2); + create_accounts({"bigcode"_n}); + + constexpr int n_code = 224; + + auto params = genesis_state::default_initial_wasm_configuration; + params.max_code_bytes = n_code; + set_wasm_params(params); + + set_code("bigcode"_n, small_contract_wasm); + push_action("bigcode"_n); + --params.max_code_bytes; + set_wasm_params(params); + produce_block(); + push_action("bigcode"_n); + produce_block(); + set_code("bigcode"_n, vector{}); // clear existing code + BOOST_CHECK_THROW(set_code("bigcode"_n, small_contract_wasm), wasm_exception); +} + +static const char access_biggest_memory_wast[] = R"=====( +(module + (memory 0) + (func (export "apply") (param i64 i64 i64) + (drop (grow_memory (i32.wrap/i64 (get_local 2)))) + (i32.store (i32.mul (i32.wrap/i64 (get_local 2)) (i32.const 65536)) (i32.const 0)) + ) +) +)====="; + +static const char intrinsic_biggest_memory_wast[] = R"=====( +(module + (import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32))) + (memory 0) + (func (export "apply") (param i64 i64 i64) + (drop (grow_memory (i32.wrap/i64 (get_local 2)))) + (drop (call $memcpy (i32.mul (i32.wrap/i64 (get_local 2)) (i32.const 65536)) (i32.const 0) (i32.const 0))) + ) +) +)====="; + +BOOST_FIXTURE_TEST_CASE( max_pages, wasm_config_tester ) try { + produce_blocks(2); + + create_accounts( { "bigmem"_n, "accessmem"_n, "intrinsicmem"_n } ); + set_code("accessmem"_n, access_biggest_memory_wast); + set_code("intrinsicmem"_n, intrinsic_biggest_memory_wast); + produce_block(); + auto params = genesis_state::default_initial_wasm_configuration; + for(uint64_t max_pages : {600, 400}) { // above and below the default limit + params.max_pages = max_pages; + set_wasm_params(params); + produce_block(); + + string biggest_memory_wast_f = fc::format_string(biggest_memory_variable_wast, fc::mutable_variant_object( + "MAX_WASM_PAGES", params.max_pages - 1)); + + set_code("bigmem"_n, biggest_memory_wast_f.c_str()); + produce_blocks(1); + + auto pushit = [&](uint64_t extra_pages) { + action act; + act.account = "bigmem"_n; + act.name = name(extra_pages); + act.authorization = vector{{"bigmem"_n,config::active_name}}; + signed_transaction trx; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( "bigmem"_n, "active" ), control->get_chain_id()); + //but should not be able to grow beyond largest page + push_transaction(trx); + }; + + // verify that page accessibility cannot leak across wasm executions + auto checkaccess = [&](uint64_t pagenum) { + action act; + act.account = "accessmem"_n; + act.name = name(pagenum); + act.authorization = vector{{"accessmem"_n,config::active_name}}; + signed_transaction trx; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( "accessmem"_n, "active" ), control->get_chain_id()); + BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception); + }; + + + // verify checking of intrinsic arguments + auto pushintrinsic = [&](uint64_t pages) { + action act; + act.account = "intrinsicmem"_n; + act.name = name(pages); + act.authorization = vector{{"intrinsicmem"_n,config::active_name}}; + signed_transaction trx; + trx.actions.push_back(act); + + set_transaction_headers(trx); + trx.sign(get_private_key( "intrinsicmem"_n, "active" ), control->get_chain_id()); + BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception); + }; + + pushit(1); + checkaccess(max_pages - 1); + pushintrinsic(max_pages); + produce_blocks(1); + + // Increase memory limit + ++params.max_pages; + set_wasm_params(params); + produce_block(); + pushit(2); + checkaccess(max_pages); + + // Decrease memory limit + params.max_pages -= 2; + set_wasm_params(params); + produce_block(); + pushit(0); + + // Reduce memory limit below initial memory + --params.max_pages; + set_wasm_params(params); + produce_block(); + BOOST_CHECK_THROW(pushit(0), eosio::chain::wasm_exception); + + params.max_pages = max_pages; + set_wasm_params(params); + string too_big_memory_wast_f = fc::format_string(too_big_memory_wast, fc::mutable_variant_object( + "MAX_WASM_PAGES_PLUS_ONE", params.max_pages+1)); + BOOST_CHECK_THROW(set_code("bigmem"_n, too_big_memory_wast_f.c_str()), eosio::chain::wasm_exception); + + // Check that the max memory defined by the contract is respected + string memory_over_max_wast = fc::format_string(max_memory_wast, fc::mutable_variant_object() + ("INIT_WASM_PAGES", params.max_pages - 3) + ("MAX_WASM_PAGES", params.max_pages - 1)); + set_code("bigmem"_n, memory_over_max_wast.c_str()); + produce_block(); + pushit(2); + + // Move max_pages in between the contract's initial and maximum memories + params.max_pages -= 2; + set_wasm_params(params); + produce_block(); + pushit(1); + + // Move it back + params.max_pages += 2; + set_wasm_params(params); + produce_block(); + pushit(2); + } +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( call_depth, wasm_config_tester ) try { + produce_block(); + create_accounts( {"depth"_n} ); + produce_block(); + + uint32_t max_call_depth = 150; + auto high_params = genesis_state::default_initial_wasm_configuration; + high_params.max_call_depth = max_call_depth + 1; + wasm_config low_params = high_params; + low_params.max_call_depth = 50; + set_wasm_params(high_params); + produce_block(); + + signed_transaction trx; + trx.actions.emplace_back(vector{{"depth"_n,config::active_name}}, "depth"_n, ""_n, bytes{}); + trx.actions[0].authorization = vector{{"depth"_n,config::active_name}}; + + auto pushit = [&]() { + produce_block(); + trx.signatures.clear(); + set_transaction_headers(trx); + trx.sign(get_private_key("depth"_n, "active"), control->get_chain_id()); + push_transaction(trx); + }; + + //strictly wasm recursion to maximum_call_depth & maximum_call_depth+1 + string wasm_depth_okay = fc::format_string(depth_assert_wasm, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth)); + set_code("depth"_n, wasm_depth_okay.c_str()); + pushit(); + + // The depth should not be cached. + set_wasm_params(low_params); + BOOST_CHECK_THROW(pushit(), wasm_execution_error); + set_wasm_params(high_params); + produce_block(); + pushit(); + produce_block(); + + string wasm_depth_one_over = fc::format_string(depth_assert_wasm, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth+1)); + set_code("depth"_n, wasm_depth_one_over.c_str()); + BOOST_CHECK_THROW(pushit(), wasm_execution_error); + + //wasm recursion but call an intrinsic as the last function instead + string intrinsic_depth_okay = fc::format_string(depth_assert_intrinsic, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth)); + set_code("depth"_n, intrinsic_depth_okay.c_str()); + pushit(); + + string intrinsic_depth_one_over = fc::format_string(depth_assert_intrinsic, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth+1)); + set_code("depth"_n, intrinsic_depth_one_over.c_str()); + BOOST_CHECK_THROW(pushit(), wasm_execution_error); + + //add a float operation in the mix to ensure any injected softfloat call doesn't count against limit + string wasm_float_depth_okay = fc::format_string(depth_assert_wasm_float, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth)); + set_code("depth"_n, wasm_float_depth_okay.c_str()); + pushit(); + + string wasm_float_depth_one_over = fc::format_string(depth_assert_wasm_float, fc::mutable_variant_object() + ("MAX_DEPTH", max_call_depth+1)); + set_code("depth"_n, wasm_float_depth_one_over.c_str()); + BOOST_CHECK_THROW(pushit(), wasm_execution_error); + +} FC_LOG_AND_RETHROW() + +// This contract is one of the smallest that can be used to reset +// the wasm parameters. It should be impossible to set parameters +// that would prevent it from being set on the eosio account and executing. +static const char min_set_parameters_wast[] = R"======( +(module + (import "env" "set_wasm_parameters_packed" (func $set_wasm_parameters_packed (param i32 i32))) + (import "env" "read_action_data" (func $read_action_data (param i32 i32) (result i32))) + (memory 1) + (func (export "apply") (param i64 i64 i64) + (br_if 0 (i32.eqz (i32.eqz (i32.wrap/i64 (get_local 2))))) + (drop (call $read_action_data (i32.const 4) (i32.const 44))) + (call $set_wasm_parameters_packed (i32.const 0) (i32.const 48)) + ) +) +)======"; + +BOOST_FIXTURE_TEST_CASE(reset_chain_tests, wasm_config_tester) { + produce_block(); + + wasm_config min_params = { + .max_mutable_global_bytes = 0, + .max_table_elements = 0, + .max_section_elements = 4, + .max_linear_memory_init = 0, + .max_func_local_bytes = 8, + .max_nested_structures = 1, + .max_symbol_bytes = 32, + .max_module_bytes = 256, + .max_code_bytes = 32, + .max_pages = 1, + .max_call_depth = 2 + }; + + auto check_minimal = [&](auto& member) { + if (member > 0) { + --member; + BOOST_CHECK_THROW(set_wasm_params(min_params), fc::exception); + ++member; + } + }; + check_minimal(min_params.max_mutable_global_bytes); + check_minimal(min_params.max_table_elements); + check_minimal(min_params.max_section_elements); + check_minimal(min_params.max_func_local_bytes); + check_minimal(min_params.max_linear_memory_init); + check_minimal(min_params.max_nested_structures); + check_minimal(min_params.max_symbol_bytes); + check_minimal(min_params.max_module_bytes); + check_minimal(min_params.max_code_bytes); + check_minimal(min_params.max_pages); + check_minimal(min_params.max_call_depth); + + set_wasm_params(min_params); + produce_block(); + + // Reset parameters and system contract + { + signed_transaction trx; + auto make_setcode = [](const std::vector& code) { + return setcode{ "eosio"_n, 0, 0, bytes(code.begin(), code.end()) }; + }; + trx.actions.push_back({ { { "eosio"_n, config::active_name} }, make_setcode(wast_to_wasm(min_set_parameters_wast)) }); + trx.actions.push_back({ { { "eosio"_n, config::active_name} }, "eosio"_n, ""_n, fc::raw::pack(genesis_state::default_initial_wasm_configuration) }); + trx.actions.push_back({ { { "eosio"_n, config::active_name} }, make_setcode(contracts::eosio_bios_wasm()) }); + set_transaction_headers(trx); + trx.sign(get_private_key("eosio"_n, "active"), control->get_chain_id()); + push_transaction(trx); + } + produce_block(); + // Make sure that a normal contract works + set_wasm_params(genesis_state::default_initial_wasm_configuration); + produce_block(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/wasm_tests.cpp b/unittests/wasm_tests.cpp index d816f85b54d..07ba74547dd 100644 --- a/unittests/wasm_tests.cpp +++ b/unittests/wasm_tests.cpp @@ -15,6 +15,8 @@ #include #include +#include +#include #include #include @@ -37,6 +39,7 @@ using namespace eosio; using namespace eosio::chain; using namespace eosio::testing; using namespace fc; +namespace bdata = boost::unit_test::data; struct assertdef { @@ -68,6 +71,11 @@ FC_REFLECT_EMPTY(provereset); BOOST_AUTO_TEST_SUITE(wasm_tests) +#warning Change this back to using TESTER +struct old_wasm_tester : tester { + old_wasm_tester() : tester{setup_policy::old_wasm_parser} {} +}; + /** * Prove that action reading and assertions are working */ @@ -640,7 +648,10 @@ BOOST_FIXTURE_TEST_CASE( check_global_reset, TESTER ) try { } FC_LOG_AND_RETHROW() //Make sure we can create a wasm with maximum pages, but not grow it any -BOOST_FIXTURE_TEST_CASE( big_memory, TESTER ) try { +BOOST_DATA_TEST_CASE_F( old_wasm_tester, big_memory, bdata::make({false, true}), activate_wasm_config ) try { + if(activate_wasm_config) + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); + produce_blocks(2); @@ -669,11 +680,13 @@ BOOST_FIXTURE_TEST_CASE( big_memory, TESTER ) try { string too_big_memory_wast_f = fc::format_string(too_big_memory_wast, fc::mutable_variant_object( "MAX_WASM_PAGES_PLUS_ONE", eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024)+1)); - BOOST_CHECK_THROW(set_code(N(bigmem), too_big_memory_wast_f.c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(bigmem), too_big_memory_wast_f.c_str()), eosio::chain::wasm_exception); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( table_init_tests, TESTER ) try { +BOOST_DATA_TEST_CASE_F( old_wasm_tester, table_init_tests, bdata::make({false, true}), activate_wasm_config ) try { + if(activate_wasm_config) + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); produce_blocks(2); create_accounts( {N(tableinit)} ); @@ -682,7 +695,7 @@ BOOST_FIXTURE_TEST_CASE( table_init_tests, TESTER ) try { set_code(N(tableinit), valid_sparse_table); produce_blocks(1); - BOOST_CHECK_THROW(set_code(N(tableinit), too_big_table), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(tableinit), too_big_table), eosio::chain::wasm_exception); } FC_LOG_AND_RETHROW() @@ -702,7 +715,7 @@ BOOST_FIXTURE_TEST_CASE( table_init_oob, TESTER ) try { //the unspecified_exception_code comes from WAVM, which manages to throw a WAVM specific exception // up to where exec_one captures it and doesn't understand it - BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_exception); }; set_code(N(tableinitoob), table_init_oob_wast); @@ -720,6 +733,11 @@ BOOST_FIXTURE_TEST_CASE( table_init_oob, TESTER ) try { //an elem w/o a table is a setcode fail though BOOST_CHECK_THROW(set_code(N(tableinitoob), table_init_oob_no_table_wast), eosio::chain::wasm_exception); + set_code(N(tableinitoob), table_init_oob_empty_wast); + produce_block(); + pushit_and_expect_fail(); + pushit_and_expect_fail(); + } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( memory_init_border, TESTER ) try { @@ -731,8 +749,8 @@ BOOST_FIXTURE_TEST_CASE( memory_init_border, TESTER ) try { set_code(N(memoryborder), memory_init_borderline); produce_blocks(1); - BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_toolong), eosio::chain::wasm_execution_error); - BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_negative), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_toolong), eosio::chain::wasm_exception); + BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_negative), eosio::chain::wasm_exception); } FC_LOG_AND_RETHROW() @@ -779,7 +797,7 @@ BOOST_FIXTURE_TEST_CASE( nested_limit_test, TESTER ) try { for(unsigned int i = 0; i < 1024; ++i) ss << ")"; ss << "))"; - BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_exception); } // nested blocks @@ -801,7 +819,7 @@ BOOST_FIXTURE_TEST_CASE( nested_limit_test, TESTER ) try { for(unsigned int i = 0; i < 1024; ++i) ss << ")"; ss << "))"; - BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_exception); } // nested ifs { @@ -822,7 +840,7 @@ BOOST_FIXTURE_TEST_CASE( nested_limit_test, TESTER ) try { for(unsigned int i = 0; i < 1024; ++i) ss << "))"; ss << "))"; - BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_exception); } // mixed nested { @@ -855,13 +873,16 @@ BOOST_FIXTURE_TEST_CASE( nested_limit_test, TESTER ) try { for(unsigned int i = 0; i < 224; ++i) ss << "))"; ss << "))"; - BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(nested), ss.str().c_str()), eosio::chain::wasm_exception); } } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_globals, TESTER ) try { +BOOST_DATA_TEST_CASE_F( old_wasm_tester, lotso_globals, bdata::make({false, true}), activate_wasm_config ) try { + if(activate_wasm_config) + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); + produce_blocks(2); create_accounts( {N(globals)} ); @@ -886,10 +907,10 @@ BOOST_FIXTURE_TEST_CASE( lotso_globals, TESTER ) try { //1028 should fail BOOST_CHECK_THROW(set_code(N(globals), string(ss.str() + "(global $z (mut i64) (i64.const -12)))") - .c_str()), eosio::chain::wasm_execution_error); + .c_str()), eosio::chain::wasm_exception); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( offset_check, TESTER ) try { +BOOST_FIXTURE_TEST_CASE( offset_check_old, old_wasm_tester ) try { produce_blocks(2); create_accounts( {N(offsets)} ); @@ -937,7 +958,7 @@ BOOST_FIXTURE_TEST_CASE( offset_check, TESTER ) try { ss << "(drop (" << s << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory+4 << " (i32.const 0)))"; ss << ") (export \"apply\" (func $apply)) )"; - BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_exception); produce_block(); } for(const vector& o : storeops) { @@ -946,7 +967,51 @@ BOOST_FIXTURE_TEST_CASE( offset_check, TESTER ) try { ss << "(" << o[0] << " offset=" << eosio::chain::wasm_constraints::maximum_linear_memory+4 << " (i32.const 0) (" << o[1] << ".const 0))"; ss << ") (export \"apply\" (func $apply)) )"; - BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_exception); + produce_block(); + } + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( offset_check, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(offsets)} ); + produce_block(); + + vector loadops = { + "i32.load", "i64.load", "f32.load", "f64.load", "i32.load8_s", "i32.load8_u", + "i32.load16_s", "i32.load16_u", "i64.load8_s", "i64.load8_u", "i64.load16_s", + "i64.load16_u", "i64.load32_s", "i64.load32_u" + }; + vector> storeops = { + {"i32.store", "i32"}, + {"i64.store", "i64"}, + {"f32.store", "f32"}, + {"f64.store", "f64"}, + {"i32.store8", "i32"}, + {"i32.store16", "i32"}, + {"i64.store8", "i64"}, + {"i64.store16", "i64"}, + {"i64.store32", "i64"}, + }; + + for(const string& s : loadops) { + std::stringstream ss; + ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)"; + ss << "(drop (" << s << " offset=" << 0xFFFFFFFFu << " (i32.const 0)))"; + ss << ") (export \"apply\" (func $apply)) )"; + + set_code(N(offsets), ss.str().c_str()); + produce_block(); + } + for(const vector& o : storeops) { + std::stringstream ss; + ss << "(module (memory $0 " << eosio::chain::wasm_constraints::maximum_linear_memory/(64*1024) << ") (func $apply (param $0 i64) (param $1 i64) (param $2 i64)"; + ss << "(" << o[0] << " offset=" << 0xFFFFFFFFu << " (i32.const 0) (" << o[1] << ".const 0))"; + ss << ") (export \"apply\" (func $apply)) )"; + + set_code(N(offsets), ss.str().c_str()); produce_block(); } @@ -1053,7 +1118,7 @@ BOOST_FIXTURE_TEST_CASE(eosio_abi, TESTER) try { produce_block(); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( check_big_deserialization, TESTER ) try { +BOOST_FIXTURE_TEST_CASE( check_big_deserialization, old_wasm_tester ) try { produce_blocks(2); create_accounts( {N(cbd)} ); produce_block(); @@ -1307,193 +1372,6 @@ BOOST_FIXTURE_TEST_CASE( protected_globals, TESTER ) try { produce_blocks(1); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_1, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - { - std::stringstream ss; - ss << "(module "; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64))"; - ss << " (func "; - for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes; i+=4) - ss << "(local i32)"; - ss << " )"; - ss << ")"; - set_code(N(stackz), ss.str().c_str()); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() - -BOOST_FIXTURE_TEST_CASE( lotso_stack_2, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - { - std::stringstream ss; - ss << "(module "; - ss << "(import \"env\" \"require_auth\" (func $require_auth (param i64)))"; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64) (call $require_auth (i64.const 14288945783897063424)))"; - ss << " (func "; - for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes; i+=8) - ss << "(local f64)"; - ss << " )"; - ss << ")"; - set_code(N(stackz), ss.str().c_str()); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_3, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - //try to use contract with this many locals (so that it actually gets compiled). Note that - //at this time not having an apply() is an acceptable non-error. - { - signed_transaction trx; - action act; - act.account = N(stackz); - act.name = N(); - act.authorization = vector{{N(stackz),config::active_name}}; - trx.actions.push_back(act); - - set_transaction_headers(trx); - trx.sign(get_private_key( N(stackz), "active" ), control->get_chain_id()); - push_transaction(trx); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_4, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - //too many locals! should fail validation - { - std::stringstream ss; - ss << "(module "; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64) (param $1 i64) (param $2 i64))"; - ss << " (func "; - for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes+4; i+=4) - ss << "(local i32)"; - ss << " )"; - ss << ")"; - BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), wasm_serialization_error); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_5, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - //try again but with parameters - { - std::stringstream ss; - ss << "(module "; - ss << "(import \"env\" \"require_auth\" (func $require_auth (param i64)))"; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64)(param $1 i64)(param $2 i64) (call $require_auth (i64.const 14288945783897063424)))"; - ss << " (func "; - for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes; i+=4) - ss << "(param i32)"; - ss << " )"; - ss << ")"; - set_code(N(stackz), ss.str().c_str()); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_6, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - //try to use contract with this many params - { - signed_transaction trx; - action act; - act.account = N(stackz); - act.name = N(); - act.authorization = vector{{N(stackz),config::active_name}}; - trx.actions.push_back(act); - - set_transaction_headers(trx); - trx.sign(get_private_key( N(stackz), "active" ), control->get_chain_id()); - push_transaction(trx); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_7, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - //too many params! - { - std::stringstream ss; - ss << "(module "; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64) (param $1 i64) (param $2 i64))"; - ss << " (func "; - for(unsigned int i = 0; i < wasm_constraints::maximum_func_local_bytes+4; i+=4) - ss << "(param i32)"; - ss << " )"; - ss << ")"; - BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), wasm_execution_error); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_8, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - //let's mix params and locals are make sure it's counted correctly in mixed case - { - std::stringstream ss; - ss << "(module "; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64) (param $1 i64) (param $2 i64))"; - ss << " (func (param i64) (param f32) "; - for(unsigned int i = 12; i < wasm_constraints::maximum_func_local_bytes; i+=4) - ss << "(local i32)"; - ss << " )"; - ss << ")"; - set_code(N(stackz), ss.str().c_str()); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( lotso_stack_9, TESTER ) try { - produce_blocks(2); - - create_accounts( {N(stackz)} ); - produce_block(); - - { - std::stringstream ss; - ss << "(module "; - ss << "(export \"apply\" (func $apply))"; - ss << " (func $apply (param $0 i64) (param $1 i64) (param $2 i64))"; - ss << " (func (param i64) (param f32) "; - for(unsigned int i = 12; i < wasm_constraints::maximum_func_local_bytes+4; i+=4) - ss << "(local f32)"; - ss << " )"; - ss << ")"; - BOOST_CHECK_THROW(set_code(N(stackz), ss.str().c_str()), wasm_execution_error); - produce_blocks(1); - } -} FC_LOG_AND_RETHROW() - BOOST_FIXTURE_TEST_CASE( apply_export_and_signature, TESTER ) try { produce_blocks(2); create_accounts( {N(bbb)} ); @@ -1546,6 +1424,16 @@ BOOST_FIXTURE_TEST_CASE( protect_injected, TESTER ) try { produce_blocks(1); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( import_signature, TESTER ) try { + produce_blocks(2); + + create_accounts( {N(imp)} ); + produce_block(); + + BOOST_CHECK_THROW(set_code(N(imp), import_wrong_signature_wast), wasm_exception); + produce_blocks(1); +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE( mem_growth_memset, TESTER ) try { produce_blocks(2); @@ -1749,15 +1637,15 @@ BOOST_FIXTURE_TEST_CASE( fuzz, TESTER ) try { } { vector wasm(gdeep_loops_ext_reportData, gdeep_loops_ext_reportData + gdeep_loops_ext_reportSize); - BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_exception); } { vector wasm(g80k_deep_loop_with_retData, g80k_deep_loop_with_retData + g80k_deep_loop_with_retSize); - BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_exception); } { vector wasm(g80k_deep_loop_with_voidData, g80k_deep_loop_with_voidData + g80k_deep_loop_with_voidSize); - BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_execution_error); + BOOST_CHECK_THROW(set_code(N(fuzzy), wasm), wasm_exception); } produce_blocks(1); @@ -1794,7 +1682,10 @@ BOOST_FIXTURE_TEST_CASE( big_maligned_host_ptr, TESTER ) try { produce_blocks(1); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( depth_tests, TESTER ) try { +BOOST_DATA_TEST_CASE_F( old_wasm_tester, depth_tests, bdata::make({false, true}), activate_wasm_config ) try { + if(activate_wasm_config) + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); + produce_block(); create_accounts( {N(depth)} ); produce_block(); @@ -1846,14 +1737,16 @@ BOOST_FIXTURE_TEST_CASE( depth_tests, TESTER ) try { } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( varuint_memory_flags_tests, TESTER ) try { +BOOST_FIXTURE_TEST_CASE( varuint_memory_flags_tests, old_wasm_tester ) try { produce_block(); + create_accounts( {N(memflags)} ); produce_block(); set_code(N(memflags), varuint_memory_flags); produce_block(); + { signed_transaction trx; action act; act.account = N(memflags); @@ -1864,6 +1757,28 @@ BOOST_FIXTURE_TEST_CASE( varuint_memory_flags_tests, TESTER ) try { trx.sign(get_private_key( N(memflags), "active" ), control->get_chain_id()); push_transaction(trx); produce_block(); + } + + // Activate new parser + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); + produce_block(); + + // We should still be able to execute the old code + { + signed_transaction trx; + action act; + act.account = N(memflags); + act.name = N(); + act.authorization = vector{{N(memflags),config::active_name}}; + trx.actions.push_back(act); + set_transaction_headers(trx); + trx.sign(get_private_key( N(memflags), "active" ), control->get_chain_id()); + push_transaction(trx); + produce_block(); + } + + set_code(N(memflags), std::vector{}); + BOOST_REQUIRE_THROW(set_code(N(memflags), varuint_memory_flags), wasm_exception); } FC_LOG_AND_RETHROW() // TODO: Update to use eos-vm once merged From 03f29983a02c4577ea280b951480bc880f5bee0c Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Thu, 13 Jan 2022 14:17:57 -0500 Subject: [PATCH 2/6] Port test_compatible_versions test from 2.1 --- .../include/eosio/testing/snapshot_suites.hpp | 110 +++++++ unittests/snapshot_tests.cpp | 294 +++++++----------- unittests/snapshots.hpp.in | 105 ++++++- unittests/snapshots/CMakeLists.txt | 8 + unittests/snapshots/blocks.log | Bin 0 -> 4873 bytes unittests/snapshots/prod_sched/README | 2 + unittests/snapshots/prod_sched/blocks.log | Bin 0 -> 11423 bytes .../prod_sched/snapshot_tests.cpp.diff | 108 +++++++ unittests/snapshots/snap_v2.bin.gz | Bin 15717 -> 9645 bytes unittests/snapshots/snap_v2.json.gz | Bin 38299 -> 28843 bytes unittests/snapshots/snap_v2_prod_sched.bin.gz | Bin 12897 -> 14161 bytes .../snapshots/snap_v2_prod_sched.json.gz | Bin 38593 -> 35402 bytes unittests/snapshots/snap_v3.bin.gz | Bin 0 -> 9661 bytes unittests/snapshots/snap_v3.json.gz | Bin 0 -> 28926 bytes unittests/snapshots/snap_v4.bin.gz | Bin 0 -> 9693 bytes unittests/snapshots/snap_v4.json.gz | Bin 0 -> 29108 bytes unittests/snapshots/snap_v5.bin.gz | Bin 0 -> 9694 bytes unittests/snapshots/snap_v5.json.gz | Bin 0 -> 29125 bytes 18 files changed, 444 insertions(+), 183 deletions(-) create mode 100644 libraries/testing/include/eosio/testing/snapshot_suites.hpp create mode 100644 unittests/snapshots/blocks.log create mode 100644 unittests/snapshots/prod_sched/README create mode 100644 unittests/snapshots/prod_sched/blocks.log create mode 100644 unittests/snapshots/prod_sched/snapshot_tests.cpp.diff create mode 100644 unittests/snapshots/snap_v3.bin.gz create mode 100644 unittests/snapshots/snap_v3.json.gz create mode 100644 unittests/snapshots/snap_v4.bin.gz create mode 100644 unittests/snapshots/snap_v4.json.gz create mode 100644 unittests/snapshots/snap_v5.bin.gz create mode 100644 unittests/snapshots/snap_v5.json.gz diff --git a/libraries/testing/include/eosio/testing/snapshot_suites.hpp b/libraries/testing/include/eosio/testing/snapshot_suites.hpp new file mode 100644 index 00000000000..adec77a22b5 --- /dev/null +++ b/libraries/testing/include/eosio/testing/snapshot_suites.hpp @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +using namespace eosio::chain; +using namespace eosio::testing; + +struct variant_snapshot_suite { + using writer_t = variant_snapshot_writer; + using reader_t = variant_snapshot_reader; + using write_storage_t = fc::mutable_variant_object; + using snapshot_t = fc::variant; + + struct writer : public writer_t { + writer( const std::shared_ptr& storage ) + :writer_t(*storage) + ,storage(storage) + { + + } + + std::shared_ptr storage; + }; + + struct reader : public reader_t { + explicit reader(const snapshot_t& storage) + :reader_t(storage) + {} + }; + + + static auto get_writer() { + return std::make_shared(std::make_shared()); + } + + static auto finalize(const std::shared_ptr& w) { + w->finalize(); + return snapshot_t(*w->storage); + } + + static auto get_reader( const snapshot_t& buffer) { + return std::make_shared(buffer); + } + + static snapshot_t load_from_file(const std::string& filename) { + snapshot_input_file file(filename); + return file.read(); + } + + static void write_to_file( const std::string& basename, const snapshot_t& snapshot ) { + snapshot_output_file file(basename); + file.write(snapshot); + } +}; + +struct buffered_snapshot_suite { + using writer_t = ostream_snapshot_writer; + using reader_t = istream_snapshot_reader; + using write_storage_t = std::ostringstream; + using snapshot_t = std::string; + using read_storage_t = std::istringstream; + + struct writer : public writer_t { + writer( const std::shared_ptr& storage ) + :writer_t(*storage) + ,storage(storage) + { + + } + + std::shared_ptr storage; + }; + + struct reader : public reader_t { + explicit reader(const std::shared_ptr& storage) + :reader_t(*storage) + ,storage(storage) + {} + + std::shared_ptr storage; + }; + + + static auto get_writer() { + return std::make_shared(std::make_shared()); + } + + static auto finalize(const std::shared_ptr& w) { + w->finalize(); + return w->storage->str(); + } + + static auto get_reader( const snapshot_t& buffer) { + return std::make_shared(std::make_shared(buffer)); + } + + static snapshot_t load_from_file(const std::string& filename) { + snapshot_input_file file(filename); + return file.read_as_string(); + } + + static void write_to_file( const std::string& basename, const snapshot_t& snapshot ) { + snapshot_output_file file(basename); + file.write(snapshot); + } +}; + +using snapshot_suites = boost::mpl::list; + diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp index 0f9575ced54..bb3b54e356e 100644 --- a/unittests/snapshot_tests.cpp +++ b/unittests/snapshot_tests.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -14,6 +15,7 @@ using namespace eosio; using namespace testing; using namespace chain; +namespace bfs = boost::filesystem; chainbase::bfs::path get_parent_path(chainbase::bfs::path blocks_dir, int ordinal) { chainbase::bfs::path leaf_dir = blocks_dir.filename(); @@ -76,100 +78,8 @@ class snapshotted_tester : public base_tester { bool validate() { return true; } }; -struct variant_snapshot_suite { - using writer_t = variant_snapshot_writer; - using reader_t = variant_snapshot_reader; - using write_storage_t = fc::mutable_variant_object; - using snapshot_t = fc::variant; - - struct writer : public writer_t { - writer( const std::shared_ptr& storage ) - :writer_t(*storage) - ,storage(storage) - { - - } - - std::shared_ptr storage; - }; - - struct reader : public reader_t { - explicit reader(const snapshot_t& storage) - :reader_t(storage) - {} - }; - - - static auto get_writer() { - return std::make_shared(std::make_shared()); - } - - static auto finalize(const std::shared_ptr& w) { - w->finalize(); - return snapshot_t(*w->storage); - } - - static auto get_reader( const snapshot_t& buffer) { - return std::make_shared(buffer); - } - - template - static snapshot_t load_from_file() { - return Snapshot::json(); - } -}; - -struct buffered_snapshot_suite { - using writer_t = ostream_snapshot_writer; - using reader_t = istream_snapshot_reader; - using write_storage_t = std::ostringstream; - using snapshot_t = std::string; - using read_storage_t = std::istringstream; - - struct writer : public writer_t { - writer( const std::shared_ptr& storage ) - :writer_t(*storage) - ,storage(storage) - { - - } - - std::shared_ptr storage; - }; - - struct reader : public reader_t { - explicit reader(const std::shared_ptr& storage) - :reader_t(*storage) - ,storage(storage) - {} - - std::shared_ptr storage; - }; - - - static auto get_writer() { - return std::make_shared(std::make_shared()); - } - - static auto finalize(const std::shared_ptr& w) { - w->finalize(); - return w->storage->str(); - } - - static auto get_reader( const snapshot_t& buffer) { - return std::make_shared(std::make_shared(buffer)); - } - - template - static snapshot_t load_from_file() { - return Snapshot::bin(); - } -}; - BOOST_AUTO_TEST_SUITE(snapshot_tests) -using snapshot_suites = boost::mpl::list; - namespace { void variant_diff_helper(const fc::variant& lhs, const fc::variant& rhs, std::function&& out){ if (lhs.get_type() != rhs.get_type()) { @@ -428,106 +338,142 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_chain_id_in_snapshot, SNAPSHOT_SUITE, snapsho verify_integrity_hash(*chain.control, *snap_chain.control); } +static auto get_extra_args() { + bool save_snapshot = false; + bool generate_log = false; + + auto argc = boost::unit_test::framework::master_test_suite().argc; + auto argv = boost::unit_test::framework::master_test_suite().argv; + std::for_each(argv, argv + argc, [&](const std::string &a){ + if (a == "--save-snapshot") { + save_snapshot = true; + } else if (a == "--generate-snapshot-log") { + generate_log = true; + } + }); + + return std::make_tuple(save_snapshot, generate_log); +} + BOOST_AUTO_TEST_CASE_TEMPLATE(test_compatible_versions, SNAPSHOT_SUITE, snapshot_suites) { const uint32_t legacy_default_max_inline_action_size = 4 * 1024; - tester chain(setup_policy::preactivate_feature_and_new_bios, db_read_mode::SPECULATIVE, {legacy_default_max_inline_action_size}); + bool save_snapshot = false; + bool generate_log = false; + std::tie(save_snapshot, generate_log) = get_extra_args(); + const auto source_log_dir = bfs::path(snapshot_file::base_path); + + if (generate_log) { + ///< Begin deterministic code to generate blockchain for comparison + + tester chain(setup_policy::none, db_read_mode::SPECULATIVE, {legacy_default_max_inline_action_size}); + chain.create_account("snapshot"_n); + chain.produce_blocks(1); + chain.set_code("snapshot"_n, contracts::snapshot_test_wasm()); + chain.set_abi("snapshot"_n, contracts::snapshot_test_abi().data()); + chain.produce_blocks(1); + chain.control->abort_block(); - ///< Begin deterministic code to generate blockchain for comparison - // TODO: create a utility that will write new bin/json gzipped files based on this - chain.create_account(N(snapshot)); - chain.produce_blocks(1); - chain.set_code(N(snapshot), contracts::snapshot_test_wasm()); - chain.set_abi(N(snapshot), contracts::snapshot_test_abi().data()); - chain.produce_blocks(1); - chain.control->abort_block(); + // continue until all the above blocks are in the blocks.log + auto head_block_num = chain.control->head_block_num(); + while (chain.control->last_irreversible_block_num() < head_block_num) { + chain.produce_blocks(1); + } + + auto source = chain.get_config().blocks_dir / "blocks.log"; + auto dest = bfs::path(snapshot_file::base_path) / "blocks.log"; + bfs::copy_file(source, source_log_dir / "blocks.log", bfs::copy_option::overwrite_if_exists); + chain.close(); + } + + auto config = tester::default_config(fc::temp_directory(), legacy_default_max_inline_action_size).first; + auto genesis = eosio::chain::block_log::extract_genesis_state(source_log_dir); + bfs::create_directories(config.blocks_dir); + bfs::copy(source_log_dir / "blocks.log", config.blocks_dir / "blocks.log"); + tester base_chain(config, *genesis); + std::string current_version = "v5"; + + int ordinal = 0; + for(std::string version : {"v2", "v3", "v4" /*, "v5"*/}) { + if(save_snapshot && version == current_version) continue; static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); - auto v2 = SNAPSHOT_SUITE::template load_from_file(); - int ordinal = 0; - snapshotted_tester v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(v2), ordinal++); - verify_integrity_hash(*chain.control, *v2_tester.control); - + auto old_snapshot = SNAPSHOT_SUITE::load_from_file("snap_" + version); + BOOST_TEST_CHECKPOINT("loading snapshot: " << version); + snapshotted_tester old_snapshot_tester(base_chain.get_config(), SNAPSHOT_SUITE::get_reader(old_snapshot), ordinal++); + verify_integrity_hash(*base_chain.control, *old_snapshot_tester.control); + // create a latest snapshot auto latest_writer = SNAPSHOT_SUITE::get_writer(); - v2_tester.control->write_snapshot(latest_writer); + old_snapshot_tester.control->write_snapshot(latest_writer); auto latest = SNAPSHOT_SUITE::finalize(latest_writer); - + // load the latest snapshot - snapshotted_tester latest_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), ordinal++); - verify_integrity_hash(*v2_tester.control, *latest_tester.control); + snapshotted_tester latest_tester(base_chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), ordinal++); + verify_integrity_hash(*base_chain.control, *latest_tester.control); } -} - -BOOST_AUTO_TEST_CASE_TEMPLATE(test_pending_schedule_snapshot, SNAPSHOT_SUITE, snapshot_suites) -{ - const uint32_t legacy_default_max_inline_action_size = 4 * 1024; - tester chain(setup_policy::preactivate_feature_and_new_bios, db_read_mode::SPECULATIVE, {legacy_default_max_inline_action_size}); - auto genesis = chain::block_log::extract_genesis_state(chain.get_config().blocks_dir); - BOOST_REQUIRE(genesis); - BOOST_REQUIRE_EQUAL(genesis->compute_chain_id(), chain.control->get_chain_id()); - const auto& gpo = chain.control->get_global_properties(); - BOOST_REQUIRE_EQUAL(gpo.chain_id, chain.control->get_chain_id()); - auto block = chain.produce_block(); - BOOST_REQUIRE_EQUAL(block->block_num(), 3); // ensure that test setup stays consistent with original snapshot setup - chain.create_account(N(snapshot)); - block = chain.produce_block(); - BOOST_REQUIRE_EQUAL(block->block_num(), 4); + // This isn't quite fully automated. The snapshots still need to be gzipped and moved to + // the correct place in the source tree. + if (save_snapshot) + { + // create a latest snapshot + auto latest_writer = SNAPSHOT_SUITE::get_writer(); + base_chain.control->write_snapshot(latest_writer); + auto latest = SNAPSHOT_SUITE::finalize(latest_writer); - BOOST_REQUIRE_EQUAL(gpo.proposed_schedule.version, 0); - BOOST_REQUIRE_EQUAL(gpo.proposed_schedule.producers.size(), 0); + SNAPSHOT_SUITE::write_to_file("snap_" + current_version, latest); + } +} - auto res = chain.set_producers_legacy( {N(snapshot)} ); - block = chain.produce_block(); - BOOST_REQUIRE_EQUAL(block->block_num(), 5); - chain.control->abort_block(); - ///< End deterministic code to generate blockchain for comparison +/* +When WTMSIG changes were introduced in 1.8.x, the snapshot had to be able +to store more than a single producer key. +This test intends to make sure that a snapshot from before that change could +be correctly loaded into a new version to facilitate upgrading from 1.8.x +to v2.0.x without a replay. - BOOST_REQUIRE_EQUAL(gpo.proposed_schedule.version, 1); - BOOST_REQUIRE_EQUAL(gpo.proposed_schedule.producers.size(), 1); - BOOST_REQUIRE_EQUAL(gpo.proposed_schedule.producers[0].producer_name.to_string(), "snapshot"); +The original test simulated a snapshot from 1.8.x with an inflight schedule change, loaded it on the newer version and reconstructed the chain via +push_transaction. This is too fragile. +The fix is to save block.log and its corresponding snapshot with infight +schedule changes, load the snapshot and replay the block.log on the new +version, and verify their integrity. +*/ +BOOST_AUTO_TEST_CASE_TEMPLATE(test_pending_schedule_snapshot, SNAPSHOT_SUITE, snapshot_suites) +{ static_assert(chain_snapshot_header::minimum_compatible_version <= 2, "version 2 unit test is no longer needed. Please clean up data files"); - auto v2 = SNAPSHOT_SUITE::template load_from_file(); - int ordinal = 0; - //////////////////////////////////////////////////////////////////////// - // Verify that the controller gets its chain_id from the snapshot - //////////////////////////////////////////////////////////////////////// - - auto reader = SNAPSHOT_SUITE::get_reader(v2); - snapshotted_tester v2_tester(chain.get_config(), reader, ordinal++); - auto chain_id = chain::controller::extract_chain_id(*reader); - BOOST_REQUIRE_EQUAL(chain_id, v2_tester.control->get_chain_id()); - BOOST_REQUIRE_EQUAL(chain.control->get_chain_id(), v2_tester.control->get_chain_id()); - verify_integrity_hash(*chain.control, *v2_tester.control); - - // create a latest version snapshot from the loaded v2 snapthos - auto latest_from_v2_writer = SNAPSHOT_SUITE::get_writer(); - v2_tester.control->write_snapshot(latest_from_v2_writer); - auto latest_from_v2 = SNAPSHOT_SUITE::finalize(latest_from_v2_writer); - - // load the latest snapshot in a new tester and compare integrity - snapshotted_tester latest_from_v2_tester(chain.get_config(), SNAPSHOT_SUITE::get_reader(latest_from_v2), ordinal++); - verify_integrity_hash(*v2_tester.control, *latest_from_v2_tester.control); - - const auto& v2_gpo = v2_tester.control->get_global_properties(); - BOOST_REQUIRE_EQUAL(v2_gpo.proposed_schedule.version, 1); - BOOST_REQUIRE_EQUAL(v2_gpo.proposed_schedule.producers.size(), 1); - BOOST_REQUIRE_EQUAL(v2_gpo.proposed_schedule.producers[0].producer_name.to_string(), "snapshot"); - - // produce block - auto new_block = chain.produce_block(); - // undo the auto-pending from tester - chain.control->abort_block(); - - // push that block to all sub testers and validate the integrity of the database after it. - v2_tester.push_block(new_block); - verify_integrity_hash(*chain.control, *v2_tester.control); - - latest_from_v2_tester.push_block(new_block); - verify_integrity_hash(*chain.control, *latest_from_v2_tester.control); + // consruct a chain by replaying the saved blocks.log + std::string source_log_dir_str = snapshot_file::base_path; + source_log_dir_str += "prod_sched"; + const auto source_log_dir = bfs::path(source_log_dir_str.c_str()); + const uint32_t legacy_default_max_inline_action_size = 4 * 1024; + auto config = tester::default_config(fc::temp_directory(), legacy_default_max_inline_action_size).first; + auto genesis = eosio::chain::block_log::extract_genesis_state(source_log_dir); + bfs::create_directories(config.blocks_dir); + bfs::copy(source_log_dir / "blocks.log", config.blocks_dir / "blocks.log"); + tester blockslog_chain(config, *genesis); + + // consruct a chain by loading the saved snapshot + auto ordinal = 0; + auto old_snapshot = SNAPSHOT_SUITE::load_from_file("snap_v2_prod_sched"); + snapshotted_tester snapshot_chain(blockslog_chain.get_config(), SNAPSHOT_SUITE::get_reader(old_snapshot), ordinal++); + + // make sure blockslog_chain and snapshot_chain agree to each other + verify_integrity_hash(*blockslog_chain.control, *snapshot_chain.control); + + // extra round of testing + // create a latest snapshot + auto latest_writer = SNAPSHOT_SUITE::get_writer(); + snapshot_chain.control->write_snapshot(latest_writer); + auto latest = SNAPSHOT_SUITE::finalize(latest_writer); + + // construct a chain from the latest snapshot + snapshotted_tester latest_chain(blockslog_chain.get_config(), SNAPSHOT_SUITE::get_reader(latest), ordinal++); + + // make sure both chains agree + verify_integrity_hash(*blockslog_chain.control, *latest_chain.control); } BOOST_AUTO_TEST_CASE_TEMPLATE(test_restart_with_existing_state_and_truncated_block_log, SNAPSHOT_SUITE, snapshot_suites) diff --git a/unittests/snapshots.hpp.in b/unittests/snapshots.hpp.in index 2af307f6c3f..762872b6ff3 100644 --- a/unittests/snapshots.hpp.in +++ b/unittests/snapshots.hpp.in @@ -1,17 +1,104 @@ #pragma once -#define MAKE_READ_SNAPSHOT(NAME) \ - struct NAME {\ - static fc::variant json() { return read_json_snapshot ("${CMAKE_BINARY_DIR}/unittests/snapshots/" #NAME ".json.gz"); } \ - static std::string bin() { return read_binary_snapshot("${CMAKE_BINARY_DIR}/unittests/snapshots/" #NAME ".bin.gz" ); } \ - };\ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include namespace eosio { namespace testing { - struct snapshots { - // v2 - MAKE_READ_SNAPSHOT(snap_v2) - MAKE_READ_SNAPSHOT(snap_v2_prod_sched) + namespace snapshot { + // tags for snapshot type + struct json {}; + struct binary {}; + } // ns eosio::testing::snapshot + + static inline constexpr snapshot::json json_tag; + static inline constexpr snapshot::binary binary_tag; + + namespace snapshot { + template + static inline constexpr bool is_json_v = std::is_same_v, snapshot::json>; + + template + static inline constexpr bool is_binary_v =std::is_same_v, snapshot::binary>; + } // ns eosio::testing::snapshot + + template + struct snapshot_file { + static constexpr auto base_path = "${CMAKE_BINARY_DIR}/unittests/snapshots/"; + static inline constexpr auto file_suffix() { + if constexpr (snapshot::is_json_v) + return ".json.gz"; + else + return ".bin.gz"; + } + + std::string file_name; + + protected: + snapshot_file(const std::string_view& fn) + : file_name(base_path + std::string(fn.data(), fn.size()) + file_suffix()) { } + }; + + template + struct snapshot_input_file : public snapshot_file { + snapshot_input_file(const std::string_view& fn) + : snapshot_file(fn) {} + + auto read_as_string() const { + std::ifstream file(this->file_name); + boost::iostreams::filtering_istream buf_in; + buf_in.push(boost::iostreams::gzip_decompressor()); + buf_in.push(file); + std::stringstream unzipped; + boost::iostreams::copy(buf_in, unzipped); + return unzipped.str(); + } + + auto read() const { + if constexpr (snapshot::is_json_v) { + return fc::json::from_string(read_as_string()); + } else { + static_assert(snapshot::is_binary_v, "unsupported type"); + return fc::json::from_string(read_as_string()); + } + } + }; + + template + struct snapshot_output_file : public snapshot_file { + snapshot_output_file(const std::string_view& fn) + : snapshot_file(fn) {} + + template + void write( const Snapshot& snapshot ) { + std::string out_string; + + if constexpr (snapshot::is_json_v) { + out_string = fc::json::to_string(snapshot, fc::time_point::maximum()); + } else { + static_assert(snapshot::is_binary_v, "unsupported type"); + std::ostringstream out_stream; + out_stream.write(snapshot.data(), snapshot.size()); + out_string = out_stream.str(); + } + + std::istringstream inStream(out_string); + std::ofstream file(this->file_name); + boost::iostreams::filtering_streambuf buf_in; + buf_in.push(boost::iostreams::gzip_compressor()); + buf_in.push(inStream); + boost::iostreams::copy(buf_in, file); + } }; } /// eosio::testing } /// eosio diff --git a/unittests/snapshots/CMakeLists.txt b/unittests/snapshots/CMakeLists.txt index 8b7b147e494..0e56a057ead 100644 --- a/unittests/snapshots/CMakeLists.txt +++ b/unittests/snapshots/CMakeLists.txt @@ -1,4 +1,12 @@ +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/blocks.log ${CMAKE_CURRENT_BINARY_DIR}/blocks.log COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/prod_sched/blocks.log ${CMAKE_CURRENT_BINARY_DIR}/prod_sched/blocks.log COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2_prod_sched.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2_prod_sched.bin.gz COPYONLY ) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v2_prod_sched.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v2_prod_sched.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v3.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v3.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v3.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v3.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v4.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v4.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v4.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v4.json.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v5.bin.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v5.bin.gz COPYONLY ) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/snap_v5.json.gz ${CMAKE_CURRENT_BINARY_DIR}/snap_v5.json.gz COPYONLY ) diff --git a/unittests/snapshots/blocks.log b/unittests/snapshots/blocks.log new file mode 100644 index 0000000000000000000000000000000000000000..4f12898d4688531da3002090bff36ba8de0b4b5e GIT binary patch literal 4873 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 fm + ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ + using namespace eosio; + using namespace testing; + using namespace chain; +@@ -252,4 +260,88 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_replay_over_snapshot, SNAPSHOT_SUITE, snapsho + BOOST_REQUIRE_EQUAL(expected_post_integrity_hash.str(), snap_chain.control->calculate_integrity_hash().str()); + } + ++// This generates blocks.log with an in-flight producer schedule change ++BOOST_FIXTURE_TEST_CASE( generate_blocks_log, tester ) try { ++ namespace bfs = boost::filesystem; ++ ++ const auto& gpo = control->get_global_properties(); ++ ++ create_accounts( {N(alice),N(bob),N(carol)} ); ++ while (control->head_block_num() < 3) { ++ produce_block(); ++ } ++ ++ // Propose a schedule of producers and ++ // verify the producers are scheduled ++ auto res = set_producers( {N(alice),N(bob),N(carol)} ); ++ BOOST_REQUIRE_EQUAL(true, control->proposed_producers().valid() ); ++ BOOST_REQUIRE_EQUAL( gpo.proposed_schedule.version, 1 ); ++ BOOST_REQUIRE_EQUAL( gpo.proposed_schedule.producers.size(), 3 ); ++ BOOST_REQUIRE_EQUAL( gpo.proposed_schedule.producers[0].producer_name.to_string(), "alice" ); ++ BOOST_CHECK_EQUAL( control->pending_producers().version, 0u ); ++ ++ // Starts a new block which promotes the proposed schedule to pending ++ produce_block(); ++ BOOST_CHECK_EQUAL( control->pending_producers().version, 1u ); ++ BOOST_CHECK_EQUAL( control->active_producers().version, 0u ); ++ ++ produce_block(); ++ ++ // continue until all the above blocks are in the blocks.log ++ auto head_block_num = control->head_block_num(); ++ while (control->last_irreversible_block_num() < head_block_num) { ++ produce_blocks(1); ++ } ++ control->abort_block(); ++ ++ // Save the blocks.log ++ auto source = get_config().blocks_dir / "blocks.log"; ++ auto dest = bfs::path("/tmp") / "blocks.log"; ++ bfs::copy(source, dest); ++ close(); ++} FC_LOG_AND_RETHROW() ++ ++BOOST_AUTO_TEST_CASE_TEMPLATE(generate_snapshot, SNAPSHOT_SUITE, snapshot_suites) { ++ namespace bfs = boost::filesystem; ++ auto tempdir = fc::temp_directory(); ++ ++ controller::config cfg; ++ cfg.blocks_dir = tempdir.path() / config::default_blocks_dir_name; ++ cfg.state_dir = tempdir.path() / config::default_state_dir_name; ++ cfg.state_size = 1024*1024*16; ++ cfg.state_guard_size = 0; ++ cfg.reversible_cache_size = 1024*1024*8; ++ cfg.reversible_guard_size = 0; ++ cfg.contracts_console = true; ++ cfg.read_mode = db_read_mode::SPECULATIVE; ++ cfg.genesis.initial_timestamp = fc::time_point::from_iso_string("2020-01-01T00:00:00.000"); ++ cfg.genesis.initial_key = tester::get_public_key( config::system_account_name, "active" ); ++ bfs::create_directories(cfg.blocks_dir); ++ bfs::copy(bfs::path("/tmp") / "blocks.log", cfg.blocks_dir / "blocks.log"); ++ ++ tester base_chain(cfg); ++ auto latest_writer = SNAPSHOT_SUITE::get_writer(); ++ base_chain.control->write_snapshot(latest_writer); ++ auto latest = SNAPSHOT_SUITE::finalize(latest_writer); ++ ++ std::ostringstream out_str; ++ std::string ext = ""; ++ if constexpr (std::is_same_v) { ++ auto json_str = fc::json::to_string(latest, fc::time_point::maximum()); ++ out_str.write(json_str.data(), json_str.size()); ++ ext = "json"; ++ } else { ++ static_assert(std::is_same_v, "unsupported type"); ++ out_str.write(latest.data(), latest.size()); ++ ext = "bin"; ++ } ++ ++ std::istringstream inStream(out_str.str()); ++ std::ofstream file(std::string("/tmp/snap_v2_prod_sched.")+ext+".gz"); ++ boost::iostreams::filtering_streambuf buf_in; ++ buf_in.push(boost::iostreams::gzip_compressor()); ++ buf_in.push(inStream); ++ boost::iostreams::copy(buf_in, file); ++} ++ + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/snapshots/snap_v2.bin.gz b/unittests/snapshots/snap_v2.bin.gz index 6c2818acee121589a09bd8675162009b12dc0ef0..addcc0206cee6eb12170784164ef0fd5e4b01e59 100644 GIT binary patch literal 9645 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 literal 15717 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 literal 38299 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 Z($`Uf*=h3@Fp_*=&M$ev$dEsO`ail8K5YO1 literal 12897 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´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 literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v3.json.gz b/unittests/snapshots/snap_v3.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..2772bdaf06e075ca74a6b0b8b7b5323120533b28 GIT binary patch literal 28926 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 literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v4.json.gz b/unittests/snapshots/snap_v4.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..752b926bf651408b7aa9cb558ee19cbc53dc96c3 GIT binary patch literal 29108 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 literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v5.bin.gz b/unittests/snapshots/snap_v5.bin.gz new file mode 100644 index 0000000000000000000000000000000000000000..004cacc4d820e611738d33ee1b1c7d682df58526 GIT binary patch literal 9694 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 R%OLndk4)Lzb;GMa{SVeGfz$v1 literal 0 HcmV?d00001 diff --git a/unittests/snapshots/snap_v5.json.gz b/unittests/snapshots/snap_v5.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..5268375e01d80c61170b25168f3299e520b33d8b GIT binary patch literal 29125 zcmeI*`CAfL+c@y=^UOQb%rxaRV`^#gOq(sH+2WE5PfhAriJAL`W9EuWYNDvX`@Bxs zXiDXph)gb-B9bYhA~KCBDJdx_DIzH%3JL)Nv47|K{tMsV-ivcx=Q_Wi>%Q;LxqrD2 zpzQ1a^Z%A{a{-dc_CcTiXj~znWA0boy85NE81ov`Fox&6Z)^Jgmq#znMdk;JBDblM zhmX_7q4i1Fr0ze-4m_weT@_S4?*6^8tk2~{*gjksHWiG9^ zVHY?pF42Y*|B~_ig#(k39FK9?_#7HPz7EZL@9*`^R!J0a)~JyaT)2TVhUaij{nX=# zVZ>%rzAGS29}MrQF85{5<)8T$DeIg?Uz5#%v@l#+|FqbBR*J?S{%+%FraznGfmZz- zJEsYU_3Q;boMb=!WAjxifw;8wZuFLysNyJP9-bWXO>=#&9qj9W1KFD61wFYKuH$Yq z>*YUGR9&c%Xlq0mMe8d944OgVf|iU0@~tj1yrxagAj_B83~VY%AzYq%Riur(9ixRJ z%|Y2{Ro7t8zwW)+YotW{*R3<({>OH;Qsga&qW@!;Zv?m@7Z<~BKZgsn!_TqZrJ#lT z^3J{k=OJ0{8EaYYze77BlI*%j%~$3!TFNo~5$(@lM<7T2%vpVgqMndcyfWGAI|0gA z@aV>qgNX)7 zyfzW%xgT0k`6~4_^0y|iD>pPjN~ph;)w+~1_p|OXsCoFuzdkHgG%(n*Kjr$H z8_v07(iN8!(R-jRkZkL{e2sp5PuKfSmp4CGn2r6T;$#+|dou8|@oK9qNQ&GBEMJZa zkVet_5+tR+*6yq4Zu`hLf>bLQ%$5>LjLbaOkoywI7B>k25t; z{0&iXY8Jc=bE~@L$$u3(``u%RF zj`XNo>c~{MM>4!L66*jpo58WoVz~_9sGrAkphrv3+Q?|udHrn*&aUCtR2NV)#*xQe z3k#FBU358+s{$mJi`(SQsw4q+p_s$tP2bAYzI0;y0e2DBlBnq@FAjjJg`c}dDjj%i z)^qR?tG14|q$=FF>5c2$)sDVK_P%k>x*CJIa;WRxlm1Hn)T&pGU#G@ZIYw}DYo5CiGy^HQtVrEYYvMA_Ay}|3iCc^=RCe;c` zdRBGoKe<7sTttbSGU2#uxd=0A7}u@X3#kVh|4Y?HomQ?spRwI!u%!mp<{!vPC7MUq zCmsQEMbvVXPi>NKB~)=osTN73&lAa<;DrRno#Wa0cPxF;X{bx6wzCJWSFptQY=TZ=b z-WmS(5>DgH2+wUOP(E7a98o z-TEXbgQl$!can!?bdifrmo@A4S~HSLhO=Y%*#(GfwMQ7M>!N-+-Da_v zsXhoyoF|(Vs#1!6hP z!vn{rwN;r;)1qJgv?d$bU_EriK;6PXP1_$;4|hh`Oibt^)<#AII752jyP>J63eDEC zAa)LQYV-6OPm-Y-19JC@mT2hWdB--nATqvW$uhU_+sC@cuL(M#|o&A-(y1 zUXvTZK+%>9w%u_=yUybh@BnxRPD7gQ#5N=}r@1NhLd}IA=iU}GT~{@Os+9OG^;Be^ zOY|VQN63#r))l#K+yZDwah_e^2E|Bye+L(?RsKBZKNWCksR02mo#e~>Y;GV?<2 z4KBxXH*A_>26MW?Ba7O20i~`UL+)QozsV_@FZmuTj<4vIXb4Jucc!){O)|8}9wCo% zX|BK7=&N>va42~KDLe=DS3!?asG&sX#MWT%Dw1v&7?o+jZ+ZPGW}0f<=a&tF`CoH7 z3ul`V(A#%%_N~n>Whh-M4w_j)^y4zyynQ8KHr<~!+>9Cg781N&g%l(_NG;q6G2Z+@ zh;BrBeJwT15#6@wl9se(QR;#urOr5_ZtK%_U%zE^jh?ExNFB45B2*qM3pjO`==K`{ zk0r_4!G}4o$e`cLj32kV&Eh82mfClid%1`kjqxg9)y$cDj&z_s_8VtOH?SmE;1FH; z&~7@9W_9yIfSE>q7+fJxO2%?z=Q}O+VH35CQz*#$zba6J8y}Co#@(Ms)Byp=f%_^! z9Od|!hevFK{Bs2LS*@T}f5&Ap=N<=?4b>gd-u|!)GR5l&$?mJ?&ig-N#Cd)PQPM^p z2FY*g9U3c@9c2oYoP|Sq!@AdcOTutE?e`6%kws)O$t9d)CkYkFv-R4~DJng?=$-pe zBcP&@@#<4%rs~rQTfY^eybT+^?;kOw?Spp9!TuF2xs}y&f8FD)y>7wCvEcZGCTLJDzo)X_>CP>A|3{Rwu5w$$T zHQL#}jJ-6P^!o<&7=Fo0Uwln3!D=L>_TNtH_9sr+FDm{zu&zhsuhb_d;p~K;-tYD0 zynFWJLeZ(lHE5pW(mnf_x!B;Pd(@wHj{F!?=S!ad*R5cZJ;0-;HUbuLSu^zZ-Acnx z>`Q&ff#>#;6U!g6^8a~ZL!h0M3LF6t$Ugl>JG@^W>sk0ie*u-O9nkVga(QJM3GkqK z_S^4gZf8X}BD_2n%LyZHm%4_n0$|G{)kdjr_eHO(m-;IK$fM}hE%e+uqcfvq>pD1g zNE>;w-9lpbvIyUD1Kd9OKQG5+@frMBon=ADaQKNyb^a6Qz#M`5JZr-)j49(!PyTV9 z3~($k@56fi3z_-S!y3*XT|a3gJQ$V%_vusb8IdkJV!}pAJSz5qXMvY8IcGEC)Ya4! zTN0!klR9_pWESOdE1O-N`Vy0oC)8XP+!h@3{M(q;HXpF-@uc0{9Vn- z9>0id2&fWu=XvbWFz*j1i9YZo?c|*OljyrENxLF>Q0bb>>veyaPIL0z`Bq!jiVtrx zwEo)2Poq1xSZwc~_B3uH8o5VR%j;L3d>)BFxIl7`!j>iqnBM=!?spun{Jdj-qWZoOLjqVaR-nRVqEe?RXLyT)aRJ;c=J>HN*4%ny=X^0}9zy{hj)fw*&|SlpycYJaVJ zp-C(~`CEuuR+qr@^FjyQ!=qb(jM|K7uEPvY=Zq_ob31R9D?z8eJHF5!+;rN-dh%Fi z3ws097iy58fnc`q9A@i?0kw?V22&#uZ z`?bh3^2@lIl4jr0@1C1W(laYJJ|2ncwRxN5CrxK%FSM$9RwmPef3Lk5@qSHT^w2EH z`aGSWo)hj5JPUrm_af-%@_*t^*Dsd*p}KS;oh6!0xa=;EBk!Hr+W0M?WVbv;ihx}} zzh%=^uOWgVKudGj?)?uYTvq6^{+Y$!h0-$?{6SUfq-%?yx9(+m0=8*F)hk@+ zi#)VgCZdH|v{T_h!=L+pGRC3V8T}9S;C&C5ZaZ)6YNJ5axiPrvTl1Yk_Tsplg)K$_ zid{JiEj-Sf!VNLVB`>U8+kC4=k%(!dq&TuaL=n*G?up^y76iTeuJ#8FTiX*g_AXq#6sA7k!Px@N8A@8S7nKD;K5 z4gKwI=DD|xVG3W8`tYv2kN&Avi8DU0c$VFwyOl4lFRfa8GhY;C*4Se?&Shhafc*}m zWBH}}Fq%AEn(g5|dy;p&)MYHle8?&w@N=4n_w-tR>Ek3`ggM2E0Zos0jZ@7|cTU9a zKKg#>OwIK{r>Ymmn}?rn&j0(;_?qjbS3Pl6M;v{oCw$~GAI+6{9B=-4k=yeS#mPfa zA?e>`hJ3#H09TsyMa6HzsG_SCln%GgA zjYD(s&V?xJxIMAyW?|M`TeBx#8NC`58by>ZG3RE!8J~q@UiOUst&^zy{>6z%@@S2> z$i?7iNMAa6k9vPflQGl>*extCgzf89l>C;GrQ-BoE(?gkbr1#r$wuOpC1KR$msv+F zzNB0JQ`CMAu4r<^M%w}+Y_TDUe$p@gY~$|EsQGQ)vhgy>8TmQb3v{<_`<;i!U^#91 zKPR`Je$EdaI)B_NT{pSo{-&3?Y5@TtxS z9~~*j7P``t)F61v@FpW7=jq~MgBL{qPVJGC1Z(Yvrd}4}@|CP~UD*UFxZ%S=bYMa_1 zQyXMzgG?HlG&E^w($J)#Nkfx{CJjv*nlv61CEE%t?%zl&| z?yVG&#}yK)8RM%;92ZHY9*E}bx~HlX;Z!Ud0CZ19)hkGB(FP}zYGhFLP?;@M1nXxxvA47y4@R#iB?+gY#TK z0L$lUbM@Hsf!^W47rniMgYUxkSJag3+)1~aBnHNafbu_m;^X4t;90Pg^!ypCU8t}U zdh_y(aA91tzIrEjIsbO{;81?{P=CSDVz_k+Jrr%%tXQ7-qx(hgg>WO&EIK06>Kuy| zIyV;@1`4yV7U6Ir=^xC>$}!n^O3sTHeT!-Kjo*P-7U(kE$(<0~)#lmOYMz4sO1SXr zQ#B51g^k8sb_4lHkh2GYRAj#A1@*-*)<%+w$vyo){RIra;FYqwb}rP?S41mc%Z|S@ z^l~QLrqhPd(0Ey4!LneX+a?vQMCPmUV=H}wR_T3bqHT7ET8#Kd|Kd%O^ZR4>N@HPO zUAuSlN=BOGwM|jMZDDkz+krgr>u%Zh%Vdm0*XTeqb(L2J;ivShuVM)@hIiHrFNdzV z*j1_}E4Bf|q@W!^G=BeLcmQiuuXRk#gW99D<3)i$h`<}p352I*9iQL26HwI--=EQq(UtlS^rh;^kD4O%GzjLjxq7hfHB;hO=NS7=hDDM7m}gF z-=Vdr=BDQ|IPWgAcx{@K)x-Q+PXG9;>g6#_Pa)K9N11y$?^7Z}_@1%gmoVVA;Og!v z>~gdLBZli@El)j?Y&|PPTMFzj$8vJi!aoMA6PAYD7h;WPgk3;e#i4(y)%3HF#l?-H z!VEY!F5 z*;6+)gTsmXje4F~Gcx^`MlD@2toD%-E7aG=K<5{cjNL@ zY35nUtXwvhG(J_r!4_<-y@ddrhaI`oVd;jm&Gjk*RPnh(9^sK|<~AjN<5vE2Z`llPA5%bJ9?Alhi?V%S(t z+lW}ex0-jft0eNye6@-*=&$_RMrpFL)P~Fwz!tn(d}Z0I*<^mMupdoM*ei$(?3jyd z3#-CsRV6M$EX`a1$}*6nxV_YnYr`HW_(w)9BwLV)rJAw}gd=`e^;DiEi4tg4T7f$RB0&A~6l8N%q z%7il|O$%0*67kW|!__@54okLuOFsBmCF#&N-cJ~~)>kmXhbI|r1y#%@oz>EnM~w90 z`#e_GP)Kjd)>i0rsnw!6^X84BeSKuLTzPX3U*y|kF34iUPfsbX@wvL&+g1q`*)^1` zHjngcc?W&)2Jt)%9H=O)>~LVJ$Dq@c3=7KvkCI})NA#KdFN5)0Sx);%eNmJS4xz71 z8uMB`6w})Q^&kL_qfmo3@@ssQ%_8-v%xGdKe@6VX}Q zI86}k4?LDlp^XI7mj0y&{$9{4P$>^~@kUp2J_NQQ?rgtOkHuBcxB44K+hdVpq}*-A zS~__0g0!=V8UY5*Is%c=cReMQ_?2uy>e(rhAU(nh368jP@TlPUSM8458ht2GSg*@a zJ?lIO!kZgGKw<&`Nj&GkDCDO^GpUjJta3!4c|f|ksx`?n3GdQQs7k;UV!RwJAnk8Q z`_D^^l)1Q^eA}!KwgTCAW!bkaoNP0}j0ucC_Xu#K_5Rno1p%Hp$BOZ^|p3Y$F?uAI`k z(9tlKMfuB^?Y^1rt@||lF1hqaWM13xDRd?x8%ZPuZ8rzhj;4#8Vwh;<7#Z`%H_AKm zSxdJP>M}OJl_hoLtQtnqK0NE%>8bYLT@0+nZ~?J8Pd=0V%a4~{wtZB>#yw*Ds6q1I`Qj4@~3 zzdW51ATcEKD0T9i8L2?!1%0t%OEaAD-TP#&N1}=jl|Qq|xRX)HGr&syw&2AR=RCCN=;Jv{#@~B9BxV#c^R#>M=DxGviNssvo%^XLLLCerf`9MZ& zhz-L{fAxg#l;ROilrySZrLnfCj;fd?7oT>M?f6g(Ryh7c`EBlrLJE$p=U<-L}vJkTc;bJky9m zpE+=q)iyA2z+dV#+sc`Ye@13!R<&maZ)HW3;V}*6j3zjHS}ASFIuW&xWu9XDR^RtX zlNX8FOxL#}4IRG}j(nhx7PK}fmNS7QkR99mZD7K-2>RA=0 zC*1JjR17%Lj`5rPB)LqYwqml%rf;MyZm%q! zLlKVAz+RU8)b~@T;UGp7p7!er(a)>20>n71dn)GBM=(C8??z{?q^&HnY#O75__ z7Q6?x^6P~ROlFq^%W2I{pVaznlX?yc*l{t0`!Jzo0Am-hwXbXiQ&%GU?5MSS1UX9K z4DRyjUK$3;>Eg8VN`W9$u~h&ec`!s{B80+f;K-Te*O|@OF=Pm%40BHP9_}iPNMChb zMSuzWvH`y^xEKNi?7cH{PJ~ap44+uJI`I$1(2Fcfea;PvpWK&lAZLVbUc3IIV!vIj z%3hM&i2uFA5cnQ;W2ke2;+IFOBFzM0kSfN0nIY~P`D8&)xG6QQ6k@DWD(&i7vEjruo{lv!XkqD<`Peird zLdsO(eqTloXw@K`&%eq$v~@Nyof^E_pNV8s7kgU z_CvjD$Nun3Kr>p&>dy&Nz>N<$M19V13Qp*ozE{LDFX4sfO|863@uWY(cQmXa-rtAB z>oQN3AIeGVOz^_o-OzNilGaTbzz7^qmhQj>SWT2RoM_~37 znvkP9c9|UJ7_8#GSIv0gmmqr;ji(4lXx?&P#`-Lx{%R0RG{aJV1bO^dtUMt(e4Uv@9z6fWTwC9J|dE>lzDjwy8Jkdqx^3pkqZ$T$Llz?Xr4P)k}^c~6H zC2pODmHKV7=NDjJ7Fx%$64YpKX|8;YxqqOO?K)-@r*9iSNF=pR8Atgr6*TTP8LK)P zqKY>RgrvV^fpe*K+@2_hp&@Om2;J%Cyf;N6p6=ZxH?wqdo_LVl9mWDc+_k;1OS|<1qUMiA>FtmIhPw`BD{o037&!_=U5fQ=( zC>znhLJ;7jVb-_g^V~}BsUgIKT>9Xpw8t}|s{N7bR+OUgJQmWLgC}nN{p6*t`_tnA_6eB6_~sO!i64yVC@@yaeIAGus67Nm zies(pxqOm|9Go}pj9H}ZpXO>{l5_9{cZ#3n4^1v2Ie298t37RllZo0# zgObZ?oJJq?09TM{&O1Jpo22$6Y}qjpj`*$kqGxB$(+cr2lmg~-QluIHPEdq&Z35}lNRN0PImyFIjY%4i(WGw%&d{2p z1QVR9lcdCfm*SnV{0T#7=kb}y7CC(WP(W|vgy9@QD6OKif0s2PqLzvRaKhDY_iLi( z9qvf(4_bZ}rV_iEg@_GmeYvW7dR#p19QcfK$6onh6eQgqUgLOVxOdmM61eUacAQ?v zW86^hqSD;!nJF_OEb&IHXSlI0C#ad_P8ZFBrk1CNd2bwH0cg?t=M(B-DMGpATHQT6 zrCk+10jPT)G+due$1FJ2dg8G{{mNNta%P!;zlTtc3g>udvJHR#Ct`tPW#FrdsbJ~> ze%^oF<^-`+vrbiQ3J{#eL6O)n>H)2!ie11-qPjol0(-TUp%R5XU{vGo{1>ySI%ft1 zA-jOfc2^4L#mKrAQGt4pGOrbf66!hJDRpOu#8KGbOs00oW8p~sX7zGQQDICkK&0}) zmyc98gC zFu7vE%f}y+QxcX&D)OQz!Nb`#Si6Cu;t0=Y?Y113Tu(4xG?r^4)C|LI2}0P=#NS&Z z*F39hJp+{yG2xed_t7b^eL*owSx@X3aSytjrWv$BXQx18DkhWXzH)Q6hD`;)U>;HO z?Fo-($l;^*B2@-Dg=4S}3s&pt%zO}h!@tpT%qhuX)b`V;eO`-7nnK}lVVmCGyZ{8t zQhmJ9*i@R_GI0K8n4(PAcr2!N?eAgnmZjv3dTuoEG?cAM=@I?Y$qZ24^mDRLJdKX z!LZ5lDY}&YYW-T#N|D*OJe-~Iatu65>pIQEwxMY)d3>hA{bV#A5;t^ zx4>EyHy!8x?dzDmWZR|yr80)T`Xxsz_>7D<3?Eu6J3|uPNynv1YA|L*^{jT~M6LT4 z3h0ACzm2a}(T?YxdWOjEuM`zLT*}R06@Khkuo@ocr}McKZ}5R<=L;tYa;{SgafY|4 zGJnLJ7zKL@uz7QTPip@SO>*no5kO;V2j38y88EmfdHAga549CYmSbD{0EE`ub^qBZsuyy3+JfHYeAkQ(o7>P$wn{8aQ--X+h-bh2Cdoljja7&EhGci~gwH&H$8UAy0SUk$7}u zIv#dbCZfzlV>(Ik|7YT|Lc^GtRM;J@nOid zh!udfoDty5^(D>4JsZoZO*3*kOO>_l04E$t z4Nh-367SxaTQs0cd4B@fB5M`cy0`$3=2-DPDnj1GX)?ypGUjy40|?8y_*fxl|X)Rd#JlG=iLLVTvhF3^in2ic(cg0h@`tc^Z@{%Mgajs?vdhLmR&JjZ`NGC z70nGKopqZ^06wttfe$8GI*}2s84fP@YB6TrHow4bO`4X2QC-5)Sy!q9y01SR+SD&YL~Q3Ld768{94XE zR+x%ZysX!1Ur9&DIY3!?Kf01lALnpT)DQV*`#t?aKA+kR4j_OsM1Z90x;swy3q1e+ z@hD8T^5!p%(1rV5nyyfj$k+W9tpF4uZzEdJq$NUk9+s2LCbl2LD(5AXVu8tWMyty4ko8n1tW2r?LTp}K(1Kl2i(0~A}1o@%4Z zJn_l#d;Kw4&>Dzz`B4`Zr4%Fy_YPd|D8;^m8CW4m-n7kj$7v%+f&&`BBuM5)Mp^zvFQIZrjRMPBwlAJd;s ztbkUSeRZ;!JYB1NHD}NWmV32j`}j#tMWG_XU*W0}UAYl?x{8}vC*i4+X6V2l-rWTb zT#sN|du&$h24ji$D)FLbcF#0_dij&Q76lj!BjC(9mGo4Pg3s9+S-dE43>5L1WZwXukZ@tbqNBujSFS z;q^3H@IwGYYINcschmR0mD{S@9w3kQndKErQ3n4h)Fm{*A9GBl60xUOQ*ZCVG!M)~ zr`ez1Kl#CLmxC>630qXrTy}M`*P1+7*njPdLmhQxM!~~1;R@@@A-DF0vuy(l(VB#- zhE-U(0F~l_yv$>LsaKRUuJ*~AAA3p;7|Xq*z-n)lHpu_|1EU2N2+2y{FkYX+{<~}^ zJ$CN}htJOzadgPZNn{6_apMgKzA6K3D=(%QnXb>ddvpiCDq~z7>hB+Zv47V1OgVXM zoAuH0rgU%r(2K>i&QplJQ>}OJ5~*#6;d}EQU~BPG+H{iH{bm(QRa~ZG+Z4SFaP+C=`nD9G%kqPpmoeX?Jhm(1nvo zeH=fw Date: Fri, 14 Jan 2022 10:36:36 -0500 Subject: [PATCH 3/6] Port fix get_wasm_parameters_packed pack calculation size based on https://github.com/EOSIO/eos/pull/10773 and use a new protocol feature hash. --- libraries/chain/protocol_feature_manager.cpp | 7 +- libraries/chain/webassembly/privileged.cpp | 2 +- unittests/wasm_config_tests.cpp | 74 ++++++++++++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index 452283b19e6..4e6ed626faa 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -187,13 +187,14 @@ may use a new `set_proposed_producers_ex` intrinsic to access extended features. {} } ) ( builtin_protocol_feature_t::configurable_wasm_limits, builtin_protocol_feature_spec{ - "CONFIGURABLE_WASM_LIMITS", - fc::variant("67f5f1e92cbf6f7276e7b3fc8c2ad23e63448e657641a1e5de69bccd114542d6").as(), + "CONFIGURABLE_WASM_LIMITS2", + fc::variant("171c8d56482bcdefafc1af46059b3f0953dd618c247ac27b597aeea9668d0a30").as(), // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). /* -Builtin protocol feature: CONFIGURABLE_WASM_LIMITS +Builtin protocol feature: CONFIGURABLE_WASM_LIMITS2 Allows privileged contracts to set the constraints on WebAssembly code. +Includes the behavior of GET_WASM_PARAMETERS_PACKED_FIX. */ {} } ) diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 418a7ee1691..1ca8b30162d 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -87,7 +87,7 @@ namespace eosio { namespace chain { namespace webassembly { auto& params = gpo.wasm_configuration; uint32_t version = std::min( max_version, uint32_t(0) ); - auto s = fc::raw::pack_size( params ); + auto s = fc::raw::pack_size( version ) + fc::raw::pack_size( params ); if ( packed_parameters.size() == 0 ) return s; diff --git a/unittests/wasm_config_tests.cpp b/unittests/wasm_config_tests.cpp index 7df9414bdd9..ba1e7ecff4b 100644 --- a/unittests/wasm_config_tests.cpp +++ b/unittests/wasm_config_tests.cpp @@ -948,4 +948,78 @@ BOOST_FIXTURE_TEST_CASE(reset_chain_tests, wasm_config_tester) { produce_block(); } +// Verifies the result of get_wasm_parameters_packed +static const char check_get_wasm_parameters_wast[] = R"======( +(module + (import "env" "get_wasm_parameters_packed" (func $get_wasm_parameters_packed (param i32 i32 i32) (result i32))) + (import "env" "read_action_data" (func $read_action_data (param i32 i32) (result i32))) + (import "env" "action_data_size" (func $action_data_size (result i32))) + (import "env" "memcmp" (func $memcmp (param i32 i32 i32) (result i32))) + (import "env" "memset" (func $memset (param i32 i32 i32) (result i32))) + (import "env" "printhex" (func $printhex (param i32 i32))) + (import "env" "eosio_assert" (func $eosio_assert (param i32 i32))) + (memory 1) + (func (export "apply") (param i64 i64 i64) + (drop (call $read_action_data (i32.const 0) (call $action_data_size))) + (drop (call $memset (i32.const 256) (i32.const 255) (call $action_data_size))) + (drop (call $get_wasm_parameters_packed (i32.const 256) (call $action_data_size) (i32.const 0))) + (if (call $memcmp (i32.const 0) (i32.const 256) (call $action_data_size)) + (then + (call $printhex (i32.const 256) (call $action_data_size)) + (call $eosio_assert (i32.const 0) (i32.const 512)) + ) + ) + ) + (data (i32.const 512) "Wrong result for get_wasm_parameters_packed") +) +)======"; + +BOOST_FIXTURE_TEST_CASE(get_wasm_parameters_test, TESTER) { + produce_block(); + + create_account( "test"_n ); + + produce_block(); + + wasm_config original_params = { + .max_mutable_global_bytes = 1024, + .max_table_elements = 1024, + .max_section_elements = 8192, + .max_linear_memory_init = 65536, + .max_func_local_bytes = 8192, + .max_nested_structures = 1024, + .max_symbol_bytes = 8192, + .max_module_bytes = 20971520, + .max_code_bytes = 20971520, + .max_pages = 528, + .max_call_depth = 251 + }; + + set_code("test"_n, check_get_wasm_parameters_wast); + produce_block(); + + auto check_wasm_params = [&](const std::vector& params){ + signed_transaction trx; + trx.actions.emplace_back(vector{{"test"_n,config::active_name}}, "test"_n, ""_n, + params); + set_transaction_headers(trx); + trx.sign(get_private_key("test"_n, "active"), control->get_chain_id()); + push_transaction(trx); + }; + + BOOST_CHECK_THROW(check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params))), unaccessible_api); + + push_action( config::system_account_name, "setpriv"_n, config::system_account_name, + fc::mutable_variant_object()("account", "test"_n)("is_priv", true) ); + + check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params))); + // Extra space is left unmodified + check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config(original_params), static_cast(0xFF))); + // Does nothing if the buffer is too small + check_wasm_params(std::vector(fc::raw::pack_size(uint32_t{0}) + fc::raw::pack_size(original_params) - 1, '\xFF')); + + // Test case sanity check + BOOST_CHECK_THROW(check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config{ 0,0,0,0,0,0,0,0,0,0,0 })), fc::exception); +} + BOOST_AUTO_TEST_SUITE_END() From cd4efd72ef1267494e6d80c2006c65686b7e0682 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 14 Jan 2022 11:21:37 -0500 Subject: [PATCH 4/6] Don't fail execution when a wasm contains a custom section larger than 65536 byes. --- libraries/chain/protocol_feature_manager.cpp | 5 ++-- .../wasm-jit/Include/Inline/Serialization.h | 2 +- unittests/wasm_config_tests.cpp | 28 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index 4e6ed626faa..882c721b1f0 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -188,13 +188,14 @@ may use a new `set_proposed_producers_ex` intrinsic to access extended features. } ) ( builtin_protocol_feature_t::configurable_wasm_limits, builtin_protocol_feature_spec{ "CONFIGURABLE_WASM_LIMITS2", - fc::variant("171c8d56482bcdefafc1af46059b3f0953dd618c247ac27b597aeea9668d0a30").as(), + fc::variant("8139e99247b87f18ef7eae99f07f00ea3adf39ed53f4d2da3f44e6aa0bfd7c62").as(), // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). /* Builtin protocol feature: CONFIGURABLE_WASM_LIMITS2 Allows privileged contracts to set the constraints on WebAssembly code. -Includes the behavior of GET_WASM_PARAMETERS_PACKED_FIX. +Includes the behavior of GET_WASM_PARAMETERS_PACKED_FIX and +also removes an inadvertent restriction on custom sections. */ {} } ) diff --git a/libraries/wasm-jit/Include/Inline/Serialization.h b/libraries/wasm-jit/Include/Inline/Serialization.h index 57b78cad421..7b0933339cf 100644 --- a/libraries/wasm-jit/Include/Inline/Serialization.h +++ b/libraries/wasm-jit/Include/Inline/Serialization.h @@ -134,7 +134,7 @@ namespace Serialization { memcpy(stream.advance(numBytes),bytes,numBytes); } FORCEINLINE void serializeBytes(InputStream& stream,U8* bytes,Uptr numBytes) { - if ( numBytes < eosio::chain::wasm_constraints::wasm_page_size ) + if ( numBytes < eosio::chain::wasm_constraints::wasm_page_size || !WASM::check_limits) memcpy(bytes,stream.advance(numBytes),numBytes); else throw FatalSerializationException(std::string("Trying to deserialize bytes of size : " + std::to_string((uint64_t)numBytes))); diff --git a/unittests/wasm_config_tests.cpp b/unittests/wasm_config_tests.cpp index ba1e7ecff4b..2cdbfcd82de 100644 --- a/unittests/wasm_config_tests.cpp +++ b/unittests/wasm_config_tests.cpp @@ -1022,4 +1022,32 @@ BOOST_FIXTURE_TEST_CASE(get_wasm_parameters_test, TESTER) { BOOST_CHECK_THROW(check_wasm_params(fc::raw::pack(uint32_t{0}, wasm_config{ 0,0,0,0,0,0,0,0,0,0,0 })), fc::exception); } +// Uses a custom section with large size +BOOST_FIXTURE_TEST_CASE(large_custom_section, TESTER) +{ + std::vector custom_section_wasm{ + 0x00, 'a', 's', 'm', 0x01, 0x00, 0x00, 0x00, + 0x01, 0x07, 0x01, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, //type section containing a function as void(i64,i64,i64) + 0x03, 0x02, 0x01, 0x00, //a function + + 0x07, 0x09, 0x01, 0x05, 'a', 'p', 'p', 'l', 'y', 0x00, 0x00, //export function 0 as "apply" + 0x0a, 0x04, 0x01, //code section + 0x02, 0x00, //function body start with length 3; no locals + 0x0b, //end + 0x00, //custom section + 0x80, 0x80, 0x08, //size 2^17 + 0x04, 'h', 'u', 'g', 'e' //name + }; + + custom_section_wasm.resize(custom_section_wasm.size() + 131072 - 5); + create_account( "hugecustom"_n ); + set_code( "hugecustom"_n, custom_section_wasm ); + + signed_transaction trx; + trx.actions.emplace_back(vector{{"hugecustom"_n,config::active_name}}, "hugecustom"_n, ""_n, std::vector{}); + set_transaction_headers(trx); + trx.sign(get_private_key("hugecustom"_n, "active"), control->get_chain_id()); + push_transaction(trx); +} + BOOST_AUTO_TEST_SUITE_END() From 92844eb60efd21906cea630131d17e15e59c765a Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 14 Jan 2022 11:37:56 -0500 Subject: [PATCH 5/6] Check the pre-hardfork behavior of large custom sections. --- unittests/wasm_config_tests.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/unittests/wasm_config_tests.cpp b/unittests/wasm_config_tests.cpp index 2cdbfcd82de..91cce7ce4cf 100644 --- a/unittests/wasm_config_tests.cpp +++ b/unittests/wasm_config_tests.cpp @@ -1023,8 +1023,10 @@ BOOST_FIXTURE_TEST_CASE(get_wasm_parameters_test, TESTER) { } // Uses a custom section with large size -BOOST_FIXTURE_TEST_CASE(large_custom_section, TESTER) +BOOST_FIXTURE_TEST_CASE(large_custom_section, old_wasm_tester) { + create_account( "hugecustom"_n ); + std::vector custom_section_wasm{ 0x00, 'a', 's', 'm', 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01, 0x60, 0x03, 0x7e, 0x7e, 0x7e, 0x00, //type section containing a function as void(i64,i64,i64) @@ -1035,12 +1037,23 @@ BOOST_FIXTURE_TEST_CASE(large_custom_section, TESTER) 0x02, 0x00, //function body start with length 3; no locals 0x0b, //end 0x00, //custom section - 0x80, 0x80, 0x08, //size 2^17 + 0x85, 0x80, 0x04, //size 2^16 + 5 0x04, 'h', 'u', 'g', 'e' //name }; - custom_section_wasm.resize(custom_section_wasm.size() + 131072 - 5); - create_account( "hugecustom"_n ); + custom_section_wasm.resize(custom_section_wasm.size() + 65536); + BOOST_CHECK_THROW(set_code( "hugecustom"_n, custom_section_wasm ), wasm_serialization_error); + + // One byte less and it should pass + auto okay_custom = custom_section_wasm; + --okay_custom[okay_custom.size() - 65536 - 5 - 3]; + okay_custom.pop_back(); + set_code( "hugecustom"_n, okay_custom ); + + // It's also okay once CONFIGURABLE_WASM_LIMITS is activated + preactivate_builtin_protocol_features({builtin_protocol_feature_t::configurable_wasm_limits}); + produce_block(); + set_code( "hugecustom"_n, custom_section_wasm ); signed_transaction trx; From 9c05385fb37296ffac770af2a162564d8a639dfb Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 14 Jan 2022 11:40:13 -0500 Subject: [PATCH 6/6] feedback --- unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp index f3a9c82ca83..ad21574b6f9 100644 --- a/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp +++ b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp @@ -1,10 +1,8 @@ #include extern "C" __attribute__((eosio_wasm_import)) void set_wasm_parameters_packed(const void*, std::size_t); -#ifdef USE_EOSIO_CDT_1_7_X extern "C" __attribute__((eosio_wasm_import)) uint32_t read_action_data( void* msg, uint32_t len ); extern "C" __attribute__((eosio_wasm_import)) uint32_t action_data_size(); -#endif struct wasm_config { std::uint32_t max_mutable_global_bytes;