ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

EOS 提交交易失败分析

2021-11-20 10:33:09  阅读:224  来源: 互联网

标签:CPU EOS limit 提交 usage 失败 net billed cpu


https://feelncut.com/2020/02/02/288.html

https://uzshare.com/view/829601

 https://blog.csdn.net/yhc166188/article/details/84862880

EOS 提交交易失败分析

February 2, 2020

EOS 向节点提交交易时失败,提示 billed CPU time (Y us) is greater than the maximum billable CPU time for the transaction (X us).

本文通过分析源代码来一探这个失败的原因,首先给出结论:

  • 当前时间窗口内(24小时)用户最大 CPU 时间 = 全网总 CPU 时间 * 当前用户质押 EOS 数量 / 所有用户质押 EOS 数量

  • 当前时间窗口内用户可用 CPU 时间 = 当前用户最大 CPU 时间 - 当前用户已经使用 CPU 时间

  • 当前用户已经使用 CPU 时间是实时变化的:(1 - t / 24) * t 时间之前使用的资源大小,直到距离上次 CPU 资源使用 24 小时后(t = 24)完全恢复

  • Get Account 接口看到的不是实时可用的资源使用量,而是上一笔交易之后缓存的资源使用量

  • 向 RPC 节点提交交易时 RPC 节点会计算出当前交易 CPU 使用量,这个 CPU 使用量和当前 RPC 节点 CPU 使用情况有关,通过系统计时器计算时间,因此,RPC 节点计算出的交易 CPU 使用量不是最终上链时的交易使用量,最终交易时的 CPU 使用量由打包节点决定。

  • 因此,当质押 CPU EOS 数量固定时,向 RPC 节点提交交易时,CPU 资源需要满足 交易使用 CPU 资源的大小 < 账户可用 CPU 资源大小,因此提交交易能否成功有两方面原因:

    • RPC 节点 CPU 状态,决定了交易使用 CPU 资源的大小
    • 全网可用 CPU 资源,决定了账户可用 CPU 资源大小
  • 所以
    • 可以把交易提交到更快(CPU 空闲 / 服务器配置高)的 RPC 节点
    • 等待全网可用 CPU 资源增多,账户分配的可用 CPU 增多(错峰)
    • 增大 CPU 资源 EOS 质押比例,获得更多的 CPU 使用权重(质押更多 EOS 在 CPU 资源上)

看完结论如果想继续看实现,请看下文,不过,我,懒,简单贴几段代码…

错误报出的位置

函数:validate_cpu_usage_to_bill

https://github.com/EOSIO/eos/blob/e19afc8072219282a7c3fc20e47aa80cb70299e4/libraries/chain/transaction_context.cpp


  1. void transaction_context::validate_cpu_usage_to_bill( int64_t billed_us, bool check_minimum )const {
  2. if (!control.skip_trx_checks()) {
  3. if( check_minimum ) {
  4. const auto& cfg = control.get_global_properties().configuration;
  5. EOS_ASSERT( billed_us >= cfg.min_transaction_cpu_usage, transaction_exception,
  6. "cannot bill CPU time less than the minimum of ${min_billable} us",
  7. ("min_billable", cfg.min_transaction_cpu_usage)("billed_cpu_time_us", billed_us)
  8. );
  9. }
  10. if( billing_timer_exception_code == block_cpu_usage_exceeded::code_value ) {
  11. EOS_ASSERT( billed_us <= objective_duration_limit.count(),
  12. block_cpu_usage_exceeded,
  13. "billed CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)",
  14. ("billed", billed_us)("billable", objective_duration_limit.count())
  15. );
  16. } else {
  17. if (cpu_limit_due_to_greylist) {
  18. EOS_ASSERT( billed_us <= objective_duration_limit.count(),
  19. greylist_cpu_usage_exceeded,
  20. "billed CPU time (${billed} us) is greater than the maximum greylisted billable CPU time for the transaction (${billable} us)",
  21. ("billed", billed_us)("billable", objective_duration_limit.count())
  22. );
  23. } else {
  24. // 这里抛出的错误
  25. EOS_ASSERT( billed_us <= objective_duration_limit.count(),
  26. tx_cpu_usage_exceeded,
  27. "billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)",
  28. ("billed", billed_us)("billable", objective_duration_limit.count())
  29. );
  30. }
  31. }
  32. }
  33. }

从函数中可以发现,错误在与 billed_us <= objective_duration_limit.count() 条件没有满足。其中 billed_us 指交易所需要的 CPU 时间,objective_duration_limit.count() 指一个上限,后文介绍上限是多少。

运行交易所需要的时间

从 transaction_context init 到 finalize

看中文注释:


  1. void transaction_context::finalize() {
  2. EOS_ASSERT( is_initialized, transaction_exception, "must first initialize" );
  3. if( is_input ) {
  4. auto& am = control.get_mutable_authorization_manager();
  5. for( const auto& act : trx.actions ) {
  6. for( const auto& auth : act.authorization ) {
  7. am.update_permission_usage( am.get_permission(auth) );
  8. }
  9. }
  10. }
  11. auto& rl = control.get_mutable_resource_limits_manager();
  12. for( auto a : validate_ram_usage ) {
  13. rl.verify_account_ram_usage( a );
  14. }
  15. // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed
  16. int64_t account_net_limit = 0;
  17. int64_t account_cpu_limit = 0;
  18. bool greylisted_net = false, greylisted_cpu = false;
  19. std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay();
  20. net_limit_due_to_greylist |= greylisted_net;
  21. cpu_limit_due_to_greylist |= greylisted_cpu;
  22. // Possibly lower net_limit to what the billed accounts can pay
  23. if( static_cast<uint64_t>(account_net_limit) <= net_limit ) {
  24. // NOTE: net_limit may possibly not be objective anymore due to net greylisting, but it should still be no greater than the truly objective net_limit
  25. net_limit = static_cast<uint64_t>(account_net_limit);
  26. net_limit_due_to_block = false;
  27. }
  28. // Possibly lower objective_duration_limit to what the billed accounts can pay
  29. if( account_cpu_limit <= objective_duration_limit.count() ) {
  30. // NOTE: objective_duration_limit may possibly not be objective anymore due to cpu greylisting, but it should still be no greater than the truly objective objective_duration_limit
  31. objective_duration_limit = fc::microseconds(account_cpu_limit);
  32. billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
  33. }
  34. net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes)
  35. eager_net_limit = net_limit;
  36. check_net_usage();
  37. auto now = fc::time_point::now();
  38. trace->elapsed = now - start;
  39. // 交易结束后获取当前时间,然后计算 now 到 交易开始时的时间,保存到 billed_cpu_time_us
  40. // 具体可以跟进这个函数看
  41. update_billed_cpu_time( now );
  42. // 调用时间资源验证函数
  43. validate_cpu_usage_to_bill( billed_cpu_time_us );
  44. rl.add_transaction_usage( bill_to_accounts, static_cast<uint64_t>(billed_cpu_time_us), net_usage,
  45. block_timestamp_type(control.pending_block_time()).slot ); // Should never fail
  46. }

update_billed_cpu_time 如下:


  1. uint32_t transaction_context::update_billed_cpu_time( fc::time_point now ) {
  2. if( explicit_billed_cpu_time ) return static_cast<uint32_t>(billed_cpu_time_us);
  3. const auto& cfg = control.get_global_properties().configuration;
  4. // 当前时间减去 pseudo_start
  5. billed_cpu_time_us = std::max( (now - pseudo_start).count(), static_cast<int64_t>(cfg.min_transaction_cpu_usage) );
  6. return static_cast<uint32_t>(billed_cpu_time_us);
  7. }
  8. void transaction_context::pause_billing_timer() {
  9. if( explicit_billed_cpu_time || pseudo_start == fc::time_point() ) return; // either irrelevant or already paused
  10. auto now = fc::time_point::now();
  11. billed_time = now - pseudo_start;
  12. deadline_exception_code = deadline_exception::code_value; // Other timeout exceptions cannot be thrown while billable timer is paused.
  13. // 时间赋值
  14. pseudo_start = fc::time_point();
  15. transaction_timer.stop();
  16. }

时间相关操作在另一个仓库 fc 里:https://github.com/EOSIO/fc/blob/e95a03eed1796a3054e02e67f1171f8c9fdb57e5/src/time.cpp

同时要注意 void transaction_context::finalize() 函数里的 objective_duration_limit


  1. // Possibly lower objective_duration_limit to what the billed accounts can pay
  2. if( account_cpu_limit <= objective_duration_limit.count() ) {
  3. // NOTE: objective_duration_limit may possibly not be objective anymore due to cpu greylisting, but it should still be no greater than the truly objective objective_duration_limit
  4. // 如果用户的可用 cpu 时间小于 objective_duration_limit
  5. // objective_duration_limit 赋值为 account_cpu_limit 用户可用的时间
  6. objective_duration_limit = fc::microseconds(account_cpu_limit);
  7. billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;

从上面可以看出,交易的运行时间为交易执行开始到结束时间之差,那么问题来了,如果服务器 CPU 空闲,在交易执行时没有其他任务打断,该交易执行时间会比较短;当 CPU 繁忙的,频繁的上下文切换会导致交易执行时间增加。交易也因提交到 RPC 所在服务器的配置,服务器 CPU 情况而定。

那最终以哪个为准呢?以打包该交易的节点所计算出的时间为准,打包该交易时会把交易 CPU 用时放在交易中,然后广播给其他超级节点。

交易可用时间上限


  1. // 下面函数 init 函数中用到了
  2. uint64_t resource_limits_manager::get_block_cpu_limit() const {
  3. const auto& state = _db.get<resource_limits_state_object>();
  4. const auto& config = _db.get<resource_limits_config_object>();
  5. // cpu_limit_parameters 可以发现是从配置中获取的
  6. // cpu_limit_parameters 设定来自:https://github.com/EOSIO/eos/blob/a2317d380de2ca4b8753673ee42fdfd6fb1b4819/libraries/chain/include/eosio/chain/resource_limits_private.hpp
  7. // 可以发现 max 指的是 default_max_block_cpu_usage https://github.com/EOSIO/eos/blob/e19afc8072219282a7c3fc20e47aa80cb70299e4/libraries/chain/include/eosio/chain/config.hpp
  8. // config.hpp 中
  9. // const static uint32_t default_max_block_cpu_usage = 200'000; /// max block
  10. // pending_cpu_usage 指队列中的时间
  11. return config.cpu_limit_parameters.max - state.pending_cpu_usage;
  12. }
  13. void transaction_context::init(uint64_t initial_net_usage)
  14. {
  15. EOS_ASSERT( !is_initialized, transaction_exception, "cannot initialize twice" );
  16. const static int64_t large_number_no_overflow = std::numeric_limits<int64_t>::max()/2;
  17. const auto& cfg = control.get_global_properties().configuration;
  18. auto& rl = control.get_mutable_resource_limits_manager();
  19. net_limit = rl.get_block_net_limit();
  20. // get_block_cpu_limit 首先获得一个可用时间
  21. objective_duration_limit = fc::microseconds( rl.get_block_cpu_limit() );
  22. _deadline = start + objective_duration_limit;
  23. // Possibly lower net_limit to the maximum net usage a transaction is allowed to be billed
  24. if( cfg.max_transaction_net_usage <= net_limit ) {
  25. net_limit = cfg.max_transaction_net_usage;
  26. net_limit_due_to_block = false;
  27. }
  28. // 和配置单个交易最大可用 CPU 时间比较
  29. // Possibly lower objective_duration_limit to the maximum cpu usage a transaction is allowed to be billed
  30. if( cfg.max_transaction_cpu_usage <= objective_duration_limit.count() ) {
  31. objective_duration_limit = fc::microseconds(cfg.max_transaction_cpu_usage);
  32. billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
  33. _deadline = start + objective_duration_limit;
  34. }
  35. // Possibly lower net_limit to optional limit set in the transaction header
  36. uint64_t trx_specified_net_usage_limit = static_cast<uint64_t>(trx.max_net_usage_words.value) * 8;
  37. if( trx_specified_net_usage_limit > 0 && trx_specified_net_usage_limit <= net_limit ) {
  38. net_limit = trx_specified_net_usage_limit;
  39. net_limit_due_to_block = false;
  40. }
  41. // 和交易头比较
  42. // Possibly lower objective_duration_limit to optional limit set in transaction header
  43. if( trx.max_cpu_usage_ms > 0 ) {
  44. auto trx_specified_cpu_usage_limit = fc::milliseconds(trx.max_cpu_usage_ms);
  45. if( trx_specified_cpu_usage_limit <= objective_duration_limit ) {
  46. objective_duration_limit = trx_specified_cpu_usage_limit;
  47. billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
  48. _deadline = start + objective_duration_limit;
  49. }
  50. }
  51. initial_objective_duration_limit = objective_duration_limit;
  52. if( billed_cpu_time_us > 0 ) // could also call on explicit_billed_cpu_time but it would be redundant
  53. validate_cpu_usage_to_bill( billed_cpu_time_us, false ); // Fail early if the amount to be billed is too high
  54. // Record accounts to be billed for network and CPU usage
  55. if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) {
  56. bill_to_accounts.insert( trx.first_authorizer() );
  57. } else {
  58. for( const auto& act : trx.actions ) {
  59. for( const auto& auth : act.authorization ) {
  60. bill_to_accounts.insert( auth.actor );
  61. }
  62. }
  63. }
  64. validate_ram_usage.reserve( bill_to_accounts.size() );
  65. // Update usage values of accounts to reflect new time
  66. rl.update_account_usage( bill_to_accounts, block_timestamp_type(control.pending_block_time()).slot );
  67. // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed
  68. int64_t account_net_limit = 0;
  69. int64_t account_cpu_limit = 0;
  70. bool greylisted_net = false, greylisted_cpu = false;
  71. std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay();
  72. net_limit_due_to_greylist |= greylisted_net;
  73. cpu_limit_due_to_greylist |= greylisted_cpu;
  74. eager_net_limit = net_limit;
  75. // Possible lower eager_net_limit to what the billed accounts can pay plus some (objective) leeway
  76. auto new_eager_net_limit = std::min( eager_net_limit, static_cast<uint64_t>(account_net_limit + cfg.net_usage_leeway) );
  77. if( new_eager_net_limit < eager_net_limit ) {
  78. eager_net_limit = new_eager_net_limit;
  79. net_limit_due_to_block = false;
  80. }
  81. // Possibly limit deadline if the duration accounts can be billed for (+ a subjective leeway) does not exceed current delta
  82. if( (fc::microseconds(account_cpu_limit) + leeway) <= (_deadline - start) ) {
  83. _deadline = start + fc::microseconds(account_cpu_limit) + leeway;
  84. billing_timer_exception_code = leeway_deadline_exception::code_value;
  85. }
  86. billing_timer_duration_limit = _deadline - start;
  87. // Check if deadline is limited by caller-set deadline (only change deadline if billed_cpu_time_us is not set)
  88. if( explicit_billed_cpu_time || deadline < _deadline ) {
  89. _deadline = deadline;
  90. deadline_exception_code = deadline_exception::code_value;
  91. } else {
  92. deadline_exception_code = billing_timer_exception_code;
  93. }
  94. eager_net_limit = (eager_net_limit/8)*8; // Round down to nearest multiple of word size (8 bytes) so check_net_usage can be efficient
  95. if( initial_net_usage > 0 )
  96. add_net_usage( initial_net_usage ); // Fail early if current net usage is already greater than the calculated limit
  97. checktime(); // Fail early if deadline has already been exceeded
  98. if(control.skip_trx_checks())
  99. transaction_timer.start(fc::time_point::maximum());
  100. else
  101. transaction_timer.start(_deadline);
  102. is_initialized = true;
  103. }

还记得 交易运行时间章节,验证交易 CPU 使用时间之前还判断了 objective_duration_limit 与 account_cpu_limit。因此可以发现,objective_duration_limit 是个上限,和 block cpu limit 又关系,和 单个交易的 cpu limit 又关系,和交易头的配置又关系,和用户可用的 cpu limit 又关系。对于用户而言,正常能改变的只有质押换来的 CPU 时间。

用户可用 CPU 时间

那,account_cpu_limit 怎么计算的?

直接看代码吧:


  1. int64_t account_cpu_limit = large_number_no_overflow;
  2. auto [cpu_limit, cpu_was_greylisted] = rl.get_account_cpu_limit(a, greylist_limit);
  3. if( cpu_limit >= 0 ) {
  4. // 获取用户 cpu limit
  5. account_cpu_limit = std::min( account_cpu_limit, cpu_limit );
  6. greylisted_cpu |= cpu_was_greylisted;
  7. }
  8. std::pair<int64_t, bool> resource_limits_manager::get_account_cpu_limit( const account_name& name, uint32_t greylist_limit ) const {
  9. auto [arl, greylisted] = get_account_cpu_limit_ex(name, greylist_limit);
  10. return {arl.available, greylisted};
  11. }
  12. std::pair<account_resource_limit, bool> resource_limits_manager::get_account_cpu_limit_ex( const account_name& name, uint32_t greylist_limit ) const {
  13. const auto& state = _db.get<resource_limits_state_object>();
  14. const auto& usage = _db.get<resource_usage_object, by_owner>(name);
  15. const auto& config = _db.get<resource_limits_config_object>();
  16. int64_t cpu_weight, x, y;
  17. get_account_limits( name, x, y, cpu_weight );
  18. if( cpu_weight < 0 || state.total_cpu_weight == 0 ) {
  19. return {{ -1, -1, -1 }, false};
  20. }
  21. account_resource_limit arl;
  22. uint128_t window_size = config.account_cpu_usage_average_window;
  23. uint64_t greylisted_virtual_cpu_limit = config.cpu_limit_parameters.max * greylist_limit;
  24. bool greylisted = false;
  25. uint128_t virtual_cpu_capacity_in_window = window_size;
  26. if( greylisted_virtual_cpu_limit < state.virtual_cpu_limit ) {
  27. virtual_cpu_capacity_in_window *= greylisted_virtual_cpu_limit;
  28. greylisted = true;
  29. } else {
  30. virtual_cpu_capacity_in_window *= state.virtual_cpu_limit;
  31. }
  32. uint128_t user_weight = (uint128_t)cpu_weight;
  33. uint128_t all_user_weight = (uint128_t)state.total_cpu_weight;
  34. // 重点是这里
  35. // 窗口内可用时间 * 质押 EOS 获得的权重 / 所有质押权重
  36. // window_size 24小时
  37. // rate_limiting_precision 1000*1000 都可以从头文件中查到
  38. auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight;
  39. // 已经用过的 cpu
  40. auto cpu_used_in_window = impl::integer_divide_ceil((uint128_t)usage.cpu_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);
  41. if( max_user_use_in_window <= cpu_used_in_window )
  42. arl.available = 0;
  43. else
  44. // 可用的就是 最大 cpu 时间 减去 已经用过的 cpu 时间
  45. arl.available = impl::downgrade_cast<int64_t>(max_user_use_in_window - cpu_used_in_window);
  46. arl.used = impl::downgrade_cast<int64_t>(cpu_used_in_window);
  47. arl.max = impl::downgrade_cast<int64_t>(max_user_use_in_window);
  48. return {arl, greylisted};
  49. }

所以,当前时间窗口内(24小时)用户最大 CPU 时间 = 全网总 CPU 时间 * 当前用户质押 EOS 数量 / 所有用户质押 EOS 数量

当前时间窗口内用户可用 CPU 时间 = 当前用户最大 CPU 时间 - 当前用户已经使用 CPU 时间

当前用户已经使用 CPU 时间是实时变化的:(1 - t / 24) * t 时间之前使用的资源大小,直到距离上次 CPU 资源使用 24 小时后(t = 24)完全恢复

总结

  • 当前时间窗口内(24小时)用户最大 CPU 时间 = 全网总 CPU 时间 * 当前用户质押 EOS 数量 / 所有用户质押 EOS 数量

  • 当前时间窗口内用户可用 CPU 时间 = 当前用户最大 CPU 时间 - 当前用户已经使用 CPU 时间

  • 当前用户已经使用 CPU 时间是实时变化的:(1 - t / 24) * t 时间之前使用的资源大小,直到距离上次 CPU 资源使用 24 小时后(t = 24)完全恢复

  • Get Account 接口看到的不是实时可用的资源使用量,而是上一笔交易之后缓存的资源使用量

  • 向 RPC 节点提交交易时 RPC 节点会计算出当前交易 CPU 使用量,这个 CPU 使用量和当前 RPC 节点 CPU 使用情况有关,通过系统计时器计算时间,因此,RPC 节点计算出的交易 CPU 使用量不是最终上链时的交易使用量,最终交易时的 CPU 使用量由打包节点决定。

  • 因此,当质押 CPU EOS 数量固定时,向 RPC 节点提交交易时,CPU 资源需要满足 交易使用 CPU 资源的大小 < 账户可用 CPU 资源大小,因此提交交易能否成功有两方面原因:

    • RPC 节点 CPU 状态,决定了交易使用 CPU 资源的大小
    • 全网可用 CPU 资源,决定了账户可用 CPU 资源大小
  • 所以
    • 可以把交易提交到更快(CPU 空闲 / 服务器配置高)的 RPC 节点
    • 等待全网可用 CPU 资源增多,账户分配的可用 CPU 增多(错峰)
    • 增大 CPU 资源 EOS 质押比例,获得更多的 CPU 使用权重(质押更多 EOS 在 CPU 资源上)

标签:CPU,EOS,limit,提交,usage,失败,net,billed,cpu
来源: https://blog.csdn.net/bigcarp/article/details/121435733

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有