eos cpu_limit
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數