Eos中,通过抵押token的方式,提供cpu运算时间以供用户完成交易。
net资源计算方式与cpu类似。
本文旨在理清变量间关系,故略过大部分中间变量,仅记录关联度较大的部分。
在eos测试中,用账号进行高频的transfer(转账)操作,出现了以下报错。
所以决定做一份关于cpu限制的笔记。
3080004 tx_cpu_usage_exceeded: Transaction exceeded the current CPU usage limit imposed on the transaction
billed CPU time (916 us) is greater than the maximum billable CPU time for the transaction (372 us)
{"billed":916,"billable":372}
thread-0 transaction_context.cpp:361 validate_cpu_usage_to_bill
交易CPU验证三步曲
主要由transaction_context承担验证工作。
-
transaction_context::init
objective_duration_limit = 等于以下四者最小值。
_deadline = start + objective_duration_limit。config::max_transaction_cpu_usage
:config.hpptrx.max_cpu_usage_ms
:nodeos --max-cpu-usage-msresource_limits_manager::get_block_cpu_limit()
:动态变化_db.get<resource_limits_config_object>().cpu_limit_parameters.max - _db.get<resource_limits_state_object>().pending_cpu_usage
resource_limits_manager::get_account_cpu_limit + leeway
:动态变化,用户窗口期可用总值-已使用值。
-
transaction_context::exec
非延迟类交易立即执行,延迟类交易计算挂起费用(网络、内存),其中内存费用由交易发起者支付。 -
transaction_context::finalize
验证交易执行结果,计算费用。cpu账单需满足以下断言:
billed_us >= cfg.min_transaction_cpu_usage
:config.hppbilled_us <= objective_duration_limit.count()
:上面的objective_duration_limit
用户CPU资源使用计算
上面我们大概了解了什么因素会影响cpu_limit的判定,现在要开始弄明白用户cpu的计算方式。
使用cleos get account
我们可以看到cpu相关参数如下:
cpu bandwidth:
staked: 4448951.0162 SYS (total stake delegated from account to self)
delegated: 0.0000 SYS (total staked delegated to account from others)
used: 47.11 ms
available: 85.42 hr
limit: 85.42 hr
// 打印代码如下:
std::cout << indent << std::left << std::setw(11) << "used:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.used ) << "\n";
std::cout << indent << std::left << std::setw(11) << "available:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.available ) << "\n";
std::cout << indent << std::left << std::setw(11) << "limit:" << std::right << std::setw(18) << to_pretty_time( res.cpu_limit.max ) << "\n";
其中res.cpu_limit的值源自get_account_cpu_limit_ex
,最终与_db
中resource_limits*
字段相关联。
那么我们需要弄懂相关字段的含义。
-
account_limit
_db.get<resource_limits_object, by_owner>(boost::make_tuple(bool pending, account_name account).cpu_weight
:
用户抵押货币后理论cpu时间最大值。- pending:false时,记录上个块结束时的旧值。
- pending:true时,记录当前块发生交易后的新值。
当用户产生交易后,在controller_impl::finalize_block()中更新:
_db<resource_limits_object, by_owner>(false, account) = _db<resource_limits_object, by_owner>(true, account)
-
account_usage
_db.get<resource_usage_object, by_owner>
:
窗口期内用户使用的资源,在add_transaction_usage
内增加。
-
block_limit
_db.get<resource_limits_state_object>
:整个块中用户计算资源消耗累计,累计方式同account_usage,根据下文config_object的值决定virtual_net_limit,动态调节限制。
可以参考virtual_net_limit代码注释:/**
* The virtual number of bytes that would be consumed over blocksize_average_window_ms
* if all blocks were at their maximum virtual size. This is virtual because the
* real maximum block is less, this virtual number is only used for rate limiting users.
*
* It's lowest possible value is max_block_size * blocksize_average_window_ms / block_interval
* It's highest possible value is 1000 times its lowest possible value
*
* This means that the most an account can consume during idle periods is 1000x the bandwidth
* it is gauranteed under congestion.
*
* Increases when average_block_size < target_block_size, decreases when
* average_block_size > target_block_size, with a cap at 1000x max_block_size
* and a floor at max_block_size;
**/
_db.get<resource_limits_config_object>
:记录着整个硬件资源限制的配置,目前来看似乎是定值。
class resource_limits_config_object : public chainbase::object<resource_limits_config_object_type, resource_limits_config_object> {
OBJECT_CTOR(resource_limits_config_object);
id_type id;
struct elastic_limit_parameters {
uint64_t target; // the desired usage
uint64_t max; // the maximum usage
uint32_t periods; // the number of aggregation periods that contribute to the average usage
uint32_t max_multiplier; // the multiplier by which virtual space can oversell usage when uncongested
ratio contract_rate; // the rate at which a congested resource contracts its limit
ratio expand_rate; // the rate at which an uncongested resource expands its limits
void validate()const; // throws if the parameters do not satisfy basic sanity checks
};
elastic_limit_parameters cpu_limit_parameters = {EOS_PERCENT(config::default_max_block_cpu_usage, config::default_target_block_cpu_usage_pct), config::default_max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}};
elastic_limit_parameters net_limit_parameters = {EOS_PERCENT(config::default_max_block_net_usage, config::default_target_block_net_usage_pct), config::default_max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, 1000, {99, 100}, {1000, 999}};
// window_size = account_cpu_usage_average_window
uint32_t account_cpu_usage_average_window = config::account_cpu_usage_average_window_ms / config::block_interval_ms;
uint32_t account_net_usage_average_window = config::account_net_usage_average_window_ms / config::block_interval_ms;
}
// 在controller_impl::finalize_block中执行更新
uint64_t CPU_TARGET = EOS_PERCENT(chain_config.max_block_cpu_usage, chain_config.target_block_cpu_usage_pct);
resource_limits.set_block_parameters(
{ CPU_TARGET, chain_config.max_block_cpu_usage, config::block_cpu_usage_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}},
{EOS_PERCENT(chain_config.max_block_net_usage, chain_config.target_block_net_usage_pct), chain_config.max_block_net_usage, config::block_size_average_window_ms / config::block_interval_ms, max_virtual_mult, {99, 100}, {1000, 999}}
);
-
窗口期(window)
exponential_moving_average_accumulator
:EMA(指数滑动平均线)计数器- 这篇博客有提到EMA
- window_size:一个窗口期占用的block数