未验证 提交 5b2695b1 编写于 作者: W wanderingbort 提交者: GitHub

Merge pull request #2035 from EOSIO/1755-check-signatures

Check signatures, transaction validation checks, and many other bug fixes
......@@ -28,7 +28,7 @@ namespace eosio {
:expiration(exp),region(r)
{}
void send(uint64_t sender_id, account_name payer = account_name(0), time delay_until = 0) const {
void send(uint64_t sender_id, account_name payer = account_name(0), time delay_until = now()) const {
auto serialize = pack(*this);
send_deferred(sender_id, payer, delay_until, serialize.data(), serialize.size());
}
......
......@@ -34,7 +34,7 @@ void test_action::read_action_normal() {
eosio_assert(total == sizeof(dummy_action), "read_action(sizeof(dummy_action))" );
dummy_action *dummy13 = reinterpret_cast<dummy_action *>(buffer);
eosio_assert(dummy13->a == DUMMY_ACTION_DEFAULT_A, "dummy13->a == DUMMY_ACTION_DEFAULT_A");
eosio_assert(dummy13->b == DUMMY_ACTION_DEFAULT_B, "dummy13->b == DUMMY_ACTION_DEFAULT_B");
eosio_assert(dummy13->c == DUMMY_ACTION_DEFAULT_C, "dummy13->c == DUMMY_ACTION_DEFAULT_C");
......@@ -175,7 +175,7 @@ void test_action::test_current_receiver(uint64_t receiver, uint64_t code, uint64
(void)code;(void)action;
account_name cur_rec;
read_action_data(&cur_rec, sizeof(account_name));
eosio_assert( receiver == cur_rec, "the current receiver does not match" );
}
......
......@@ -68,7 +68,7 @@ extern "C" {
if ( action == N(dummy_action) ) {
test_action::test_dummy_action();
return;
}
}
//test_print
WASM_TEST_HANDLER(test_print, test_prints);
WASM_TEST_HANDLER(test_print, test_prints_l);
......@@ -122,6 +122,7 @@ extern "C" {
WASM_TEST_HANDLER(test_transaction, send_transaction_empty);
WASM_TEST_HANDLER(test_transaction, send_transaction_large);
WASM_TEST_HANDLER(test_transaction, send_action_sender);
WASM_TEST_HANDLER(test_transaction, send_transaction_expiring_late);
WASM_TEST_HANDLER(test_transaction, deferred_print);
WASM_TEST_HANDLER(test_transaction, send_deferred_transaction);
WASM_TEST_HANDLER(test_transaction, cancel_deferred_transaction);
......
......@@ -200,6 +200,7 @@ struct test_transaction {
static void send_transaction_empty();
static void send_transaction_max();
static void send_transaction_large();
static void send_transaction_expiring_late();
static void send_action_sender();
static void deferred_print();
static void send_deferred_transaction();
......
......@@ -23,11 +23,11 @@ struct test_action_action {
template <typename DataStream>
friend DataStream& operator << ( DataStream& ds, const test_action_action& a ) {
for ( auto c : a.data )
for ( auto c : a.data )
ds << c;
return ds;
}
/*
/*
template <typename DataStream>
friend DataStream& operator >> ( DataStream& ds, test_action_action& a ) {
return ds;
......@@ -56,7 +56,7 @@ struct test_dummy_action {
ds << da.c;
return ds;
}
template <typename DataStream>
friend DataStream& operator >> ( DataStream& ds, test_dummy_action& da ) {
ds >> da.a;
......@@ -95,7 +95,7 @@ void test_transaction::send_action_large() {
using namespace eosio;
char large_message[8 * 1024];
test_action_action<N(testapi), WASM_TEST_ACTION("test_action", "read_action_normal")> test_action;
copy_data(large_message, 8*1024, test_action.data);
copy_data(large_message, 8*1024, test_action.data);
action act(vector<permission_level>{{N(testapi), N(active)}}, test_action);
act.send();
eosio_assert(false, "send_message_large() should've thrown an error");
......@@ -110,9 +110,9 @@ void test_transaction::send_action_recurse() {
read_action_data(buffer, 1024);
test_action_action<N(testapi), WASM_TEST_ACTION("test_transaction", "send_action_recurse")> test_action;
copy_data(buffer, 1024, test_action.data);
copy_data(buffer, 1024, test_action.data);
action act(vector<permission_level>{{N(testapi), N(active)}}, test_action);
act.send();
}
......@@ -166,8 +166,8 @@ void test_transaction::send_transaction() {
dummy_action payload = {DUMMY_ACTION_DEFAULT_A, DUMMY_ACTION_DEFAULT_B, DUMMY_ACTION_DEFAULT_C};
test_action_action<N(testapi), WASM_TEST_ACTION("test_action", "read_action_normal")> test_action;
copy_data((char*)&payload, sizeof(dummy_action), test_action.data);
copy_data((char*)&payload, sizeof(dummy_action), test_action.data);
auto trx = transaction();
trx.actions.emplace_back(vector<permission_level>{{N(testapi), N(active)}}, test_action);
trx.send(0);
......@@ -202,7 +202,7 @@ void test_transaction::send_transaction_large() {
for (int i = 0; i < 32; i ++) {
char large_message[1024];
test_action_action<N(testapi), WASM_TEST_ACTION("test_action", "read_action_normal")> test_action;
copy_data(large_message, 1024, test_action.data);
copy_data(large_message, 1024, test_action.data);
trx.actions.emplace_back(vector<permission_level>{{N(testapi), N(active)}}, test_action);
}
......@@ -211,6 +211,20 @@ void test_transaction::send_transaction_large() {
eosio_assert(false, "send_transaction_large() should've thrown an error");
}
void test_transaction::send_transaction_expiring_late() {
using namespace eosio;
account_name cur_send;
read_action_data( &cur_send, sizeof(account_name) );
test_action_action<N(testapi), WASM_TEST_ACTION("test_action", "test_current_sender")> test_action;
copy_data((char*)&cur_send, sizeof(account_name), test_action.data);
auto trx = transaction(now() + 60*60*24*365);
trx.actions.emplace_back(vector<permission_level>{{N(testapi), N(active)}}, test_action);
trx.send(0);
eosio_assert(false, "send_transaction_expiring_late() should've thrown an error");
}
/**
* deferred transaction
*/
......
......@@ -231,8 +231,8 @@ void apply_context::execute_inline( action&& a ) {
if( a.account != receiver ) {
const auto delay = controller.check_authorization({a}, vector<action>(), flat_set<public_key_type>(), false, {receiver});
FC_ASSERT( trx_meta.published + delay <= controller.head_block_time(),
"inline action uses a permission that imposes a delay that is not met, add an action of mindelay with delay of atleast ${delay}",
("delay", delay.sec_since_epoch()) );
"inline action uses a permission that imposes a delay that is not met, add an action of mindelay with delay of at least ${delay} seconds",
("delay", delay.to_seconds()) );
}
}
_inline_actions.emplace_back( move(a) );
......@@ -245,11 +245,20 @@ void apply_context::execute_context_free_inline( action&& a ) {
void apply_context::execute_deferred( deferred_transaction&& trx ) {
try {
FC_ASSERT( trx.expiration > (controller.head_block_time() + fc::milliseconds(2*config::block_interval_ms)),
"transaction is expired when created" );
trx.set_reference_block(controller.head_block_id()); // No TaPoS check necessary
trx.sender = receiver;
controller.validate_transaction_without_state(trx);
// transaction_api::send_deferred guarantees that trx.execute_after is at least head block time, so no need to check expiration.
// Any other called of this function needs to similarly meet that precondition.
EOS_ASSERT( trx.execute_after < trx.expiration,
transaction_exception,
"Transaction expires at ${trx.expiration} which is before the contract-imposed first allowed time to execute at ${trx.execute_after}",
("trx.expiration",trx.expiration)("trx.execute_after",trx.execute_after) );
FC_ASSERT( trx.execute_after < trx.expiration, "transaction expires before it can execute" );
FC_ASSERT( !trx.actions.empty(), "transaction must have at least one action");
controller.validate_expiration_not_too_far(trx, trx.execute_after);
controller.validate_referenced_accounts(trx);
controller.validate_uniqueness(trx); // TODO: Move this out of here when we have concurrent shards to somewhere we can check for conflicts between shards.
if (trx.payer != receiver) {
require_authorization(trx.payer);
......@@ -258,6 +267,8 @@ void apply_context::execute_deferred( deferred_transaction&& trx ) {
const auto& gpo = controller.get_global_properties();
FC_ASSERT( results.deferred_transactions_count < gpo.configuration.max_generated_transaction_count );
fc::microseconds delay;
// privileged accounts can do anything, no need to check auth
if( !privileged ) {
......@@ -271,15 +282,21 @@ void apply_context::execute_deferred( deferred_transaction&& trx ) {
}
}
if( check_auth ) {
const auto delay = controller.check_authorization(trx.actions, vector<action>(), flat_set<public_key_type>(), false, {receiver});
delay = controller.check_authorization(trx.actions, vector<action>(), 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, add an action of mindelay with delay of atleast ${delay}",
("delay", delay.sec_since_epoch()) );
"deferred transaction uses a permission that imposes a delay that is not met, add an action of mindelay with delay of at least ${delay} seconds",
("delay", delay.to_seconds()) );
}
}
trx.sender = receiver; // "Attempting to send from another account"
trx.set_reference_block(controller.head_block_id());
auto now = controller.head_block_time();
if( delay.count() ) {
trx.execute_after = std::max(trx.execute_after, time_point_sec(now + delay + fc::microseconds(999'999)) /* rounds up nearest second */ );
EOS_ASSERT( trx.execute_after < trx.expiration,
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) );
}
results.deferred_transaction_requests.push_back(move(trx));
results.deferred_transactions_count++;
......
......@@ -315,7 +315,7 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi)
{"block_mroot", "checksum256"},
{"producer", "account_name"},
{"schedule_version", "uint32"},
{"new_producers", "producer_schedule?"}
{"new_producers", "producer_schedule?"}
}
});
......@@ -330,6 +330,10 @@ abi_def chain_initializer::eos_contract_abi(const abi_def& eosio_system_abi)
void chain_initializer::prepare_database( chain_controller& chain,
chainbase::database& db) {
/// Reserve id of 0 in permission_index by creating dummy permission_object as the very first object in the index:
db.create<permission_object>([&](permission_object& p) {
});
/// Create the native contract accounts manually; sadly, we can't run their contracts to make them create themselves
auto create_native_account = [this, &chain, &db](account_name name) {
db.create<account_object>([this, &name](account_object& a) {
......
......@@ -29,14 +29,14 @@ namespace eosio { namespace chain { namespace contracts {
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.find<permission_object, by_owner>(boost::make_tuple(a.permission.actor, a.permission.permission));
context.db.get<permission_object, by_owner>(boost::make_tuple(a.permission.actor, a.permission.permission));
}
}
/**
* This method is called assuming precondition_system_newaccount succeeds a
*/
void apply_eosio_newaccount(apply_context& context) {
void apply_eosio_newaccount(apply_context& context) {
auto create = context.act.data_as<newaccount>();
try {
context.require_authorization(create.creator);
......@@ -49,12 +49,16 @@ void apply_eosio_newaccount(apply_context& context) {
auto& db = context.mutable_db;
EOS_ASSERT( create.name.to_string().size() <= 12, action_validate_exception, "account names can only be 12 chars long" );
auto name_str = name(create.name).to_string();
EOS_ASSERT( !create.name.empty(), action_validate_exception, "account name cannot be empty" );
EOS_ASSERT( name_str.size() <= 12, action_validate_exception, "account names can only be 12 chars long" );
// Check if the creator is privileged
const auto &creator = db.get<account_object, by_name>(create.creator);
if( !creator.privileged ) {
EOS_ASSERT( name(create.name).to_string().find( "eosio." ) == std::string::npos, action_validate_exception, "only privileged accounts can have names that contain 'eosio.'" );
EOS_ASSERT( name_str.find( "eosio." ) != 0, action_validate_exception,
"only privileged accounts can have names that start with 'eosio.'" );
}
auto existing_account = db.find<account_object, by_name>(create.name);
......@@ -94,8 +98,10 @@ void apply_eosio_newaccount(apply_context& context) {
return result;
};
const auto& owner_permission = create_permission("owner", 0, std::move(create.owner));
create_permission("active", owner_permission.id, std::move(create.active));
// 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));
} FC_CAPTURE_AND_RETHROW( (create) ) }
......@@ -124,7 +130,7 @@ void apply_eosio_setcode(apply_context& context) {
// wlog( "set code: ${size}", ("size",act.code.size()));
db.modify( account, [&]( auto& a ) {
/** TODO: consider whether a microsecond level local timestamp is sufficient to detect code version changes*/
#warning TODO: update setcode message to include the hash, then validate it in validate
#warning TODO: update setcode message to include the hash, then validate it in validate
a.code_version = code_id;
// Added resize(0) here to avoid bug in boost vector container
a.code.resize( 0 );
......@@ -181,18 +187,22 @@ 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& db = context.mutable_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 != update.parent, action_validate_exception,
"Cannot set an authority as its own parent");
EOS_ASSERT( update.permission.to_string().find( "eosio." ) != 0, action_validate_exception,
"Permission names that start with 'eosio.' are reserved" );
EOS_ASSERT(update.permission != update.parent, action_validate_exception, "Cannot set an authority as its own parent");
db.get<account_object, by_name>(update.account);
EOS_ASSERT(validate(update.data), action_validate_exception,
"Invalid authority: ${auth}", ("auth", update.data));
if (update.permission == "active")
EOS_ASSERT(update.parent == "owner", action_validate_exception, "Cannot change active authority's parent from owner", ("update.parent", update.parent) );
if (update.permission == "owner")
if( update.permission == config::active_name )
EOS_ASSERT(update.parent == config::owner_name, action_validate_exception, "Cannot change active authority's parent from owner", ("update.parent", update.parent) );
if (update.permission == config::owner_name)
EOS_ASSERT(update.parent.empty(), action_validate_exception, "Cannot change owner authority's parent");
auto& db = context.mutable_db;
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();
......@@ -206,7 +216,7 @@ void apply_eosio_updateauth(apply_context& context) {
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,
"Fail to retrieve permission for: {\"actor\": \"${actor}\", \"permission\": \"${permission}\" }",
"Failed to retrieve permission for: {\"actor\": \"${actor}\", \"permission\": \"${permission}\" }",
("actor", update.account)("permission", update.parent));
while(current->name != config::owner_name) {
......@@ -221,13 +231,14 @@ void apply_eosio_updateauth(apply_context& context) {
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");
db.get<account_object, by_name>(update.account);
validate_authority_precondition(context, update.data);
auto permission = db.find<permission_object, by_owner>(boost::make_tuple(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 != "owner") {
if(update.permission != config::owner_name) {
auto& parent = db.get<permission_object, by_owner>(boost::make_tuple(update.account, update.parent));
parent_id = parent.id;
}
......@@ -237,6 +248,9 @@ void apply_eosio_updateauth(apply_context& context) {
"Changing parent authority is not currently supported");
// TODO: Depending on an implementation detail like sizeof(permission_object) for consensus-affecting side effects like
// RAM usage seems like a bad idea. For example, an upgrade of the implementation of boost::interprocess::vector
// could cause a hardfork unless the old size calculation behavior was also carefully replicated in the same upgrade.
int64_t old_size = (int64_t)(sizeof(permission_object) + permission->auth.get_billable_size());
// TODO/QUESTION: If we are updating an existing permission, should we check if the message declared
......@@ -245,7 +259,7 @@ void apply_eosio_updateauth(apply_context& context) {
po.auth = update.data;
po.parent = parent_id;
po.last_updated = context.controller.head_block_time();
po.delay = time_point_sec(update.delay.convert_to<uint64_t>());
po.delay = fc::seconds(update.delay);
});
int64_t new_size = (int64_t)(sizeof(permission_object) + permission->auth.get_billable_size());
......@@ -264,7 +278,7 @@ void apply_eosio_updateauth(apply_context& context) {
po.auth = update.data;
po.parent = parent_id;
po.last_updated = context.controller.head_block_time();
po.delay = time_point_sec(update.delay.convert_to<uint64_t>());
po.delay = fc::seconds(update.delay);
});
resources.add_account_ram_usage(
......@@ -279,11 +293,15 @@ void apply_eosio_updateauth(apply_context& context) {
void apply_eosio_deleteauth(apply_context& context) {
auto& resources = context.mutable_controller.get_mutable_resource_limits_manager();
auto remove = context.act.data_as<deleteauth>();
EOS_ASSERT(remove.permission != "active", action_validate_exception, "Cannot delete active authority");
EOS_ASSERT(remove.permission != "owner", action_validate_exception, "Cannot delete owner authority");
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& db = context.mutable_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));
{ // Check for children
......@@ -314,24 +332,24 @@ void apply_eosio_linkauth(apply_context& context) {
EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty");
context.require_authorization(requirement.account);
auto& db = context.mutable_db;
const auto *account = db.find<account_object, by_name>(requirement.account);
EOS_ASSERT(account != nullptr, account_query_exception,
"Fail to retrieve account: ${account}", ("account", requirement.account));
"Failed to retrieve account: ${account}", ("account", requirement.account)); // Redundant?
const auto *code = db.find<account_object, by_name>(requirement.code);
EOS_ASSERT(code != nullptr, account_query_exception,
"Fail to retrieve code for account: ${account}", ("account", requirement.code));
if( requirement.requirement != N(eosio.any) ) {
"Failed to retrieve code for account: ${account}", ("account", requirement.code));
if( requirement.requirement != config::eosio_any_name ) {
const auto *permission = db.find<permission_object, by_name>(requirement.requirement);
EOS_ASSERT(permission != nullptr, permission_query_exception,
"Fail to retrieve permission: ${permission}", ("permission", requirement.requirement));
"Failed to retrieve permission: ${permission}", ("permission", requirement.requirement));
}
auto link_key = boost::make_tuple(requirement.account, requirement.code, requirement.type);
auto link = db.find<permission_link_object, by_action_name>(link_key);
if (link) {
if( link ) {
EOS_ASSERT(link->required_permission != requirement.requirement, action_validate_exception,
"Attempting to update required authority, but new requirement is same as old");
db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) {
......@@ -351,7 +369,7 @@ void apply_eosio_linkauth(apply_context& context) {
"New Permission Link ${code}::${act} -> ${a}@${p}", _V("code", l.code)("act",l.message_type)("a", l.account)("p",l.required_permission)
);
}
} FC_CAPTURE_AND_RETHROW((requirement))
} FC_CAPTURE_AND_RETHROW((requirement))
}
void apply_eosio_unlinkauth(apply_context& context) {
......@@ -549,7 +567,7 @@ void apply_eosio_vetorecovery(apply_context& context) {
void apply_eosio_canceldelay(apply_context& context) {
auto cancel = context.act.data_as<canceldelay>();
const auto sender_id = cancel.sender_id.convert_to<uint32_t>();
const auto sender_id = cancel.sender_id;
const auto& generated_transaction_idx = context.controller.get_database().get_index<generated_transaction_multi_index>();
const auto& generated_index = generated_transaction_idx.indices().get<by_sender_id>();
const auto& itr = generated_index.lower_bound(boost::make_tuple(config::system_account_name, sender_id));
......
......@@ -6,6 +6,8 @@
#include <chainbase/chainbase.hpp>
#include <eosio/chain/transaction.hpp>
#include <type_traits>
namespace eosio { namespace chain {
......@@ -59,6 +61,21 @@ struct shared_authority {
}
size_t get_billable_size() const {
/**
* public_key_type contains a static_variant and so its size could change if we later wanted to add a new public key type of
* of larger size, thus increasing the returned value from shared_authority::get_billable_size() for old authorities that
* do not even use the new public key type.
*
* Although adding a new public key type is a hardforking change anyway, the current implementation means we would need to:
* - track historical sizes of public_key_type,
* - branch on hardfork versions within this function, and
* - calculate billable size of the authority based on the appropriate historical size of public_key_type,
* all in order to avoid retroactively changing the billable size of authorities.
* TODO: Better implementation of get_billable_size()?
* Perhaps it would require changes to how public_key_type is stored in shared_authority?
* For example: change keys (of type shared_vector<key_weight>) to packed_keys (of type shared_vector<char>)
* which store the packed data of vector<key_weight>, and then charge based on that packed size.
*/
return keys.size() * sizeof(key_weight) + accounts.size() * sizeof(permission_level_weight);
}
};
......@@ -76,6 +93,15 @@ inline bool validate( const Authority& auth ) {
const key_weight* prev = nullptr;
decltype(auth.threshold) total_weight = 0;
static_assert( std::is_same<decltype(auth.threshold), uint32_t>::value &&
std::is_same<weight_type, uint16_t>::value &&
std::is_same<typename decltype(auth.keys)::value_type, key_weight>::value &&
std::is_same<typename decltype(auth.accounts)::value_type, permission_level_weight>::value,
"unexpected type for threshold and/or weight in authority" );
if( ( auth.keys.size() + auth.accounts.size() ) > (1 << 16) )
return false; // overflow protection (assumes weight_type is uint16_t and threshold is of type uint32_t)
for( const auto& k : auth.keys ) {
if( !prev ) prev = &k;
else if( prev->key < k.key ) return false;
......
......@@ -6,6 +6,7 @@
#include <eosio/chain/types.hpp>
#include <eosio/chain/authority.hpp>
#include <eosio/chain/exceptions.hpp>
#include <eosio/utilities/parallel_markers.hpp>
......@@ -80,12 +81,20 @@ namespace detail {
}
uint32_t operator()(const permission_level_weight& permission) {
if (recursion_depth < checker.recursion_depth_limit) {
checker.permission_visitor.push_undo();
checker.permission_visitor(permission.permission);
if( checker.has_permission( permission.permission.actor ) )
if( checker.has_permission( permission.permission.actor ) ) {
total_weight += permission.weight;
else if( checker.satisfied(permission.permission, recursion_depth + 1) )
checker.permission_visitor.squash_undo();
// Satisfied by owner may throw off visitor expectations...
// TODO: Figure out what a permission_visitor actually needs and should expect.
} else if( checker.satisfied(permission.permission, recursion_depth + 1) ) {
total_weight += permission.weight;
checker.permission_visitor.squash_undo();
} else {
checker.permission_visitor.pop_undo();
}
}
return total_weight;
}
......@@ -109,8 +118,13 @@ namespace detail {
{}
bool satisfied(const permission_level& permission, uint16_t depth = 0) {
return has_permission( permission.actor ) ||
satisfied(permission_to_authority(permission), depth);
if( has_permission( permission.actor ) )
return true;
try {
return satisfied(permission_to_authority(permission), depth);
} catch( const permission_query_exception& e ) {
return false;
}
}
template<typename AuthorityType>
......@@ -132,9 +146,9 @@ namespace detail {
// Check all permissions, from highest weight to lowest, seeing if signing_keys satisfies them or not
weight_tally_visitor visitor(*this, depth);
for (const auto& permission : permissions)
for( const auto& permission : permissions )
// If we've got enough weight, to satisfy the authority, return!
if (permission.visit(visitor) >= authority.threshold) {
if( permission.visit(visitor) >= authority.threshold ) {
KeyReverter.cancel();
return true;
}
......@@ -155,15 +169,24 @@ namespace detail {
const PermissionVisitorFunc& get_permission_visitor() {
return permission_visitor;
}
}; /// authority_checker
template<typename PermissionToAuthorityFunc, typename PermissionVisitorFunc>
auto make_auth_checker(PermissionToAuthorityFunc&& pta,
PermissionVisitorFunc&& permission_visitor,
uint16_t recursion_depth_limit,
uint16_t recursion_depth_limit,
const flat_set<public_key_type>& signing_keys,
const flat_set<account_name>& accounts = flat_set<account_name>() ) {
return authority_checker<PermissionToAuthorityFunc, PermissionVisitorFunc>(std::forward<PermissionToAuthorityFunc>(pta), std::forward<PermissionVisitorFunc>(permission_visitor), recursion_depth_limit, signing_keys, accounts);
}
class noop_permission_visitor {
public:
void push_undo() {}
void pop_undo() {}
void squash_undo() {}
void operator()(const permission_level& perm_level) {}
};
} } // namespace eosio::chain
......@@ -269,7 +269,7 @@ namespace eosio { namespace chain {
uint32_t head_block_num()const;
block_id_type head_block_id()const;
account_name head_block_producer()const;
block_header head_block_header()const;
block_header head_block_header()const;
uint32_t last_irreversible_block_num() const;
......@@ -290,14 +290,14 @@ namespace eosio { namespace chain {
* @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 time_point set to the max delay that this authorization requires to complete
* @return fc::microseconds set to the max delay that this authorization requires to complete
*/
time_point check_authorization( const vector<action>& actions,
const vector<action>& context_free_actions,
const flat_set<public_key_type>& provided_keys,
bool allow_unused_signatures = false,
flat_set<account_name> provided_accounts = flat_set<account_name>()
)const;
fc::microseconds check_authorization( const vector<action>& actions,
const vector<action>& context_free_actions,
const flat_set<public_key_type>& provided_keys,
bool allow_unused_signatures = false,
flat_set<account_name> provided_accounts = flat_set<account_name>()
)const;
/**
* @param account - the account owner of the permission
......@@ -339,6 +339,8 @@ namespace eosio { namespace chain {
template<typename TransactionProcessing>
transaction_trace wrap_transaction_processing( transaction_metadata&& data, TransactionProcessing trx_processing );
transaction_trace delayed_transaction_processing( const transaction_metadata& mtrx );
/// Reset the object graph in-memory
void _initialize_indexes();
void _initialize_chain(contracts::chain_initializer& starter);
......@@ -361,39 +363,30 @@ namespace eosio { namespace chain {
return f();
}
time_point check_transaction_authorization(const transaction& trx,
const vector<signature_type>& signatures,
const vector<bytes>& cfd = vector<bytes>(),
bool allow_unused_signatures = false)const;
fc::microseconds check_transaction_authorization(const transaction& trx,
const vector<signature_type>& signatures,
const vector<bytes>& cfd = vector<bytes>(),
bool allow_unused_signatures = false)const;
void require_scope(const scope_name& name) const;
void require_account(const account_name& name) const;
/**
* This method performs some consistency checks on a transaction.
* @throw transaction_exception if the transaction is invalid
*/
template<typename T>
void validate_transaction(const T& trx) const {
try {
EOS_ASSERT(trx.actions.size() > 0, transaction_exception, "A transaction must have at least one action");
validate_expiration(trx);
validate_uniqueness(trx);
validate_tapos(trx);
} FC_CAPTURE_AND_RETHROW( (trx) ) }
/// Validate transaction helpers @{
void validate_uniqueness(const transaction& trx)const;
void validate_tapos(const transaction& trx)const;
void validate_referenced_accounts(const transaction& trx)const;
void validate_expiration(const transaction& trx) const;
void record_transaction(const transaction& trx);
void update_resource_usage( transaction_trace& trace, const transaction_metadata& meta );
void validate_uniqueness( const transaction& trx )const;
void validate_tapos( const transaction& trx )const;
void validate_referenced_accounts( const transaction& trx )const;
void validate_not_expired( const transaction& trx )const;
void validate_expiration_not_too_far( const transaction& trx, fc::time_point reference_time )const;
void validate_transaction_without_state( const transaction& trx )const;
void validate_transaction_with_minimal_state( const transaction& trx )const;
void validate_transaction_with_minimal_state( const packed_transaction& packed_trx, const transaction* trx_ptr = nullptr )const;
/// @}
void record_transaction( const transaction& trx );
void update_resource_usage( transaction_trace& trace, const transaction_metadata& meta );
/**
* @brief Find the lowest authority level required for @ref authorizer_account to authorize a message of the
* specified type
......@@ -418,11 +411,11 @@ namespace eosio { namespace chain {
bool should_check_for_duplicate_transactions()const { return !(_skip_flags&skip_transaction_dupe_check); }
bool should_check_tapos()const { return !(_skip_flags&skip_tapos_check); }
bool should_check_signatures()const { return !(_skip_flags&skip_transaction_signatures); }
///Steps involved in applying a new block
///@{
const producer_object& validate_block_header(uint32_t skip, const signed_block& next_block)const;
const producer_object& _validate_block_header(const signed_block& next_block)const;
void create_block_summary(const signed_block& next_block);
void update_global_properties(const signed_block& b);
......@@ -441,7 +434,7 @@ namespace eosio { namespace chain {
void _start_pending_cycle();
void _finalize_pending_cycle();
void _apply_cycle_trace( const cycle_trace& trace );
void _finalize_block( const block_trace& b );
void _finalize_block( const block_trace& b, const producer_object& signing_producer );
transaction _get_on_block_transaction();
void _apply_on_block_transaction();
......
......@@ -26,6 +26,7 @@ const static uint64_t eosio_all_scope = N(eosio.all);
const static uint64_t active_name = N(active);
const static uint64_t owner_name = N(owner);
const static uint64_t eosio_any_name = N(eosio.any);
const static int block_interval_ms = 500;
const static int block_interval_us = block_interval_ms*1000;
......@@ -87,7 +88,7 @@ const static int producer_repetitions = 12;
/**
* In block production, at the begining of each block we schedule deferred transactions until reach this time
*/
const static uint32_t deffered_transactions_max_time_per_block_us = 20*1000; //20ms
const static uint32_t deferred_transactions_max_time_per_block_us = 20*1000; //20ms
/**
* The number of blocks produced per round is based upon all producers having a chance
......
......@@ -150,7 +150,7 @@ struct updateauth {
permission_name permission;
permission_name parent;
authority data;
uint32 delay;
uint32_t delay;
static account_name get_account() {
return config::system_account_name;
......@@ -270,7 +270,7 @@ struct vetorecovery {
};
struct canceldelay {
uint32 sender_id;
uint128_t sender_id;
static account_name get_account() {
return config::system_account_name;
......@@ -282,7 +282,7 @@ struct canceldelay {
};
struct mindelay {
uint32 delay;
uint32_t delay;
static account_name get_account() {
return config::system_account_name;
......
......@@ -13,49 +13,49 @@ namespace eosio { namespace chain {
id_type id;
account_name owner; ///< the account this permission belongs to
id_type parent; ///< parent permission
id_type parent; ///< parent permission
permission_name name; ///< human-readable name for the permission
shared_authority auth; ///< authority required to execute this permission
time_point last_updated; ///< the last time this authority was updated
time_point delay; ///< delay associated with this permission
fc::microseconds delay; ///< delay associated with this permission
/**
* @brief Checks if this permission is equivalent or greater than other
* @tparam Index The permission_index
* @return a time_point set to the maximum delay encountered between this and the permission that is other;
* @return a fc::microseconds set to the maximum delay encountered between this and the permission that is other;
* empty optional otherwise
*
* Permissions are organized hierarchically such that a parent permission is strictly more powerful than its
* children/grandchildren. This method checks whether this permission is of greater or equal power (capable of
* satisfying) permission @ref other. The returned value is an optional<time_point> that will indicate the
* maximum delay encountered walking the hierarchy between this permission and other, if other satisfies this,
* satisfying) permission @ref other. The returned value is an optional<fc::microseconds> that will indicate the
* maximum delay encountered walking the hierarchy between this permission and other, if this satisfies other,
* otherwise an empty optional is returned.
*/
template <typename Index>
optional<time_point> satisfies(const permission_object& other, const Index& permission_index) const {
optional<fc::microseconds> satisfies(const permission_object& other, const Index& permission_index) const {
// If the owners are not the same, this permission cannot satisfy other
if (owner != other.owner)
return optional<time_point>();
if( owner != other.owner )
return optional<fc::microseconds>();
// if other satisfies this permission, then other's delay and this delay will have to contribute
// if this permission satisfies other, then other's delay and this delay will have to contribute
auto max_delay = other.delay > delay ? other.delay : delay;
// If this permission matches other, or is the immediate parent of other, then this permission satisfies other
if (id == other.id || id == other.parent)
return optional<time_point>(max_delay);
if( id == other.id || id == other.parent )
return optional<fc::microseconds>(max_delay);
// Walk up other's parent tree, seeing if we find this permission. If so, this permission satisfies other
const permission_object* parent = &*permission_index.template get<by_id>().find(other.parent);
while (parent) {
if (max_delay < parent->delay)
while( parent ) {
if( max_delay < parent->delay )
max_delay = parent->delay;
if (id == parent->parent)
return optional<time_point>(max_delay);
if (parent->parent._id == 0)
return optional<time_point>();
if( id == parent->parent )
return optional<fc::microseconds>(max_delay);
if( parent->parent._id == 0 )
return optional<fc::microseconds>();
parent = &*permission_index.template get<by_id>().find(parent->parent);
}
// This permission is not a parent of other, and so does not satisfy other
return optional<time_point>();
return optional<fc::microseconds>();
}
};
......
......@@ -17,7 +17,7 @@ namespace eosio { namespace chain {
/**
* An action is performed by an actor, aka an account. It may
* be created explicitly and authorized by signatures or might be
* generated implicitly by executing application code.
* generated implicitly by executing application code.
*
* This follows the design pattern of React Flux where actions are
* named and then dispatched to one or more action handlers (aka stores).
......@@ -75,8 +75,8 @@ namespace eosio { namespace chain {
/**
* When a transaction is referenced by a block it could imply one of several outcomes which
* describe the state-transition undertaken by the block producer.
* When a transaction is referenced by a block it could imply one of several outcomes which
* describe the state-transition undertaken by the block producer.
*/
struct transaction_receipt {
enum status_enum {
......@@ -101,11 +101,11 @@ namespace eosio { namespace chain {
*
* All transactions have an expiration time after which they
* may no longer be included in the blockchain. Once a block
* with a block_header::timestamp greater than expiration is
* with a block_header::timestamp greater than expiration is
* deemed irreversible, then a user can safely trust the transaction
* will never be included.
* will never be included.
*
* Each region is an independent blockchain, it is included as routing
* information for inter-blockchain communication. A contract in this
* region might generate or authorize a transaction intended for a foreign
......@@ -141,7 +141,10 @@ namespace eosio { namespace chain {
transaction_id_type id()const;
digest_type sig_digest( const chain_id_type& chain_id, const vector<bytes>& cfd = vector<bytes>() )const;
flat_set<public_key_type> get_signature_keys( const vector<signature_type>& signatures, const chain_id_type& chain_id, const vector<bytes>& cfd = vector<bytes>() )const;
flat_set<public_key_type> get_signature_keys( const vector<signature_type>& signatures,
const chain_id_type& chain_id,
const vector<bytes>& cfd = vector<bytes>(),
bool allow_duplicate_keys = false )const;
};
......@@ -162,7 +165,7 @@ namespace eosio { namespace chain {
const signature_type& sign(const private_key_type& key, const chain_id_type& chain_id);
signature_type sign(const private_key_type& key, const chain_id_type& chain_id)const;
flat_set<public_key_type> get_signature_keys( const chain_id_type& chain_id )const;
flat_set<public_key_type> get_signature_keys( const chain_id_type& chain_id, bool allow_duplicate_keys = false )const;
};
struct packed_transaction {
......@@ -253,6 +256,3 @@ FC_REFLECT_ENUM( eosio::chain::packed_transaction::compression_type, (none)(zlib
FC_REFLECT( eosio::chain::packed_transaction, (signatures)(context_free_data)(compression)(data) )
FC_REFLECT_DERIVED( eosio::chain::deferred_transaction, (eosio::chain::transaction), (sender_id)(sender)(payer)(execute_after) )
FC_REFLECT( eosio::chain::deferred_reference, (sender_id)(sender) )
......@@ -44,6 +44,7 @@ class transaction_metadata {
uint32_t shard_index = 0;
uint32_t bandwidth_usage = 0;
time_point published;
fc::microseconds delay;
// things for processing deferred transactions
optional<account_name> sender;
......@@ -54,9 +55,9 @@ class transaction_metadata {
size_t raw_size = 0;
vector<char> packed_trx;
// is this transaction implicit
bool is_implicit = false;
bool is_implicit = false;
// scopes available to this transaction if we are applying a block
optional<const vector<shard_lock>*> allowed_read_locks;
......
......@@ -57,7 +57,7 @@ bool transaction_header::verify_reference_block( const block_id_type& reference_
}
transaction_id_type transaction::id() const {
transaction_id_type transaction::id() const {
digest_type::encoder enc;
fc::raw::pack( enc, *this );
return enc.result();
......@@ -73,7 +73,7 @@ digest_type transaction::sig_digest( const chain_id_type& chain_id, const vector
return enc.result();
}
flat_set<public_key_type> transaction::get_signature_keys( const vector<signature_type>& signatures, const chain_id_type& chain_id, const vector<bytes>& cfd )const
flat_set<public_key_type> transaction::get_signature_keys( const vector<signature_type>& signatures, const chain_id_type& chain_id, const vector<bytes>& cfd, bool allow_duplicate_keys )const
{ try {
using boost::adaptors::transformed;
......@@ -85,13 +85,19 @@ flat_set<public_key_type> transaction::get_signature_keys( const vector<signatur
for(const signature_type& sig : signatures) {
recovery_cache_type::index<by_sig>::type::iterator it = recovery_cache.get<by_sig>().find(sig);
public_key_type recov;
if(it == recovery_cache.get<by_sig>().end() || it->trx_id != id()) {
public_key_type recov = public_key_type(sig, digest);
recov = public_key_type(sig, digest);
recovery_cache.emplace_back( cached_pub_key{id(), recov, sig} ); //could fail on dup signatures; not a problem
recovered_pub_keys.insert(recov);
continue;
} else {
recov = it->pub_key;
}
recovered_pub_keys.insert(it->pub_key);
bool successful_insertion = false;
std::tie(std::ignore, successful_insertion) = recovered_pub_keys.insert(recov);
EOS_ASSERT( allow_duplicate_keys || successful_insertion, tx_irrelevant_sig,
"transaction includes more than one signature signed using the same key associated with public key: ${key}",
("key", recov)
);
}
while(recovery_cache.size() > recovery_cache_size)
......@@ -110,9 +116,9 @@ signature_type signed_transaction::sign(const private_key_type& key, const chain
return key.sign(sig_digest(chain_id, context_free_data));
}
flat_set<public_key_type> signed_transaction::get_signature_keys( const chain_id_type& chain_id )const
flat_set<public_key_type> signed_transaction::get_signature_keys( const chain_id_type& chain_id, bool allow_duplicate_keys )const
{
return transaction::get_signature_keys(signatures, chain_id, context_free_data);
return transaction::get_signature_keys(signatures, chain_id, context_free_data, allow_duplicate_keys);
}
namespace bio = boost::iostreams;
......
......@@ -545,7 +545,7 @@ class softfloat_api : public context_aware_api {
}
int32_t _eosio_f32_trunc_i32s( float af ) {
float32_t a = to_softfloat32(af);
if (_eosio_f32_ge(af, 2147483648.0f) || _eosio_f32_le(af, -2147483649.0f))
if (_eosio_f32_ge(af, 2147483648.0f) || _eosio_f32_le(af, -2147483649.0f))
FC_THROW_EXCEPTION( eosio::chain::wasm_execution_error, "Error, f32.convert_s/i32 overflow" );
if (is_nan(a))
......@@ -1066,7 +1066,9 @@ class transaction_api : public context_aware_api {
fc::raw::unpack<transaction>(data, data_len, dtrx);
dtrx.sender = context.receiver;
dtrx.sender_id = (unsigned __int128)sender_id;
dtrx.execute_after = execute_after;
dtrx.execute_after = std::max( execute_after,
time_point_sec( (context.controller.head_block_time() + fc::seconds(dtrx.delay_sec))
+ fc::microseconds(999'999) ) /* rounds up to nearest second */ );
dtrx.payer = actual_payer;
context.execute_deferred(std::move(dtrx));
} FC_CAPTURE_AND_RETHROW((fc::to_hex(data, data_len)));
......
......@@ -9,6 +9,9 @@ namespace fc {
scoped_exit( C&& c ):callback( std::forward<C>(c) ){}
scoped_exit( scoped_exit&& mv ):callback( std::move( mv.callback ) ){}
scoped_exit( const scoped_exit& ) = delete;
scoped_exit& operator=( const scoped_exit& ) = delete;
void cancel() { canceled = true; }
~scoped_exit() {
......@@ -17,13 +20,16 @@ namespace fc {
}
scoped_exit& operator = ( scoped_exit&& mv ) {
callback = std::move(mv.callback);
if( this != &mv ) {
~scoped_exit();
callback = std::move(mv.callback);
canceled = mv.canceled;
mv.canceled = true;
}
return *this;
}
private:
scoped_exit( const scoped_exit& );
scoped_exit& operator=( const scoped_exit& );
Callback callback;
bool canceled = false;
};
......
......@@ -361,7 +361,7 @@ BOOST_FIXTURE_TEST_CASE(action_tests, TESTER) { try {
auto res = push_transaction(trx);
BOOST_CHECK_EQUAL(res.status, transaction_receipt::executed);
}
uint32_t now = control->head_block_time().sec_since_epoch();
CALL_TEST_FUNCTION( *this, "test_action", "now", fc::raw::pack(now));
......@@ -412,7 +412,7 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try {
// need at least one normal action
BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_action,
[](const fc::assert_exception& e) {
return expect_assert_message(e, "transactions require at least one context-aware action");
return expect_assert_message(e, "transaction must have at least one action");
}
);
......@@ -425,10 +425,9 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try {
// signing a transaction with only context_free_actions should not be allowed
auto sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type());
BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_irrelevant_sig,
BOOST_CHECK_EXCEPTION(push_transaction(trx), tx_no_action,
[](const fc::exception& e) {
edump((e.what()));
return expect_assert_message(e, "signatures");
return expect_assert_message(e, "transaction must have at least one action");
}
);
......@@ -468,7 +467,7 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try {
trx.context_free_actions.push_back(act);
trx.context_free_data.emplace_back(fc::raw::pack<uint32_t>(100)); // verify payload matches context free data
trx.context_free_data.emplace_back(fc::raw::pack<uint32_t>(200));
trx.actions.push_back(act1);
// attempt to access non context free api
for (uint32_t i = 200; i <= 204; ++i) {
......@@ -653,13 +652,13 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
{
signed_transaction trx;
auto tm = test_api_action<TEST_METHOD("test_action", "require_auth")>{};
action act({}, tm);
action act({}, tm);
trx.actions.push_back(act);
set_transaction_headers(trx);
BOOST_CHECK_EXCEPTION(push_transaction(trx), transaction_exception,
[](const fc::exception& e) {
return expect_assert_message(e, "transactions require at least one authorization");
return expect_assert_message(e, "transaction must have at least one authorization");
}
);
}
......@@ -718,6 +717,13 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
}
);
// test send_transaction_expiring_late
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_transaction", "send_transaction_expiring_late", fc::raw::pack(N(testapi))),
eosio::chain::transaction_exception, [](const eosio::chain::transaction_exception& e) {
return expect_assert_message(e, "Transaction expiration is too far");
}
);
BOOST_REQUIRE_EQUAL( validate(), true );
} FC_LOG_AND_RETHROW() }
......@@ -1310,7 +1316,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try {
auto get_result_uint64 = [&]() -> uint64_t {
const auto& db = control->get_database();
const auto* t_id = db.find<table_id_object, by_code_scope_table>(boost::make_tuple(N(testapi), N(testapi), N(testapi)));
FC_ASSERT(t_id != 0, "Table id not found");
const auto& idx = db.get_index<key_value_index, by_scope_primary>();
......@@ -1358,18 +1364,16 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try {
}
);
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization",
CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization",
fc::raw::pack( check_auth {
.account = N(noname),
.permission = N(active),
.pubkeys = {
get_public_key(N(testapi), "active")
}
})), fc::exception,
[](const fc::exception& e) {
return expect_assert_message(e, "unknown key");
}
})
);
BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() );
CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization",
fc::raw::pack( check_auth {
......@@ -1380,6 +1384,18 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try {
);
BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() );
CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization",
fc::raw::pack( check_auth {
.account = N(testapi),
.permission = N(noname),
.pubkeys = {
get_public_key(N(testapi), "active")
}
})
);
BOOST_CHECK_EQUAL( uint64_t(0), get_result_uint64() );
/*
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION( *this, "test_permission", "check_authorization",
fc::raw::pack( check_auth {
.account = N(testapi),
......@@ -1392,6 +1408,7 @@ BOOST_FIXTURE_TEST_CASE(permission_tests, TESTER) { try {
return expect_assert_message(e, "unknown key");
}
);
*/
} FC_LOG_AND_RETHROW() }
......
......@@ -273,12 +273,12 @@ try {
BOOST_CHECK_EXCEPTION(chain.create_account("aaaaaaaaaaaaa"), action_validate_exception,
assert_message_is("account names can only be 12 chars long"));
// Creating account with eosio. prefix with priveleged account
// Creating account with eosio. prefix with privileged account
chain.create_account("eosio.test1");
// Creating account with eosio. prefix with non-privileged account, should fail
BOOST_CHECK_EXCEPTION(chain.create_account("eosio.test2", "joe"), action_validate_exception,
assert_message_is("only privileged accounts can have names that contain 'eosio.'"));
assert_message_is("only privileged accounts can have names that start with 'eosio.'"));
} FC_LOG_AND_RETHROW() }
......@@ -296,7 +296,7 @@ BOOST_AUTO_TEST_CASE( any_auth ) { try {
chain.set_authority("bob", "spending", bob_spending_pub_key, "active");
/// this should fail because spending is not active which is default for reqauth
BOOST_REQUIRE_THROW( chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }),
BOOST_REQUIRE_THROW( chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key }),
tx_irrelevant_auth );
chain.produce_block();
......@@ -308,7 +308,7 @@ BOOST_AUTO_TEST_CASE( any_auth ) { try {
/// this should succeed because eosio::reqauth is linked to any permission
chain.push_reqauth("alice", { permission_level{N(alice), "spending"} }, { spending_priv_key });
/// this should fail because bob cannot authorize for alice, the permission given must be one-of alices
BOOST_REQUIRE_THROW( chain.push_reqauth("alice", { permission_level{N(bob), "spending"} }, { spending_priv_key }),
tx_missing_auth );
......
......@@ -652,6 +652,7 @@ BOOST_AUTO_TEST_CASE(wipe)
}
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_CASE(irrelevant_sig_soft_check) {
try {
tester chain;
......@@ -676,14 +677,23 @@ BOOST_AUTO_TEST_CASE(irrelevant_sig_soft_check) {
trx.sign( chain.get_private_key( name("random"), "active" ), chain_id_type() );
// Check that it throws for irrelevant signatures
BOOST_CHECK_THROW(chain.push_transaction( trx ), tx_irrelevant_sig);
BOOST_REQUIRE_THROW(chain.push_transaction( trx ), tx_irrelevant_sig);
// Check that it throws for multiple signatures by the same key
trx.signatures.clear();
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
BOOST_REQUIRE_THROW(chain.push_transaction( trx ), tx_irrelevant_sig);
// Sign the transaction properly and push to the block
trx.signatures.clear();
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
chain.push_transaction( trx );
// Push it through with a skip flag
chain.push_transaction( trx, skip_transaction_signatures);
// Produce block so the transaction gets included in the block
chain.produce_blocks();
// Now check that a second blockchain accepts the block with the oversigned transaction
// Now check that a second blockchain accepts the block
tester newchain;
tester_network net;
net.connect_blockchain(chain);
......@@ -692,5 +702,76 @@ BOOST_AUTO_TEST_CASE(irrelevant_sig_soft_check) {
} FC_LOG_AND_RETHROW()
}
//
BOOST_AUTO_TEST_CASE(irrelevant_sig_hard_check) {
try {
{
tester chain;
// Make an account, but add an extra signature to the transaction
signed_transaction trx;
name new_account_name = name("alice");
authority owner_auth = authority( chain.get_public_key( new_account_name, "owner" ) );
trx.actions.emplace_back( vector<permission_level>{{ config::system_account_name, config::active_name}},
contracts::newaccount{
.creator = config::system_account_name,
.name = new_account_name,
.owner = owner_auth,
.active = authority( chain.get_public_key( new_account_name, "active" ) ),
.recovery = authority( chain.get_public_key( new_account_name, "recovery" ) ),
});
chain.set_transaction_headers(trx);
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
trx.sign( chain.get_private_key( name("random"), "active" ), chain_id_type() );
// Force push transaction with irrelevant signatures using a skip flag
chain.push_transaction( trx, skip_transaction_signatures );
// Produce block so the transaction gets included in the block
chain.produce_blocks();
// Now check that a second blockchain rejects the block with the oversigned transaction
tester newchain;
tester_network net;
net.connect_blockchain(chain);
BOOST_CHECK_THROW(net.connect_blockchain(newchain), tx_irrelevant_sig);
}
{
tester chain;
// Make an account, but add an extra signature to the transaction
signed_transaction trx;
name new_account_name = name("alice");
authority owner_auth = authority( chain.get_public_key( new_account_name, "owner" ) );
trx.actions.emplace_back( vector<permission_level>{{ config::system_account_name, config::active_name}},
contracts::newaccount{
.creator = config::system_account_name,
.name = new_account_name,
.owner = owner_auth,
.active = authority( chain.get_public_key( new_account_name, "active" ) ),
.recovery = authority( chain.get_public_key( new_account_name, "recovery" ) ),
});
chain.set_transaction_headers(trx);
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
trx.sign( chain.get_private_key( config::system_account_name, "active" ), chain_id_type() );
// Force push transaction with multiple signatures by the same key using a skip flag
chain.push_transaction( trx, skip_transaction_signatures );
// Produce block so the transaction gets included in the block
chain.produce_blocks();
// Now check that a second blockchain rejects the block with the oversigned transaction
tester newchain;
tester_network net;
net.connect_blockchain(chain);
BOOST_CHECK_THROW(net.connect_blockchain(newchain), tx_irrelevant_sig);
}
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END()
......@@ -86,9 +86,37 @@ BOOST_AUTO_TEST_CASE(deterministic_distributions)
struct permission_visitor {
std::vector<permission_level> permissions;
std::vector<size_t> size_stack;
bool _log;
permission_visitor(bool log = false) : _log(log) {}
void operator()(const permission_level& permission) {
permissions.push_back(permission);
}
void push_undo() {
if( _log )
ilog("push_undo called");
size_stack.push_back(permissions.size());
}
void pop_undo() {
if( _log )
ilog("pop_undo called");
FC_ASSERT( size_stack.back() <= permissions.size() && size_stack.size() >= 1,
"invariant failure in test permission_visitor" );
permissions.erase( permissions.begin() + size_stack.back(), permissions.end() );
size_stack.pop_back();
}
void squash_undo() {
if( _log )
ilog("squash_undo called");
FC_ASSERT( size_stack.size() >= 1, "invariant failure in test permission_visitor" );
size_stack.pop_back();
}
};
BOOST_AUTO_TEST_CASE(authority_checker)
......@@ -193,6 +221,33 @@ BOOST_AUTO_TEST_CASE(authority_checker)
BOOST_TEST(checker.unused_keys().count(c) == 1);
}
A = authority(3, {key_weight{a, 2}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 3}});
pv._log = true;
{
pv.permissions.clear();
pv.size_stack.clear();
auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b});
BOOST_TEST(checker.satisfied(A));
BOOST_TEST(checker.all_keys_used());
BOOST_TEST(pv.permissions.size() == 0);
}
{
pv.permissions.clear();
pv.size_stack.clear();
auto checker = make_auth_checker(GetCAuthority, pv, 2, {a, b, c});
BOOST_TEST(checker.satisfied(A));
BOOST_TEST(!checker.all_keys_used());
BOOST_TEST(checker.used_keys().size() == 1);
BOOST_TEST(checker.used_keys().count(c) == 1);
BOOST_TEST(checker.unused_keys().size() == 2);
BOOST_TEST(checker.unused_keys().count(a) == 1);
BOOST_TEST(checker.unused_keys().count(b) == 1);
BOOST_TEST(pv.permissions.size() == 1);
BOOST_TEST(pv.permissions.back().actor == "hello");
BOOST_TEST(pv.permissions.back().permission == "world");
}
pv._log = false;
A = authority(2, {key_weight{a, 1}, key_weight{b, 1}}, {permission_level_weight{{"hello", "world"}, 1}});
BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {a}).satisfied(A));
BOOST_TEST(!make_auth_checker(GetCAuthority, pv, 2, {b}).satisfied(A));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册