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..882c721b1f0 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -183,6 +183,19 @@ 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_LIMITS2", + 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 and +also removes an inadvertent restriction on custom sections. */ {} } ) 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..1ca8b30162d 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( version ) + 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/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/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/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/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/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 00000000000..4f12898d468 Binary files /dev/null and b/unittests/snapshots/blocks.log differ diff --git a/unittests/snapshots/prod_sched/README b/unittests/snapshots/prod_sched/README new file mode 100644 index 00000000000..ee5e621d3c8 --- /dev/null +++ b/unittests/snapshots/prod_sched/README @@ -0,0 +1,2 @@ +snapshot_tests.cpp.diff is the patch to build blocks.log, +snap_v2.json.gz, and snap_v2.bin.gz on release-1.8.x. diff --git a/unittests/snapshots/prod_sched/blocks.log b/unittests/snapshots/prod_sched/blocks.log new file mode 100644 index 00000000000..3c8c2b36ef0 Binary files /dev/null and b/unittests/snapshots/prod_sched/blocks.log differ diff --git a/unittests/snapshots/prod_sched/snapshot_tests.cpp.diff b/unittests/snapshots/prod_sched/snapshot_tests.cpp.diff new file mode 100644 index 00000000000..50924d7d6aa --- /dev/null +++ b/unittests/snapshots/prod_sched/snapshot_tests.cpp.diff @@ -0,0 +1,108 @@ +diff --git a/unittests/snapshot_tests.cpp b/unittests/snapshot_tests.cpp +index a3749f965..ab81fcff4 100644 +--- a/unittests/snapshot_tests.cpp ++++ b/unittests/snapshot_tests.cpp +@@ -12,6 +12,14 @@ + + #include + ++#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 6c2818acee1..addcc0206ce 100644 Binary files a/unittests/snapshots/snap_v2.bin.gz and b/unittests/snapshots/snap_v2.bin.gz differ diff --git a/unittests/snapshots/snap_v2.json.gz b/unittests/snapshots/snap_v2.json.gz index 6689f080f00..823f86e44d8 100644 Binary files a/unittests/snapshots/snap_v2.json.gz and b/unittests/snapshots/snap_v2.json.gz differ diff --git a/unittests/snapshots/snap_v2_prod_sched.bin.gz b/unittests/snapshots/snap_v2_prod_sched.bin.gz index 3516218f216..71352224022 100644 Binary files a/unittests/snapshots/snap_v2_prod_sched.bin.gz and b/unittests/snapshots/snap_v2_prod_sched.bin.gz differ diff --git a/unittests/snapshots/snap_v2_prod_sched.json.gz b/unittests/snapshots/snap_v2_prod_sched.json.gz index 6975eedea85..0fded9e4279 100644 Binary files a/unittests/snapshots/snap_v2_prod_sched.json.gz and b/unittests/snapshots/snap_v2_prod_sched.json.gz differ diff --git a/unittests/snapshots/snap_v3.bin.gz b/unittests/snapshots/snap_v3.bin.gz new file mode 100644 index 00000000000..675465e1633 Binary files /dev/null and b/unittests/snapshots/snap_v3.bin.gz differ diff --git a/unittests/snapshots/snap_v3.json.gz b/unittests/snapshots/snap_v3.json.gz new file mode 100644 index 00000000000..2772bdaf06e Binary files /dev/null and b/unittests/snapshots/snap_v3.json.gz differ diff --git a/unittests/snapshots/snap_v4.bin.gz b/unittests/snapshots/snap_v4.bin.gz new file mode 100644 index 00000000000..b168bdaa4b9 Binary files /dev/null and b/unittests/snapshots/snap_v4.bin.gz differ diff --git a/unittests/snapshots/snap_v4.json.gz b/unittests/snapshots/snap_v4.json.gz new file mode 100644 index 00000000000..752b926bf65 Binary files /dev/null and b/unittests/snapshots/snap_v4.json.gz differ diff --git a/unittests/snapshots/snap_v5.bin.gz b/unittests/snapshots/snap_v5.bin.gz new file mode 100644 index 00000000000..004cacc4d82 Binary files /dev/null and b/unittests/snapshots/snap_v5.bin.gz differ diff --git a/unittests/snapshots/snap_v5.json.gz b/unittests/snapshots/snap_v5.json.gz new file mode 100644 index 00000000000..5268375e01d Binary files /dev/null and b/unittests/snapshots/snap_v5.json.gz differ 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..ad21574b6f9 --- /dev/null +++ b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.cpp @@ -0,0 +1,33 @@ +#include + +extern "C" __attribute__((eosio_wasm_import)) void set_wasm_parameters_packed(const void*, std::size_t); +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(); + +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 00000000000..e844249fd3c Binary files /dev/null and b/unittests/test-contracts/wasm_config_bios/wasm_config_bios.wasm differ diff --git a/unittests/wasm_config_tests.cpp b/unittests/wasm_config_tests.cpp new file mode 100644 index 00000000000..91cce7ce4cf --- /dev/null +++ b/unittests/wasm_config_tests.cpp @@ -0,0 +1,1066 @@ +#include +#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(); +} + +// 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); +} + +// Uses a custom section with large size +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) + 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 + 0x85, 0x80, 0x04, //size 2^16 + 5 + 0x04, 'h', 'u', 'g', 'e' //name + }; + + 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; + 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() 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