提交 e11f524d 编写于 作者: B Bart Wyatt

basic recovery test and fixes for all the bugs it exposed

上级 2bef5f16
......@@ -137,7 +137,10 @@ void apply_context::require_recipient( account_name code ) {
}
void apply_context::execute_inline( action &&a ) {
controller.check_authorization( {a}, flat_set<public_key_type>(), false, {receiver} );
// todo: rethink this special case
if (receiver != config::system_account_name) {
controller.check_authorization({a}, flat_set<public_key_type>(), false, {receiver});
}
_inline_actions.emplace_back( move(a) );
}
......@@ -153,7 +156,10 @@ void apply_context::execute_deferred( deferred_transaction&& trx ) {
FC_ASSERT( !trx.actions.empty(), "transaction must have at least one action");
controller.check_authorization( trx.actions, flat_set<public_key_type>(), false, {receiver} );
// todo: rethink this special case
if (receiver != config::system_account_name) {
controller.check_authorization(trx.actions, flat_set<public_key_type>(), false, {receiver});
}
controller.validate_scope( trx );
......
......@@ -675,15 +675,19 @@ void chain_controller::check_authorization( const vector<action>& actions,
for( const auto& act : actions ) {
for( const auto& declared_auth : act.authorization ) {
const auto& min_permission = lookup_minimum_permission(declared_auth.actor,
act.scope, act.name);
if ((_skip_flags & skip_authority_check) == false) {
const auto& index = _db.get_index<permission_index>().indices();
EOS_ASSERT(get_permission(declared_auth).satisfies(min_permission, index),
tx_irrelevant_auth,
"action declares irrelevant authority '${auth}'; minimum authority is ${min}",
("auth", declared_auth)("min", min_permission.name));
// check a minimum permission if one is set, otherwise assume the contract code will validate
auto min_permission_name = lookup_minimum_permission(declared_auth.actor, act.scope, act.name);
if (min_permission_name) {
const auto& min_permission = _db.get<permission_object, by_owner>(boost::make_tuple(declared_auth.actor, *min_permission_name));
if ((_skip_flags & skip_authority_check) == false) {
const auto &index = _db.get_index<permission_index>().indices();
EOS_ASSERT(get_permission(declared_auth).satisfies(min_permission, index),
tx_irrelevant_auth,
"action declares irrelevant authority '${auth}'; minimum authority is ${min}",
("auth", declared_auth)("min", min_permission.name));
}
}
if ((_skip_flags & skip_transaction_signatures) == false) {
EOS_ASSERT(checker.satisfied(declared_auth), tx_missing_sigs,
......@@ -737,9 +741,15 @@ void chain_controller::validate_scope( const transaction& trx )const {
FC_ASSERT( intersection.size() == 0, "a transaction may not redeclare scope in readscope" );
}
const permission_object& chain_controller::lookup_minimum_permission(account_name authorizer_account,
optional<permission_name> chain_controller::lookup_minimum_permission(account_name authorizer_account,
account_name scope,
action_name act_name) const {
// updateauth is a special case where any permission _may_ be suitable depending
// on the contents of the action
if (scope == config::system_account_name && act_name == N(updateauth)) {
return optional<permission_name>();
}
try {
// First look up a specific link for this message act_name
auto key = boost::make_tuple(authorizer_account, scope, act_name);
......@@ -751,10 +761,10 @@ const permission_object& chain_controller::lookup_minimum_permission(account_nam
}
// If no specific or default link found, use active permission
auto permission_key = boost::make_tuple<account_name, permission_name>(authorizer_account, config::active_name );
if (link != nullptr)
get<1>(permission_key) = link->required_permission;
return _db.get<permission_object, by_owner>(permission_key);
return link->required_permission;
else
return N(active);
} FC_CAPTURE_AND_RETHROW((authorizer_account)(scope)(act_name))
}
......@@ -1141,18 +1151,20 @@ void chain_controller::update_global_dynamic_data(const signed_block& b) {
// if (missed_blocks)
// wlog("Blockchain continuing after gap of ${b} missed blocks", ("b", missed_blocks));
for(uint32_t i = 0; i < missed_blocks; ++i) {
const auto& producer_missed = get_producer(get_scheduled_producer(i+1));
if(producer_missed.owner != b.producer) {
/*
const auto& producer_account = producer_missed.producer_account(*this);
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
wlog( "Producer ${name} missed block ${n} around ${t}", ("name",producer_account.name)("n",b.block_num())("t",b.timestamp) );
*/
_db.modify( producer_missed, [&]( producer_object& w ) {
w.total_missed++;
});
if (!(_skip_flags & skip_missed_block_penalty)) {
for (uint32_t i = 0; i < missed_blocks; ++i) {
const auto &producer_missed = get_producer(get_scheduled_producer(i + 1));
if (producer_missed.owner != b.producer) {
/*
const auto& producer_account = producer_missed.producer_account(*this);
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
wlog( "Producer ${name} missed block ${n} around ${t}", ("name",producer_account.name)("n",b.block_num())("t",b.timestamp) );
*/
_db.modify(producer_missed, [&](producer_object &w) {
w.total_missed++;
});
}
}
}
......@@ -1541,10 +1553,18 @@ void chain_controller::update_usage( transaction_metadata& meta, uint32_t act_us
// for any transaction not sent by code, update the affirmative last time a given permission was used
if (!meta.sender) {
const auto &puo = _db.get<permission_usage_object, by_account_permission>(boost::make_tuple(authaccnt.first, authaccnt.second));
_db.modify(puo, [this](auto &pu) {
pu.last_used = head_block_time();
});
const auto *puo = _db.find<permission_usage_object, by_account_permission>(boost::make_tuple(authaccnt.first, authaccnt.second));
if (puo) {
_db.modify(*puo, [this](permission_usage_object &pu) {
pu.last_used = head_block_time();
});
} else {
_db.create<permission_usage_object>([this, &authaccnt](permission_usage_object &pu){
pu.account = authaccnt.first;
pu.permission = authaccnt.second;
pu.last_used = head_block_time();
});
}
}
}
......
......@@ -107,12 +107,6 @@ void apply_eosio_newaccount(apply_context& context) {
});
auto create_permission = [owner=create.name, &db, &context](const permission_name& name, permission_object::id_type parent, authority &&auth) {
db.create<permission_usage_object>([&](permission_usage_object& pu){
pu.account = owner;
pu.permission = name;
pu.last_used = context.controller.head_block_time();
});
return db.create<permission_object>([&](permission_object& p) {
p.name = name;
p.parent = parent;
......@@ -431,10 +425,27 @@ void apply_eosio_updateauth(apply_context& context) {
auto& db = context.mutable_db;
bool is_system = context.act.authorization.size() == 1 && context.act.authorization.front().actor == config::system_account_name && context.sender && context.sender == config::system_account_name;
if (!is_system) {
context.require_authorization(update.account);
}
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;
}
auto current = db.get<permission_object, by_owner>(boost::make_tuple(update.account, update.permission));
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");
db.get<account_object, by_name>(update.account);
validate_authority_precondition(context, update.data);
......@@ -561,26 +572,29 @@ static const abi_serializer& get_abi_serializer() {
}
static optional<variant> get_pending_recovery(apply_context& context, account_name account ) {
const auto* t_id = context.find_table(account, config::system_account_name, N(pending_recovery));
ilog("Fetching recovery for ${account}:", ("account", account));
const auto* t_id = context.find_table(account, config::system_account_name, N(recovery));
if (t_id) {
uint64_t key = N(account);
uint64_t key = account;
int32_t record_size = context.front_record<key_value_index, by_scope_primary>(*t_id, &key, nullptr, 0);
if (record_size > 0) {
bytes value(record_size);
record_size = context.front_record<key_value_index, by_scope_primary>(*t_id, &key, value.data(), value.size());
assert(record_size == value.size());
bytes value(record_size + sizeof(uint64_t));
uint64_t* key_p = reinterpret_cast<uint64_t *>(value.data());
*key_p = key;
record_size = context.front_record<key_value_index, by_scope_primary>(*t_id, &key, value.data() + sizeof(uint64_t), value.size() - sizeof(uint64_t));
assert(record_size == value.size() - sizeof(uint64_t));
return get_abi_serializer().binary_to_variant("pending_recovery", value);
}
}
return optional<variant_object>();
}
static uint32_t get_next_sender_id(apply_context& context) {
context.require_write_scope( config::eosio_auth_scope );
const auto& t_id = context.find_or_create_table(config::eosio_auth_scope, config::system_account_name, N(deferred_sequence));
const auto& t_id = context.find_or_create_table(config::eosio_auth_scope, config::system_account_name, N(deferred.seq));
uint64_t key = N(config::eosio_auth_scope);
uint32_t next_serial = 0;
context.front_record<key_value_index, by_scope_primary>(t_id, &key, (char *)&next_serial, sizeof(uint32_t));
......@@ -590,6 +604,20 @@ static uint32_t get_next_sender_id(apply_context& context) {
return result;
}
static auto get_account_creation(const apply_context& context, const account_name& account) {
auto const& accnt = context.db.get<account_object, by_name>(account);
return (time_point)accnt.creation_date;
};
static auto get_permission_last_used(const apply_context& context, const account_name& account, const permission_name& permission) {
auto const* perm = context.db.find<permission_usage_object, by_account_permission>(boost::make_tuple(account, permission));
if (perm) {
return optional<time_point>(perm->last_used);
}
return optional<time_point>();
};
void apply_eosio_postrecovery(apply_context& context) {
context.require_write_scope( config::eosio_auth_scope );
......@@ -609,11 +637,23 @@ void apply_eosio_postrecovery(apply_context& context) {
delay_lock = fc::days(30);
} else {
// process lost password
auto const& owner_perm = context.db.get<permission_usage_object, by_account_permission>(boost::make_tuple(account, N(owner)));
auto const& active_perm = context.db.get<permission_usage_object, by_account_permission>(boost::make_tuple(account, N(active)));
FC_ASSERT(owner_perm.last_used >= now - fc::days(30), "Account ${account} has had owner key activity recently and cannot be recovered yet!", ("account",account));
FC_ASSERT(active_perm.last_used >= now - fc::days(30), "Account ${account} has had active key activity recently and cannot be recovered yet!", ("account",account));
auto owner_last_used = get_permission_last_used(context, account, N(owner));
auto active_last_used = get_permission_last_used(context, account, N(active));
if (!owner_last_used || !active_last_used) {
auto account_creation = get_account_creation(context, account);
if (!owner_last_used) {
owner_last_used.emplace(account_creation);
}
if (!active_last_used) {
active_last_used.emplace(account_creation);
}
}
FC_ASSERT(*owner_last_used <= now - fc::days(30), "Account ${account} has had owner key activity recently and cannot be recovered yet!", ("account",account));
FC_ASSERT(*active_last_used <= now - fc::days(30), "Account ${account} has had active key activity recently and cannot be recovered yet!", ("account",account));
delay_lock = fc::days(7);
}
......@@ -628,8 +668,8 @@ void apply_eosio_postrecovery(apply_context& context) {
uint32_t request_id = get_next_sender_id(context);
auto record = mutable_variant_object()
("account", (string)account)
auto record_data = mutable_variant_object()
("account", account)
("request_id", request_id)
("update", update)
("memo", recover_act.memo);
......@@ -641,29 +681,33 @@ void apply_eosio_postrecovery(apply_context& context) {
dtrx.execute_after = context.controller.head_block_time() + delay_lock;
dtrx.set_reference_block(context.controller.head_block_id());
dtrx.expiration = dtrx.execute_after + fc::seconds(60);
dtrx.write_scope = sort_names({account, config::eosio_auth_scope});
dtrx.actions.emplace_back(vector<permission_level>{{config::system_account_name,config::active_name}},
dtrx.write_scope = sort_names({account, config::eosio_auth_scope, config::system_account_name});
dtrx.actions.emplace_back(vector<permission_level>{{account,config::active_name}},
passrecovery { account });
context.execute_deferred(std::move(dtrx));
const auto& t_id = context.find_or_create_table(account, config::system_account_name, N(pending_recovery));
auto data = get_abi_serializer().variant_to_binary("pending_recovery", record);
context.store_record<key_value_object>(t_id,(uint64_t *)data.data(), data.data() + sizeof(uint64_t), data.size() - sizeof(uint64_t));
const auto& t_id = context.find_or_create_table(account, config::system_account_name, N(recovery));
auto data = get_abi_serializer().variant_to_binary("pending_recovery", record_data);
context.store_record<key_value_object>(t_id,&account.value, data.data() + sizeof(uint64_t), data.size() - sizeof(uint64_t));
context.console_append_formatted("Recovery Started for account ${account} : ${memo}\n", mutable_variant_object()("account", account)("memo", recover_act.memo));
}
static void remove_pending_recovery(apply_context& context, const account_name& account) {
const auto& t_id = context.find_or_create_table(account, config::system_account_name, N(pending_recovery));
const auto& t_id = context.find_or_create_table(account, config::system_account_name, N(recovery));
context.remove_record<key_value_object>(t_id, &account.value);
}
void apply_eosio_passrecovery(apply_context& context) {
context.require_authorization(config::system_account_name);
auto pass_act = context.act.as<passrecovery>();
const auto& account = pass_act.account;
// ensure this is only processed if it is a deferred transaction from the system account
FC_ASSERT(context.sender && *context.sender == config::system_account_name);
context.require_authorization(account);
auto maybe_recovery = get_pending_recovery(context, account);
FC_ASSERT(maybe_recovery, "No pending recovery found for account ${account}", ("account", account));
auto recovery = *maybe_recovery;
......@@ -671,7 +715,7 @@ void apply_eosio_passrecovery(apply_context& context) {
updateauth update;
fc::from_variant(recovery["update"], update);
action act(vector<permission_level>{{config::system_account_name,config::active_name}}, update);
action act(vector<permission_level>{{account,config::owner_name}}, update);
context.execute_inline(move(act));
remove_pending_recovery(context, account);
......
......@@ -457,11 +457,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -477,11 +481,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -501,11 +509,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -539,12 +551,16 @@ using apply_handler = std::function<void(apply_context&)>;
}
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -575,11 +591,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -595,11 +615,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
template <typename IndexType, typename Scope>
......@@ -615,11 +639,15 @@ using apply_handler = std::function<void(apply_context&)>;
impl::key_helper<typename IndexType::value_type>::get(keys, *itr);
auto copylen = std::min<size_t>(itr->value.size(),valuelen);
if( copylen ) {
itr->value.copy(value, copylen);
if (valuelen) {
auto copylen = std::min<size_t>(itr->value.size(), valuelen);
if (copylen) {
itr->value.copy(value, copylen);
}
return copylen;
} else {
return itr->value.size();
}
return copylen;
}
} } // namespace eosio::chain
......
......@@ -52,7 +52,8 @@ namespace eosio { namespace chain {
pushed_transaction = 1 << 14, ///< used to indicate that the origination of the call was from a push_transaction, to determine time allotment
created_block = 1 << 15, ///< used to indicate that the origination of the call was for creating a block, to determine time allotment
received_block = 1 << 16, ///< used to indicate that the origination of the call was for a received block, to determine time allotment
genesis_setup = 1 << 17 ///< used to indicate that the origination of the call was for a genesis transaction
genesis_setup = 1 << 17, ///< used to indicate that the origination of the call was for a genesis transaction
skip_missed_block_penalty = 1 << 18, ///< used to indicate that missed blocks shouldn't count against producers (used in long unit tests)
};
......@@ -377,7 +378,7 @@ namespace eosio { namespace chain {
* @param type The type of message
* @return
*/
const permission_object& lookup_minimum_permission( account_name authorizer_account,
optional<permission_name> lookup_minimum_permission( account_name authorizer_account,
scope_name code_account,
action_name type) const;
......
......@@ -253,7 +253,7 @@ namespace eosio { namespace chain {
// grab the lock and put this in the cache as unavailble
with_lock([&,this]() {
// find or create a new entry
auto iter = _cache.emplace(code_id, std::move(code_info(mem_end, std::move(mem_image)))).first;
auto iter = _cache.emplace(code_id, code_info(mem_end, std::move(mem_image))).first;
iter->second.instances.emplace_back(std::make_unique<wasm_cache::entry>(instance, module));
pending_result = optional_entry_ref(*iter->second.instances.back().get());
......
......@@ -76,7 +76,7 @@ namespace eosio { namespace testing {
auto sch_pro = control->get_scheduled_producer(slot);
auto priv_key = get_private_key( sch_pro, "producer" );
return control->generate_block( next_time, sch_pro, priv_key );
return control->generate_block( next_time, sch_pro, priv_key, skip_missed_block_penalty );
}
......@@ -96,7 +96,7 @@ namespace eosio { namespace testing {
trx.write_scope = { creator, config::eosio_auth_scope };
trx.actions.emplace_back( vector<permission_level>{{creator,config::active_name}},
contracts::newaccount{
contracts::newaccount{
.creator = creator,
.name = a,
.owner = authority( get_public_key( a, "owner" ) ),
......
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/staked_balance_objects.hpp>
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::chain::contracts;
using namespace eosio::testing;
BOOST_AUTO_TEST_SUITE(recovery_tests)
BOOST_FIXTURE_TEST_CASE( test_recovery_owner, tester ) try {
produce_blocks(1000);
create_account(N(alice), asset::from_string("1000.000 EOS"));
produce_block();
fc::time_point expected_recovery(fc::seconds(control->head_block_time().sec_since_epoch()) +fc::days(30));
transaction_id_type recovery_txid;
{
signed_transaction trx;
trx.write_scope = {config::eosio_auth_scope, N(alice)};
trx.actions.emplace_back( vector<permission_level>{{N(alice),config::active_name}},
postrecovery{
.account = N(alice),
.data = authority(get_public_key(N(alice), "owner.recov")),
.memo = "Test recovery"
} );
set_tapos(trx);
trx.sign(get_private_key(N(alice), "active"), chain_id_type());
auto trace = push_transaction(trx);
BOOST_REQUIRE_EQUAL(trace.deferred_transactions.size(), 1);
recovery_txid = trace.deferred_transactions.front().id();
produce_block();
BOOST_REQUIRE_EQUAL(chain_has_transaction(trx.id()), true);
}
auto push_nonce = [this](const string& role) {
// ensure the old owner key is valid
signed_transaction trx;
trx.write_scope = {N(alice)};
trx.actions.emplace_back( vector<permission_level>{{N(alice),config::owner_name}},
nonce{
.value = control->head_block_num()
} );
set_tapos(trx);
trx.sign(get_private_key(N(alice), role), chain_id_type());
push_transaction(trx);
return trx.id();
};
auto skip_time = expected_recovery - control->head_block_time() - fc::milliseconds(config::block_interval_ms);
produce_block(skip_time);
control->push_deferred_transactions(true);
auto last_old_nonce_id = push_nonce("owner");
produce_block();
control->push_deferred_transactions(true);
BOOST_REQUIRE_EQUAL(chain_has_transaction(last_old_nonce_id), true);
BOOST_REQUIRE_THROW(push_nonce("owner"), tx_missing_sigs);
auto first_new_nonce_id = push_nonce("owner.recov");
produce_block();
BOOST_REQUIRE_EQUAL(chain_has_transaction(first_new_nonce_id), true);
} 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.
先完成此消息的编辑!
想要评论请 注册