提交 1177b6a7 编写于 作者: B Bart Wyatt

- add enforcement of block maximums at the time that transactions are accounted for

- add test cases for ram and block limits
上级 06f499d9
......@@ -438,10 +438,8 @@ void chain_controller::_finalize_block( const block_trace& trace ) { try {
update_last_irreversible_block();
_resource_limits.process_account_limit_updates();
// for block usage tracking the ordinal is based on actual blocks, this means that gaps from skipped blocks are
// do not affect the calculation of elastic target/maximums.
uint32_t ordinal = b.block_num();
_resource_limits.add_block_usage(trace.calculate_cpu_usage(), fc::raw::pack_size(trace.block), ordinal);
// trigger an update of our elastic values for block limits
_resource_limits.process_block_usage(b.block_num());
applied_block( trace ); //emit
if (_currently_replaying_blocks)
......@@ -874,7 +872,7 @@ void chain_controller::update_resource_usage( transaction_trace& trace, const tr
// for account usage, the ordinal is based on possible blocks not actual blocks. This means that as blocks are
// skipped account usage will still decay.
uint32_t ordinal = (uint32_t)(head_block_time().time_since_epoch().count() / fc::milliseconds(config::block_interval_ms).count());
_resource_limits.add_account_usage(bill_to_accounts, trace.cpu_usage, trace.net_usage, ordinal);
_resource_limits.add_transaction_usage(bill_to_accounts, trace.cpu_usage, trace.net_usage, ordinal);
}
......
......@@ -29,6 +29,7 @@ namespace eosio { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( block_tx_output_exception, eosio::chain::block_validate_exception, 3020001, "transaction outputs in block do not match transaction outputs from applying block" )
FC_DECLARE_DERIVED_EXCEPTION( block_concurrency_exception, eosio::chain::block_validate_exception, 3020002, "block does not guarantee concurrent exection without conflicts" )
FC_DECLARE_DERIVED_EXCEPTION( block_lock_exception, eosio::chain::block_validate_exception, 3020003, "shard locks in block are incorrect or mal-formed" )
FC_DECLARE_DERIVED_EXCEPTION( block_resource_exhausted, eosio::chain::block_validate_exception, 3020004, "block exhausted allowed resources" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_auth, eosio::chain::transaction_exception, 3030001, "missing required authority" )
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_sigs, eosio::chain::transaction_exception, 3030002, "signatures do not satisfy declared authorizations" )
......
......@@ -15,21 +15,25 @@ namespace eosio { namespace chain { namespace resource_limits {
void initialize_chain();
void initialize_account( const account_name& account );
void add_account_usage( const vector<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal );
void add_transaction_usage( const vector<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal );
void add_account_ram_usage( const account_name account, int64_t ram_delta, const char* use_format = "Unspecified", const fc::variant_object& args = fc::variant_object() );
void add_block_usage( uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal);
void set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight);
void get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight) const;
void process_account_limit_updates();
void process_block_usage( uint32_t block_num );
// accessors
uint64_t get_virtual_block_cpu_limit() const;
uint64_t get_virtual_block_net_limit() const;
int64_t get_account_block_cpu_limit( const account_name& name ) const;
int64_t get_account_block_net_limit( const account_name& name ) const;
uint64_t get_block_cpu_limit() const;
uint64_t get_block_net_limit() const;
int64_t get_account_cpu_limit( const account_name& name ) const;
int64_t get_account_net_limit( const account_name& name ) const;
private:
chainbase::database& _db;
......
......@@ -174,9 +174,12 @@ namespace eosio { namespace chain { namespace resource_limits {
void update_virtual_net_limit( const resource_limits_config_object& cfg );
void update_virtual_cpu_limit( const resource_limits_config_object& cfg );
uint64_t total_net_weight = 0;
uint64_t total_cpu_weight = 0;
uint64_t total_ram_bytes = 0;
uint64_t pending_net_usage = 0ULL;
uint64_t pending_cpu_usage = 0ULL;
uint64_t total_net_weight = 0ULL;
uint64_t total_cpu_weight = 0ULL;
uint64_t total_ram_bytes = 0ULL;
/**
* The virtual number of bytes that would be consumed over blocksize_average_window_ms
......@@ -193,12 +196,12 @@ namespace eosio { namespace chain { namespace resource_limits {
* average_block_size > target_block_size, with a cap at 1000x max_block_size
* and a floor at max_block_size;
**/
uint64_t virtual_net_limit = 0;
uint64_t virtual_net_limit = 0ULL;
/**
* Increases when average_bloc
*/
uint64_t virtual_cpu_limit = 0;
uint64_t virtual_cpu_limit = 0ULL;
};
......
......@@ -57,7 +57,7 @@ void resource_limits_manager::initialize_account(const account_name& account) {
});
}
void resource_limits_manager::add_account_usage(const vector<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t block_num ) {
void resource_limits_manager::add_transaction_usage(const vector<account_name>& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t time_slot ) {
const auto& state = _db.get<resource_limits_state_object>();
const auto& config = _db.get<resource_limits_config_object>();
set<std::pair<account_name, permission_name>> authorizing_accounts;
......@@ -67,8 +67,8 @@ void resource_limits_manager::add_account_usage(const vector<account_name>& acco
const auto& usage = _db.get<resource_usage_object,by_owner>( a );
const auto& limits = _db.get<resource_limits_object,by_owner>( boost::make_tuple(false, a));
_db.modify( usage, [&]( auto& bu ){
bu.net_usage.add( net_usage, block_num, config.net_limit_parameters.periods );
bu.cpu_usage.add( cpu_usage, block_num, config.cpu_limit_parameters.periods );
bu.net_usage.add( net_usage, time_slot, config.net_limit_parameters.periods );
bu.cpu_usage.add( cpu_usage, time_slot, config.cpu_limit_parameters.periods );
});
uint128_t consumed_cpu_ex = usage.cpu_usage.consumed * config::rate_limiting_precision;
......@@ -88,7 +88,7 @@ void resource_limits_manager::add_account_usage(const vector<account_name>& acco
EOS_ASSERT( limits.net_weight < 0 || (consumed_net_ex * state.total_net_weight) <= (limits.net_weight * capacity_net_ex),
tx_resource_exhausted,
"authorizing account '${n}' has insufficient cpu resources for this transaction",
"authorizing account '${n}' has insufficient net resources for this transaction",
("n", name(a))
("consumed", (double)consumed_net_ex/(double)config::rate_limiting_precision)
("net_weight", limits.net_weight)
......@@ -96,6 +96,15 @@ void resource_limits_manager::add_account_usage(const vector<account_name>& acco
("total_net_weight", state.total_net_weight)
);
}
// account for this transaction in the block and do not exceed those limits either
_db.modify(state, [&](resource_limits_state_object& rls){
rls.pending_cpu_usage += cpu_usage;
rls.pending_net_usage += net_usage;
});
EOS_ASSERT( state.pending_cpu_usage <= config.cpu_limit_parameters.max, block_resource_exhausted, "Block has insufficient cpu resources" );
EOS_ASSERT( state.pending_net_usage <= config.net_limit_parameters.max, block_resource_exhausted, "Block has insufficient net resources" );
}
void resource_limits_manager::add_account_ram_usage( const account_name account, int64_t ram_delta, const char* use_format, const fc::variant_object& args ) {
......@@ -114,18 +123,6 @@ void resource_limits_manager::add_account_ram_usage( const account_name account,
});
}
void resource_limits_manager::add_block_usage( uint64_t cpu_usage, uint64_t net_usage, uint32_t block_num ) {
const auto& s = _db.get<resource_limits_state_object>();
const auto& config = _db.get<resource_limits_config_object>();
_db.modify(s, [&](resource_limits_state_object& state){
state.average_block_net_usage.add(net_usage, block_num, config.net_limit_parameters.periods);
state.update_virtual_net_limit(config);
state.average_block_cpu_usage.add(cpu_usage, block_num, config.cpu_limit_parameters.periods);
state.update_virtual_cpu_limit(config);
});
}
void resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
const auto& usage = _db.get<resource_usage_object>();
if (ram_bytes >= 0) {
......@@ -217,6 +214,23 @@ void resource_limits_manager::process_account_limit_updates() {
});
}
void resource_limits_manager::process_block_usage(uint32_t block_num) {
const auto& s = _db.get<resource_limits_state_object>();
const auto& config = _db.get<resource_limits_config_object>();
_db.modify(s, [&](resource_limits_state_object& state){
// apply pending usage, update virtual limits and reset the pending
state.average_block_cpu_usage.add(state.pending_cpu_usage, block_num, config.cpu_limit_parameters.periods);
state.update_virtual_cpu_limit(config);
state.pending_cpu_usage = 0;
state.average_block_net_usage.add(state.pending_net_usage, block_num, config.net_limit_parameters.periods);
state.update_virtual_net_limit(config);
state.pending_net_usage = 0;
});
}
uint64_t resource_limits_manager::get_virtual_block_cpu_limit() const {
const auto& state = _db.get<resource_limits_state_object>();
return state.virtual_cpu_limit;
......@@ -227,7 +241,19 @@ uint64_t resource_limits_manager::get_virtual_block_net_limit() const {
return state.virtual_net_limit;
}
int64_t resource_limits_manager::get_account_block_cpu_limit( const account_name& name ) const {
uint64_t resource_limits_manager::get_block_cpu_limit() const {
const auto& state = _db.get<resource_limits_state_object>();
const auto& config = _db.get<resource_limits_config_object>();
return config.cpu_limit_parameters.max - state.pending_cpu_usage;
}
uint64_t resource_limits_manager::get_block_net_limit() const {
const auto& state = _db.get<resource_limits_state_object>();
const auto& config = _db.get<resource_limits_config_object>();
return config.net_limit_parameters.max - state.pending_net_usage;
}
int64_t resource_limits_manager::get_account_cpu_limit( const account_name& name ) const {
const auto& state = _db.get<resource_limits_state_object>();
const auto& usage = _db.get<resource_usage_object, by_owner>(name);
const auto& limits = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, name));
......@@ -246,7 +272,7 @@ int64_t resource_limits_manager::get_account_block_cpu_limit( const account_name
return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision);
}
int64_t resource_limits_manager::get_account_block_net_limit( const account_name& name ) const {
int64_t resource_limits_manager::get_account_net_limit( const account_name& name ) const {
const auto& state = _db.get<resource_limits_state_object>();
const auto& usage = _db.get<resource_usage_object, by_owner>(name);
const auto& limits = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, name));
......
......@@ -29,8 +29,8 @@ class resource_limits_fixture: private chainbase_fixture<512*1024>, public resou
}
};
constexpr int expected_elastic_iterations(uint64_t from, uint64_t to, uint64_t rate_num, uint64_t rate_den ) {
int result = 0;
constexpr uint64_t expected_elastic_iterations(uint64_t from, uint64_t to, uint64_t rate_num, uint64_t rate_den ) {
uint64_t result = 0;
uint64_t cur = from;
while((from < to && cur < to) || (from > to && cur > to)) {
......@@ -42,12 +42,12 @@ constexpr int expected_elastic_iterations(uint64_t from, uint64_t to, uint64_t r
}
constexpr int expected_exponential_average_iterations( uint64_t from, uint64_t to, uint64_t value, uint64_t window_size ) {
int result = 0;
constexpr uint64_t expected_exponential_average_iterations( uint64_t from, uint64_t to, uint64_t value, uint64_t window_size ) {
uint64_t result = 0;
uint64_t cur = from;
while((from < to && cur < to) || (from > to && cur > to)) {
cur = cur * (uint128_t)(window_size - 1) / (uint64_t)(window_size);
cur = cur * (uint64_t)(window_size - 1) / (uint64_t)(window_size);
cur += value / (uint64_t)(window_size);
result += 1;
}
......@@ -62,18 +62,24 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
*/
BOOST_FIXTURE_TEST_CASE(elastic_cpu_relax_contract, resource_limits_fixture) try {
const uint64_t desired_virtual_limit = config::default_max_block_cpu_usage * 1000ULL;
const int expected_relax_iterations = expected_elastic_iterations( config::default_max_block_cpu_usage, desired_virtual_limit, 1000, 999 );
const uint64_t expected_relax_iterations = expected_elastic_iterations( config::default_max_block_cpu_usage, desired_virtual_limit, 1000, 999 );
// this is enough iterations for the average to reach/exceed the target (triggering congestion handling) and then the iterations to contract down to the min
// subtracting 1 for the iteration that pulls double duty as reaching/exceeding the target and starting congestion handling
const int expected_contract_iterations =
const uint64_t expected_contract_iterations =
expected_exponential_average_iterations(0, config::default_target_block_cpu_usage, config::default_max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms ) +
expected_elastic_iterations( desired_virtual_limit, config::default_max_block_cpu_usage, 99, 100 ) - 1;
const account_name account(1);
initialize_account(account);
set_account_limits(account, -1, -1, -1);
process_account_limit_updates();
// relax from the starting state (congested) to the idle state as fast as possible
int iterations = 0;
uint32_t iterations = 0;
while (get_virtual_block_cpu_limit() < desired_virtual_limit && iterations <= expected_relax_iterations) {
add_block_usage(0,0,iterations++);
add_transaction_usage({account},0,0,iterations);
process_block_usage(iterations++);
}
BOOST_REQUIRE_EQUAL(iterations, expected_relax_iterations);
......@@ -82,7 +88,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
// push maximum resources to go from idle back to congested as fast as possible
iterations = 0;
while (get_virtual_block_cpu_limit() > config::default_max_block_cpu_usage && iterations <= expected_contract_iterations) {
add_block_usage(config::default_max_block_cpu_usage, 0, iterations++);
add_transaction_usage({account}, config::default_max_block_cpu_usage, 0, iterations);
process_block_usage(iterations++);
}
BOOST_REQUIRE_EQUAL(iterations, expected_contract_iterations);
......@@ -94,18 +101,24 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
*/
BOOST_FIXTURE_TEST_CASE(elastic_net_relax_contract, resource_limits_fixture) try {
const uint64_t desired_virtual_limit = config::default_max_block_size * 1000ULL;
const int expected_relax_iterations = expected_elastic_iterations( config::default_max_block_size, desired_virtual_limit, 1000, 999 );
const uint64_t expected_relax_iterations = expected_elastic_iterations( config::default_max_block_size, desired_virtual_limit, 1000, 999 );
// this is enough iterations for the average to reach/exceed the target (triggering congestion handling) and then the iterations to contract down to the min
// subtracting 1 for the iteration that pulls double duty as reaching/exceeding the target and starting congestion handling
const int expected_contract_iterations =
const uint64_t expected_contract_iterations =
expected_exponential_average_iterations(0, config::default_target_block_size, config::default_max_block_size, config::block_size_average_window_ms / config::block_interval_ms ) +
expected_elastic_iterations( desired_virtual_limit, config::default_max_block_size, 99, 100 ) - 1;
const account_name account(1);
initialize_account(account);
set_account_limits(account, -1, -1, -1);
process_account_limit_updates();
// relax from the starting state (congested) to the idle state as fast as possible
int iterations = 0;
uint32_t iterations = 0;
while (get_virtual_block_net_limit() < desired_virtual_limit && iterations <= expected_relax_iterations) {
add_block_usage(0,0,iterations++);
add_transaction_usage({account},0,0,iterations);
process_block_usage(iterations++);
}
BOOST_REQUIRE_EQUAL(iterations, expected_relax_iterations);
......@@ -114,7 +127,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
// push maximum resources to go from idle back to congested as fast as possible
iterations = 0;
while (get_virtual_block_net_limit() > config::default_max_block_size && iterations <= expected_contract_iterations) {
add_block_usage(0, config::default_max_block_size, iterations++);
add_transaction_usage({account},0, config::default_max_block_size, iterations);
process_block_usage(iterations++);
}
BOOST_REQUIRE_EQUAL(iterations, expected_contract_iterations);
......@@ -140,16 +154,16 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
for (int64_t idx = 0; idx < weights.size(); idx++) {
const account_name account(idx + 100);
BOOST_CHECK_EQUAL(get_account_block_cpu_limit(account), expected_limits.at(idx));
BOOST_CHECK_EQUAL(get_account_cpu_limit(account), expected_limits.at(idx));
{ // use the expected limit, should succeed ... roll it back
auto s = start_session();
add_account_usage({account}, expected_limits.at(idx), 0, 0);
add_transaction_usage({account}, expected_limits.at(idx), 0, 0);
s.undo();
}
// use too much, and expect failure;
BOOST_REQUIRE_THROW(add_account_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_resource_exhausted);
BOOST_REQUIRE_THROW(add_transaction_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_resource_exhausted);
}
} FC_LOG_AND_RETHROW();
......@@ -172,19 +186,90 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
for (int64_t idx = 0; idx < weights.size(); idx++) {
const account_name account(idx + 100);
BOOST_CHECK_EQUAL(get_account_block_net_limit(account), expected_limits.at(idx));
BOOST_CHECK_EQUAL(get_account_net_limit(account), expected_limits.at(idx));
{ // use the expected limit, should succeed ... roll it back
auto s = start_session();
add_account_usage({account}, 0, expected_limits.at(idx), 0);
add_transaction_usage({account}, 0, expected_limits.at(idx), 0);
s.undo();
}
// use too much, and expect failure;
BOOST_REQUIRE_THROW(add_account_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_resource_exhausted);
BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_resource_exhausted);
}
} FC_LOG_AND_RETHROW();
BOOST_FIXTURE_TEST_CASE(enforce_block_limits_cpu, resource_limits_fixture) try {
const account_name account(1);
initialize_account(account);
set_account_limits(account, -1, -1, -1 );
process_account_limit_updates();
const uint64_t increment = 1000;
const uint64_t expected_iterations = (config::default_max_block_cpu_usage + increment - 1 ) / increment;
for (int idx = 0; idx < expected_iterations - 1; idx++) {
add_transaction_usage({account}, increment, 0, 0);
}
BOOST_REQUIRE_THROW(add_transaction_usage({account}, increment, 0, 0), block_resource_exhausted);
} FC_LOG_AND_RETHROW();
BOOST_FIXTURE_TEST_CASE(enforce_block_limits_net, resource_limits_fixture) try {
const account_name account(1);
initialize_account(account);
set_account_limits(account, -1, -1, -1 );
process_account_limit_updates();
const uint64_t increment = 1000;
const uint64_t expected_iterations = (config::default_max_block_size + increment - 1 ) / increment;
for (int idx = 0; idx < expected_iterations - 1; idx++) {
add_transaction_usage({account}, 0, increment, 0);
}
BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, increment, 0), block_resource_exhausted);
} FC_LOG_AND_RETHROW();
BOOST_FIXTURE_TEST_CASE(enforce_account_ram_limit, resource_limits_fixture) try {
const uint64_t limit = 1000;
const uint64_t increment = 77;
const uint64_t expected_iterations = (limit + increment - 1 ) / increment;
const account_name account(1);
initialize_account(account);
set_account_limits(account, limit, -1, -1 );
process_account_limit_updates();
for (int idx = 0; idx < expected_iterations - 1; idx++) {
add_account_ram_usage(account, increment);
}
BOOST_REQUIRE_THROW(add_account_ram_usage(account, increment), tx_resource_exhausted);
} FC_LOG_AND_RETHROW();
BOOST_FIXTURE_TEST_CASE(enforce_account_ram_commitment, resource_limits_fixture) try {
const int64_t limit = 1000;
const int64_t commit = 600;
const int64_t increment = 77;
const int64_t expected_iterations = (limit - commit + increment - 1 ) / increment;
const account_name account(1);
initialize_account(account);
set_account_limits(account, limit, -1, -1 );
process_account_limit_updates();
add_account_ram_usage(account, commit);
for (int idx = 0; idx < expected_iterations - 1; idx++) {
set_account_limits(account, limit - increment * idx, -1, -1);
process_account_limit_updates();
}
BOOST_REQUIRE_THROW(set_account_limits(account, limit - increment * expected_iterations, -1, -1), wasm_execution_error);
} FC_LOG_AND_RETHROW();
BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册