提交 b72535ce 编写于 作者: A arhag

move authorization code into its own class (authorization_manager)

Also update native action handlers to reflect changes in PR #2440.
上级 d4d27e3e
......@@ -132,9 +132,10 @@ namespace eosiosystem {
}
ACTION( SystemAccount, canceldelay ) {
permission_level canceling_auth;
transaction_id_type trx_id;
EOSLIB_SERIALIZE( canceldelay, (trx_id) )
EOSLIB_SERIALIZE( canceldelay, (canceling_auth)(trx_id) )
};
static void on( const canceldelay& ) {
......
......@@ -10,6 +10,7 @@ add_library( eosio_chain
block_state.cpp
fork_database.cpp
controller.cpp
authorization_manager.cpp
resource_limits.cpp
block_log.cpp
genesis_state.cpp
......@@ -41,7 +42,7 @@ add_library( eosio_chain
)
target_link_libraries( eosio_chain eos_utilities fc chainbase Logging IR WAST WASM Runtime
wasm asmjs passes cfg ast emscripten-optimizer support softfloat
wasm asmjs passes cfg ast emscripten-optimizer support softfloat
)
target_include_directories( eosio_chain
PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include"
......@@ -52,5 +53,3 @@ target_include_directories( eosio_chain
#if(MSVC)
# set_source_files_properties( db_init.cpp db_block.cpp database.cpp block_log.cpp PROPERTIES COMPILE_FLAGS "/bigobj" )
#endif(MSVC)
......@@ -15,7 +15,7 @@
using namespace boost;
namespace eosio { namespace chain {
namespace eosio { namespace chain {
using boost::algorithm::ends_with;
using std::string;
......@@ -96,6 +96,7 @@ namespace eosio { namespace chain {
built_in_types.emplace("permission_name", pack_unpack<permission_name>());
built_in_types.emplace("action_name", pack_unpack<action_name>());
built_in_types.emplace("scope_name", pack_unpack<scope_name>());
built_in_types.emplace("permission_level", pack_unpack<permission_level>());
built_in_types.emplace("producer_schedule", pack_unpack<producer_schedule_type>());
built_in_types.emplace("newaccount", pack_unpack<newaccount>());
}
......
......@@ -4,6 +4,7 @@
#include <eosio/chain/exceptions.hpp>
#include <eosio/chain/wasm_interface.hpp>
#include <eosio/chain/generated_transaction_object.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/scope_sequence_object.hpp>
#include <eosio/chain/account_object.hpp>
......@@ -18,7 +19,7 @@ action_trace apply_context::exec_one()
const auto& gpo = control.get_global_properties();
auto start = fc::time_point::now();
cpu_usage = gpo.configuration.base_per_action_cpu_usage;
cpu_usage = gpo.configuration.base_per_action_cpu_usage;
try {
const auto &a = control.get_account(receiver);
privileged = a.privileged;
......@@ -217,7 +218,7 @@ void apply_context::require_recipient( account_name recipient ) {
void apply_context::execute_inline( action&& a ) {
if ( !privileged ) {
if( a.account != receiver ) {
const auto delay = control.check_authorization({a}, flat_set<public_key_type>(), false, {receiver});
const auto delay = control.get_authorization_manager().check_authorization({a}, flat_set<public_key_type>(), false, {receiver});
FC_ASSERT( published_time + delay <= control.head_block_time(),
"inline action uses a permission that imposes a delay that is not met, set delay_sec in transaction header to at least ${delay} seconds",
("delay", delay.to_seconds()) );
......@@ -239,8 +240,8 @@ void apply_context::schedule_deferred_transaction( deferred_transaction&& trx )
transaction_exception,
"Transaction expires at ${trx.expiration} which is before the first allowed time to execute at ${trx.execute_after}",
("trx.expiration",trx.expiration)("trx.execute_after",trx.execute_after) );
control.validate_referenced_accounts( trx );
control.validate_expiration( trx );
control.validate_referenced_accounts( trx );
control.validate_expiration( trx );
if( !privileged ) {
if (trx.payer != receiver) {
......@@ -265,7 +266,7 @@ void apply_context::schedule_deferred_transaction( deferred_transaction&& trx )
fc::datastream<char*> ds( gtx.packed_trx.data(), trx_size );
fc::raw::pack( ds, trx );
});
auto& rl = control.get_mutable_resource_limits_manager();
rl.add_pending_account_ram_usage( trx.payer, config::billable_size_v<generated_transaction_object> + trx_size );
checktime( trx_size * 4 ); /// 4 instructions per byte of packed generated trx (estimated)
......@@ -301,7 +302,7 @@ void apply_context::schedule_deferred_transaction( deferred_transaction&& trx )
}
}
if( check_auth ) {
delay = controller.check_authorization(trx.actions, flat_set<public_key_type>(), false, {receiver});
delay = controller..get_authorization_manager().check_authorization(trx.actions, flat_set<public_key_type>(), false, {receiver});
FC_ASSERT( trx_meta.published + delay <= controller.head_block_time(),
"deferred transaction uses a permission that imposes a delay that is not met, set delay_sec in transaction header to at least ${delay} seconds",
("delay", delay.to_seconds()) );
......@@ -432,11 +433,11 @@ int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t b
}
void apply_context::check_auth( const transaction& trx, const vector<permission_level>& perm ) {
control.check_authorization( trx.actions,
{},
true,
{},
flat_set<permission_level>(perm.begin(), perm.end()) );
control.get_authorization_manager().check_authorization( trx.actions,
{},
true,
{},
flat_set<permission_level>(perm.begin(), perm.end()) );
}
int apply_context::db_store_i64( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) {
......
此差异已折叠。
......@@ -9,11 +9,10 @@
#include <eosio/chain/block_summary_object.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <eosio/chain/contract_table_objects.hpp>
#include <eosio/chain/action_objects.hpp>
#include <eosio/chain/generated_transaction_object.hpp>
#include <eosio/chain/transaction_object.hpp>
#include <eosio/chain/permission_link_object.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <chainbase/chainbase.hpp>
......@@ -51,6 +50,7 @@ struct controller_impl {
fork_database fork_db;
wasm_interface wasmif;
resource_limits_manager resource_limits;
authorization_manager authorization;
controller::config conf;
controller& self;
......@@ -94,6 +94,7 @@ struct controller_impl {
fork_db( cfg.shared_memory_dir ),
wasmif( cfg.wasm_runtime ),
resource_limits( db ),
authorization( s ),
conf( cfg ),
self( s )
{
......@@ -120,11 +121,26 @@ struct controller_impl {
void init() {
// ilog( "${c}", ("c",fc::json::to_pretty_string(cfg)) );
initialize_indicies();
add_indices();
initialize_fork_db();
/**
* The undable state contains state transitions from blocks
* The fork database needs an initial block_state to be set before
* it can accept any new blocks. This initial block state can be found
* in the database (whose head block state should be irreversible) or
* it would be the genesis state.
*/
if( !head ) {
initialize_fork_db(); // set head to genesis state
#warning What if head is empty because the user deleted forkdb.dat? Will this not corrupt the database?
db.set_revision( head->block_num );
initialize_database();
}
FC_ASSERT( db.revision() == head->block_num, "fork database is inconsistent with shared memory",
("db",db.revision())("head",head->block_num) );
/**
* The undoable state contains state transitions from blocks
* in the fork database that could be reversed. Because this
* is a new startup and the fork database is empty, we must
* unwind that pending state. This state will be regenerated
......@@ -141,13 +157,9 @@ struct controller_impl {
db.flush();
}
void initialize_indicies() {
void add_indices() {
db.add_index<account_index>();
db.add_index<account_sequence_index>();
db.add_index<permission_index>();
db.add_index<permission_usage_index>();
db.add_index<permission_link_index>();
db.add_index<action_permission_index>();
db.add_index<table_id_multi_index>();
db.add_index<key_value_index>();
......@@ -163,7 +175,8 @@ struct controller_impl {
db.add_index<generated_transaction_multi_index>();
db.add_index<scope_sequence_multi_index>();
resource_limits.initialize_database();
authorization.add_indices();
resource_limits.add_indices();
}
void abort_pending_block() {
......@@ -183,47 +196,36 @@ struct controller_impl {
}
/**
* The fork database needs an initial block_state to be set before
* it can accept any new blocks. This initial block state can be found
* in the database (whose head block state should be irreversible) or
* it would be the genesis state.
* Sets fork database head to the genesis state.
*/
void initialize_fork_db() {
head = fork_db.head();
if( !head ) {
wlog( " Initializing new blockchain with genesis state " );
producer_schedule_type initial_schedule{ 0, {{N(eosio), conf.genesis.initial_key}} };
wlog( " Initializing new blockchain with genesis state " );
producer_schedule_type initial_schedule{ 0, {{N(eosio), conf.genesis.initial_key}} };
block_header_state genheader;
genheader.active_schedule = initial_schedule;
genheader.pending_schedule = initial_schedule;
genheader.pending_schedule_hash = fc::sha256::hash(initial_schedule);
genheader.header.timestamp = conf.genesis.initial_timestamp;
genheader.header.action_mroot = conf.genesis.compute_chain_id();
genheader.id = genheader.header.id();
genheader.block_num = genheader.header.block_num();
block_header_state genheader;
genheader.active_schedule = initial_schedule;
genheader.pending_schedule = initial_schedule;
genheader.pending_schedule_hash = fc::sha256::hash(initial_schedule);
genheader.header.timestamp = conf.genesis.initial_timestamp;
genheader.header.action_mroot = conf.genesis.compute_chain_id();
genheader.id = genheader.header.id();
genheader.block_num = genheader.header.block_num();
head = std::make_shared<block_state>( genheader );
signed_block genblock(genheader.header);
head = std::make_shared<block_state>( genheader );
signed_block genblock(genheader.header);
edump((genheader.header));
edump((genblock));
blog.append( genblock );
edump((genheader.header));
edump((genblock));
blog.append( genblock );
db.set_revision( head->block_num );
fork_db.set( head );
initialize_database();
}
FC_ASSERT( db.revision() == head->block_num, "fork database is inconsistant with shared memory",
("db",db.revision())("head",head->block_num) );
fork_db.set( head );
}
void create_native_account( account_name name ) {
void create_native_account( account_name name, const authority& owner, const authority& active, bool is_privileged = false ) {
db.create<account_object>([&](auto& a) {
a.name = name;
a.creation_date = conf.genesis.initial_timestamp;
a.privileged = true;
a.privileged = is_privileged;
if( name == config::system_account_name ) {
a.set_abi(eosio_contract_abi(abi_def()));
......@@ -233,21 +235,20 @@ struct controller_impl {
a.name = name;
});
const auto& owner = db.create<permission_object>([&](auto& p) {
p.owner = name;
p.name = "owner";
p.auth.threshold = 1;
p.auth.keys.push_back( key_weight{ .key = conf.genesis.initial_key, .weight = 1 } );
});
db.create<permission_object>([&](auto& p) {
p.owner = name;
p.parent = owner.id;
p.name = "active";
p.auth.threshold = 1;
p.auth.keys.push_back( key_weight{ .key = conf.genesis.initial_key, .weight = 1 } );
});
const auto& owner_permission = authorization.create_permission(name, config::owner_name, 0,
owner, conf.genesis.initial_timestamp );
const auto& active_permission = authorization.create_permission(name, config::active_name, owner_permission.id,
active, conf.genesis.initial_timestamp );
resource_limits.initialize_account(name);
resource_limits.add_pending_account_ram_usage(
name,
(int64_t)(config::billable_size_v<permission_object> + owner_permission.auth.get_billable_size())
);
resource_limits.add_pending_account_ram_usage(
name,
(int64_t)(config::billable_size_v<permission_object> + active_permission.auth.get_billable_size())
);
}
void initialize_database() {
......@@ -255,53 +256,30 @@ struct controller_impl {
for (int i = 0; i < 0x10000; i++)
db.create<block_summary_object>([&](block_summary_object&) {});
const auto& tapos_block_summary = db.get<block_summary_object>(1);
db.modify( tapos_block_summary, [&]( auto& bs ) {
bs.block_id = head->id;
});
db.create<global_property_object>([&](auto& gpo ){
gpo.configuration = conf.genesis.initial_configuration;
});
db.create<dynamic_global_property_object>([](auto&){});
db.create<permission_object>([](auto&){}); /// reserve perm 0 (used else where)
resource_limits.initialize_chain();
create_native_account( config::system_account_name );
// Create special accounts
auto create_special_account = [&](account_name name, const auto& owner, const auto& active) {
db.create<account_object>([&](account_object& a) {
a.name = name;
a.creation_date = conf.genesis.initial_timestamp;
});
db.create<account_sequence_object>([&](auto & a) {
a.name = name;
});
const auto& owner_permission = db.create<permission_object>([&](permission_object& p) {
p.name = config::owner_name;
p.parent = 0;
p.owner = name;
p.auth = move(owner);
});
db.create<permission_object>([&](permission_object& p) {
p.name = config::active_name;
p.parent = owner_permission.id;
p.owner = owner_permission.owner;
p.auth = move(active);
});
};
authorization.initialize_database();
resource_limits.initialize_database();
authority system_auth(conf.genesis.initial_key);
create_native_account( config::system_account_name, system_auth, system_auth, true );
auto empty_authority = authority(0, {}, {});
auto active_producers_authority = authority(0, {}, {});
active_producers_authority.accounts.push_back({{config::system_account_name, config::active_name}, 1});
create_special_account(config::nobody_account_name, empty_authority, empty_authority);
create_special_account(config::producers_account_name, empty_authority, active_producers_authority);
const auto& tapos_block_summary = db.get<block_summary_object>(1);
db.modify( tapos_block_summary, [&]( auto& bs ) {
bs.block_id = head->id;
});
create_native_account( config::nobody_account_name, empty_authority, empty_authority );
create_native_account( config::producers_account_name, empty_authority, active_producers_authority );
}
void set_pending_tapos() {
const auto& tapos_block_summary = db.get<block_summary_object>((uint16_t)pending->_pending_block_state->block_num);
db.modify( tapos_block_summary, [&]( auto& bs ) {
......@@ -636,6 +614,14 @@ resource_limits_manager& controller::get_mutable_resource_limits_manager
return my->resource_limits;
}
const authorization_manager& controller::get_authorization_manager()const
{
return my->authorization;
}
authorization_manager& controller::get_mutable_authorization_manager()
{
return my->authorization;
}
controller::controller( const controller::config& cfg )
:my( new controller_impl( cfg, *this ) )
......@@ -789,36 +775,6 @@ void controller::pop_block() {
}
/**
* @param actions - the actions to check authorization across
* @param provided_keys - the set of public keys which have authorized the transaction
* @param allow_unused_signatures - true if method should not assert on unused signatures
* @param provided_accounts - the set of accounts which have authorized the transaction (presumed to be owner)
*
* @return fc::microseconds set to the max delay that this authorization requires to complete
*/
fc::microseconds controller::check_authorization( const vector<action>& actions,
const flat_set<public_key_type>& provided_keys,
bool allow_unused_signatures,
flat_set<account_name> provided_accounts,
flat_set<permission_level> provided_levels
)const {
return fc::microseconds();
}
/**
* @param account - the account owner of the permission
* @param permission - the permission name to check for authorization
* @param provided_keys - a set of public keys
*
* @return true if the provided keys are sufficient to authorize the account permission
*/
bool controller::check_authorization( account_name account, permission_name permission,
flat_set<public_key_type> provided_keys,
bool allow_unused_signatures)const {
return true;
}
void controller::set_active_producers( const producer_schedule_type& sch ) {
FC_ASSERT( !my->pending->_pending_block_state->header.new_producers, "this block has already set new producers" );
FC_ASSERT( !my->pending->_pending_block_state->pending_schedule.producers.size(), "there is already a pending schedule, wait for it to become active" );
......
......@@ -21,9 +21,10 @@
#include <eosio/chain/wasm_interface.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/resource_limits.hpp>
namespace eosio { namespace chain {
namespace eosio { namespace chain {
......@@ -35,7 +36,7 @@ uint128_t transaction_id_to_sender_id( const transaction_id_type& tid ) {
void validate_authority_precondition( const apply_context& context, const authority& auth ) {
for(const auto& a : auth.accounts) {
context.db.get<account_object, by_name>(a.permission.actor);
context.db.get<permission_object, by_owner>(boost::make_tuple(a.permission.actor, a.permission.permission));
context.mutable_controller.get_authorization_manager().get_permission({a.permission.actor, a.permission.permission});
}
}
......@@ -47,7 +48,8 @@ void apply_eosio_newaccount(apply_context& context) {
try {
context.require_authorization(create.creator);
// context.require_write_lock( config::eosio_auth_scope );
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto& authorization = context.mutable_controller.get_mutable_authorization_manager();
EOS_ASSERT( validate(create.owner), action_validate_exception, "Invalid owner authority");
EOS_ASSERT( validate(create.active), action_validate_exception, "Invalid active authority");
......@@ -85,32 +87,25 @@ void apply_eosio_newaccount(apply_context& context) {
a.name = create.name;
});
resources.initialize_account(create.name);
const auto& owner_permission = authorization.create_permission( create.name, config::owner_name, 0,
std::move(create.owner) );
const auto& active_permission = authorization.create_permission( create.name, config::active_name, owner_permission.id,
std::move(create.active) );
resources.add_pending_account_ram_usage(
create.creator,
(int64_t)config::overhead_per_account_ram_bytes
);
auto create_permission = [owner=create.name, &db, &context, &resources](const permission_name& name, permission_object::id_type parent, authority &&auth) {
const auto& result = db.create<permission_object>([&](permission_object& p) {
p.name = name;
p.parent = parent;
p.owner = owner;
p.auth = std::move(auth);
});
resources.add_pending_account_ram_usage(
owner,
(int64_t)(config::billable_size_v<permission_object> + result.auth.get_billable_size())
);
return result;
};
// If a parent_id of 0 is going to be used to indicate the absence of a parent, then we need to make sure that the chain
// initializes permission_index with a dummy object that reserves the id of 0.
const auto& owner_permission = create_permission(config::owner_name, 0, std::move(create.owner));
create_permission(config::active_name, owner_permission.id, std::move(create.active));
resources.initialize_account(create.name);
resources.add_pending_account_ram_usage(
create.name,
(int64_t)(config::billable_size_v<permission_object> + owner_permission.auth.get_billable_size())
);
resources.add_pending_account_ram_usage(
create.name,
(int64_t)(config::billable_size_v<permission_object> + active_permission.auth.get_billable_size())
);
} FC_CAPTURE_AND_RETHROW( (create) ) }
......@@ -191,12 +186,15 @@ void apply_eosio_setabi(apply_context& context) {
}
void apply_eosio_updateauth(apply_context& context) {
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
// context.require_write_lock( config::eosio_auth_scope );
auto update = context.act.data_as<updateauth>();
context.require_authorization(update.account); // only here to mark the single authority on this action as used
auto& authorization = context.mutable_controller.get_mutable_authorization_manager();
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto& db = context.db;
auto update = context.act.data_as<updateauth>();
EOS_ASSERT(!update.permission.empty(), action_validate_exception, "Cannot create authority with empty name");
EOS_ASSERT( update.permission.to_string().find( "eosio." ) != 0, action_validate_exception,
"Permission names that start with 'eosio.' are reserved" );
......@@ -211,54 +209,25 @@ void apply_eosio_updateauth(apply_context& context) {
else
EOS_ASSERT(!update.parent.empty(), action_validate_exception, "Only owner permission can have empty parent" );
FC_ASSERT(context.act.authorization.size(), "updateauth can only have one action authorization");
const auto& act_auth = context.act.authorization.front();
// lazy evaluating loop
auto permission_is_valid_for_update = [&](){
if (act_auth.permission == config::owner_name || act_auth.permission == update.permission) {
return true;
}
const permission_object *current = db.find<permission_object, by_owner>(boost::make_tuple(update.account, update.permission));
// Permission doesn't exist yet, check parent permission
if (current == nullptr) current = db.find<permission_object, by_owner>(boost::make_tuple(update.account, update.parent));
// Ensure either the permission or parent's permission exists
EOS_ASSERT(current != nullptr, permission_query_exception,
"Failed to retrieve permission for: {\"actor\": \"${actor}\", \"permission\": \"${permission}\" }",
("actor", update.account)("permission", update.parent));
while(current->name != config::owner_name) {
if (current->name == act_auth.permission) {
return true;
}
current = &db.get<permission_object>(current->parent);
}
return false;
};
FC_ASSERT(act_auth.actor == update.account && permission_is_valid_for_update(), "updateauth must carry a permission equal to or in the ancestery of permission it updates");
validate_authority_precondition(context, update.auth);
auto permission = db.find<permission_object, by_owner>(boost::make_tuple(update.account, update.permission));
auto permission = authorization.find_permission({update.account, update.permission});
// If a parent_id of 0 is going to be used to indicate the absence of a parent, then we need to make sure that the chain
// initializes permission_index with a dummy object that reserves the id of 0.
permission_object::id_type parent_id = 0;
if(update.permission != config::owner_name) {
auto& parent = db.get<permission_object, by_owner>(boost::make_tuple(update.account, update.parent));
authorization_manager::permission_id_type parent_id = 0;
if( update.permission != config::owner_name ) {
auto& parent = authorization.get_permission({update.account, update.parent});
parent_id = parent.id;
}
if (permission) {
if( permission ) {
EOS_ASSERT(parent_id == permission->parent, action_validate_exception,
"Changing parent authority is not currently supported");
int64_t old_size = (int64_t)(config::billable_size_v<permission_object> + permission->auth.get_billable_size());
// TODO/QUESTION: If we are updating an existing permission, should we check if the message declared
// permission satisfies the permission we want to modify?
db.modify(*permission, [&update, &parent_id, &context](permission_object& po) {
po.auth = update.auth;
po.parent = parent_id;
......@@ -273,38 +242,29 @@ void apply_eosio_updateauth(apply_context& context) {
new_size - old_size
);
} else {
// TODO/QUESTION: If we are creating a new permission, should we check if the message declared
// permission satisfies the parent permission?
const auto& p = db.create<permission_object>([&update, &parent_id, &context](permission_object& po) {
po.name = update.permission;
po.owner = update.account;
po.auth = update.auth;
po.parent = parent_id;
po.last_updated = context.control.head_block_time();
po.delay = fc::seconds(update.auth.delay_sec);
});
const auto& p = authorization.create_permission( update.account, update.permission, parent_id, update.auth );
resources.add_pending_account_ram_usage(
p.owner,
update.account,
(int64_t)(config::billable_size_v<permission_object> + p.auth.get_billable_size())
);
}
}
void apply_eosio_deleteauth(apply_context& context) {
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
// context.require_write_lock( config::eosio_auth_scope );
auto remove = context.act.data_as<deleteauth>();
context.require_authorization(remove.account); // only here to mark the single authority on this action as used
EOS_ASSERT(remove.permission != config::active_name, action_validate_exception, "Cannot delete active authority");
EOS_ASSERT(remove.permission != config::owner_name, action_validate_exception, "Cannot delete owner authority");
auto& authorization = context.mutable_controller.get_authorization_manager();
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto& db = context.db;
context.require_authorization(remove.account);
// TODO/QUESTION:
// Inconsistency between permissions that can be satisfied to create/modify (via updateauth) a permission and the
// stricter requirements for deleting the permission using deleteauth.
// If a permission can be updated, shouldn't it also be allowed to delete it without higher permissions required?
const auto& permission = db.get<permission_object, by_owner>(boost::make_tuple(remove.account, remove.permission));
const auto& permission = authorization.get_permission({remove.account, remove.permission});
{ // Check for children
const auto& index = db.get_index<permission_index, by_parent>();
......@@ -328,12 +288,14 @@ void apply_eosio_deleteauth(apply_context& context) {
}
void apply_eosio_linkauth(apply_context& context) {
// context.require_write_lock( config::eosio_auth_scope );
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto requirement = context.act.data_as<linkauth>();
try {
EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty");
context.require_authorization(requirement.account);
context.require_authorization(requirement.account); // only here to mark the single authority on this action as used
auto& db = context.db;
const auto *account = db.find<account_object, by_name>(requirement.account);
......@@ -374,11 +336,13 @@ void apply_eosio_linkauth(apply_context& context) {
}
void apply_eosio_unlinkauth(apply_context& context) {
// context.require_write_lock( config::eosio_auth_scope );
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto& db = context.db;
auto unlink = context.act.data_as<unlinkauth>();
context.require_authorization(unlink.account);
context.require_authorization(unlink.account); // only here to mark the single authority on this action as used
auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);
......@@ -391,7 +355,6 @@ void apply_eosio_unlinkauth(apply_context& context) {
db.remove(*link);
}
void apply_eosio_onerror(apply_context& context) {
FC_ASSERT(context.sender != account_name(), "onerror action cannot be called directly");
context.require_recipient(context.sender);
......@@ -567,6 +530,8 @@ void apply_eosio_vetorecovery(apply_context& context) {
void apply_eosio_canceldelay(apply_context& context) {
auto cancel = context.act.data_as<canceldelay>();
context.require_authorization(cancel.canceling_auth.actor); // only here to mark the single authority on this action as used
const auto& trx_id = cancel.trx_id;
const auto& generated_transaction_idx = context.control.db().get_index<generated_transaction_multi_index>();
......@@ -576,23 +541,18 @@ void apply_eosio_canceldelay(apply_context& context) {
"cannot cancel trx_id=${tid}, there is no deferred transaction with that transaction id",("tid", trx_id));
auto dtrx = fc::raw::unpack<deferred_transaction>(itr->packed_trx.data(), itr->packed_trx.size());
set<account_name> accounts;
for (const auto& act : dtrx.actions) {
for (const auto& auth : act.authorization) {
accounts.insert(auth.actor);
}
}
bool found = false;
for (const auto& auth : context.act.authorization) {
if (auth.permission == config::active_name && accounts.count(auth.actor)) {
found = true;
break;
for( const auto& act : dtrx.actions ) {
for( const auto& auth : act.authorization ) {
if( auth == cancel.canceling_auth ) {
found = true;
break;
}
}
if( found ) break;
}
FC_ASSERT (found, "canceldelay action must be signed with the \"active\" permission for one of the actors"
" provided in the authorizations on the original transaction");
FC_ASSERT (found, "canceling_auth in canceldelay action was not found as authorization in the original delayed transaction");
context.cancel_deferred_transaction(transaction_id_to_sender_id(trx_id));
}
......
......@@ -28,8 +28,8 @@ abi_def eosio_contract_abi(const abi_def& eosio_system_abi)
eos_abi.actions.push_back( action_def{name("onerror"), "onerror",""} );
eos_abi.actions.push_back( action_def{name("onblock"), "onblock",""} );
eos_abi.actions.push_back( action_def{name("canceldelay"), "canceldelay",""} );
// TODO add any ricardian_clauses
// TODO add any ricardian_clauses
//
// ACTION PAYLOADS
......@@ -116,6 +116,7 @@ abi_def eosio_contract_abi(const abi_def& eosio_system_abi)
eos_abi.structs.emplace_back( struct_def {
"canceldelay", "", {
{"canceling_auth", "permission_level"},
{"trx_id", "transaction_id_type"},
}
});
......@@ -223,7 +224,7 @@ abi_def eosio_contract_abi(const abi_def& eosio_system_abi)
eos_abi.structs.emplace_back( struct_def {
"clause_pair", "", {
{"id", "string"},
{"body", "string"}
{"body", "string"}
}
});
eos_abi.structs.emplace_back( struct_def {
......
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#pragma once
#include <eosio/chain/types.hpp>
#include <eosio/chain/permission_object.hpp>
#include "multi_index_includes.hpp"
namespace eosio { namespace chain {
/**
* Maps the permission level on the code to the permission level specififed by owner, when specifying a contract the
* contract will specify 1 permission_object per action, and by default the parent of that permission object will be
* the active permission of the contract; however, the contract owner could group their actions any way they like.
*
* When it comes time to evaluate whether User can call Action on Contract with UserPermissionLevel the algorithm
* operates as follows:
*
* let scope_permission = action_code.permission
* while( ! mapping for (scope_permission / owner )
* scope_permission = scope_permission.parent
* if( !scope_permission )
* user permission => active
* break;
*
* Now that we know the required user permission...
*
* while( ! transaction.has( user_permission ) )
* user_permission = user_permission.parent
* if( !user_permission )
* throw invalid permission
* pass
*/
class action_permission_object : public chainbase::object<action_permission_object_type, action_permission_object>
{
OBJECT_CTOR(action_permission_object)
id_type id;
account_name owner; ///< the account whose permission we seek
permission_object::id_type scope_permission; ///< the scope permission defined by the contract for the action
permission_object::id_type owner_permission; ///< the owner permission that is required
};
struct by_owner_scope;
using action_permission_index = chainbase::shared_multi_index_container<
action_permission_object,
indexed_by<
ordered_unique<tag<by_id>, member<action_permission_object, action_permission_object::id_type, &action_permission_object::id>>,
ordered_unique<tag<by_owner_scope>,
composite_key< action_permission_object,
member<action_permission_object, account_name, &action_permission_object::owner>,
member<action_permission_object, permission_object::id_type, &action_permission_object::scope_permission>
>
>
>
>;
} } // eosio::chain
CHAINBASE_SET_INDEX_TYPE(eosio::chain::action_permission_object, eosio::chain::action_permission_index)
FC_REFLECT(eosio::chain::action_permission_object, (id)(owner)(owner_permission)(scope_permission) )
......@@ -170,7 +170,7 @@ namespace detail {
return {range.begin(), range.end()};
}
const PermissionVisitorFunc& get_permission_visitor() {
PermissionVisitorFunc& get_permission_visitor() {
return permission_visitor;
}
......
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#pragma once
#include <eosio/chain/types.hpp>
#include <eosio/chain/permission_object.hpp>
namespace eosio { namespace chain {
class controller;
struct updateauth;
struct deleteauth;
struct linkauth;
struct unlinkauth;
struct canceldelay;
class authorization_manager {
public:
using permission_id_type = permission_object::id_type;
explicit authorization_manager(controller& c);
void add_indices();
void initialize_database();
const permission_object& create_permission( account_name account,
permission_name name,
permission_id_type parent,
const authority& auth,
time_point initial_creation_time = time_point()
);
const permission_object& create_permission( account_name account,
permission_name name,
permission_id_type parent,
authority&& auth,
time_point initial_creation_time = time_point()
);
const permission_object* find_permission( const permission_level& level )const;
const permission_object& get_permission( const permission_level& level )const;
/**
* @brief Find the lowest authority level required for @ref authorizer_account to authorize a message of the
* specified type
* @param authorizer_account The account authorizing the message
* @param code_account The account which publishes the contract that handles the message
* @param type The type of message
*/
optional<permission_name> lookup_minimum_permission( account_name authorizer_account,
scope_name code_account,
action_name type
)const;
/**
* @param actions - the actions to check authorization across
* @param provided_keys - the set of public keys which have authorized the transaction
* @param allow_unused_signatures - true if method should not assert on unused signatures
* @param provided_accounts - the set of accounts which have authorized the transaction (presumed to be owner)
*
* @return fc::microseconds set to the max delay that this authorization requires to complete
*/
fc::microseconds check_authorization( const vector<action>& actions,
const flat_set<public_key_type>& provided_keys,
bool allow_unused_signatures = false,
flat_set<account_name> provided_accounts = flat_set<account_name>(),
flat_set<permission_level> provided_levels = flat_set<permission_level>()
)const;
/**
* @param account - the account owner of the permission
* @param permission - the permission name to check for authorization
* @param provided_keys - a set of public keys
*
* @return true if the provided keys are sufficient to authorize the account permission
*/
bool check_authorization( account_name account, permission_name permission,
flat_set<public_key_type> provided_keys,
bool allow_unused_signatures
)const;
flat_set<public_key_type> get_required_keys( const transaction& trx,
const flat_set<public_key_type>& candidate_keys
)const;
private:
const controller& _control;
chainbase::database& _db;
optional<fc::microseconds> check_updateauth_authorization( const updateauth& update, const vector<permission_level>& auths )const;
fc::microseconds check_deleteauth_authorization( const deleteauth& del, const vector<permission_level>& auths )const;
fc::microseconds check_linkauth_authorization( const linkauth& link, const vector<permission_level>& auths )const;
fc::microseconds check_unlinkauth_authorization( const unlinkauth& unlink, const vector<permission_level>& auths )const;
void check_canceldelay_authorization( const canceldelay& cancel, const vector<permission_level>& auths )const;
optional<permission_name> lookup_linked_permission( account_name authorizer_account,
scope_name code_account,
action_name type
)const;
};
} } /// namespace eosio::chain
......@@ -7,7 +7,7 @@
#include <boost/multiprecision/cpp_int.hpp>
namespace eosio { namespace chain {
namespace eosio { namespace chain {
using fixed_string32 = fc::fixed_string<fc::array<uint64_t,4>>;
using fixed_string16 = fc::fixed_string<>;
......@@ -63,7 +63,7 @@ struct action_def {
action_name name;
type_name type;
string ricardian_contract;
string ricardian_contract;
};
struct table_def {
......@@ -82,7 +82,7 @@ struct table_def {
struct clause_pair {
clause_pair() = default;
clause_pair( const string& id, const string& body )
: id(id), body(body)
: id(id), body(body)
{}
string id;
......@@ -271,6 +271,7 @@ struct vetorecovery {
};
struct canceldelay {
permission_level canceling_auth;
transaction_id_type trx_id;
static account_name get_account() {
......@@ -302,4 +303,4 @@ FC_REFLECT( eosio::chain::unlinkauth , (account)(code)(typ
FC_REFLECT( eosio::chain::postrecovery , (account)(auth)(memo) )
FC_REFLECT( eosio::chain::passrecovery , (account) )
FC_REFLECT( eosio::chain::vetorecovery , (account) )
FC_REFLECT( eosio::chain::canceldelay , (trx_id) )
FC_REFLECT( eosio::chain::canceldelay , (canceling_auth)(trx_id) )
......@@ -11,6 +11,8 @@ namespace chainbase {
namespace eosio { namespace chain {
class authorization_manager;
namespace resource_limits {
class resource_limits_manager;
};
......@@ -87,7 +89,7 @@ namespace eosio { namespace chain {
void commit_block();
void log_irreversible_blocks();
void pop_block();
void push_block( const signed_block_ptr& b );
chainbase::database& db()const;
......@@ -108,6 +110,8 @@ namespace eosio { namespace chain {
const permission_object& get_permission( const permission_level& level )const;
const resource_limits_manager& get_resource_limits_manager()const;
resource_limits_manager& get_mutable_resource_limits_manager();
const authorization_manager& get_authorization_manager()const;
authorization_manager& get_mutable_authorization_manager();
block_id_type head_block_id()const;
account_name head_block_producer()const;
......@@ -121,32 +125,6 @@ namespace eosio { namespace chain {
signed_block_ptr fetch_block_by_number( uint32_t block_num )const;
signed_block_ptr fetch_block_by_id( block_id_type id )const;
/**
* @param actions - the actions to check authorization across
* @param provided_keys - the set of public keys which have authorized the transaction
* @param allow_unused_signatures - true if method should not assert on unused signatures
* @param provided_accounts - the set of accounts which have authorized the transaction (presumed to be owner)
*
* @return fc::microseconds set to the max delay that this authorization requires to complete
*/
fc::microseconds check_authorization( const vector<action>& actions,
const flat_set<public_key_type>& provided_keys,
bool allow_unused_signatures = false,
flat_set<account_name> provided_accounts = flat_set<account_name>(),
flat_set<permission_level> provided_levels = flat_set<permission_level>()
)const;
/**
* @param account - the account owner of the permission
* @param permission - the permission name to check for authorization
* @param provided_keys - a set of public keys
*
* @return true if the provided keys are sufficient to authorize the account permission
*/
bool check_authorization( account_name account, permission_name permission,
flat_set<public_key_type> provided_keys,
bool allow_unused_signatures )const;
void validate_referenced_accounts( const transaction& t )const;
void validate_expiration( const transaction& t )const;
void validate_tapos( const transaction& t )const;
......@@ -180,9 +158,9 @@ namespace eosio { namespace chain {
} } /// eosio::chain
FC_REFLECT( eosio::chain::controller::config::runtime_limits, (max_push_block_us)(max_push_transaction_us)(max_deferred_transactions_us) )
FC_REFLECT( eosio::chain::controller::config,
FC_REFLECT( eosio::chain::controller::config,
(block_log_dir)
(shared_memory_dir)(shared_memory_size)(read_only)
(genesis)
(limits)(wasm_runtime)
(limits)(wasm_runtime)
)
......@@ -31,8 +31,8 @@ namespace eosio { namespace chain { namespace resource_limits {
{
}
void add_indices();
void initialize_database();
void initialize_chain();
void initialize_account( const account_name& account );
void set_block_parameters( const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters );
......@@ -61,4 +61,3 @@ namespace eosio { namespace chain { namespace resource_limits {
chainbase::database& _db;
};
} } } /// eosio::chain
......@@ -125,7 +125,6 @@ namespace eosio { namespace chain {
index128_object_type,
index256_object_type,
index_double_object_type,
action_permission_object_type,
global_property_object_type,
dynamic_global_property_object_type,
block_summary_object_type,
......@@ -184,7 +183,6 @@ FC_REFLECT_ENUM(eosio::chain::object_type,
(index128_object_type)
(index256_object_type)
(index_double_object_type)
(action_permission_object_type)
(global_property_object_type)
(dynamic_global_property_object_type)
(block_summary_object_type)
......
......@@ -26,14 +26,14 @@ void resource_limits_state_object::update_virtual_net_limit( const resource_limi
virtual_net_limit = update_elastic_limit(virtual_net_limit, average_block_net_usage.average(), cfg.net_limit_parameters);
}
void resource_limits_manager::initialize_database() {
void resource_limits_manager::add_indices() {
_db.add_index<resource_limits_index>();
_db.add_index<resource_usage_index>();
_db.add_index<resource_limits_state_index>();
_db.add_index<resource_limits_config_index>();
}
void resource_limits_manager::initialize_chain() {
void resource_limits_manager::initialize_database() {
const auto& config = _db.create<resource_limits_config_object>([](resource_limits_config_object& config){
// see default settings in the declaration
});
......@@ -134,9 +134,9 @@ void resource_limits_manager::add_pending_account_ram_usage( const account_name
FC_ASSERT( usage.ram_usage >= 0, "cannot have negative usage!" );
EOS_ASSERT(ram_delta < 0 || UINT64_MAX - usage.ram_usage>= (uint64_t)ram_delta, transaction_exception,
EOS_ASSERT(ram_delta < 0 || UINT64_MAX - usage.ram_usage>= (uint64_t)ram_delta, transaction_exception,
"Ram usage delta would overflow UINT64_MAX");
EOS_ASSERT(ram_delta > 0 || usage.ram_usage >= (uint64_t)(-ram_delta), transaction_exception,
EOS_ASSERT(ram_delta > 0 || usage.ram_usage >= (uint64_t)(-ram_delta), transaction_exception,
"Ram usage delta would underflow UINT64_MAX");
if( limits.ram_bytes >= 0 && usage.ram_usage > limits.ram_bytes ) {
......
......@@ -13,10 +13,8 @@ namespace eosio { namespace chain {
EOS_ASSERT( trx.max_kcpu_usage.value < UINT32_MAX / 1024UL, transaction_exception, "declared max_kcpu_usage overflows when expanded to max cpu usage" );
EOS_ASSERT( trx.max_net_usage_words.value < UINT32_MAX / 8UL, transaction_exception, "declared max_net_usage_words overflows when expanded to max net usage" );
control.record_transaction( trx_meta ); /// checks for dupes
control.validate_tapos( trx );
control.validate_tapos( trx );
control.validate_referenced_accounts( trx );
control.validate_expiration( trx );
......
......@@ -5,6 +5,7 @@
#include <eosio/chain/exceptions.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/wasm_interface_private.hpp>
#include <eosio/chain/wasm_eosio_validation.hpp>
......@@ -766,7 +767,7 @@ class permission_api : public context_aware_api {
pub_keys.emplace_back(pub);
}
return context.control.check_authorization(
return context.control.get_authorization_manager().check_authorization(
account, permission,
{pub_keys.begin(), pub_keys.end()},
false
......
......@@ -86,6 +86,7 @@ namespace eosio { namespace testing {
transaction_trace_ptr push_action( const account_name& code, const action_name& acttype, const account_name& actor, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 );
transaction_trace_ptr push_action( const account_name& code, const action_name& acttype, const vector<account_name>& actors, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 );
transaction_trace_ptr push_action( const account_name& code, const action_name& acttype, const vector<permission_level>& auths, const variant_object& data, uint32_t expiration = DEFAULT_EXPIRATION_DELTA, uint32_t delay_sec = 0 );
void set_tapos( signed_transaction& trx, uint32_t expiration = DEFAULT_EXPIRATION_DELTA ) const;
......
......@@ -200,25 +200,43 @@ namespace eosio { namespace testing {
return success();
}
transaction_trace_ptr base_tester::push_action( const account_name& code,
const action_name& acttype,
const account_name& actor,
const variant_object& data,
uint32_t expiration,
uint32_t delay_sec)
const action_name& acttype,
const account_name& actor,
const variant_object& data,
uint32_t expiration,
uint32_t delay_sec
)
{ try {
return push_action(code, acttype, vector<account_name>{ actor }, data, expiration, delay_sec);
vector<permission_level> auths;
auths.push_back(permission_level{actor, config::active_name});
return push_action(code, acttype, auths, data, expiration, delay_sec);
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(actor)(data)(expiration) ) }
transaction_trace_ptr base_tester::push_action( const account_name& code,
const action_name& acttype,
const vector<account_name>& actors,
const variant_object& data,
uint32_t expiration,
uint32_t delay_sec
)
{ try {
vector<permission_level> auths;
for (const auto& actor : actors) {
auths.push_back(permission_level{actor, config::active_name});
}
return push_action(code, acttype, auths, data, expiration, delay_sec);
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(actors)(data)(expiration) ) }
transaction_trace_ptr base_tester::push_action( const account_name& code,
const action_name& acttype,
const vector<account_name>& actors,
const variant_object& data,
uint32_t expiration,
uint32_t delay_sec)
const action_name& acttype,
const vector<permission_level>& auths,
const variant_object& data,
uint32_t expiration,
uint32_t delay_sec
)
{ try {
const auto& acnt = control->db().get<account_object,by_name>(code);
......@@ -233,21 +251,18 @@ namespace eosio { namespace testing {
action act;
act.account = code;
act.name = acttype;
for (const auto& actor : actors) {
act.authorization.push_back(permission_level{actor, config::active_name});
}
act.authorization = auths;
act.data = abis.variant_to_binary(action_type_name, data);
signed_transaction trx;
trx.actions.emplace_back(std::move(act));
set_transaction_headers(trx, expiration, delay_sec);
for (const auto& actor : actors) {
trx.sign(get_private_key(actor, "active"), chain_id_type());
for (const auto& auth : auths) {
trx.sign(get_private_key(auth.actor, auth.permission.to_string()), chain_id_type());
}
return push_transaction(trx);
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(actors)(data)(expiration) ) }
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(auths)(data)(expiration) ) }
transaction_trace_ptr base_tester::push_reqauth( account_name from, const vector<permission_level>& auths, const vector<private_key_type>& keys ) {
variant pretty_trx = fc::mutable_variant_object()
......
......@@ -6,7 +6,7 @@
#include <eosio/chain/fork_database.hpp>
#include <eosio/chain/block_log.hpp>
#include <eosio/chain/exceptions.hpp>
#include <eosio/chain/permission_object.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/producer_object.hpp>
#include <eosio/chain/config.hpp>
#include <eosio/chain/types.hpp>
......@@ -487,7 +487,7 @@ read_only::abi_bin_to_json_result read_only::abi_bin_to_json( const read_only::a
read_only::get_required_keys_result read_only::get_required_keys( const get_required_keys_params& params )const {
transaction pretty_input;
from_variant(params.transaction, pretty_input);
auto required_keys_set = db.get_required_keys(pretty_input, params.available_keys);
auto required_keys_set = db.get_authorization_manager().get_required_keys(pretty_input, params.available_keys);
get_required_keys_result result;
result.required_keys = required_keys_set;
return result;
......
......@@ -78,6 +78,7 @@ BOOST_AUTO_TEST_CASE(update_auths) {
try {
TESTER chain;
chain.create_account("alice");
chain.create_account("bob");
// Deleting active or owner should fail
BOOST_CHECK_THROW(chain.delete_authority("alice", "active"), action_validate_exception);
......@@ -132,6 +133,11 @@ try {
auto trading_priv_key = chain.get_private_key("alice", "trading");
auto trading_pub_key = trading_priv_key.get_public_key();
// Bob attempts to create new spending auth for Alice
BOOST_CHECK_THROW( chain.set_authority( "alice", "spending", authority(spending_pub_key), "active",
{ permission_level{"bob", "active"} }, { chain.get_private_key("bob", "active") } ),
transaction_exception );
// Create new spending auth
chain.set_authority("alice", "spending", authority(spending_pub_key), "active",
{ permission_level{"alice", "active"} }, { new_active_priv_key });
......
......@@ -1494,8 +1494,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try {
// send canceldelay for first delayed transaction
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), config::active_name}},
chain::canceldelay{ids[0]});
trx.actions.back().authorization.push_back({N(tester), config::active_name});
chain::contracts::canceldelay{{N(tester), config::active_name}, ids[0]});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type());
......@@ -1514,7 +1513,7 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try {
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
// first transfer will finally be performed
// update auth will finally be performed
chain.produce_blocks();
liquid_balance = get_currency_balance(chain, N(tester));
......@@ -1560,6 +1559,259 @@ BOOST_AUTO_TEST_CASE( canceldelay_test ) { try {
BOOST_REQUIRE_EQUAL(asset::from_string("85.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("15.0000 CUR"), liquid_balance);
} FC_LOG_AND_RETHROW() }/// schedule_test
} FC_LOG_AND_RETHROW() }
// test canceldelay action under different permission levels
BOOST_AUTO_TEST_CASE( canceldelay_test2 ) { try {
TESTER chain;
const auto& tester_account = N(tester);
std::vector<transaction_id_type> ids;
chain.set_code(config::system_account_name, eosio_system_wast);
chain.set_abi(config::system_account_name, eosio_system_abi);
chain.produce_blocks();
chain.create_account(N(currency));
chain.produce_blocks();
chain.set_code(N(currency), currency_wast);
chain.set_abi(N(currency), currency_abi);
chain.produce_blocks();
chain.create_account(N(tester));
chain.create_account(N(tester2));
chain.produce_blocks();
chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object()
("account", "tester")
("permission", "first")
("parent", "active")
("data", authority(chain.get_public_key(tester_account, "first")))
("delay", 5));
chain.push_action(config::system_account_name, contracts::updateauth::get_name(), tester_account, fc::mutable_variant_object()
("account", "tester")
("permission", "second")
("parent", "first")
("data", authority(chain.get_public_key(tester_account, "second")))
("delay", 0));
chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object()
("account", "tester")
("code", "currency")
("type", "transfer")
("requirement", "first"));
chain.produce_blocks();
chain.push_action(N(currency), N(create), N(currency), mutable_variant_object()
("issuer", "currency" )
("maximum_supply", "9000000.0000 CUR" )
("can_freeze", 0)
("can_recall", 0)
("can_whitelist", 0)
);
chain.push_action(N(currency), name("issue"), N(currency), fc::mutable_variant_object()
("to", "currency")
("quantity", "1000000.0000 CUR")
("memo", "for stuff")
);
auto trace = chain.push_action(N(currency), name("transfer"), N(currency), fc::mutable_variant_object()
("from", "currency")
("to", "tester")
("quantity", "100.0000 CUR")
("memo", "hi" )
);
BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status);
BOOST_REQUIRE_EQUAL(0, trace.deferred_transaction_requests.size());
chain.produce_blocks();
auto liquid_balance = get_currency_balance(chain, N(currency));
BOOST_REQUIRE_EQUAL(asset::from_string("999900.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
ilog("attempting first delayed transfer");
{
// this transaction will be delayed 10 blocks
trace = chain.push_action(N(currency), name("transfer"), vector<permission_level>{{N(tester), N(first)}}, fc::mutable_variant_object()
("from", "tester")
("to", "tester2")
("quantity", "1.0000 CUR")
("memo", "hi" ),
30, 5
);
auto trx_id = trace.id;
BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
BOOST_REQUIRE_EQUAL(0, trace.action_traces.size());
const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get<deferred_transaction>().sender_id;
chain.produce_blocks();
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
// attempt canceldelay with wrong canceling_auth for delayed transfer of 1.0000 CUR
{
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), config::active_name}},
chain::contracts::canceldelay{{N(tester), config::active_name}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type());
BOOST_REQUIRE_THROW( chain.push_transaction(trx), transaction_exception );
}
// attempt canceldelay with "second" permission for delayed transfer of 1.0000 CUR
{
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), N(second)}},
chain::contracts::canceldelay{{N(tester), N(first)}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "second"), chain_id_type());
BOOST_REQUIRE_THROW( chain.push_transaction(trx), tx_irrelevant_auth );
}
// canceldelay with "active" permission for delayed transfer of 1.0000 CUR
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), config::active_name}},
chain::contracts::canceldelay{{N(tester), N(first)}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type());
trace = chain.push_transaction(trx);
BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
const auto sender_id_canceled = trace.deferred_transaction_requests[0].get<deferred_reference>().sender_id;
BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled)));
chain.produce_blocks(10);
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
}
ilog("reset minimum permission of transfer to second permission");
chain.push_action(config::system_account_name, contracts::linkauth::get_name(), tester_account, fc::mutable_variant_object()
("account", "tester")
("code", "currency")
("type", "transfer")
("requirement", "second"),
30, 5
);
chain.produce_blocks(10);
ilog("attempting second delayed transfer");
{
// this transaction will be delayed 10 blocks
trace = chain.push_action(N(currency), name("transfer"), vector<permission_level>{{N(tester), N(second)}}, fc::mutable_variant_object()
("from", "tester")
("to", "tester2")
("quantity", "5.0000 CUR")
("memo", "hi" ),
30, 5
);
auto trx_id = trace.id;
BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
BOOST_REQUIRE_EQUAL(0, trace.action_traces.size());
const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get<deferred_transaction>().sender_id;
chain.produce_blocks();
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
// canceldelay with "first" permission for delayed transfer of 5.0000 CUR
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), N(first)}},
chain::contracts::canceldelay{{N(tester), N(second)}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "first"), chain_id_type());
trace = chain.push_transaction(trx);
BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
const auto sender_id_canceled = trace.deferred_transaction_requests[0].get<deferred_reference>().sender_id;
BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled)));
chain.produce_blocks(10);
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
}
ilog("attempting third delayed transfer");
{
// this transaction will be delayed 10 blocks
trace = chain.push_action(N(currency), name("transfer"), vector<permission_level>{{N(tester), config::owner_name}}, fc::mutable_variant_object()
("from", "tester")
("to", "tester2")
("quantity", "10.0000 CUR")
("memo", "hi" ),
30, 5
);
auto trx_id = trace.id;
BOOST_REQUIRE_EQUAL(transaction_receipt::delayed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
BOOST_REQUIRE_EQUAL(0, trace.action_traces.size());
const auto sender_id_to_cancel = trace.deferred_transaction_requests[0].get<deferred_transaction>().sender_id;
chain.produce_blocks();
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
// attempt canceldelay with "active" permission for delayed transfer of 10.0000 CUR
{
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), N(active)}},
chain::contracts::canceldelay{{N(tester), config::owner_name}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "active"), chain_id_type());
BOOST_REQUIRE_THROW( chain.push_transaction(trx), tx_irrelevant_auth );
}
// canceldelay with "owner" permission for delayed transfer of 10.0000 CUR
signed_transaction trx;
trx.actions.emplace_back(vector<permission_level>{{N(tester), config::owner_name}},
chain::contracts::canceldelay{{N(tester), config::owner_name}, trx_id});
chain.set_transaction_headers(trx);
trx.sign(chain.get_private_key(N(tester), "owner"), chain_id_type());
trace = chain.push_transaction(trx);
BOOST_REQUIRE_EQUAL(transaction_receipt::executed, trace.status);
BOOST_REQUIRE_EQUAL(1, trace.deferred_transaction_requests.size());
const auto sender_id_canceled = trace.deferred_transaction_requests[0].get<deferred_reference>().sender_id;
BOOST_REQUIRE_EQUAL(std::string(uint128(sender_id_to_cancel)), std::string(uint128(sender_id_canceled)));
chain.produce_blocks(10);
liquid_balance = get_currency_balance(chain, N(tester));
BOOST_REQUIRE_EQUAL(asset::from_string("100.0000 CUR"), liquid_balance);
liquid_balance = get_currency_balance(chain, N(tester2));
BOOST_REQUIRE_EQUAL(asset::from_string("0.0000 CUR"), liquid_balance);
}
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册