Fix #39 Refactor assert_operation

Reasons:
  1. The protocol should not depend upon implementation details such as
  how the database objects are structured or reflected
  2. The protocol should deal in abstract concepts
  3. Should use fc::datastream rather than istringstream for performance
  and memory allocation reasons
  4. Fees should be charged proportional to the size of the operation
  5. Validate on the assert operation should also perform sanity checks
  on types
  6. Protocol definition objects should never depend upon the database
  because they may be used in situations where the database and
  evaluators are not present.
  7. Reflected field names should never have '_' in them because they
  become part of the *PUBLIC* json definition.
This commit is contained in:
Daniel Larimer 2015-06-23 09:08:34 -04:00
parent 1b96210212
commit 8ac4bc1d58
9 changed files with 100 additions and 431 deletions

View file

@ -1,35 +1,12 @@
file(GLOB HEADERS "include/graphene/chain/*.hpp")
add_executable( field_reflector
field_reflector.cpp
types.cpp
type_id.cpp
address.cpp
key_object.cpp
${HEADERS} )
target_include_directories( field_reflector
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" )
target_link_libraries( field_reflector fc graphene_db )
add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp
COMMAND field_reflector ${CMAKE_CURRENT_SOURCE_DIR}/db_reflect_cmp.tmpl ${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp.new
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp.new ${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp
COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp.new
DEPENDS field_reflector db_reflect_cmp.tmpl
)
## SORT .cpp by most likely to change / break compile
add_library( graphene_chain
types.cpp
type_id.cpp
${CMAKE_CURRENT_BINARY_DIR}/db_reflect_cmp.cpp
address.cpp
asset.cpp
predicate.cpp
operations.cpp

View file

@ -24,61 +24,44 @@
namespace graphene { namespace chain {
namespace detail {
struct predicate_check_visitor
struct predicate_visitor
{
predicate_check_visitor( const database& d ): _db(d){}
typedef bool result_type;
template<typename Predicate> bool operator()( const Predicate& pred )const
const database& db;
predicate_visitor( const database& d ):db(d){}
bool operator()( const verify_account_name& p )const
{
return pred.check_predicate( _db );
return p.account_id(db).name == p.account_name;
}
bool operator()( const verify_symbol& p )const
{
return p.asset_id(db).symbol == p.symbol;
}
const database& _db;
};
} // graphene::chain::detail
void_result assert_evaluator::do_evaluate( const assert_operation& o )
{
const database& _db = db();
uint32_t skip = _db.get_node_properties().skip_flags;
// TODO: Skip flags
if( skip & database::skip_assert_evaluation )
return void_result();
for( const vector<char>& s_pred : o.predicates )
for( const vector<char>& pdata : o.predicates )
{
std::istringstream is( string( s_pred.begin(), s_pred.end() ) );
// de-serialize just the static_variant tag
unsigned_int t;
fc::raw::unpack( is, t );
// everyone checks: delegates must have allocated an opcode for it
FC_ASSERT( t.value < _db.get_global_properties().parameters.max_predicate_opcode );
if( t.value >= predicate::count() )
fc::datastream<const char*> ds( pdata.data(), pdata.size() );
predicate p;
try {
fc::raw::unpack( ds, p );
} catch ( const fc::exception& e )
{
//
// delegates allocated an opcode, but our client doesn't know
// the semantics (i.e. we are running an old client)
//
// skip_unknown_predicate indicates we're cool with assuming
// unknown predicates pass
//
if( skip & database::skip_unknown_predicate )
continue;
//
// ok, unknown predicate must die
//
FC_ASSERT( false, "unknown predicate" );
throw;
}
// rewind to beginning, unpack it, and check it
is.clear();
is.seekg(0);
predicate pred;
fc::raw::unpack( is, pred );
bool pred_passed = pred.visit( detail::predicate_check_visitor( _db ) );
FC_ASSERT( pred_passed );
FC_ASSERT( p.visit( predicate_visitor( _db ) ) );
}
return void_result();
}

View file

@ -1,239 +0,0 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <fc/io/json.hpp>
#include <fc/variant.hpp>
#include <fc/variant_object.hpp>
#include <graphene/chain/wild_object.hpp>
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
using namespace graphene::chain;
fc::mutable_variant_object g_vo_object_types;
std::vector< fc::mutable_variant_object > g_vo_fields;
struct serialize_object_type_member_visitor
{
public:
template<typename Member, class Class, Member (Class::*member)>
void operator()( const char* name )const
{
fc::mutable_variant_object vo;
vo["name"] = name;
vo["type"] = fc::get_typename<Member>::name();
vo["id"] = g_vo_fields.size();
g_vo_fields.push_back( vo );
}
};
struct serialize_object_type_visitor
{
typedef void result_type;
int t = 0;
serialize_object_type_visitor(int _t ):t(_t){}
template<typename Type>
result_type operator()( const Type& op )const
{
fc::mutable_variant_object vo;
vo["space_id"] = Type::space_id;
vo["type_id"] = Type::type_id;
g_vo_fields.clear();
// visit all members
fc::reflector<Type>::visit( serialize_object_type_member_visitor() );
vo["fields"] = g_vo_fields;
g_vo_object_types[ fc::get_typename<Type>::name() ] = vo;
}
};
struct getattr_switch_table_entry
{
uint32_t _switch_val; // (space << 24) | (type << 16) | fieldnum
string _object_typename;
string _field_typename;
string _field_name;
};
vector< getattr_switch_table_entry > build_switch_table()
{
vector< getattr_switch_table_entry > result;
for( const auto& item : g_vo_object_types )
{
const variant_object& vo = item.value().get_object();
uint32_t top = (vo["space_id"].as_uint64() << 24) | (vo["type_id"].as_uint64() << 16);
for( const auto& field : vo["fields"].get_array() )
{
getattr_switch_table_entry e;
e._switch_val = top | field["id"].as_uint64();
e._object_typename = item.key();
e._field_typename = field["type"].get_string();
e._field_name = field["name"].get_string();
result.push_back( e );
}
}
std::sort( result.begin(), result.end(),
[]( const getattr_switch_table_entry& a,
const getattr_switch_table_entry& b )
{
return a._switch_val < b._switch_val;
} );
return result;
}
std::string generate_cmp_attr_impl( const vector< getattr_switch_table_entry >& switch_table )
{
std::ostringstream out;
// switch( space )
// switch( type )
// switch( fieldnum )
// switch( opc )
std::map< uint8_t,
std::map< uint8_t,
std::map< uint16_t,
const getattr_switch_table_entry* > > > index;
for( const getattr_switch_table_entry& e : switch_table )
{
uint8_t sp = (e._switch_val >> 24) & 0xFF;
uint8_t ty = (e._switch_val >> 16) & 0xFF;
uint16_t fn = (e._switch_val ) & 0xFFFF;
auto& i0 = index;
if( i0.find( sp ) == i0.end() )
i0[sp] = std::map< uint8_t, std::map< uint16_t, const getattr_switch_table_entry* > >();
auto& i1 = i0[sp];
if( i1.find( ty ) == i1.end() )
i1[ty] = std::map< uint16_t, const getattr_switch_table_entry* >();
auto& i2 = i1[ty];
i2[fn] = &e;
}
out << " switch( obj.id.space() )\n"
" {\n";
for( const auto& e0 : index )
{
out << " case " << int( e0.first ) << ":\n"
" switch( obj.id.type() )\n"
" {\n";
for( const auto& e1 : e0.second )
{
out << " case " << int( e1.first ) << ":\n"
" switch( field_num )\n"
" {\n";
for( const auto& e2 : e1.second )
{
const std::string& ft = e2.second->_field_typename;
const std::string& ot = e2.second->_object_typename;
const std::string& fn = e2.second->_field_name;
out << " case " << int( e2.first ) << ":\n"
" {\n"
" // " << ft
<< " " << ot
<< "." << fn
<< "\n"
" const " << ft << "& dbval = object_database::cast< " << ot << " >( obj )." << fn << ";\n"
" return _cmp< " << ft << " >( dbval, lit, opc );\n"
" }\n";
}
out << " default:\n"
" FC_ASSERT( false, \"unrecognized field_num\" );\n"
" }\n";
}
out << " default:\n"
" FC_ASSERT( false, \"unrecognized object type\" );\n"
" }\n";
}
out << " default:\n"
" FC_ASSERT( false, \"unrecognized object space\" );\n"
" }\n";
return out.str();
}
static const char generated_file_banner[] =
"// _ _ __ _ _ //\n"
"// | | | | / _(_) | //\n"
"// __ _ ___ _ __ ___ _ __ __ _| |_ ___ __| | | |_ _| | ___ //\n"
"// / _` |/ _ \\ '_ \\ / _ \\ '__/ _` | __/ _ \\/ _` | | _| | |/ _ \\ //\n"
"// | (_| | __/ | | | __/ | | (_| | || __/ (_| | | | | | | __/ //\n"
"// \\__, |\\___|_| |_|\\___|_| \\__,_|\\__\\___|\\__,_| |_| |_|_|\\___| //\n"
"// __/ | //\n"
"// |___/ //\n"
"// //\n"
"// Generated by: programs/field_reflector/main.cpp //\n"
"// //\n"
"// Warning: This is a generated file, any changes made here will be //\n"
"// overwritten by the build process. If you need to change what //\n"
"// is generated here, you should either modify the reflected //\n"
"// types, or modify the code generator itself. //\n"
"// //\n"
;
int main( int argc, char** argv )
{
try
{
if( argc != 3 )
{
std::cout << "syntax: " << argv[0] << " <template_filename> <output_filename>\n";
return 1;
}
graphene::chain::impl::wild_object wo;
for( int32_t i = 0; i < wo.count(); ++i )
{
wo.set_which(i);
wo.visit( serialize_object_type_visitor(i) );
}
vector< getattr_switch_table_entry > switch_table = build_switch_table();
fc::mutable_variant_object tmpl_params;
tmpl_params["generated_file_banner"] = generated_file_banner;
tmpl_params["object_descriptor"] = fc::json::to_string( g_vo_object_types );
tmpl_params["cmp_attr_impl_body"] = generate_cmp_attr_impl( switch_table );
std::ifstream template_file( argv[1] );
if (!template_file)
FC_THROW("Error opening template file ${template_file}", ("template_file", argv[1]));
std::stringstream ss;
ss << template_file.rdbuf();
std::string result = fc::format_string( ss.str(), tmpl_params );
std::ofstream result_file( argv[2] );
result_file << result;
}
catch ( const fc::exception& e )
{
edump((e.to_detail_string()));
return 1;
}
return 0;
}

View file

@ -105,17 +105,21 @@ namespace graphene { namespace chain {
/**
* @brief assert that some conditions are true.
* @ingroup operations
*
* This operation performs no changes to the database state, but can but used to verify
* pre or post conditions for other operations.
*
*/
struct assert_operation
{
asset fee;
account_id_type fee_paying_account;
vector< vector< char > > predicates;
asset fee;
account_id_type fee_paying_account;
vector< vector< char > > predicates;
flat_set<account_id_type> required_auths;
account_id_type fee_payer()const { return fee_paying_account; }
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
share_type calculate_fee( const fee_schedule_type& k )const{ return k.assert_op_fee; }
share_type calculate_fee( const fee_schedule_type& k )const;
void validate()const;
void get_balance_delta( balance_accumulator& acc, const operation_result& result = asset())const { acc.adjust( fee_payer(), -fee ); }

View file

@ -15,46 +15,55 @@
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <graphene/chain/types.hpp>
#include <vector>
namespace graphene { namespace chain {
class database;
class pred_field_lit_cmp
/**
* Used to verify that account_id->name is account_name
*/
struct verify_account_name
{
public:
pred_field_lit_cmp(
object_id_type obj_id,
uint16_t field_num,
const vector<char>& lit,
uint8_t opc
) :
_obj_id( obj_id ),
_field_num( field_num ),
_lit( lit ),
_opc( opc )
{}
pred_field_lit_cmp() {} // necessary for instantiating static_variant
~pred_field_lit_cmp() {}
account_id_type account_id;
string account_name;
bool check_predicate( const database& db )const;
object_id_type _obj_id;
uint16_t _field_num;
vector<char> _lit;
uint8_t _opc;
/**
* Perform state independent checks, such as verifying that
* account_name is a valid name for an account.
*/
bool validate()const { return is_valid_name( account_name ); }
};
/**
* Used to verify that account_id->name is account_name
*/
struct verify_symbol
{
asset_id_type asset_id;
string symbol;
/**
* Perform state independent checks, such as verifying that
* account_name is a valid name for an account.
*/
bool validate()const { return is_valid_symbol( symbol ); }
};
/**
* When defining predicates do not make the protocol dependent upon
* implementation details.
*/
typedef static_variant<
pred_field_lit_cmp
verify_account_name,
verify_symbol
> predicate;
} }
FC_REFLECT( graphene::chain::pred_field_lit_cmp, (_obj_id)(_field_num)(_lit)(_opc) );
FC_REFLECT( graphene::chain::verify_account_name, (account_id)(account_name) )
FC_REFLECT( graphene::chain::verify_symbol, (asset_id)(symbol) )

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <fc/static_variant.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/call_order_object.hpp>
#include <graphene/chain/delegate_object.hpp>
#include <graphene/chain/key_object.hpp>
#include <graphene/chain/limit_order_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/witness_object.hpp>
namespace graphene { namespace chain { namespace impl {
/**
* A static_variant of all object types.
*
* Used by field_reflector, this ultimately determines the object
* types which may be inspected by pred_field_lit_cmp.
*/
typedef fc::static_variant<
//null_object,
//base_object,
key_object,
account_object,
asset_object,
force_settlement_object,
delegate_object,
witness_object,
limit_order_object,
call_order_object,
//custom_object,
proposal_object,
operation_history_object,
withdraw_permission_object,
vesting_balance_object,
worker_object
> wild_object;
} } }

View file

@ -17,6 +17,7 @@
*/
#include <graphene/chain/database.hpp>
#include <graphene/chain/operations.hpp>
#include <graphene/chain/predicate.hpp>
#include <fc/crypto/aes.hpp>
namespace graphene { namespace chain {
@ -813,9 +814,34 @@ share_type account_upgrade_operation::calculate_fee(const fee_schedule_type& k)
return k.membership_annual_fee;
}
struct predicate_validator
{
typedef void result_type;
template<typename T>
void operator()( const T& p )const
{
p.validate();
}
};
void assert_operation::validate()const
{
FC_ASSERT( fee.amount >= 0 );
for( const auto& item : predicates )
{
FC_ASSERT( item.size() > 0 );
fc::datastream<const char*> ds( item.data(), item.size() );
predicate p;
try {
fc::raw::unpack( ds, p );
}
catch ( const fc::exception& e )
{
continue;
}
p.visit( predicate_validator() );
}
}
void assert_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const
{
@ -823,4 +849,12 @@ void assert_operation::get_required_auth(flat_set<account_id_type>& active_auth_
active_auth_set.insert(required_auths.begin(), required_auths.end());
}
/**
* The fee for assert operations is proportional to their size
*/
share_type assert_operation::calculate_fee( const fee_schedule_type& k )const
{
return (k.assert_op_fee * fc::raw::pack_size(*this)) / 1024;
}
} } // namespace graphene::chain

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <graphene/chain/assert_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/db_reflect_cmp.hpp>
#include <graphene/chain/predicate.hpp>
#include <sstream>
#include <vector>
namespace graphene { namespace chain {
bool pred_field_lit_cmp::check_predicate( const database& db )const
{
return graphene::chain::impl::cmp_attr_impl( db.get_object( _obj_id ), _field_num, _lit, _opc );
}
} } // graphene::chain

View file

@ -820,9 +820,7 @@ BOOST_AUTO_TEST_CASE( assert_op_test )
op.predicates = vector< vector< char > >();
op.predicates.push_back(
fc::raw::pack(
predicate(
pred_field_lit_cmp( nathan_key_id, 1, fc::raw::pack( lit_key ), opc_equal_to )
)
predicate( verify_account_name{ nathan_id, "nathan" } )
) );
trx.operations.push_back(op);
trx.sign( nathan_key_id, nathan_private_key );
@ -831,9 +829,7 @@ BOOST_AUTO_TEST_CASE( assert_op_test )
// nathan checks that his public key is not equal to the given value (fail)
op.predicates.back() =
fc::raw::pack(
predicate(
pred_field_lit_cmp( nathan_key_id, 1, fc::raw::pack( lit_key ), opc_not_equal_to )
)
predicate( verify_account_name{ nathan_id, "dan" } )
);
trx.operations.back() = op;
trx.sign( nathan_key_id, nathan_private_key );