diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index d5991dc0ff..dda385e33e 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -207,6 +207,26 @@ bool apply_context::is_account( const account_name& account )const { return nullptr != db.find( account ); } +void apply_context::get_code_hash( + account_name account, uint64_t& code_sequence, fc::sha256& code_hash, uint8_t& vm_type, uint8_t& vm_version) const { + + auto obj = db.find(account); + if(!obj || obj->code_hash == fc::sha256{}) { + if(obj) + code_sequence = obj->code_sequence; + else + code_sequence = 0; + code_hash = {}; + vm_type = 0; + vm_version = 0; + } else { + code_sequence = obj->code_sequence; + code_hash = obj->code_hash; + vm_type = obj->vm_type; + vm_version = obj->vm_version; + } +} + void apply_context::require_authorization( const account_name& account ) { for( uint32_t i=0; i < act->authorization.size(); i++ ) { if( act->authorization[i].actor == account ) { diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 979a274f6e..64d2bfc86c 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -331,6 +331,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); @@ -3428,6 +3429,13 @@ void controller_impl::on_activation +void controller_impl::on_activation() { + db.modify( db.get(), [&]( auto& ps ) { + add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "get_code_hash" ); + } ); +} + /// End of protocol feature activation handlers } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index e8fbc403f2..9232bd07d8 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -487,6 +487,9 @@ class apply_context { */ bool is_account(const account_name& account)const; + void get_code_hash( + account_name account, uint64_t& code_sequence, fc::sha256& code_hash, uint8_t& vm_type, uint8_t& vm_version) const; + /** * Requires that the current action be delivered to account */ diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index 72ff529d5e..0c544b5b5d 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -25,7 +25,8 @@ enum class builtin_protocol_feature_t : uint32_t { wtmsig_block_signatures, action_return_value, configurable_wasm_limits, - blockchain_parameters + blockchain_parameters, + get_code_hash, }; struct protocol_feature_subjective_restrictions { 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 b92b36e1de..4e968e3945 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 @@ -258,7 +258,8 @@ inline constexpr auto get_intrinsic_table() { "env.get_wasm_parameters_packed", "env.set_wasm_parameters_packed", "env.get_parameters_packed", - "env.set_parameters_packed" + "env.set_parameters_packed", + "env.get_code_hash" ); } inline constexpr std::size_t find_intrinsic_index(std::string_view hf) { diff --git a/libraries/chain/include/eosio/chain/webassembly/interface.hpp b/libraries/chain/include/eosio/chain/webassembly/interface.hpp index 384732d344..d20a2dab14 100644 --- a/libraries/chain/include/eosio/chain/webassembly/interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/interface.hpp @@ -508,6 +508,31 @@ namespace webassembly { */ bool is_account(account_name account) const; + /** + * Retrieves the code hash for an account, if any. + * + * The result is the packed version of this struct: + * + * struct { + * varuint32 struct_version; + * uint64_t code_sequence; + * fc::sha256 code_hash; + * uint8_t vm_type; + * uint8_t vm_version; + * } result; + * + * @ingroup authorization + * @param account - name of the account to check. + * @param struct_version - use 0. + * @param packed_result - receives the packed result. + * + * @return the size of the packed result. + */ + uint32_t get_code_hash( + account_name account, + uint32_t struct_version, + vm::span packed_result) const; + /** * Returns the time in microseconds from 1970 of the current block. * @@ -734,13 +759,13 @@ namespace webassembly { * @param itr - iterator to the table row containing the record to update. * @param payer - the account that pays for the storage costs. * @param buffer - new updated record. - * - * @remark This function does not allow changing the primary key of a - * table row. The serialized data that is stored in the table row of a - * primary table may include a primary key and that primary key value - * could be changed by the contract calling the db_update_i64 intrinsic; + * + * @remark This function does not allow changing the primary key of a + * table row. The serialized data that is stored in the table row of a + * primary table may include a primary key and that primary key value + * could be changed by the contract calling the db_update_i64 intrinsic; * but that does not change the actual primary key of the table row. - * + * * @pre `itr` points to an existing table row in the table. * @post the record contained in the table row pointed to by `itr` is replaced with the new updated record. */ diff --git a/libraries/chain/include/eosio/chain/webassembly/preconditions.hpp b/libraries/chain/include/eosio/chain/webassembly/preconditions.hpp index 3416557904..93340f7d31 100644 --- a/libraries/chain/include/eosio/chain/webassembly/preconditions.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/preconditions.hpp @@ -70,7 +70,7 @@ namespace eosio { namespace chain { namespace webassembly { inline static bool is_aliasing(const T& s1, const U& s2) { std::uintptr_t a_begin = reinterpret_cast(s1.data()); std::uintptr_t a_end = a_begin + s1.size_bytes(); - + std::uintptr_t b_begin = reinterpret_cast(s2.data()); std::uintptr_t b_end = b_begin + s2.size_bytes(); @@ -86,7 +86,7 @@ namespace eosio { namespace chain { namespace webassembly { // Intersection interval is [b_begin, std::min(a_end, b_end)). - if (std::min(a_end, b_end) == b_begin) // intersection interval has zero size + if (std::min(a_end, b_end) == b_begin) // intersection interval has zero size return false; return true; @@ -120,8 +120,8 @@ namespace eosio { namespace chain { namespace webassembly { namespace detail { template - vm::span to_span(const vm::argument_proxy& val) { - return {static_cast(val.get_original_pointer()), sizeof(T)}; + vm::span to_span(const vm::argument_proxy& val) { + return {static_cast(val.get_original_pointer()), sizeof(T)}; } template diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index e41453d67f..1de3633d55 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -221,6 +221,17 @@ Builtin protocol feature: BLOCKCHAIN_PARAMETERS Allows privileged contracts to get and set subsets of blockchain parameters. */ + ( builtin_protocol_feature_t::get_code_hash, builtin_protocol_feature_spec{ + "GET_CODE_HASH", + fc::variant("d2596697fed14a0840013647b99045022ae6a885089f35a7e78da7a43ad76ed4").as(), + // SHA256 hash of the raw message below within the comment delimiters (do not modify message below). +/* +Builtin protocol feature: GET_CODE_HASH + +Enables new `get_code_hash` intrinsic which gets the current code hash of an account. +*/ + {} + } ) ; diff --git a/libraries/chain/webassembly/authorization.cpp b/libraries/chain/webassembly/authorization.cpp index ff70058387..43af5cdce6 100644 --- a/libraries/chain/webassembly/authorization.cpp +++ b/libraries/chain/webassembly/authorization.cpp @@ -22,4 +22,31 @@ namespace eosio { namespace chain { namespace webassembly { bool interface::is_account( account_name account ) const { return context.is_account( account ); } + + struct get_code_hash_result { + unsigned_int struct_version; + uint64_t code_sequence; + fc::sha256 code_hash; + uint8_t vm_type; + uint8_t vm_version; + }; + + uint32_t interface::get_code_hash( + account_name account, + uint32_t struct_version, + vm::span packed_result + ) const { + struct_version = std::min(uint32_t(0), struct_version); + get_code_hash_result result = {struct_version}; + context.get_code_hash(account, result.code_sequence, result.code_hash, result.vm_type, result.vm_version); + + auto s = fc::raw::pack_size(result); + if (s <= packed_result.size()) { + datastream ds(packed_result.data(), s); + fc::raw::pack(ds, result); + } + return s; + } }}} // ns eosio::chain::webassembly + +FC_REFLECT(eosio::chain::webassembly::get_code_hash_result, (struct_version)(code_sequence)(code_hash)(vm_type)(vm_version)) diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index 71f9c13396..2bf7249e39 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -195,7 +195,7 @@ std::unique_ptr eos_vm_runtime::instan template class eos_vm_runtime; template class eos_vm_runtime; -} +} template struct host_function_registrator { @@ -358,6 +358,7 @@ REGISTER_HOST_FUNCTION(require_auth2); REGISTER_HOST_FUNCTION(has_auth); REGISTER_HOST_FUNCTION(require_recipient); REGISTER_HOST_FUNCTION(is_account); +REGISTER_HOST_FUNCTION(get_code_hash); // system api REGISTER_HOST_FUNCTION(current_time); diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index eadabd1c4f..1bf8c02047 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -1230,7 +1230,7 @@ BOOST_AUTO_TEST_CASE(deferred_inline_action_subjective_limit_failure) { try { } ); CALL_TEST_FUNCTION(chain, "test_transaction", "send_deferred_transaction_4k_action", {} ); BOOST_CHECK(!trace); - BOOST_CHECK_EXCEPTION(chain.produce_block( fc::seconds(2) ), fc::exception, + BOOST_CHECK_EXCEPTION(chain.produce_block( fc::seconds(2) ), fc::exception, [](const fc::exception& e) { return expect_assert_message(e, "inline action too big for nonprivileged account"); } @@ -3061,8 +3061,8 @@ BOOST_AUTO_TEST_CASE(action_results_tests) { try { t.set_code( config::system_account_name, contracts::action_results_wasm() ); t.produce_blocks(1); call_autoresret_and_check( config::system_account_name, - config::system_account_name, - "retmaxlim"_n, + config::system_account_name, + "retmaxlim"_n, [&]( const transaction_trace_ptr& res ) { BOOST_CHECK_EQUAL( res->receipt->status, transaction_receipt::executed ); @@ -3077,12 +3077,82 @@ BOOST_AUTO_TEST_CASE(action_results_tests) { try { expected_vec.end() ); } ); t.produce_blocks(1); - BOOST_REQUIRE_THROW(call_autoresret_and_check( config::system_account_name, - config::system_account_name, - "setliminv"_n, + BOOST_REQUIRE_THROW(call_autoresret_and_check( config::system_account_name, + config::system_account_name, + "setliminv"_n, [&]( auto res ) {}), action_validate_exception); } FC_LOG_AND_RETHROW() } +static const char get_code_hash_wast[] = R"=====( +(module + (import "env" "get_code_hash" (func $get_code_hash (param i64 i32 i32 i32) (result i32))) + (import "env" "prints_l" (func $prints_l (param i32 i32))) + (import "env" "printui" (func $printui (param i64))) + (import "env" "printhex" (func $printhex (param i32 i32))) + (memory $0 32) + (data (i32.const 4) ":") + (data (i32.const 8) "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + (export "apply" (func $apply)) + (func $apply (param $0 i64) (param $1 i64) (param $2 i64) + (call $printui (i64.extend_u/i32 + (call $get_code_hash + (get_local $2) + (i32.const 0) + (i32.const 8) + (i32.const 43) + ) + )) + (call $prints_l (i32.const 4) (i32.const 1)) + (call $printui (i64.load8_u offset=8 (i32.const 0))) + (call $prints_l (i32.const 4) (i32.const 1)) + (call $printui (i64.load offset=9 (i32.const 0))) + (call $prints_l (i32.const 4) (i32.const 1)) + (call $printhex (i32.const 17) (i32.const 32)) + (call $prints_l (i32.const 4) (i32.const 1)) + (call $printui (i64.load8_u offset=49 (i32.const 0))) + (call $prints_l (i32.const 4) (i32.const 1)) + (call $printui (i64.load8_u offset=50 (i32.const 0))) + ) +) +)====="; + +BOOST_AUTO_TEST_CASE(get_code_hash_tests) { try { + TESTER t; + t.produce_blocks(2); + t.create_account("gethash"_n); + t.create_account("test"_n); + t.set_code("gethash"_n, get_code_hash_wast); + t.produce_blocks(1); + + auto check = [&](account_name acc, uint64_t expected_seq) { + fc::sha256 expected_code_hash; + auto obj = t.control->db().find(acc); + if(obj) + expected_code_hash = obj->code_hash; + auto expected = "43:0:" + std::to_string(expected_seq) + + ":" + expected_code_hash.str() + ":0:0"; + + signed_transaction trx; + trx.actions.emplace_back(vector{{"gethash"_n, config::active_name}}, "gethash"_n, acc, bytes{}); + t.set_transaction_headers(trx, t.DEFAULT_EXPIRATION_DELTA); + trx.sign(t.get_private_key("gethash"_n, "active"), t.control->get_chain_id()); + auto tx_trace = t.push_transaction(trx); + BOOST_CHECK_EQUAL(tx_trace->receipt->status, transaction_receipt::executed); + BOOST_REQUIRE(tx_trace->action_traces.front().console == expected); + t.produce_block(); + }; + + check("gethash"_n, 1); + check("nonexisting"_n, 0); + check("test"_n, 0); + t.set_code("test"_n, contracts::test_api_wasm()); + check("test"_n, 1); + t.set_code("test"_n, get_code_hash_wast); + check("test"_n, 2); + t.set_code("test"_n, std::vector{}); + check("test"_n, 3); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END()