提交 208a6016 编写于 作者: A Anton Perkov

Merge branch 'slim' into system-contract-tests-fix

......@@ -116,8 +116,7 @@
"name": "eosio_parameters",
"base": "blockchain_parameters",
"fields": [
{"name":"max_ram_size", "type":"uint64"},
{"name":"percent_of_max_inflation_rate", "type":"uint32"}
{"name":"max_ram_size", "type":"uint64"}
]
},{
"name": "eosio_global_state",
......@@ -127,7 +126,7 @@
{"name":"total_ram_stake", "type":"asset"},
{"name":"last_producer_schedule_update", "type":"time"},
{"name":"last_pervote_bucket_fill", "type":"uint64"},
{"name":"eos_bucket", "type":"asset"},
{"name":"pervote_bucket", "type":"asset"},
{"name":"savings", "type":"asset"},
{"name":"last_producer_schedule_id", "type":"checksum160"},
{"name":"total_activatied_stake", "type":"int64"}
......
......@@ -33,7 +33,7 @@ namespace eosiosystem {
block_timestamp last_producer_schedule_update = 0;
uint64_t last_pervote_bucket_fill = 0;
eosio::asset eos_bucket;
eosio::asset pervote_bucket;
eosio::asset savings;
checksum160 last_producer_schedule_id;
......@@ -43,7 +43,7 @@ namespace eosiosystem {
EOSLIB_SERIALIZE_DERIVED( eosio_global_state, eosio_parameters, (total_ram_bytes_reserved)(total_ram_stake)
(last_producer_schedule_update)
(last_pervote_bucket_fill)
(eos_bucket)(savings)(last_producer_schedule_id)(total_activated_stake) )
(pervote_bucket)(savings)(last_producer_schedule_id)(total_activated_stake) )
};
struct producer_info {
......@@ -191,7 +191,7 @@ namespace eosiosystem {
private:
eosio::asset payment_per_block( double rate, const eosio::asset& token_supply, uint32_t num_blocks );
eosio::asset payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& eos_bucket );
eosio::asset payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& pervote_bucket );
eosio::asset supply_growth( double rate, const eosio::asset& token_supply, time seconds );
......
......@@ -29,11 +29,11 @@ namespace eosiosystem {
using namespace eosio;
/** until activated stake crosses this threshold no new rewards are paid */
if( _gstate.total_activated_stake < 1500000000000 /* 150'000'000'0000 */ )
if( _gstate.total_activated_stake < 150'000'000'0000 )
return;
if( _gstate.last_pervote_bucket_fill == 0 ) /// start the presses
_gstate.last_pervote_bucket_fill = timestamp;
_gstate.last_pervote_bucket_fill = current_time();
auto prod = _producers.find(producer);
if ( prod != _producers.end() ) {
......@@ -50,9 +50,10 @@ namespace eosiosystem {
}
eosio::asset system_contract::payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& eos_bucket ) {
eosio::asset system_contract::payment_per_vote( const account_name& owner, double owners_votes, const eosio::asset& pervote_bucket ) {
eosio::asset payment(0, S(4,EOS));
if ( eos_bucket.amount < min_daily_tokens ) {
const int64_t min_daily_amount = 100 * 10000;
if ( pervote_bucket.amount < min_daily_tokens ) {
return payment;
}
......@@ -74,8 +75,8 @@ namespace eosiosystem {
}
total_producer_votes += itr->total_votes;
running_payment_amount = (itr->total_votes) * double(eos_bucket.amount) / total_producer_votes;
if ( running_payment_amount < min_daily_tokens ) {
running_payment_amount = (itr->total_votes) * double(pervote_bucket.amount) / total_producer_votes;
if ( running_payment_amount < min_daily_amount ) {
if ( itr->owner == owner ) {
to_be_payed = false;
}
......@@ -85,7 +86,7 @@ namespace eosiosystem {
}
if ( to_be_payed ) {
payment.amount = static_cast<int64_t>( (double(eos_bucket.amount) * owners_votes) / total_producer_votes );
payment.amount = static_cast<int64_t>( (double(pervote_bucket.amount) * owners_votes) / total_producer_votes );
}
return payment;
......@@ -102,24 +103,29 @@ namespace eosiosystem {
eosio_assert(current_time() >= prod->last_claim_time + useconds_per_day, "already claimed rewards within a day");
}
auto parameters = _global.get();
const asset token_supply = token( N(eosio.token)).get_supply(symbol_type(system_token_symbol).name() );
const uint32_t secs_since_last_fill = static_cast<uint32_t>( (current_time() - parameters.last_pervote_bucket_fill) / 1000000 );
const uint32_t secs_since_last_fill = static_cast<uint32_t>( (current_time() - _gstate.last_pervote_bucket_fill) / 1000000 );
const asset to_eos_bucket = supply_growth( standby_rate, token_supply, secs_since_last_fill );
const asset to_pervote_bucket = supply_growth( standby_rate, token_supply, secs_since_last_fill );
const asset to_savings = supply_growth( continuous_rate - (perblock_rate + standby_rate), token_supply, secs_since_last_fill );
const asset perblock_pay = payment_per_block( perblock_rate, token_supply, prod->produced_blocks );
const asset issue_amount = to_eos_bucket + to_savings + perblock_pay;
const asset issue_amount = to_pervote_bucket + to_savings + perblock_pay;
const asset pervote_pay = payment_per_vote( owner, prod->total_votes, to_pervote_bucket + _gstate.pervote_bucket );
if ( perblock_pay.amount + pervote_pay.amount == 0 ) {
_producers.modify( prod, 0, [&](auto& p) {
p.last_claim_time = current_time();
});
return;
}
INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}},
{N(eosio), issue_amount, std::string("issue tokens for producer pay and savings")} );
const asset pervote_pay = payment_per_vote( owner, prod->total_votes, to_eos_bucket + parameters.eos_bucket );
parameters.eos_bucket += ( to_eos_bucket - pervote_pay );
parameters.last_pervote_bucket_fill = current_time();
parameters.savings += to_savings;
_global.set( parameters, _self );
_gstate.pervote_bucket += ( to_pervote_bucket - pervote_pay );
_gstate.last_pervote_bucket_fill = current_time();
_gstate.savings += to_savings;
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
{ N(eosio), owner, perblock_pay + pervote_pay, std::string("producer claiming rewards") } );
......
......@@ -38,9 +38,14 @@ void token::create( account_name issuer,
void token::issue( account_name to, asset quantity, string memo )
{
print( "issue" );
auto sym = quantity.symbol.name();
stats statstable( _self, sym );
const auto& st = statstable.get( sym );
auto sym = quantity.symbol;
eosio_assert( sym.is_valid(), "invalid symbol name" );
auto sym_name = sym.name();
stats statstable( _self, sym_name );
auto existing = statstable.find( sym_name );
eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
const auto& st = *existing;
require_auth( st.issuer );
eosio_assert( quantity.is_valid(), "invalid quantity" );
......@@ -55,8 +60,7 @@ void token::issue( account_name to, asset quantity, string memo )
add_balance( st.issuer, quantity, st, st.issuer );
if( to != st.issuer )
{
if( to != st.issuer ) {
SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
}
}
......
/**
* @file account.h
* @copyright defined in eos/LICENSE.txt
*/
#pragma once
#include <eosiolib/types.h>
/**
* @defgroup accountapi Account API
* @brief Define API for querying account data
* @ingroup contractdev
*/
/**
* @defgroup accountcapi Account C API
* @brief C API for querying account data
* @ingroup accountapi
* @{
*/
extern "C" {
/**
* @brief Retrieve the balance for the provided account
* @details Retrieve the balance for the provided account
*
* @param balance - a pointer to a range of memory to store balance data
* @param len - length of the range of memory to store balance data
* @return true if account information is retrieved
*
* @pre data is a valid pointer to a range of memory at least datalen bytes long
* @pre data is a pointer to a balance object
* @pre *((uint64_t*)data) stores the primary key
*
* Example:
* @code
* balance b;
* b.account = N(myaccount);
* balance(b, sizeof(balance));
* @endcode
*
*/
bool account_balance_get( void* balance, uint32_t len );
///@ } accountcapi
}
/**
* @file account.hpp
* @copyright defined in eos/LICENSE.txt
* @brief Defines types and ABI for account API interactions
*
*/
#pragma once
#include <eosiolib/account.h>
#include <eosiolib/print.hpp>
#include <eosiolib/asset.hpp>
namespace eosio { namespace account {
/**
* @defgroup accountcppapi Account C++ API
* @brief C++ API for querying account data, e.g. account balance
* @ingroup accountapi
*
* @{
*/
/**
* @brief The binary structure expected and populated by native balance function.
* @details
* Example:
* @code
* account_balance test1_balance;
* test1_balance.account = N(test1);
* if (account_api::get(test1_balance))
* {
* eosio::print("test1 balance=", test1_balance.eos_balance, "\n");
* }
* @endcode
* @{
*/
struct PACKED(account_balance) {
/**
* @brief Name of the account who's balance this is
* @details Name of the account who's balance this is
*/
account_name account;
/**
* @brief Balance for this account
* @details Balance for this account
*/
asset eos_balance;
/**
* @brief Staked balance for this account
* @details Staked balance for this account
*/
asset staked_balance;
/**
* @brief Unstaking balance for this account
* @details Unstaking balance for this account
*/
asset unstaking_balance;
/**
* @brief Time at which last unstaking occurred for this account
* @details Time at which last unstaking occurred for this account
*/
time last_unstaking_time;
};
/// @} account_balance
/**
* @brief Retrieve a populated balance structure
* @details Retrieve a populated balance structure
* @param acnt - account
* @return true if account's balance is found
*/
bool get(account_balance& acnt)
{
return account_balance_get(&acnt, sizeof(account_balance));
}
/// @} eosio
} }
......@@ -28,7 +28,7 @@ extern "C" {
* @brief Checks if a permission is authorized by a provided delay and a provided set of keys and permissions
*
* @param account - the account owner of the permission
* @param permission - the permission name to check for authorization
* @param permission - the name of the permission to check for authorization
* @param pubkeys_data - pointer to the start of the serialized vector of provided public keys
* @param pubkeys_size - size (in bytes) of serialized vector of provided public keys (can be 0 if no public keys are to be provided)
* @param perms_data - pointer to the start of the serialized vector of provided permissions (empty permission name acts as wildcard)
......@@ -44,4 +44,25 @@ extern "C" {
const char* perms_data, uint32_t perms_size,
uint64_t delay_us
);
/**
* @brief Returns the last used time of a permission
*
* @param account - the account owner of the permission
* @param permission - the name of the permission
*
* @return the last used time (in microseconds since Unix epoch) of the permission
*/
int64_t get_permission_last_used( account_name account, permission_name permission );
/**
* @brief Returns the creation time of an account
*
* @param account - the account
*
* @return the creation time (in microseconds since Unix epoch) of the account
*/
int64_t get_account_creation_time( account_name account );
}
......@@ -165,6 +165,8 @@ extern "C" {
// test permission
WASM_TEST_HANDLER_EX(test_permission, check_authorization);
WASM_TEST_HANDLER_EX(test_permission, test_permission_last_used);
WASM_TEST_HANDLER_EX(test_permission, test_account_creation_time);
//unhandled test call
eosio_assert(false, "Unknown Test");
......
......@@ -252,6 +252,8 @@ struct test_softfloat {
struct test_permission {
static void check_authorization(uint64_t receiver, uint64_t code, uint64_t action);
static void test_permission_last_used(uint64_t receiver, uint64_t code, uint64_t action);
static void test_account_creation_time(uint64_t receiver, uint64_t code, uint64_t action);
};
struct test_datastream {
......
......@@ -49,3 +49,31 @@ void test_permission::check_authorization(uint64_t receiver, uint64_t code, uint
db_update_i64(itr, self, &res64, sizeof(int64_t));
}
}
struct test_permission_last_used_msg {
account_name account;
permission_name permission;
int64_t last_used_time;
EOSLIB_SERIALIZE( test_permission_last_used_msg, (account)(permission)(last_used_time) )
};
void test_permission::test_permission_last_used(uint64_t receiver, uint64_t code, uint64_t action) {
(void)code;
(void)action;
using namespace eosio;
auto params = unpack_action_data<test_permission_last_used_msg>();
eosio_assert( get_permission_last_used(params.account, params.permission) == params.last_used_time, "unexpected last used permission time" );
}
void test_permission::test_account_creation_time(uint64_t receiver, uint64_t code, uint64_t action) {
(void)code;
(void)action;
using namespace eosio;
auto params = unpack_action_data<test_permission_last_used_msg>();
eosio_assert( get_account_creation_time(params.account) == params.last_used_time, "unexpected account creation time" );
}
......@@ -222,6 +222,7 @@ void apply_context::execute_inline( action&& a ) {
// action was made at the moment the deferred transaction was executed with potentially no forewarning?
}
}
_inline_actions.emplace_back( move(a) );
}
......@@ -476,6 +477,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char*
}
db.modify( obj, [&]( auto& o ) {
o.value.resize( 0 );
o.value.resize( buffer_size );
memcpy( o.value.data(), buffer, buffer_size );
o.payer = payer;
......
......@@ -35,15 +35,22 @@ namespace eosio { namespace chain {
time_point initial_creation_time
)
{
auto creation_time = initial_creation_time;
if( creation_time == time_point() ) {
creation_time = _control.pending_block_time();
}
const auto& perm_usage = _db.create<permission_usage_object>([&](auto& p) {
p.last_used = creation_time;
});
const auto& perm = _db.create<permission_object>([&](auto& p) {
p.name = name;
p.parent = parent;
p.owner = account;
p.auth = auth;
if( initial_creation_time == time_point())
p.last_updated = _control.pending_block_time();
else
p.last_updated = initial_creation_time;
p.usage_id = perm_usage.id;
p.parent = parent;
p.owner = account;
p.name = name;
p.last_updated = creation_time;
p.auth = auth;
});
return perm;
}
......@@ -55,19 +62,54 @@ namespace eosio { namespace chain {
time_point initial_creation_time
)
{
auto creation_time = initial_creation_time;
if( creation_time == time_point() ) {
creation_time = _control.pending_block_time();
}
const auto& perm_usage = _db.create<permission_usage_object>([&](auto& p) {
p.last_used = creation_time;
});
const auto& perm = _db.create<permission_object>([&](auto& p) {
p.name = name;
p.parent = parent;
p.owner = account;
p.auth = std::move(auth);
if( initial_creation_time == time_point())
p.last_updated = _control.pending_block_time();
else
p.last_updated = initial_creation_time;
p.usage_id = perm_usage.id;
p.parent = parent;
p.owner = account;
p.name = name;
p.last_updated = creation_time;
p.auth = std::move(auth);
});
return perm;
}
void authorization_manager::modify_permission( const permission_object& permission, const authority& auth ) {
_db.modify( permission, [&](permission_object& po) {
po.auth = auth;
po.last_updated = _control.pending_block_time();
});
}
void authorization_manager::remove_permission( const permission_object& permission ) {
const auto& index = _db.template get_index<permission_index, by_parent>();
auto range = index.equal_range(permission.id);
EOS_ASSERT( range.first == range.second, action_validate_exception,
"Cannot remove a permission which has children. Remove the children first.");
_db.get_mutable_index<permission_usage_index>().remove_object( permission.usage_id._id );
_db.remove( permission );
}
void authorization_manager::update_permission_usage( const permission_object& permission ) {
const auto& puo = _db.get<permission_usage_object, by_id>( permission.usage_id );
_db.modify( puo, [&](permission_usage_object& p) {
p.last_used = _control.pending_block_time();
});
}
fc::time_point authorization_manager::get_permission_last_used( const permission_object& permission )const {
return _db.get<permission_usage_object, by_id>( permission.usage_id ).last_used;
}
const permission_object* authorization_manager::find_permission( const permission_level& level )const
{ try {
FC_ASSERT( !level.actor.empty() && !level.permission.empty(), "Invalid permission" );
......
......@@ -245,7 +245,7 @@ namespace eosio { namespace chain {
void block_header_state::add_confirmation( const header_confirmation& conf ) {
for( const auto& c : confirmations )
FC_ASSERT( c.producer == conf.producer, "block already confirmed by this producer" );
FC_ASSERT( c.producer != conf.producer, "block already confirmed by this producer" );
auto key = active_schedule.get_producer_key( conf.producer );
FC_ASSERT( key != public_key_type(), "producer not in current schedule" );
......
......@@ -122,7 +122,9 @@ struct controller_impl {
* errors and throw exceptions. Unless those exceptions are caught it could impact consensus and/or
* cause a node to fork.
*
* TODO: define special exceptions that can be thrown to reject transactions or blocks
* If it is ever desirable to let a signal handler bubble an exception out of this method
* a full audit of its uses needs to be undertaken.
*
*/
template<typename Signal, typename Arg>
void emit( const Signal& s, Arg&& a ) {
......@@ -133,10 +135,6 @@ struct controller_impl {
}
}
void emit_transaction(const transaction_metadata_ptr& trx, const transaction_trace_ptr& trace) {
}
void on_irreversible( const block_state_ptr& s ) {
if( !blog.head() )
blog.read_head();
......@@ -410,8 +408,6 @@ struct controller_impl {
trace->receipt = push_receipt( gto.trx_id, transaction_receipt::soft_fail, trace->cpu_usage, trace->net_usage );
fc::move_append( pending->_actions, move(trx_context.executed) );
remove_scheduled_transaction( gto );
emit( self.applied_transaction, trace );
trx_context.squash();
......@@ -424,11 +420,7 @@ struct controller_impl {
return trace;
}
void remove_scheduled_transaction( const generated_transaction_object& gto, bool expire = false ) {
if( expire ) {
push_receipt( gto.trx_id, transaction_receipt::expired, 0, 0 );
}
void remove_scheduled_transaction( const generated_transaction_object& gto ) {
resource_limits.add_pending_ram_usage(
gto.payer,
-(config::billable_size_v<generated_transaction_object> + gto.packed_trx.size())
......@@ -438,17 +430,30 @@ struct controller_impl {
db.remove( gto );
}
void push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline )
bool failure_is_subjective( const fc::exception& e ) {
auto code = e.code();
return (code == tx_soft_cpu_usage_exceeded::code_value) ||
(code == tx_soft_net_usage_exceeded::code_value) ||
(code == tx_deadline_exceeded::code_value);
}
transaction_trace_ptr push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline )
{ try {
auto undo_session = db.start_undo_session(true);
fc::datastream<const char*> ds( gto.packed_trx.data(), gto.packed_trx.size() );
auto remove_retained_state = fc::make_scoped_exit([&, this](){
remove_scheduled_transaction(gto);
});
FC_ASSERT( gto.delay_until <= self.pending_block_time(), "this transaction isn't ready",
("gto.delay_until",gto.delay_until)("pbt",self.pending_block_time()) );
if( gto.expiration < self.pending_block_time() ) {
remove_scheduled_transaction( gto, true ); // expire the transaction
return;
auto trace = std::make_shared<transaction_trace>();
trace->id = gto.trx_id;
trace->scheduled = false;
trace->receipt = push_receipt( gto.trx_id, transaction_receipt::expired, 0, 0 ); // expire the transaction
return trace;
}
auto start = fc::time_point::now();
......@@ -459,7 +464,6 @@ struct controller_impl {
transaction_trace_ptr trace = trx_context.trace;
flat_set<account_name> bill_to_accounts;
uint64_t max_cpu = 0;
bool abort_on_error = false;
try {
trx_context.init_for_deferred_trx( deadline, gto.published );
bill_to_accounts = trx_context.bill_to_accounts;
......@@ -477,21 +481,13 @@ struct controller_impl {
fc::move_append( pending->_actions, move(trx_context.executed) );
remove_scheduled_transaction( gto );
emit( self.applied_transaction, trace );
try {
emit( self.applied_transaction, trace );
} catch( ... ) {
abort_on_error = true;
throw;
}
trx_context.squash();
undo_session.squash();
restore.cancel();
return;
return trace;
} catch( const fc::exception& e ) {
if( abort_on_error ) // abort_on_error should normally should not be set at this point, but if it is then that means
throw; // something went wrong while emitting the applied_transaction signal and we should abort
trace->except = e;
trace->except_ptr = std::current_exception();
trace->elapsed = fc::time_point::now() - start;
......@@ -500,7 +496,7 @@ struct controller_impl {
// Only soft or hard failure logic below:
if( gto.sender != account_name() ) {
if( gto.sender != account_name() && !failure_is_subjective(*trace->except)) {
// Attempt error handling for the generated transaction.
edump((trace->except->to_detail_string()));
auto error_trace = apply_onerror( gto, deadline, trace->cpu_usage, start );
......@@ -508,11 +504,11 @@ struct controller_impl {
trace = error_trace;
if( !trace->except_ptr ) {
undo_session.squash();
return;
return trace;
}
}
// Only hard failure logic below:
// Only hard failure OR subjective failure logic below:
trace->cpu_usage = ((trace->cpu_usage + 1023)/1024)*1024; // Round up cpu_usage to nearest multiple of 1024
trace->cpu_usage = std::min(trace->cpu_usage, max_cpu);
......@@ -520,14 +516,17 @@ struct controller_impl {
block_timestamp_type(self.pending_block_time()).slot ); // Should never fail
trace->elapsed = fc::time_point::now() - start;
trace->receipt = push_receipt( gto.trx_id, transaction_receipt::hard_fail, trace->cpu_usage, 0 );
remove_scheduled_transaction( gto );
emit( self.applied_transaction, trace );
if (failure_is_subjective(*trace->except)) {
// this is a subjective failure, don't remove the retained state so it can be
// retried at a later time and don't include any artifact of the transaction in the pending block
remove_retained_state.cancel();
} else {
trace->receipt = push_receipt(gto.trx_id, transaction_receipt::hard_fail, trace->cpu_usage, 0);
emit( self.applied_transaction, trace );
undo_session.squash();
}
undo_session.squash();
return trace;
} FC_CAPTURE_AND_RETHROW() } /// push_scheduled_transaction
......@@ -549,16 +548,6 @@ struct controller_impl {
return r;
}
bool push_next_unapplied_transaction( fc::time_point deadline ) {
auto itr = unapplied_transactions.begin();
if( itr == unapplied_transactions.end() )
return false;
// Intentionally copy transaction_metadata_ptr because it will be removed from unapplied_transactions and make the const& dangling.
push_transaction( transaction_metadata_ptr(itr->second), deadline );
return true;
}
void transaction_trace_notify( const transaction_metadata_ptr& trx, const transaction_trace_ptr& trace ) {
if( trx->on_result ) {
(trx->on_result)(trace);
......@@ -576,79 +565,80 @@ struct controller_impl {
* determine whether to execute it now or to delay it. Lastly it inserts a transaction receipt into
* the pending block.
*/
void push_transaction( const transaction_metadata_ptr& trx,
fc::time_point deadline = fc::time_point::maximum(),
bool implicit = false )
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
fc::time_point deadline,
bool implicit = false )
{
transaction_trace_ptr trace;
try {
if( deadline == fc::time_point() && !implicit ) {
unapplied_transactions[trx->signed_id] = trx;
return;
}
FC_ASSERT(deadline != fc::time_point(), "deadline cannot be uninitialized");
auto start = fc::time_point::now();
transaction_context trx_context( self, trx->trx, trx->id);
trace = trx_context.trace;
transaction_trace_ptr trace;
try {
if( implicit ) {
trx_context.init_for_implicit_trx( deadline );
} else {
unapplied_transactions.erase( trx->signed_id );
trx_context.init_for_input_trx( deadline,
trx->packed_trx.get_unprunable_size(),
trx->packed_trx.get_prunable_size(),
trx->trx.signatures.size() );
}
trx_context.delay = fc::seconds(trx->trx.delay_sec);
if( !implicit ) {
authorization.check_authorization(
trx->trx.actions,
trx->recover_keys(),
{},
trx_context.delay,
std::bind(&transaction_context::add_cpu_usage_and_check_time, &trx_context, std::placeholders::_1),
false
);
}
auto start = fc::time_point::now();
transaction_context trx_context(self, trx->trx, trx->id);
trace = trx_context.trace;
try {
if (implicit) {
trx_context.init_for_implicit_trx(deadline);
} else {
trx_context.init_for_input_trx(deadline,
trx->packed_trx.get_unprunable_size(),
trx->packed_trx.get_prunable_size(),
trx->trx.signatures.size());
}
trx_context.exec();
trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
trace->elapsed = fc::time_point::now() - start;
trx_context.delay = fc::seconds(trx->trx.delay_sec);
if (!implicit) {
authorization.check_authorization(
trx->trx.actions,
trx->recover_keys(),
{},
trx_context.delay,
std::bind(&transaction_context::add_cpu_usage_and_check_time, &trx_context,
std::placeholders::_1),
false
);
}
auto restore = make_block_restore_point();
trx_context.exec();
trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
trace->elapsed = fc::time_point::now() - start;
auto restore = make_block_restore_point();
if (!implicit) {
transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0))
? transaction_receipt::executed
: transaction_receipt::delayed;
trace->receipt = push_receipt(trx->packed_trx, s, trace->cpu_usage, trace->net_usage);
pending->_pending_block_state->trxs.emplace_back(trx);
unapplied_transactions.erase( trx->signed_id );
} else {
transaction_receipt_header r;
r.status = transaction_receipt::executed;
r.kcpu_usage = trace->cpu_usage / 1024;
r.net_usage_words = trace->net_usage / 8;
trace->receipt = r;
}
if( !implicit ) {
transaction_receipt::status_enum s = ( trx_context.delay == fc::seconds(0) )
? transaction_receipt::executed
: transaction_receipt::delayed;
trace->receipt = push_receipt( trx->packed_trx, s, trace->cpu_usage, trace->net_usage );
pending->_pending_block_state->trxs.emplace_back(trx);
} else {
transaction_receipt_header r;
r.status = transaction_receipt::executed;
r.kcpu_usage = trace->cpu_usage / 1024;
r.net_usage_words = trace->net_usage / 8;
trace->receipt = r;
}
fc::move_append(pending->_actions, move(trx_context.executed));
fc::move_append( pending->_actions, move(trx_context.executed) );
transaction_trace_notify(trx, trace);
transaction_trace_notify( trx, trace );
emit(self.applied_transaction, trace);
emit( self.applied_transaction, trace );
trx_context.squash();
restore.cancel();
return trace;
} catch (const fc::exception& e) {
trace->except = e;
trace->except_ptr = std::current_exception();
}
trx_context.squash();
restore.cancel();
return;
} catch( const fc::exception& e ) {
trace->except = e;
trace->except_ptr = std::current_exception();
}
transaction_trace_notify( trx, trace );
} FC_CAPTURE_AND_RETHROW((trace)) } /// push_transaction
transaction_trace_notify(trx, trace);
return trace;
} FC_CAPTURE_AND_RETHROW((trace))
} /// push_transaction
void start_block( block_timestamp_type when, uint16_t confirm_block_count ) {
......@@ -716,10 +706,10 @@ struct controller_impl {
if( receipt.trx.contains<packed_transaction>() ) {
auto& pt = receipt.trx.get<packed_transaction>();
auto mtrx = std::make_shared<transaction_metadata>(pt);
push_transaction( mtrx );
push_transaction( mtrx, fc::time_point::maximum() );
}
else if( receipt.trx.contains<transaction_id_type>() ) {
self.push_scheduled_transaction( receipt.trx.get<transaction_id_type>() );
self.push_scheduled_transaction( receipt.trx.get<transaction_id_type>(), fc::time_point::maximum() );
}
}
......@@ -1029,6 +1019,8 @@ void controller::startup() {
chainbase::database& controller::db()const { return my->db; }
fork_database& controller::fork_db()const { return my->fork_db; }
void controller::start_block( block_timestamp_type when, uint16_t confirm_block_count ) {
my->start_block(when, confirm_block_count);
......@@ -1059,40 +1051,15 @@ void controller::push_confirmation( const header_confirmation& c ) {
my->push_confirmation( c );
}
void controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline ) {
my->push_transaction(trx, deadline);
transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline ) {
return my->push_transaction(trx, deadline);
}
bool controller::push_next_unapplied_transaction( fc::time_point deadline ) {
return my->push_next_unapplied_transaction( deadline );
}
transaction_trace_ptr controller::sync_push( const transaction_metadata_ptr& trx, time_point deadline ) {
auto start = fc::time_point::now();
try {
FC_ASSERT( deadline != fc::time_point() );
transaction_trace_ptr trace;
trx->on_result = [&]( const transaction_trace_ptr& t ){ trace = t; };
my->push_transaction( trx, deadline );
return trace;
} FC_CAPTURE_AND_RETHROW( (fc::time_point::now()-start)(deadline) )
}
bool controller::push_next_scheduled_transaction( fc::time_point deadline ) {
const auto& idx = db().get_index<generated_transaction_multi_index,by_delay>();
auto itr = idx.begin();
if( itr != idx.end() && itr->delay_until <= pending_block_time() ) {
my->push_scheduled_transaction( *itr, deadline );
return true;
}
return false;
}
void controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline ) {
transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline ) {
const auto& idx = db().get_index<generated_transaction_multi_index,by_trx_id>();
auto itr = idx.find( trxid );
FC_ASSERT( itr != idx.end(), "unknown transaction" );
my->push_scheduled_transaction( *itr, deadline );
return my->push_scheduled_transaction( *itr, deadline );
}
uint32_t controller::head_block_num()const {
......@@ -1298,8 +1265,31 @@ const account_object& controller::get_account( account_name name )const
return my->db.get<account_object, by_name>(name);
} FC_CAPTURE_AND_RETHROW( (name) ) }
const map<digest_type, transaction_metadata_ptr>& controller::unapplied_transactions()const {
return my->unapplied_transactions;
vector<transaction_metadata_ptr> controller::get_unapplied_transactions() const {
vector<transaction_metadata_ptr> result;
result.reserve(my->unapplied_transactions.size());
for ( const auto& entry: my->unapplied_transactions ) {
result.emplace_back(entry.second);
}
return result;
}
void controller::drop_unapplied_transaction(const transaction_metadata_ptr& trx) {
my->unapplied_transactions.erase(trx->signed_id);
}
vector<transaction_id_type> controller::get_scheduled_transactions() const {
const auto& idx = db().get_index<generated_transaction_multi_index,by_delay>();
vector<transaction_id_type> result;
result.reserve(idx.size());
auto itr = idx.begin();
while( itr != idx.end() && itr->delay_until <= pending_block_time() ) {
result.emplace_back(itr->trx_id);
++itr;
}
return result;
}
void controller::validate_referenced_accounts( const transaction& trx )const {
......
......@@ -242,11 +242,7 @@ void apply_eosio_updateauth(apply_context& context) {
int64_t old_size = (int64_t)(config::billable_size_v<permission_object> + permission->auth.get_billable_size());
db.modify(*permission, [&update, &parent_id, &context](permission_object& po) {
po.auth = update.auth;
po.parent = parent_id;
po.last_updated = context.control.pending_block_time();
});
authorization.modify_permission( *permission, update.auth );
int64_t new_size = (int64_t)(config::billable_size_v<permission_object> + permission->auth.get_billable_size());
......@@ -269,17 +265,10 @@ void apply_eosio_deleteauth(apply_context& context) {
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.control.get_authorization_manager();
auto& authorization = context.control.get_mutable_authorization_manager();
auto& db = context.db;
const auto& permission = authorization.get_permission({remove.account, remove.permission});
{ // Check for children
const auto& index = db.get_index<permission_index, by_parent>();
auto range = index.equal_range(permission.id);
EOS_ASSERT(range.first == range.second, action_validate_exception,
"Cannot delete an authority which has children. Delete the children first");
}
{ // Check for links to this permission
const auto& index = db.get_index<permission_link_index, by_permission_name>();
......@@ -288,11 +277,12 @@ void apply_eosio_deleteauth(apply_context& context) {
"Cannot delete a linked authority. Unlink the authority first");
}
context.trx_context.add_ram_usage(
permission.owner,
-(int64_t)(config::billable_size_v<permission_object> + permission.auth.get_billable_size())
);
db.remove(permission);
const auto& permission = authorization.get_permission({remove.account, remove.permission});
int64_t old_size = config::billable_size_v<permission_object> + permission.auth.get_billable_size();
authorization.remove_permission( permission );
context.trx_context.add_ram_usage( remove.account, -old_size );
context.checktime( 3000 );
}
......
......@@ -128,6 +128,13 @@ struct shared_authority {
}
};
namespace config {
template<>
struct billable_size<shared_authority> {
static const uint64_t value = (3 * config::fixed_overhead_shared_vector_ram_bytes) + 4;
};
}
/**
* Makes sure all keys are unique and sorted and all account permissions are unique and sorted and that authority can
* be satisfied
......
......@@ -42,6 +42,14 @@ namespace eosio { namespace chain {
time_point initial_creation_time = time_point()
);
void modify_permission( const permission_object& permission, const authority& auth );
void remove_permission( const permission_object& permission );
void update_permission_usage( const permission_object& permission );
fc::time_point get_permission_last_used( const permission_object& permission )const;
const permission_object* find_permission( const permission_level& level )const;
const permission_object& get_permission( const permission_level& level )const;
......
......@@ -91,6 +91,7 @@ const static uint32_t ram_usage_validation_overhead_per_account = 64
const static uint32_t fixed_net_overhead_of_packed_trx = 16; // TODO: is this reasonable?
const static uint32_t fixed_overhead_shared_vector_ram_bytes = 16; ///< overhead accounts for fixed portion of size of shared_vector field
const static uint32_t overhead_per_row_per_index_ram_bytes = 32; ///< overhead accounts for basic tracking structures in a row per index
const static uint32_t overhead_per_account_ram_bytes = 2*1024; ///< overhead accounts for basic account storage and pre-pays features like account recovery
const static uint32_t setcode_ram_bytes_multiplier = 10; ///< multiplier on contract size to account for multiple copies and cached compilation
......
......@@ -31,6 +31,8 @@ namespace eosio { namespace chain {
using resource_limits::resource_limits_manager;
using apply_handler = std::function<void(apply_context&)>;
class fork_database;
class controller {
public:
struct config {
......@@ -55,39 +57,41 @@ namespace eosio { namespace chain {
*/
void start_block( block_timestamp_type time = block_timestamp_type(), uint16_t confirm_block_count = 0 );
void abort_block();
void abort_block();
/**
* These transactions were previously pushed by have since been unapplied, recalling push_transaction
* with the transaction_metadata_ptr will remove them from this map whether it fails or succeeds.
* with the transaction_metadata_ptr will remove them from the source of this data IFF it succeeds.
*
* The caller is responsible for calling drop_unapplied_transaction on a failing transaction that
* they never intend to retry
*
* @return map of the hash of a signed transaction (with context free data) to a transaction metadata
* @return vector of transactions which have been unapplied
*/
const map<digest_type, transaction_metadata_ptr>& unapplied_transactions()const;
vector<transaction_metadata_ptr> get_unapplied_transactions() const;
void drop_unapplied_transaction(const transaction_metadata_ptr& trx);
/**
* These transaction IDs represent transactions available in the head chain state as scheduled
* or otherwise generated transactions.
*
* calling push_scheduled_transaction with these IDs will remove the associated transaction from
* the chain state IFF it succeeds or objectively fails
*
* @return
*/
void push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline );
bool push_next_unapplied_transaction( fc::time_point deadline );
transaction_trace_ptr sync_push( const transaction_metadata_ptr& trx, fc::time_point deadline );
vector<transaction_id_type> get_scheduled_transactions() const;
/**
* Attempt to execute a specific transaction in our deferred trx database
*
*/
void push_scheduled_transaction( const transaction_id_type& scheduled,
fc::time_point deadline = fc::time_point::maximum() );
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline );
/**
* Attempt to execute the oldest unexecuted deferred transaction
* Attempt to execute a specific transaction in our deferred trx database
*
* @return nullptr if there is nothing pending
*/
bool push_next_scheduled_transaction( fc::time_point deadline = fc::time_point::maximum() );
transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& scheduled, fc::time_point deadline );
void finalize_block();
void sign_block( const std::function<signature_type( const digest_type& )>& signer_callback );
......@@ -105,6 +109,8 @@ namespace eosio { namespace chain {
chainbase::database& db()const;
fork_database& fork_db()const;
const account_object& get_account( account_name n )const;
const global_property_object& get_global_properties()const;
const dynamic_global_property_object& get_dynamic_global_properties()const;
......
......@@ -8,15 +8,33 @@
#include "multi_index_includes.hpp"
namespace eosio { namespace chain {
class permission_usage_object : public chainbase::object<permission_usage_object_type, permission_usage_object> {
OBJECT_CTOR(permission_usage_object)
id_type id;
time_point last_used; ///< when this permission was last used
};
struct by_account_permission;
using permission_usage_index = chainbase::shared_multi_index_container<
permission_usage_object,
indexed_by<
ordered_unique<tag<by_id>, member<permission_usage_object, permission_usage_object::id_type, &permission_usage_object::id>>
>
>;
class permission_object : public chainbase::object<permission_object_type, permission_object> {
OBJECT_CTOR(permission_object, (auth) )
id_type id;
account_name owner; ///< the account this permission belongs to
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
id_type id;
permission_usage_object::id_type usage_id;
id_type parent; ///< parent permission
account_name owner; ///< the account this permission belongs to
permission_name name; ///< human-readable name for the permission
time_point last_updated; ///< the last time this authority was updated
shared_authority auth; ///< authority required to execute this permission
/**
......@@ -80,35 +98,11 @@ namespace eosio { namespace chain {
>
>;
class permission_usage_object : public chainbase::object<permission_usage_object_type, permission_usage_object> {
OBJECT_CTOR(permission_usage_object)
id_type id;
account_name account; ///< the account this permission belongs to
permission_name permission; ///< human-readable name for the permission
time_point last_used; ///< when this permission was last used
};
struct by_account_permission;
using permission_usage_index = chainbase::shared_multi_index_container<
permission_usage_object,
indexed_by<
ordered_unique<tag<by_id>, member<permission_usage_object, permission_usage_object::id_type, &permission_usage_object::id>>,
ordered_unique<tag<by_account_permission>,
composite_key<permission_usage_object,
member<permission_usage_object, account_name, &permission_usage_object::account>,
member<permission_usage_object, permission_name, &permission_usage_object::permission>,
member<permission_usage_object, permission_usage_object::id_type, &permission_usage_object::id>
>
>
>
>;
namespace config {
template<>
struct billable_size<permission_object> {
static const uint64_t overhead = 6 * overhead_per_row_per_index_ram_bytes; ///< 6 indices 2x internal ID, parent, owner, name, name_usage
static const uint64_t value = 80 + overhead; ///< fixed field size + overhead
struct billable_size<permission_object> { // Also counts memory usage of the associated permission_usage_object
static const uint64_t overhead = 5 * overhead_per_row_per_index_ram_bytes; ///< 5 indices 2x internal ID, parent, owner, name
static const uint64_t value = (config::billable_size_v<shared_authority> + 64) + overhead; ///< fixed field size + overhead
};
}
} } // eosio::chain
......@@ -117,7 +111,7 @@ CHAINBASE_SET_INDEX_TYPE(eosio::chain::permission_object, eosio::chain::permissi
CHAINBASE_SET_INDEX_TYPE(eosio::chain::permission_usage_object, eosio::chain::permission_usage_index)
FC_REFLECT(chainbase::oid<eosio::chain::permission_object>, (_id))
FC_REFLECT(eosio::chain::permission_object, (id)(owner)(parent)(name)(auth)(last_updated))
FC_REFLECT(eosio::chain::permission_object, (id)(usage_id)(parent)(owner)(name)(last_updated)(auth))
FC_REFLECT(chainbase::oid<eosio::chain::permission_usage_object>, (_id))
FC_REFLECT(eosio::chain::permission_usage_object, (id)(account)(permission)(last_used))
FC_REFLECT(eosio::chain::permission_usage_object, (id)(last_used))
#include <eosio/chain/apply_context.hpp>
#include <eosio/chain/transaction_context.hpp>
#include <eosio/chain/authorization_manager.hpp>
#include <eosio/chain/exceptions.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/generated_transaction_object.hpp>
......@@ -213,6 +214,15 @@ namespace eosio { namespace chain {
void transaction_context::finalize() {
FC_ASSERT( is_initialized, "must first initialize" );
if( is_input ) {
auto& am = control.get_mutable_authorization_manager();
for( const auto& act : trx.actions ) {
for( const auto& auth : act.authorization ) {
am.update_permission_usage( am.get_permission(auth) );
}
}
}
add_cpu_usage( validate_ram_usage.size() * config::ram_usage_validation_overhead_per_account );
auto& rl = control.get_mutable_resource_limits_manager();
......
......@@ -823,15 +823,18 @@ class permission_api : public context_aware_api {
return false;
}
int64_t get_permission_last_used( account_name account, permission_name permission) {
return context.db.get<permission_usage_object, by_account_permission>(boost::make_tuple(account, permission)).last_used.time_since_epoch().count();
int64_t get_permission_last_used( account_name account, permission_name permission ) {
const auto& am = context.control.get_authorization_manager();
return am.get_permission_last_used( am.get_permission({account, permission}) ).time_since_epoch().count();
};
int64_t get_account_creation_date( account_name account ) {
return time_point(context.db.get<account_object, by_name>(account).creation_date).time_since_epoch().count();
int64_t get_account_creation_time( account_name account ) {
auto* acct = context.db.find<account_object, by_name>(account);
EOS_ASSERT( acct != nullptr, action_validate_exception,
"account '${account}' does not exist", ("account", account) );
return time_point(acct->creation_date).time_since_epoch().count();
}
private:
void unpack_provided_keys( flat_set<public_key_type>& keys, const char* pubkeys_data, size_t pubkeys_size ) {
keys.clear();
......@@ -1694,7 +1697,7 @@ REGISTER_INTRINSICS(permission_api,
(check_transaction_authorization, int(int, int, int, int, int, int) )
(check_permission_authorization, int(int64_t, int64_t, int, int, int, int, int64_t) )
(get_permission_last_used, int64_t(int64_t, int64_t) )
(get_account_creation_date, int64_t(int64_t) )
(get_account_creation_time, int64_t(int64_t) )
);
......
......@@ -119,10 +119,21 @@ namespace eosio { namespace testing {
}
if( !skip_pending_trxs ) {
//wlog( "pushing all input transactions in waiting queue" );
while( control->push_next_unapplied_transaction( fc::time_point::maximum() ) );
//wlog( "pushing all available deferred transactions" );
while( control->push_next_scheduled_transaction( fc::time_point::maximum() ) );
auto unapplied_trxs = control->get_unapplied_transactions();
for (const auto& trx : unapplied_trxs ) {
auto trace = control->push_transaction(trx, fc::time_point::maximum());
if(trace->except) {
trace->except->dynamic_rethrow_exception();
}
}
auto scheduled_trxs = control->get_scheduled_transactions();
for (const auto& trx : scheduled_trxs ) {
auto trace = control->push_scheduled_transaction(trx, fc::time_point::maximum());
if(trace->except) {
trace->except->dynamic_rethrow_exception();
}
}
}
......@@ -232,7 +243,7 @@ namespace eosio { namespace testing {
transaction_trace_ptr base_tester::push_transaction( packed_transaction& trx, uint32_t skip_flag, fc::time_point deadline ) { try {
if( !control->pending_block_state() )
_start_block(control->head_block_time() + fc::microseconds(config::block_interval_us));
auto r = control->sync_push( std::make_shared<transaction_metadata>(trx), deadline );
auto r = control->push_transaction( std::make_shared<transaction_metadata>(trx), deadline );
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except ) throw *r->except;
return r;
......@@ -249,7 +260,7 @@ namespace eosio { namespace testing {
c = packed_transaction::zlib;
}
auto r = control->sync_push( std::make_shared<transaction_metadata>(trx,c), deadline );
auto r = control->push_transaction( std::make_shared<transaction_metadata>(trx,c), deadline );
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except) throw *r->except;
return r;
......
......@@ -5,6 +5,7 @@
#include <eosio/producer_plugin/producer_plugin.hpp>
#include <eosio/chain/producer_object.hpp>
#include <eosio/chain/plugin_interface.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <fc/io/json.hpp>
#include <fc/smart_ref_impl.hpp>
......@@ -17,6 +18,19 @@
#include <algorithm>
#include <boost/range/adaptor/map.hpp>
#include <boost/function_output_iterator.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/ordered_index.hpp>
namespace bmi = boost::multi_index;
using bmi::indexed_by;
using bmi::ordered_non_unique;
using bmi::member;
using bmi::tag;
using bmi::hashed_unique;
using boost::multi_index_container;
using std::string;
using std::vector;
......@@ -28,6 +42,31 @@ static appbase::abstract_plugin& _producer_plugin = app().register_plugin<produc
using namespace eosio::chain;
using namespace eosio::chain::plugin_interface;
namespace {
bool failure_is_subjective(const fc::exception& e, bool deadline_is_subjective) {
auto code = e.code();
return (code == tx_soft_cpu_usage_exceeded::code_value) ||
(code == tx_soft_net_usage_exceeded::code_value) ||
(code == tx_deadline_exceeded::code_value && deadline_is_subjective);
}
}
struct blacklisted_transaction {
transaction_id_type trx_id;
fc::time_point expiry;
};
struct by_id;
struct by_expiry;
using blacklisted_transaction_index = multi_index_container<
blacklisted_transaction,
indexed_by<
hashed_unique<tag<by_id>, BOOST_MULTI_INDEX_MEMBER(blacklisted_transaction, transaction_id_type, trx_id)>,
ordered_non_unique<tag<by_expiry>, BOOST_MULTI_INDEX_MEMBER(blacklisted_transaction, fc::time_point, expiry)>
>
>;
class producer_plugin_impl {
public:
producer_plugin_impl(boost::asio::io_service& io)
......@@ -49,8 +88,7 @@ class producer_plugin_impl {
boost::asio::deadline_timer _timer;
std::map<chain::account_name, uint32_t> _producer_watermarks;
int32_t _max_deferred_transaction_time_ms;
int32_t _max_pending_transaction_time_ms;
int32_t _max_transaction_time_ms;
block_production_condition::block_production_condition_enum _prev_result = block_production_condition::produced;
uint32_t _prev_result_count = 0;
......@@ -69,6 +107,8 @@ class producer_plugin_impl {
incoming::methods::block_sync::method_type::handle _incoming_block_sync_provider;
incoming::methods::transaction_sync::method_type::handle _incoming_transaction_sync_provider;
blacklisted_transaction_index _blacklisted_transactions;
void on_block( const block_state_ptr& bsp ) {
if( bsp->header.timestamp <= _last_signed_block_time ) return;
if( bsp->header.timestamp <= _start_time ) return;
......@@ -164,13 +204,35 @@ class producer_plugin_impl {
}
transaction_trace_ptr on_incoming_transaction(const packed_transaction_ptr& trx) {
return publish_results_of(trx, _transaction_ack_channel, [&]{
chain::controller& chain = app().get_plugin<chain_plugin>().chain();
return chain.sync_push(std::make_shared<transaction_metadata>(*trx), fc::time_point::now() + fc::milliseconds(_max_pending_transaction_time_ms));
return publish_results_of(trx, _transaction_ack_channel, [&]() -> transaction_trace_ptr {
while (true) {
chain::controller& chain = app().get_plugin<chain_plugin>().chain();
auto block_time = chain.pending_block_state()->header.timestamp.to_time_point();
auto max_deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms);
auto deadline = std::min(block_time, max_deadline);
auto trace = chain.push_transaction(std::make_shared<transaction_metadata>(*trx), deadline);
// if we failed because the block was exhausted push the block out and try again
if (trace->except) {
if (failure_is_subjective(*trace->except, deadline == block_time)) {
block_production_loop();
} else {
trace->except->dynamic_rethrow_exception();
}
} else {
return trace;
}
}
});
}
bool start_block();
enum class start_block_result {
succeeded,
failed,
exhausted
};
start_block_result start_block();
};
void new_chain_banner(const eosio::chain::controller& db)
......@@ -213,10 +275,8 @@ void producer_plugin::set_program_options(
producer_options.add_options()
("enable-stale-production,e", boost::program_options::bool_switch()->notifier([this](bool e){my->_production_enabled = e;}), "Enable block production, even if the chain is stale.")
("max-pending-transaction-time", bpo::value<int32_t>()->default_value(30),
("max-transaction-time", bpo::value<int32_t>()->default_value(30),
"Limits the maximum time (in milliseconds) that is allowed a pushed transaction's code to execute before being considered invalid")
("max-deferred-transaction-time", bpo::value<int32_t>()->default_value(20),
"Limits the maximum time (in milliseconds) that is allowed a to push deferred transactions at the start of a block")
("required-participation", boost::program_options::value<uint32_t>()
->default_value(uint32_t(config::required_producer_participation/config::percent_1))
->notifier([this](uint32_t e) {
......@@ -279,8 +339,7 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_
}
}
my->_max_deferred_transaction_time_ms = options.at("max-deferred-transaction-time").as<int32_t>();
my->_max_pending_transaction_time_ms = options.at("max-pending-transaction-time").as<int32_t>();
my->_max_transaction_time_ms = options.at("max-transaction-time").as<int32_t>();
my->_incoming_block_subscription = app().get_channel<incoming::channels::block>().subscribe([this](const signed_block_ptr& block){
......@@ -336,7 +395,7 @@ void producer_plugin::plugin_shutdown() {
}
}
bool producer_plugin_impl::start_block() {
producer_plugin_impl::start_block_result producer_plugin_impl::start_block() {
chain::controller& chain = app().get_plugin<chain_plugin>().chain();
const auto& hbs = chain.head_block_state();
......@@ -380,39 +439,91 @@ bool producer_plugin_impl::start_block() {
} FC_LOG_AND_DROP();
if (chain.pending_block_state()) {
// TODO: BIG BAD WARNING, THIS WILL HAPPILY BLOW PAST DEADLINES BUT CONTROLLER IS NOT YET SAFE FOR DEADLINE USAGE
try {
while (chain.push_next_unapplied_transaction(fc::time_point::maximum()));
} FC_LOG_AND_DROP();
bool exhausted = false;
auto unapplied_trxs = chain.get_unapplied_transactions();
for (const auto& trx : unapplied_trxs) {
if (exhausted) {
break;
}
try {
while (chain.push_next_scheduled_transaction(fc::time_point::maximum()));
} FC_LOG_AND_DROP();
try {
auto deadline = std::min(block_time, fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms));
auto trace = chain.push_transaction(trx, deadline);
if (trace->except) {
if (failure_is_subjective(*trace->except, deadline == block_time)) {
exhausted = true;
} else {
// this failed our configured maximum transaction time, we don't want to replay it
chain.drop_unapplied_transaction(trx);
}
}
} FC_LOG_AND_DROP();
}
auto& blacklist_by_id = _blacklisted_transactions.get<by_id>();
auto& blacklist_by_expiry = _blacklisted_transactions.get<by_expiry>();
auto now = fc::time_point::now();
while(!blacklist_by_expiry.empty() && blacklist_by_expiry.begin()->expiry <= now) {
blacklist_by_expiry.erase(blacklist_by_expiry.begin());
}
auto scheduled_trxs = chain.get_scheduled_transactions();
for (const auto& trx : scheduled_trxs) {
if (exhausted) {
break;
}
return true;
if (blacklist_by_id.find(trx) != blacklist_by_id.end()) {
continue;
}
try {
auto deadline = std::min(block_time, fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms));
auto trace = chain.push_scheduled_transaction(trx, deadline);
if (trace->except) {
if (failure_is_subjective(*trace->except, deadline == block_time)) {
exhausted = true;
} else {
auto expiration = fc::time_point::now() + fc::seconds(chain.get_global_properties().configuration.deferred_trx_expiration_window);
// this failed our configured maximum transaction time, we don't want to replay it add it to a blacklist
_blacklisted_transactions.insert(blacklisted_transaction{trx, expiration});
}
}
} FC_LOG_AND_DROP();
}
return exhausted ? start_block_result::exhausted : start_block_result::succeeded;
}
return false;
return start_block_result::failed;
}
void producer_plugin_impl::schedule_production_loop() {
_timer.cancel();
if (start_block()) {
//_timer.async_wait(boost::bind(&producer_plugin_impl::block_production_loop, this));
auto result = start_block();
switch(result) {
case start_block_result::exhausted:
// immediately proceed to making the block
block_production_loop();
break;
case start_block_result::failed:
elog("Failed to start a pending block, will try again later");
// we failed to start a block, so try again later?
_timer.async_wait([&](const boost::system::error_code& ec) {
if (ec != boost::asio::error::operation_aborted) {
block_production_loop();
schedule_production_loop();
}
});
} else {
elog("Failed to start a pending block, will try again later");
// we failed to start a block, so try again later?
break;
case start_block_result::succeeded:
//_timer.async_wait(boost::bind(&producer_plugin_impl::block_production_loop, this));
_timer.async_wait([&](const boost::system::error_code& ec) {
if (ec != boost::asio::error::operation_aborted) {
schedule_production_loop();
block_production_loop();
}
});
break;
}
}
......
......@@ -76,7 +76,7 @@ struct test_api_action {
}
};
FC_REFLECT_TEMPLATE((uint64_t T), test_api_action<T>, BOOST_PP_SEQ_NIL);
FC_REFLECT_TEMPLATE((uint64_t T), test_api_action<T>, BOOST_PP_SEQ_NIL)
template<uint64_t NAME>
struct test_chain_action {
......@@ -89,7 +89,7 @@ struct test_chain_action {
}
};
FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action<T>, BOOST_PP_SEQ_NIL);
FC_REFLECT_TEMPLATE((uint64_t T), test_chain_action<T>, BOOST_PP_SEQ_NIL)
struct check_auth {
account_name account;
......@@ -97,7 +97,15 @@ struct check_auth {
vector<public_key_type> pubkeys;
};
FC_REFLECT(check_auth, (account)(permission)(pubkeys) );
FC_REFLECT(check_auth, (account)(permission)(pubkeys) )
struct test_permission_last_used_action {
account_name account;
permission_name permission;
fc::time_point last_used_time;
};
FC_REFLECT( test_permission_last_used_action, (account)(permission)(last_used_time) )
constexpr uint64_t TEST_METHOD(const char* CLASS, const char *METHOD) {
return ( (uint64_t(DJBH(CLASS))<<32) | uint32_t(DJBH(METHOD)) );
......@@ -774,11 +782,9 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
return expect_assert_message(e, "test_action::assert_false");
}
);
control->push_next_scheduled_transaction();
// test send_transaction
CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction", {});
control->push_next_scheduled_transaction();
// test send_transaction_empty
BOOST_CHECK_EXCEPTION(CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_empty", {}), tx_no_auths,
......@@ -786,7 +792,6 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
return expect_assert_message(e, "transaction must have at least one authorization");
}
);
control->push_next_scheduled_transaction();
{
produce_blocks(10);
......@@ -795,7 +800,6 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
// test error handling on deferred transaction failure
CALL_TEST_FUNCTION(*this, "test_transaction", "send_transaction_trigger_error_handler", {});
control->push_next_scheduled_transaction();
BOOST_CHECK(trace);
BOOST_CHECK_EQUAL(trace->receipt->status, transaction_receipt::soft_fail);
......@@ -803,7 +807,6 @@ BOOST_FIXTURE_TEST_CASE(transaction_tests, TESTER) { try {
// test test_transaction_size
CALL_TEST_FUNCTION(*this, "test_transaction", "test_transaction_size", fc::raw::pack(54) ); // TODO: Need a better way to test this.
control->push_next_scheduled_transaction();
// test test_read_transaction
// this is a bit rough, but I couldn't figure out a better way to compare the hashes
......@@ -838,13 +841,10 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
transaction_trace_ptr trace;
auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t->scheduled) { trace = t; } } );
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {} );
//check that it doesn't get executed immediately
control->push_next_scheduled_transaction();
BOOST_CHECK(!trace);
produce_block( fc::seconds(2) );
//check that it gets executed afterwards
control->push_next_scheduled_transaction();
BOOST_CHECK(trace);
//confirm printed message
......@@ -862,11 +862,14 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; ++count; } } );
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {});
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {});
produce_block( fc::seconds(2) );
produce_blocks( 3 );
//check that only one deferred transaction executed
control->push_next_scheduled_transaction();
control->push_next_scheduled_transaction();
auto dtrxs = control->get_scheduled_transactions();
BOOST_CHECK_EQUAL(dtrxs.size(), 1);
for (const auto& trx: dtrxs) {
control->push_scheduled_transaction(trx, fc::time_point::maximum());
}
BOOST_CHECK_EQUAL(1, count);
BOOST_CHECK(trace);
BOOST_CHECK_EQUAL( 1, trace->action_traces.size() );
......@@ -882,7 +885,6 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {});
CALL_TEST_FUNCTION(*this, "test_transaction", "cancel_deferred_transaction", {});
produce_block( fc::seconds(2) );
control->push_next_scheduled_transaction();
BOOST_CHECK(!trace);
c.disconnect();
}
......@@ -1742,4 +1744,112 @@ BOOST_FIXTURE_TEST_CASE(new_api_feature_tests, TESTER) { try {
BOOST_REQUIRE_EQUAL( validate(), true );
} FC_LOG_AND_RETHROW() }
/*************************************************************************************
* permission_usage_tests test cases
*************************************************************************************/
BOOST_FIXTURE_TEST_CASE(permission_usage_tests, TESTER) { try {
produce_block();
create_accounts( {N(testapi), N(alice), N(bob)} );
produce_block();
set_code(N(testapi), test_api_wast);
produce_block();
push_reqauth( N(alice), {{N(alice), config::active_name}}, {get_private_key(N(alice), "active")} );
CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(alice), config::active_name,
control->pending_block_time()
})
);
// Fails because the last used time is updated after the transaction executes.
BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(testapi), config::active_name,
control->head_block_time() + fc::milliseconds(config::block_interval_ms)
})
), fc::assert_exception );
produce_blocks(5);
set_authority( N(bob), N(perm1), authority( get_private_key(N(bob), "perm1").get_public_key() ) );
push_action(config::system_account_name, linkauth::get_name(), N(bob), fc::mutable_variant_object()
("account", "bob")
("code", "eosio")
("type", "reqauth")
("requirement", "perm1")
);
auto permission_creation_time = control->pending_block_time();
produce_blocks(5);
CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(bob), N(perm1),
permission_creation_time
})
);
produce_blocks(5);
push_reqauth( N(bob), {{N(bob), N(perm1)}}, {get_private_key(N(bob), "perm1")} );
auto perm1_last_used_time = control->pending_block_time();
CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(bob), config::active_name,
permission_creation_time
})
);
BOOST_CHECK_THROW( CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(bob), N(perm1),
permission_creation_time
})
), fc::assert_exception );
CALL_TEST_FUNCTION( *this, "test_permission", "test_permission_last_used",
fc::raw::pack(test_permission_last_used_action{
N(bob), N(perm1),
perm1_last_used_time
})
);
produce_block();
BOOST_REQUIRE_EQUAL( validate(), true );
} FC_LOG_AND_RETHROW() }
/*************************************************************************************
* account_creation_time_tests test cases
*************************************************************************************/
BOOST_FIXTURE_TEST_CASE(account_creation_time_tests, TESTER) { try {
produce_block();
create_account( N(testapi) );
produce_block();
set_code(N(testapi), test_api_wast);
produce_block();
create_account( N(alice) );
auto alice_creation_time = control->pending_block_time();
produce_blocks(10);
CALL_TEST_FUNCTION( *this, "test_permission", "test_account_creation_time",
fc::raw::pack(test_permission_last_used_action{
N(alice), config::active_name,
alice_creation_time
})
);
produce_block();
BOOST_REQUIRE_EQUAL( validate(), true );
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()
......@@ -72,7 +72,13 @@ BOOST_FIXTURE_TEST_CASE( delay_error_create_account, validating_tester) { try {
auto trace = push_transaction( trx );
edump((*trace));
produce_blocks(8);
produce_blocks(6);
auto scheduled_trxs = control->get_scheduled_transactions();
BOOST_REQUIRE_EQUAL(scheduled_trxs.size(), 1);
auto dtrace = control->push_scheduled_transaction(scheduled_trxs.front(), fc::time_point::maximum());
BOOST_REQUIRE_EQUAL(dtrace->except.valid(), true);
BOOST_REQUIRE_EQUAL(dtrace->except->code(), missing_auth_exception::code_value);
} FC_LOG_AND_RETHROW() }
......
......@@ -45,16 +45,15 @@ public:
issue(config::system_account_name, "1000000000.0000 EOS");
BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) );
// create_accounts_with_resources ( { N(inita), N(initb), N(voter1), N(voter2) }, asset::from_string("100000.0000 EOS") );
set_code( config::system_account_name, eosio_system_wast );
set_abi( config::system_account_name, eosio_system_abi );
produce_blocks();
create_account_with_resources( N(alice), N(eosio), asset::from_string("1.0000 EOS"), false );//{ N(alice), N(bob), N(carol) } );
create_account_with_resources( N(bob), N(eosio), asset::from_string("0.4500 EOS"), false );//{ N(alice), N(bob), N(carol) } );
create_account_with_resources( N(carol), N(eosio), asset::from_string("1.0000 EOS"), false );//{ N(alice), N(bob), N(carol) } );
create_account_with_resources( N(alice), N(eosio), asset::from_string("1.0000 EOS"), false );
create_account_with_resources( N(bob), N(eosio), asset::from_string("0.4500 EOS"), false );
create_account_with_resources( N(carol), N(eosio), asset::from_string("1.0000 EOS"), false );
BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) );
// eosio pays it self for these...
......@@ -62,18 +61,20 @@ public:
produce_blocks();
const auto& accnt = control->db().get<account_object,by_name>( config::system_account_name );
abi_def abi;
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
abi_ser.set_abi(abi);
/*
const global_property_object &gpo = control->get_global_properties();
FC_ASSERT(0 < gpo.active_producers.producers.size(), "No producers");
producer_name = (string)gpo.active_producers.producers.front().producer_name;
*/
{
const auto& accnt = control->db().get<account_object,by_name>( config::system_account_name );
abi_def abi;
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
abi_ser.set_abi(abi);
}
{
const auto& accnt = control->db().get<account_object,by_name>( N(eosio.token) );
abi_def abi;
BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
token_abi_ser.set_abi(abi);
}
}
void create_accounts_with_resources( vector<account_name> accounts, account_name creator = N(eosio) ) {
for( auto a : accounts ) {
create_account_with_resources( a, creator );
......@@ -113,11 +114,10 @@ public:
set_transaction_headers(trx);
trx.sign( get_private_key( creator, "active" ), chain_id_type() );
return push_transaction( trx );
}
transaction_trace_ptr create_account_with_resources( account_name a, account_name creator, asset ramfunds, bool multisig,
asset net = asset::from_string("10.0000 EOS"), asset cpu = asset::from_string("10.0000 EOS") ) {
transaction_trace_ptr create_account_with_resources( account_name a, account_name creator, asset ramfunds, bool multisig,
asset net = asset::from_string("10.0000 EOS"), asset cpu = asset::from_string("10.0000 EOS") ) {
signed_transaction trx;
set_transaction_headers(trx);
......@@ -158,6 +158,53 @@ public:
return push_transaction( trx );
}
transaction_trace_ptr setup_producer_accounts() {
std::vector<account_name> accounts;
accounts.reserve( 'z' - 'a' + 1);
std::string root( "init" );
for ( char c = 'a'; c <= 'z' ; ++c ) {
accounts.emplace_back( root + std::string(1, c) );
}
account_name creator(N(eosio));
signed_transaction trx;
set_transaction_headers(trx);
asset cpu = asset::from_string("1000000.0000 EOS");
asset net = asset::from_string("1000000.0000 EOS");
asset ram = asset::from_string("1.0000 EOS");
for (const auto& a: accounts) {
authority owner_auth( get_public_key( a, "owner" ) );
trx.actions.emplace_back( vector<permission_level>{{creator,config::active_name}},
newaccount{
.creator = creator,
.name = a,
.owner = owner_auth,
.active = authority( get_public_key( a, "active" ) )
});
trx.actions.emplace_back( get_action( N(eosio), N(buyram), vector<permission_level>{ {creator, config::active_name} },
mvo()
("payer", creator)
("receiver", a)
("quant", ram) )
);
trx.actions.emplace_back( get_action( N(eosio), N(delegatebw), vector<permission_level>{ {creator, config::active_name} },
mvo()
("from", creator)
("receiver", a)
("stake_net_quantity", net)
("stake_cpu_quantity", cpu )
)
);
}
set_transaction_headers(trx);
trx.sign( get_private_key( creator, "active" ), chain_id_type() );
return push_transaction( trx );
}
action_result buyram( const account_name& payer, account_name receiver, string eosin ) {
return push_action( payer, N(buyram), mvo()( "payer",payer)("receiver",receiver)("quant",eosin) );
}
......@@ -320,7 +367,26 @@ public:
return stake2votes( asset::from_string(s) );
}
fc::variant get_stats( const string& symbolname ) {
auto symb = eosio::chain::symbol::from_string(symbolname);
auto symbol_code = symb.to_symbol_code().value;
vector<char> data = get_row_by_account( N(eosio.token), symbol_code, N(stat), symbol_code );
return data.empty() ? fc::variant() : token_abi_ser.binary_to_variant( "currency_stats", data );
}
asset get_token_supply() {
return get_stats("4,EOS")["supply"].as<asset>();
}
fc::variant get_global_state() {
vector<char> data = get_row_by_account( N(eosio), N(eosio), N(global), N(global) );
if (data.empty()) std::cout << "\nData is empty\n" << std::endl;
return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state", data );
}
abi_serializer abi_ser;
abi_serializer token_abi_ser;
};
fc::mutable_variant_object voter( account_name acct ) {
......@@ -1193,18 +1259,12 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester) try {
create_account_with_resources( N(vota), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset );
create_account_with_resources( N(votb), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset );
issue( "vota", "400000000.0000 EOS", config::system_account_name);
produce_block();
// 1 block produced
BOOST_REQUIRE_EQUAL(success(), regproducer(N(inita)));
auto prod = get_producer_info( N(inita) );
BOOST_REQUIRE_EQUAL("inita", prod["owner"].as_string());
BOOST_REQUIRE_EQUAL(0, prod["total_votes"].as_double());
issue( "vota", "400000000.0000 EOS", config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("vota", "100000000.0000 EOS", "100000000.0000 EOS"));
BOOST_REQUIRE_EQUAL(success(), push_action(N(vota), N(voteproducer), mvo()
......@@ -1214,22 +1274,353 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester) try {
)
);
produce_blocks(200);
prod = get_producer_info("inita");
// inita is the only active producer
// produce enough blocks so new schedule kicks in and inita produces some blocks
{
produce_blocks(50);
const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64();
const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as<asset>();
const asset initial_savings = initial_global_state["savings"].as<asset>();
prod = get_producer_info("inita");
const uint32_t produced_blocks = prod["produced_blocks"].as<uint32_t>();
BOOST_REQUIRE(1 < produced_blocks);
BOOST_REQUIRE_EQUAL(0, prod["last_claim_time"].as<uint64_t>());
const asset initial_supply = get_token_supply();
const asset initial_balance = get_balance(N(inita));
BOOST_REQUIRE_EQUAL(success(), push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
const auto global_state = get_global_state();
const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64();
const asset pervote_bucket = global_state["pervote_bucket"].as<asset>();
const asset savings = global_state["savings"].as<asset>();
prod = get_producer_info("inita");
BOOST_REQUIRE_EQUAL(1, prod["produced_blocks"].as<uint32_t>());
const asset supply = get_token_supply();
const asset balance = get_balance(N(inita));
BOOST_REQUIRE_EQUAL(claim_time, prod["last_claim_time"].as<uint64_t>());
const int32_t secs_between_fills = static_cast<int32_t>((claim_time - initial_claim_time) / 1000000);
BOOST_REQUIRE_EQUAL(0, initial_pervote_bucket.amount);
BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * ((4.879-1.0)/100.0)) / (52*7*24*3600) ),
savings.amount - initial_savings.amount);
int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * (0.25/100.0) / (52*7*24*3600*2) );
int64_t from_pervote_bucket = int64_t( initial_supply.amount * secs_between_fills * (0.75/100.0) / (52*7*24*3600) );
if (from_pervote_bucket >= 100 * 10000) {
BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(0, pervote_bucket.amount);
} else {
BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(from_pervote_bucket, pervote_bucket.amount);
}
const int64_t max_supply_growth = int64_t( (initial_supply.amount * secs_between_fills * (4.879/100.0)) / (52*7*24*3600) );
BOOST_REQUIRE(max_supply_growth >= supply.amount - initial_supply.amount);
}
{
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
}
BOOST_REQUIRE(1 < prod["produced_blocks"].as<uint32_t>());
BOOST_REQUIRE_EQUAL(success(), push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
// inita waits for 23 hours and 55 minutes, can't claim rewards yet
{
produce_block(fc::seconds(23 * 3600 + 55 * 60));
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
}
prod = get_producer_info("inita");
BOOST_REQUIRE_EQUAL(1, prod["produced_blocks"].as<uint32_t>());
// wait 5 more minutes, inita can now claim rewards again
{
produce_block(fc::seconds(5 * 60));
const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64();
const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as<asset>();
const asset initial_savings = initial_global_state["savings"].as<asset>();
prod = get_producer_info("inita");
const uint32_t produced_blocks = prod["produced_blocks"].as<uint32_t>();
BOOST_REQUIRE(1 < produced_blocks);
BOOST_REQUIRE(0 < prod["last_claim_time"].as<uint64_t>());
const asset initial_supply = get_token_supply();
const asset initial_balance = get_balance(N(inita));
BOOST_REQUIRE_EQUAL(success(),
push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
const auto global_state = get_global_state();
const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64();
const asset pervote_bucket = global_state["pervote_bucket"].as<asset>();
const asset savings = global_state["savings"].as<asset>();
prod = get_producer_info("inita");
BOOST_REQUIRE_EQUAL(1, prod["produced_blocks"].as<uint32_t>());
const asset supply = get_token_supply();
const asset balance = get_balance(N(inita));
BOOST_REQUIRE_EQUAL(claim_time, prod["last_claim_time"].as<uint64_t>());
const int32_t secs_between_fills = static_cast<int32_t>((claim_time - initial_claim_time) / 1000000);
BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * ((4.879-1.0)/100.0)) / (52*7*24*3600) ),
savings.amount - initial_savings.amount);
int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * (0.25/100.0) / (52*7*24*3600*2) );
int64_t from_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * (0.75/100.0) / (52*7*24*3600) );
if (from_pervote_bucket >= 100 * 10000) {
BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(0, pervote_bucket.amount);
} else {
BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(from_pervote_bucket, pervote_bucket.amount);
}
const int64_t max_supply_growth = int64_t( (initial_supply.amount * secs_between_fills * (4.879/100.0)) / (52*7*24*3600) );
BOOST_REQUIRE(max_supply_growth >= supply.amount - initial_supply.amount);
}
// initb tries to claim rewards but he's not on the list
{
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: account name is not in producer list"),
push_action(N(initb), N(claimrewards), mvo()("owner", "initb")));
}
} FC_LOG_AND_RETHROW()
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(N(inita), N(claimrewards), mvo()("owner", "inita")));
BOOST_FIXTURE_TEST_CASE(multiple_producer_pay, eosio_system_tester) try {
produce_block(fc::seconds(1));
const auto tol = boost::test_tools::tolerance(0.0000000001);
} FC_LOG_AND_RETHROW()
const int64_t secs_per_year = 52 * 7 * 24 * 3600;
const int64_t blocks_per_year = 52 * 7* 24 * 3600 * 2;
const double cont_rate = 4.879/100.;
const double standby_rate = 0.750/100.;
const double block_rate = 0.250/100.;
const asset large_asset = asset::from_string("100000000.0000 EOS");
create_account_with_resources( N(vota), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset );
create_account_with_resources( N(votb), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset );
create_account_with_resources( N(votc), N(eosio), asset::from_string("1.0000 EOS"), false, large_asset, large_asset );
// create accounts {inita, initb, ..., initz} and register as producers
setup_producer_accounts();
std::vector<account_name> producer_names;
{
producer_names.reserve( 'z' - 'a' + 1);
const std::string root( "init" );
for ( char c = 'a'; c <= 'z' ; ++c ) {
producer_names.emplace_back(root + std::string(1, c));
regproducer( producer_names.back() );
}
BOOST_REQUIRE_EQUAL(0, get_producer_info( N(inita) )["total_votes"].as<double>());
BOOST_REQUIRE_EQUAL(0, get_producer_info( N(initz) )["total_votes"].as<double>());
}
{
issue( "vota", "100000000.0000 EOS", config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("vota", "30000000.0000 EOS", "30000000.0000 EOS"));
issue( "votb", "100000000.0000 EOS", config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("votb", "30000000.0000 EOS", "30000000.0000 EOS"));
issue( "votc", "100000000.0000 EOS", config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("votc", "30000000.0000 EOS", "30000000.0000 EOS"));
}
// vota votes for inita ... initj
// votb votes for inita ... initu
// votc votes for inita ... initz
{
BOOST_REQUIRE_EQUAL(success(), push_action(N(vota), N(voteproducer), mvo()
("voter", "vota")
("proxy", name(0).to_string())
("producers", vector<account_name>(producer_names.begin(), producer_names.begin()+10))
)
);
BOOST_REQUIRE_EQUAL(success(), push_action(N(votb), N(voteproducer), mvo()
("voter", "votb")
("proxy", name(0).to_string())
("producers", vector<account_name>(producer_names.begin(), producer_names.begin()+21))
)
);
BOOST_REQUIRE_EQUAL(success(), push_action(N(votc), N(voteproducer), mvo()
("voter", "votc")
("proxy", name(0).to_string())
("producers", vector<account_name>(producer_names.begin(), producer_names.end()))
)
);
}
{
auto proda = get_producer_info( N(inita) );
auto prodj = get_producer_info( N(initj) );
auto prodk = get_producer_info( N(initk) );
auto produ = get_producer_info( N(initu) );
auto prodv = get_producer_info( N(initv) );
auto prodz = get_producer_info( N(initz) );
BOOST_REQUIRE (0 == proda["produced_blocks"].as<uint32_t>() && 0 == prodz["produced_blocks"].as<uint32_t>());
BOOST_REQUIRE (0 == proda["last_claim_time"].as<uint64_t>() && 0 == prodz["last_claim_time"].as<uint64_t>());
// check vote ratios
BOOST_REQUIRE ( 0 < proda["total_votes"].as<double>() && 0 < prodz["total_votes"].as<double>() );
BOOST_TEST( proda["total_votes"].as<double>() == prodj["total_votes"].as<double>(), tol );
BOOST_TEST( prodk["total_votes"].as<double>() == produ["total_votes"].as<double>(), tol );
BOOST_TEST( prodv["total_votes"].as<double>() == prodz["total_votes"].as<double>(), tol );
BOOST_TEST( 2 * proda["total_votes"].as<double>() == 3 * produ["total_votes"].as<double>(), tol );
BOOST_TEST( proda["total_votes"].as<double>() == 3 * prodz["total_votes"].as<double>(), tol );
}
// give a chance for everyone to produce blocks
{
produce_blocks(21 * 12 + 20);
bool all_21_produced = true;
for (uint32_t i = 0; i < 21; ++i) {
if (0 == get_producer_info(producer_names[i])["produced_blocks"].as<uint32_t>()) {
all_21_produced= false;
}
}
bool rest_didnt_produce = true;
for (uint32_t i = 21; i < producer_names.size(); ++i) {
if (0 < get_producer_info(producer_names[i])["produced_blocks"].as<uint32_t>()) {
rest_didnt_produce = false;
}
}
BOOST_REQUIRE(all_21_produced && rest_didnt_produce);
}
std::vector<double> vote_shares(producer_names.size());
{
double total_votes = 0;
for (uint32_t i = 0; i < producer_names.size(); ++i) {
vote_shares[i] = get_producer_info(producer_names[i])["total_votes"].as<double>();
total_votes += vote_shares[i];
}
std::for_each(vote_shares.begin(), vote_shares.end(), [total_votes](double& x) { x /= total_votes; });
BOOST_TEST(double(1) == std::accumulate(vote_shares.begin(), vote_shares.end(), double(0)), tol);
BOOST_TEST(double(3./57.) == vote_shares[0], tol);
}
{
const uint32_t prod_index = 2;
const auto prod_name = producer_names[prod_index];
const auto produced_blocks = get_producer_info(prod_name)["produced_blocks"].as<uint32_t>();
const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64();
const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as<asset>();
const asset initial_savings = initial_global_state["savings"].as<asset>();
const asset initial_supply = get_token_supply();
const asset initial_balance = get_balance(prod_name);
BOOST_REQUIRE_EQUAL(success(), push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
const auto global_state = get_global_state();
const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64();
const asset pervote_bucket = global_state["pervote_bucket"].as<asset>();
const asset savings = global_state["savings"].as<asset>();
const asset supply = get_token_supply();
const asset balance = get_balance(prod_name);
const int32_t secs_between_fills = static_cast<int32_t>((claim_time - initial_claim_time) / 1000000);
BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * (cont_rate - standby_rate - block_rate)) / secs_per_year ),
savings.amount - initial_savings.amount);
int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * block_rate / blocks_per_year );
int64_t expected_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * standby_rate / secs_per_year );
int64_t from_pervote_bucket = int64_t( vote_shares[prod_index] * expected_pervote_bucket );
if (from_pervote_bucket >= 100 * 10000) {
BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(expected_pervote_bucket - from_pervote_bucket, pervote_bucket.amount);
} else {
BOOST_REQUIRE_EQUAL(block_payments, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(expected_pervote_bucket, pervote_bucket.amount);
}
produce_blocks(5);
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
}
{
const uint32_t prod_index = 23;
const auto prod_name = producer_names[prod_index];
const uint64_t initial_claim_time = get_global_state()["last_pervote_bucket_fill"].as_uint64();
const asset initial_supply = get_token_supply();
BOOST_REQUIRE_EQUAL(success(),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
BOOST_REQUIRE_EQUAL(0, get_balance(prod_name).amount);
BOOST_REQUIRE_EQUAL(initial_claim_time, get_global_state()["last_pervote_bucket_fill"].as_uint64());
BOOST_REQUIRE_EQUAL(initial_supply, get_token_supply());
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
}
produce_block(fc::seconds(24 * 3600));
{
const uint32_t prod_index = 15;
const auto prod_name = producer_names[prod_index];
const auto produced_blocks = get_producer_info(prod_name)["produced_blocks"].as<uint32_t>();
const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = initial_global_state["last_pervote_bucket_fill"].as_uint64();
const asset initial_pervote_bucket = initial_global_state["pervote_bucket"].as<asset>();
const asset initial_savings = initial_global_state["savings"].as<asset>();
const asset initial_supply = get_token_supply();
const asset initial_balance = get_balance(prod_name);
BOOST_REQUIRE_EQUAL(success(), push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
const auto global_state = get_global_state();
const uint64_t claim_time = global_state["last_pervote_bucket_fill"].as_uint64();
const asset pervote_bucket = global_state["pervote_bucket"].as<asset>();
const asset savings = global_state["savings"].as<asset>();
const asset supply = get_token_supply();
const asset balance = get_balance(prod_name);
const int32_t secs_between_fills = static_cast<int32_t>((claim_time - initial_claim_time) / 1000000);
BOOST_REQUIRE_EQUAL(int64_t( (initial_supply.amount * secs_between_fills * (cont_rate - standby_rate - block_rate)) / secs_per_year ),
savings.amount - initial_savings.amount);
int64_t block_payments = int64_t( initial_supply.amount * produced_blocks * block_rate / blocks_per_year );
int64_t expected_pervote_bucket = int64_t( initial_pervote_bucket.amount + initial_supply.amount * secs_between_fills * standby_rate / secs_per_year );
int64_t from_pervote_bucket = int64_t( vote_shares[prod_index] * expected_pervote_bucket );
BOOST_REQUIRE_EQUAL(block_payments + from_pervote_bucket, balance.amount - initial_balance.amount);
BOOST_REQUIRE_EQUAL(expected_pervote_bucket - from_pervote_bucket, pervote_bucket.amount);
produce_blocks(5);
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
}
{
const uint32_t prod_index = 23;
const auto prod_name = producer_names[prod_index];
BOOST_REQUIRE_EQUAL(success(),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
BOOST_REQUIRE(100 * 10000 <= get_balance(prod_name).amount);
BOOST_REQUIRE_EQUAL(error("condition: assertion failed: already claimed rewards within a day"),
push_action(prod_name, N(claimrewards), mvo()("owner", prod_name)));
}
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( voters_actions_affect_proxy_and_producers, eosio_system_tester ) try {
create_accounts_with_resources( { N(donald), N(producer1), N(producer2), N(producer3) } );
......
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/chain/fork_database.hpp>
#include <eosio.token/eosio.token.wast.hpp>
#include <eosio.token/eosio.token.abi.hpp>
......@@ -258,6 +259,100 @@ BOOST_AUTO_TEST_CASE( prune_remove_branch ) try {
BOOST_AUTO_TEST_CASE(confirmation) try {
tester c;
c.produce_blocks(10);
auto r = c.create_accounts( {N(dan),N(sam),N(pam),N(scott)} );
auto res = c.set_producers( {N(dan),N(sam),N(pam),N(scott)} );
private_key_type priv_sam = c.get_private_key( N(sam), "active" );
private_key_type priv_dan = c.get_private_key( N(dan), "active" );
private_key_type priv_pam = c.get_private_key( N(pam), "active" );
private_key_type priv_scott = c.get_private_key( N(scott), "active" );
private_key_type priv_invalid = c.get_private_key( N(invalid), "active" );
wlog("set producer schedule to [dan,sam,pam,scott]");
c.produce_blocks(50);
c.control->abort_block(); // discard pending block
BOOST_REQUIRE_EQUAL(61, c.control->head_block_num());
// 55 is by dan
block_state_ptr blk = c.control->fork_db().get_block_in_current_chain_by_num(55);
block_state_ptr blk61 = c.control->fork_db().get_block_in_current_chain_by_num(61);
block_state_ptr blk50 = c.control->fork_db().get_block_in_current_chain_by_num(50);
BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(0, blk->confirmations.size());
printf("bft number is %d #confirms %d\n", blk->bft_irreversible_blocknum, (int)blk->confirmations.size());
// invalid signature
BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_invalid.sign(blk->sig_digest())}),
fc::exception,
[] (const fc::exception &ex)->bool {
return ex.to_detail_string().find("confirmation not signed by expected key") != std::string::npos;
});
// invalid schedule
BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(invalid), priv_invalid.sign(blk->sig_digest())}),
fc::exception,
[] (const fc::exception &ex)->bool {
return ex.to_detail_string().find("producer not in current schedule") != std::string::npos;
});
// signed by sam
c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_sam.sign(blk->sig_digest())});
BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(1, blk->confirmations.size());
printf("bft number is %d #confirms %d\n", blk->bft_irreversible_blocknum, (int)blk->confirmations.size());
// double confirm not allowed
BOOST_REQUIRE_EXCEPTION(c.control->push_confirmation(header_confirmation{blk->id, N(sam), priv_sam.sign(blk->sig_digest())}),
fc::exception,
[] (const fc::exception &ex)->bool {
return ex.to_detail_string().find("block already confirmed by this producer") != std::string::npos;
});
// signed by dan
c.control->push_confirmation(header_confirmation{blk->id, N(dan), priv_dan.sign(blk->sig_digest())});
BOOST_REQUIRE_EQUAL(0, blk->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(2, blk->confirmations.size());
printf("bft number is %d #confirms %d\n", blk->bft_irreversible_blocknum, (int)blk->confirmations.size());
// signed by pam
c.control->push_confirmation(header_confirmation{blk->id, N(pam), priv_pam.sign(blk->sig_digest())});
// we have more than 2/3 of confirmations, bft irreversible number should be set
BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(55, blk61->bft_irreversible_blocknum); // bft irreversible number will propagate to higher block
BOOST_REQUIRE_EQUAL(0, blk50->bft_irreversible_blocknum); // bft irreversible number will not propagate to lower block
BOOST_REQUIRE_EQUAL(3, blk->confirmations.size());
printf("bft number is %d #confirms %d\n", blk->bft_irreversible_blocknum, (int)blk->confirmations.size());
// signed by scott
c.control->push_confirmation(header_confirmation{blk->id, N(scott), priv_scott.sign(blk->sig_digest())});
BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(4, blk->confirmations.size());
printf("bft number is %d #confirms %d\n", blk->bft_irreversible_blocknum, (int)blk->confirmations.size());
// let's confirm block 50 as well
c.control->push_confirmation(header_confirmation{blk50->id, N(sam), priv_sam.sign(blk50->sig_digest())});
c.control->push_confirmation(header_confirmation{blk50->id, N(dan), priv_dan.sign(blk50->sig_digest())});
c.control->push_confirmation(header_confirmation{blk50->id, N(pam), priv_pam.sign(blk50->sig_digest())});
BOOST_REQUIRE_EQUAL(50, blk50->bft_irreversible_blocknum); // bft irreversible number will not propagate to lower block
block_state_ptr blk54 = c.control->fork_db().get_block_in_current_chain_by_num(54);
BOOST_REQUIRE_EQUAL(50, blk54->bft_irreversible_blocknum);
BOOST_REQUIRE_EQUAL(55, blk->bft_irreversible_blocknum); // bft irreversible number will not be updated to lower value
BOOST_REQUIRE_EQUAL(55, blk61->bft_irreversible_blocknum);
c.produce_blocks(20);
block_state_ptr blk81 = c.control->fork_db().get_block_in_current_chain_by_num(81);
BOOST_REQUIRE_EQUAL(55, blk81->bft_irreversible_blocknum); // bft irreversible number will propagate into new blocks
} 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.
先完成此消息的编辑!
想要评论请 注册