提交 53d20fc3 编写于 作者: B Bart Wyatt

Merge branch 'feature/restore-abi-transaction-serialization' into eos-noon

......@@ -4,22 +4,27 @@
{
"name": "assertdef",
"base": "",
"fields": {
"condition": "int8",
"message": "string"
}
"fields": [
{
"name": "condition",
"type": "int8"
},{
"name": "message",
"type": "string"
}
]
}, {
"name": "nothing",
"base": "",
"fields": {}
"fields": []
}
],
"actions": [
{
"action": "procassert",
"type": "assert_def"
"name": "procassert",
"type": "assertdef"
}, {
"action": "provereset",
"name": "provereset",
"type": "nothing"
}
],
......
......@@ -210,7 +210,7 @@ std::vector<action> chain_initializer::prepare_database( chain_controller& chain
a.creation_date = genesis.initial_timestamp;
if( name == config::system_account_name ) {
// a.set_abi(eos_contract_abi());
a.set_abi(eos_contract_abi());
}
});
const auto& owner = db.create<permission_object>([&name](permission_object& p) {
......
......@@ -4,6 +4,9 @@
*/
#pragma once
#include <eosio/chain/contracts/types.hpp>
#include <eosio/chain/transaction.hpp>
#include <fc/variant_object.hpp>
namespace eosio { namespace chain { namespace contracts {
......@@ -11,6 +14,7 @@ using std::map;
using std::string;
using std::function;
using std::pair;
using namespace fc;
/**
* Describes the binary representation message and table contents so that it can
......@@ -53,6 +57,12 @@ struct abi_serializer {
fc::variant binary_to_variant(const type_name& type, fc::datastream<const char*>& binary)const;
void variant_to_binary(const type_name& type, const fc::variant& var, fc::datastream<char*>& ds)const;
template<typename T, typename Resolver>
static void to_variant( const T& o, fc::variant& vo, Resolver resolver );
template<typename T, typename Resolver>
static void from_variant( const fc::variant& v, T& o, Resolver resolver );
template<typename Vec>
static bool is_empty_abi(const Vec& abi_vec)
{
......@@ -74,4 +84,270 @@ struct abi_serializer {
void binary_to_variant(const type_name& type, fc::datastream<const char*>& stream, fc::mutable_variant_object& obj)const;
};
namespace impl {
/**
* Determine if a type contains ABI related info, perhaps deeply nested
* @tparam T - the type to check
*/
template<typename T>
constexpr bool single_type_requires_abi_v = std::is_base_of<transaction, T>::value || std::is_same<T, action>::value;
/**
* Basic constexpr for a type, aliases the basic check directly
* @tparam T - the type to check
*/
template<typename T>
constexpr bool type_requires_abi_v = single_type_requires_abi_v<T>;
/**
* specialization that catches common container patterns and checks their contained-type
* @tparam Container - a templated container type whose first argument is the contained type
*/
template<template<typename ...> class Container, typename T, typename ...Args >
constexpr bool type_requires_abi_v<Container<T, Args...>> = single_type_requires_abi_v<T>;
/**
* convenience aliases for creating overload-guards based on whether the type contains ABI related info
*/
template<typename T>
using not_require_abi_t = std::enable_if_t<!type_requires_abi_v<T>, int>;
template<typename T>
using require_abi_t = std::enable_if_t<type_requires_abi_v<T>, int>;
/**
* Reflection visitor that uses a resolver to resolve ABIs for nested types
* this will degrade to the common fc::to_variant as soon as the type no longer contains
* ABI related info
*
* @tparam Reslover - callable with the signature (const name& code_account) -> optional<abi_def>
*/
template<typename T, typename Resolver>
class abi_to_variant_visitor
{
public:
abi_to_variant_visitor( mutable_variant_object& _mvo, const T& _val, Resolver _resolver )
:_vo(_mvo)
,_val(_val)
,_resolver(_resolver)
{}
/**
* Visit a single member and add it to the variant object
* @tparam Member - the member to visit
* @tparam Class - the class we are traversing
* @tparam member - pointer to the member
* @param name - the name of the member
*/
template<typename Member, class Class, Member (Class::*member) >
void operator()( const char* name )const
{
this->add(_vo,name,(_val.*member));
}
private:
/**
* template which overloads add for types which are not relvant to ABI information
* and can be degraded to the normal ::to_variant(...) processing
*/
template<typename M, not_require_abi_t<M> = 1>
void add( mutable_variant_object& vo, const char* name, const M& v )const
{
vo(name,v);
}
/**
* template which overloads add for types which contain ABI information in their trees
* for these types we create new ABI aware visitors
*/
template<typename M, require_abi_t<M> = 1>
void add( mutable_variant_object& vo, const char* name, const M& v )const
{
mutable_variant_object mvo;
fc::reflector<M>::visit( impl::abi_to_variant_visitor<M, decltype(_resolver)>( mvo, v, _resolver ) );
vo(name, std::move(mvo));
}
/**
* template which overloads add for vectors of types which contain ABI information in their trees
* for these members we call ::add in order to trigger further processing
*/
template<typename M, require_abi_t<M> = 1>
void add( mutable_variant_object& vo, const char* name, const vector<M>& v )const
{
vector<variant> array(v.size());
for (const auto& iter: v) {
mutable_variant_object mvo;
add(mvo, "_", iter);
array.emplace_back(std::move(mvo["_"]));
}
vo(name, std::move(array));
}
/**
* Non templated overload that has priority for the action structure
* this type has members which must be directly translated by the ABI so it is
* exploded and processed explicitly
*/
void add( mutable_variant_object& vo, const char* name, const action& v )const
{
mutable_variant_object mvo;
mvo("scope", v.scope);
mvo("name", v.name);
mvo("authorization", v.authorization);
auto abi = _resolver(v.scope);
if (abi.valid()) {
auto type = abi->get_action_type(v.name);
mvo("data", abi->binary_to_variant(type, v.data));
mvo("hex_data", v.data);
} else {
mvo("data", v.data);
}
vo(name, std::move( mvo ));
}
mutable_variant_object& _vo;
const T& _val;
Resolver _resolver;
};
/**
* Reflection visitor that uses a resolver to resolve ABIs for nested types
* this will degrade to the common fc::from_variant as soon as the type no longer contains
* ABI related info
*
* @tparam Reslover - callable with the signature (const name& code_account) -> optional<abi_def>
*/
template<typename T, typename Resolver>
class abi_from_variant_visitor
{
public:
abi_from_variant_visitor( const variant_object& _vo, T& v, Resolver _resolver )
:_vo(_vo)
,_val(v)
,_resolver(_resolver)
{}
/**
* Visit a single member and extract it from the variant object
* @tparam Member - the member to visit
* @tparam Class - the class we are traversing
* @tparam member - pointer to the member
* @param name - the name of the member
*/
template<typename Member, class Class, Member (Class::*member)>
void operator()( const char* name )const
{
auto itr = _vo.find(name);
if( itr != _vo.end() )
extract( itr->value(), _val.*member );
}
private:
/**
* template which overloads extract for types which are not relvant to ABI information
* and can be degraded to the normal ::from_variant(...) processing
*/
template<typename M, not_require_abi_t<M> = 1>
void extract( const variant& v, M& o )const
{
from_variant(v, o);
}
/**
* template which overloads extract for types which contain ABI information in their trees
* for these types we create new ABI aware visitors
*/
template<typename M, require_abi_t<M> = 1>
void extract( const variant& v, M& o )const
{
const variant_object& vo = v.get_object();
fc::reflector<M>::visit( abi_from_variant_visitor<M, decltype(_resolver)>( vo, o, _resolver ) );
}
/**
* template which overloads extract for vectors of types which contain ABI information in their trees
* for these members we call ::extract in order to trigger further processing
*/
template<typename M, require_abi_t<M> = 1>
void extract( const variant& v, vector<M>& o )const
{
const variants& array = v.get_array();
o.clear();
o.reserve( array.size() );
for( auto itr = array.begin(); itr != array.end(); ++itr ) {
M o_iter;
extract(*itr, o_iter);
o.emplace_back(std::move(o_iter));
}
}
/**
* Non templated overload that has priority for the action structure
* this type has members which must be directly translated by the ABI so it is
* exploded and processed explicitly
*/
void extract( const variant& v, action& act )const
{
const variant_object& vo = v.get_object();
FC_ASSERT(vo.contains("scope"));
FC_ASSERT(vo.contains("name"));
from_variant(vo["scope"], act.scope);
from_variant(vo["name"], act.name);
if (vo.contains("authorization")) {
from_variant(vo["authorization"], act.authorization);
}
if( vo.contains( "data" ) ) {
const auto& data = vo["data"];
if( data.is_string() ) {
from_variant(data, act.data);
} else if ( data.is_object() ) {
auto abi = _resolver(act.scope);
if (abi.valid()) {
auto type = abi->get_action_type(act.name);
act.data = std::move(abi->variant_to_binary(type, data));
}
}
}
if (act.data.empty()) {
if( vo.contains( "hex_data" ) ) {
const auto& data = vo["hex_data"];
if( data.is_string() ) {
from_variant(data, act.data);
}
}
}
FC_ASSERT(!act.data.empty(), "Failed to deserialize data for ${scope}:${name}", ("scope", act.scope)("name", act.name));
}
const variant_object& _vo;
T& _val;
Resolver _resolver;
};
}
template<typename T, typename Resolver>
void abi_serializer::to_variant( const T& o, variant& vo, Resolver resolver ) try {
mutable_variant_object mvo;
fc::reflector<T>::visit( impl::abi_to_variant_visitor<T, Resolver>( mvo, o, resolver ) );
vo = std::move(mvo);
} FC_RETHROW_EXCEPTIONS(error, "Failed to serialize type", ("object",o))
template<typename T, typename Resolver>
void abi_serializer::from_variant( const variant& v, T& o, Resolver resolver ) try {
const variant_object& vo = v.get_object();
fc::reflector<T>::visit( impl::abi_from_variant_visitor<T, Resolver>( vo, o, resolver ) );
} FC_RETHROW_EXCEPTIONS(error, "Failed to deserialize variant", ("variant",v))
} } } // eosio::chain::contracts
......@@ -219,10 +219,12 @@ struct setcode {
};
struct setabi {
/*
setabi() = default;
setabi(const account_name& account, const abi_def& abi)
:account(account), abi(abi)
{}
*/
account_name account;
abi_def abi;
......
......@@ -46,6 +46,7 @@ namespace eosio { namespace testing {
private_key_type get_private_key( name keyname, string role = "owner" );
void set_code( account_name name, const char* wast );
void set_abi( account_name name, const char* abi_json );
unique_ptr<chain_controller> control;
......
......@@ -3,6 +3,7 @@
#include <eosio/chain/contracts/types.hpp>
#include <fc/utility.hpp>
#include <fc/io/json.hpp>
#include "WAST/WAST.h"
#include "WASM/WASM.h"
......@@ -211,6 +212,21 @@ namespace eosio { namespace testing {
control->push_transaction( trx );
} FC_CAPTURE_AND_RETHROW( (account)(wast) )
void tester::set_abi( account_name account, const char* abi_json) {
auto abi = fc::json::from_string(abi_json).template as<contracts::abi_def>();
signed_transaction trx;
trx.write_scope = {config::eosio_auth_scope};
trx.actions.emplace_back( vector<permission_level>{{account,config::active_name}},
contracts::setabi{
.account = account,
.abi = abi
});
set_tapos( trx );
trx.sign( get_private_key( account, "active" ), chain_id_type() );
control->push_transaction( trx );
}
bool tester::chain_has_transaction( const transaction_id_type& txid ) const {
return chain_transactions.count(txid) != 0;
}
......
......@@ -344,11 +344,24 @@ read_write::push_block_results read_write::push_block(const read_write::push_blo
read_write::push_transaction_results read_write::push_transaction(const read_write::push_transaction_params& params) {
signed_transaction pretty_input;
from_variant(params, pretty_input);
auto resolver = [&,this]( const account_name& name ) -> optional<abi_serializer> {
const auto* accnt = db.get_database().find<account_object,by_name>( name );
if (accnt != nullptr) {
abi_def abi;
if (abi_serializer::to_abi(accnt->abi, abi)) {
return abi_serializer(abi);
}
}
return optional<abi_serializer>();
};
abi_serializer::from_variant(params, pretty_input, resolver);
db.push_transaction(pretty_input, skip_flags);
#warning TODO: get transaction results asynchronously
//auto pretty_trx = db.transaction_to_variant( ptrx );
return read_write::push_transaction_results{ pretty_input.id(), fc::variant_object() };
fc::variant pretty_output;
abi_serializer::to_variant(pretty_input, pretty_output, resolver);
return read_write::push_transaction_results{ pretty_input.id(), pretty_output };
}
read_write::push_transactions_results read_write::push_transactions(const read_write::push_transactions_params& params) {
......
......@@ -33,7 +33,7 @@ target_link_libraries( chain_test eosio_testing eosio_chain chainbase eos_utilit
if(WASM_TOOLCHAIN)
target_include_directories( chain_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CMAKE_CURRENT_BINARY_DIR}/tests/contracts )
# add_dependencies(chain_test rate_limit_auth)
add_dependencies(chain_test asserter)
endif()
......
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <asserter/asserter.wast.hpp>
#include <fc/variant_object.hpp>
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::chain::contracts;
using namespace eosio::testing;
using namespace fc;
struct assertdef {
int8_t condition;
......@@ -34,6 +38,41 @@ struct provereset {
FC_REFLECT_EMPTY(provereset);
const char* const asserter_abi = R"EOF(
{
"types": [],
"structs": [
{
"name": "assertdef",
"base": "",
"fields": [
{
"name": "condition",
"type": "int8"
},{
"name": "message",
"type": "string"
}
]
}, {
"name": "nothing",
"base": "",
"fields": []
}
],
"actions": [
{
"name": "procassert",
"type": "assertdef"
}, {
"name": "provereset",
"type": "nothing"
}
],
"tables": []
}
)EOF";
BOOST_AUTO_TEST_SUITE(wasm_tests)
/**
......@@ -116,5 +155,59 @@ BOOST_FIXTURE_TEST_CASE( prove_mem_reset, tester ) try {
} FC_LOG_AND_RETHROW() /// prove_mem_reset
/**
* Prove the modifications to global variables are wiped between runs
*/
BOOST_FIXTURE_TEST_CASE( abi_from_variant, tester ) try {
produce_blocks(2);
create_accounts( {N(asserter)} );
transfer( N(inita), N(asserter), "10.0000 EOS", "memo" );
produce_block();
set_code(N(asserter), asserter_wast);
set_abi(N(asserter), asserter_abi);
produce_blocks(1);
auto resolver = [&,this]( const account_name& name ) -> optional<abi_serializer> {
try {
const auto& accnt = this->control->get_database().get<account_object,by_name>( name );
abi_def abi;
if (abi_serializer::to_abi(accnt.abi, abi)) {
return abi_serializer(abi);
}
return optional<abi_serializer>();
} FC_RETHROW_EXCEPTIONS(error, "Failed to find or parse ABI for ${name}", ("name", name))
};
variant pretty_trx = mutable_variant_object()
("actions", variants({
mutable_variant_object()
("scope", "asserter")
("name", "procassert")
("authorization", variants({
mutable_variant_object()
("actor", "asserter")
("permission", name(config::active_name).to_string())
}))
("data", mutable_variant_object()
("condition", 1)
("message", "Should Not Assert!")
)
})
);
signed_transaction trx;
abi_serializer::from_variant(pretty_trx, trx, resolver);
set_tapos( trx );
trx.sign( get_private_key( N(asserter), "active" ), chain_id_type() );
control->push_transaction( trx );
produce_blocks(1);
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
const auto& receipt = get_transaction_receipt(trx.id());
BOOST_CHECK_EQUAL(transaction_receipt::executed, receipt.status);
} FC_LOG_AND_RETHROW() /// prove_mem_reset
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册