EOS资源模型

笔者刚开始使用EOS系统的过程中,有些不习惯,甚至有些困惑,不清楚EOS的资源是怎么获得和分配的,相信不少朋友也有这个困惑。

本文从4个方面入手,结合代码(V1.0.7)分析EOS的资源模型(不懂编程的朋友,看看文字大概也能看懂)。

一、EOS资源介绍

EOS网络中主要有3种资源,即CPU、NET和RAM。

CPU和NET通过抵押EOS获得,属于可恢复资源,用于交易的计算和带宽。

RAM需要向系统购买,属于固定资源,用于存放账户相关的数据,包括账户名、授权信息、合约代码、合约abi和智能合约的数据。

RAM是固定资源,买多少就拥有多少,有多少就可以用多少,没有分配问题,也比较容易计量,这里先介绍:

账户名、授权信息、合约代码和abi占用的RAM很容易计量,智能合约数据占用的RAM通过multi_index底层的db计量

EOS采用全内存方案,将账户信息和账户数据放在RAM里面,加快了合约的处理速度。

下面重点介绍CPU和NET。

二、资源如何分配

首先查看一下账户的情况:

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get account xxxxxxxxxxxx
permissions: 
     owner     1:    1 EOS72pAJiLJVUcGypwoA57UUT6gsfwJtDBR83TL9PGaDSrgJDHLza
        active     1:    1 EOS5ANFfT6y5ubcCzPWGUYGUR6U13KKS5T3XwE32Uo7j8Ruarfi1x1 ************@active, 
memory: 
     quota:     7.953 KiB    used:     4.936 KiB  

net bandwidth: 
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:               341 bytes
     available:        1.905 MiB  
     limit:            1.906 MiB  

cpu bandwidth:
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             6.613 ms   
     available:          354 ms   
     limit:            360.6 ms   

producers:
     chainclubeos    eoscanadacom    eoshenzhenio    
     eoslaomaocom    oraclegogogo    

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get account xxxxxxxxxxxx
permissions: 
     owner     1:    1 EOS72pAJiLJVUcGypwoA57UUT6gsfwJtDBR83TL9PGaDSrgJDHLza
        active     1:    1 EOS5ANFfT6y5ubcCzPWGUYGUR6U13KKS5T3XwE32Uo7j8Ruarfi1x1 ************@active, 
memory: 
     quota:     7.953 KiB    used:     4.936 KiB  

net bandwidth: 
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:               341 bytes
     available:        1.905 MiB  
     limit:            1.906 MiB  

cpu bandwidth:
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             6.613 ms   
     available:          351 ms   
     limit:            357.6 ms   

producers:
     chainclubeos    eoscanadacom    eoshenzhenio    
     eoslaomaocom    oraclegogogo  

拿上述账号为例,CPU抵押3.2157 EOS,获得351.8 ms 的CPU时间
从上面可以看到,账户可使用的最大资源是动态变化的,已使用的资源是固定的,可使用的资源等于可使用的最大资源减去已使用的资源。

那我们现在就有一个疑问了,CPU抵押3.2157 EOS,是怎么获得CPU时间的?换句话说,CPU的分配是什么样的?
CPU和NET通过抵押EOS获得,每个账户所能获得的资源为:系统总资源 * 抵押代币 / 总的抵押代币

// 获取账户当前可用的虚拟CPU
int64_t resource_limits_manager::get_account_cpu_limit( const account_name& name ) const {
   auto arl = get_account_cpu_limit_ex(name);
   return arl.available;
}

// 获取账户的虚拟CPU限制
account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( 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& config = _db.get<resource_limits_config_object>();

   int64_t cpu_weight, x, y;
   get_account_limits( name, x, y, cpu_weight );

   if( cpu_weight < 0 || state.total_cpu_weight == 0 ) {
      return { -1, -1, -1 };
   }

   account_resource_limit arl;

   uint128_t window_size = config.account_cpu_usage_average_window;

   // 计算窗口期(在这里为24h)内的虚拟计算能力
   uint128_t virtual_cpu_capacity_in_window = (uint128_t)state.virtual_cpu_limit * window_size;
   uint128_t user_weight     = (uint128_t)cpu_weight;
   uint128_t all_user_weight = (uint128_t)state.total_cpu_weight;
   
   // 每个账户所能获得的资源为:系统总资源 * 抵押代币 / 总的抵押代币。
   auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight;
   auto cpu_used_in_window  = impl::integer_divide_ceil((uint128_t)usage.cpu_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);

   if( max_user_use_in_window <= cpu_used_in_window )
      arl.available = 0;
   else
      arl.available = impl::downgrade_cast<int64_t>(max_user_use_in_window - cpu_used_in_window);

   arl.used = impl::downgrade_cast<int64_t>(cpu_used_in_window);
   arl.max = impl::downgrade_cast<int64_t>(max_user_use_in_window);
   return arl;
}

每个账户所能获得的资源为:系统总资源 * 抵押代币 / 总的抵押代币,那么影响用户可使用的资源有2个因素:
1、系统总资源
2、权重,即抵押代币占总的抵押代币的比例

这里重点介绍系统总资源,首先看一下系统总资源:

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get info
{
  "server_version": "79651199",
  "chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
  "head_block_num": 6517556,
  "last_irreversible_block_num": 6517232,
  "last_irreversible_block_id": "006371f00de9b98bbea9d69401df1af407a37574c0eb4357341b949b5f5598c8",
  "head_block_id": "00637334f6abfab0cd5253dd92b883cfc4560ebf9f1f8cef2e3135c69188c3f6",
  "head_block_time": "2018-07-18T12:42:04.000",
  "head_block_producer": "eosswedenorg",
  "virtual_block_cpu_limit": 200000000,
  "virtual_block_net_limit": 1048576000,
  "block_cpu_limit": 199900,
  "block_net_limit": 1048576
}

block_cpu_limit为区块实际的最大CPU时间,现为200'000ms
block_net_limit为区块实际的最大带宽大小,现为 1024 * 1024 = 1048576K,也就是1M
virtual_block_cpu_limit为区块(虚拟)总CPU时间,该值大概为block_cpu_limit的1000倍
virtual_block_net_limit为区块(虚拟)总带宽大小,该值大概为block_net_limit的1000倍

其实virtual_block_cpu_limit和virtual_block_net_limit是不断变化的,上述的情况是系统空闲时候的状态。

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get info
{
  "server_version": "79651199",
  "chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
  "head_block_num": 6606018,
  "last_irreversible_block_num": 6605699,
  "last_irreversible_block_id": "0064cb83ed0342fde91c7a4299233dd4384f508b4599bbdb264ef3fc851a1115",
  "head_block_id": "0064ccc20c87cde7eb4f7bc960cdd3c1180d85a6c29ead8caec13ad5bba17ec1",
  "head_block_time": "2018-07-19T01:31:34.000",
  "head_block_producer": "eos42freedom",
  "virtual_block_cpu_limit": 46622441,
  "virtual_block_net_limit": 1048576000,
  "block_cpu_limit": 199900,
  "block_net_limit": 1048576
}
ubuntu@ubuntu:~/eos/build/programs/cleos$ 
ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get info
{
  "server_version": "79651199",
  "chain_id": "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906",
  "head_block_num": 6606065,
  "last_irreversible_block_num": 6605746,
  "last_irreversible_block_id": "0064cbb238e95912a4be0b53b0c794c69822e763e498b33108032d1736e1c341",
  "head_block_id": "0064ccf179a79585d6621d78a0d65a43bf8f7b4804111ee7de8147f0c36bd19f",
  "head_block_time": "2018-07-19T01:31:57.500",
  "head_block_producer": "eoscafeblock",
  "virtual_block_cpu_limit": 48867136,
  "virtual_block_net_limit": 1048576000,
  "block_cpu_limit": 166395,
  "block_net_limit": 1044416
}

下面说说EOS中为什么会有虚拟CPU资源和NET资源:

如同现实生活中的水、电和流量,忙时和闲时是分开定价的,使用价格手段鼓励用户错峰用电。EOS也允许用户在系统闲时使用更多的资源,在系统忙时保证能使用对应权重的资源,解决了以太坊拥堵时低gas交易无法打包的问题。

同样的钱,在闲时可以买到很多的水电,同理,抵押同样的代币,在系统闲时可使用更多的资源。
这里就引出EOS使用资源的基本原则:使用一分,记录一分。

为了实现动态调节的机理,EOS引入了虚拟资源这一概念,最大可使用的虚拟资源为实际可使用的资源的1000倍,也就是说,用户在系统闲时可用的最大资源为实际可用的1000倍。

现实生活中的水、电的忙闲是通过时段区分的,比如白天是忙时,半夜是闲时。EOS则需要通过监测60s内的区块资源的使用情况来区分,现阶段是60s内的区块资源使用低于最大可用的10%,就(小比例)增加系统可用的虚拟资源,否则就(小比例)减少。

static const uint32_t block_cpu_usage_average_window_ms    = 60*1000l;
static const uint32_t block_size_average_window_ms         = 60*1000l;

class resource_limits_state_object : public chainbase::object<resource_limits_state_object_type, resource_limits_state_object> {
      OBJECT_CTOR(resource_limits_state_object);
      id_type id;

      /**
       * Track the average netusage for blocks
       */
      usage_accumulator average_block_net_usage;

      /**
       * Track the average cpu usage for blocks
       */
      usage_accumulator average_block_cpu_usage;

      void update_virtual_net_limit( const resource_limits_config_object& cfg );
      void update_virtual_cpu_limit( const resource_limits_config_object& cfg );

      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
       * 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;
       **/
      uint64_t virtual_net_limit = 0ULL;

      /**
       *  Increases when average_bloc
       */
      uint64_t virtual_cpu_limit = 0ULL;

   };

static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) {
   uint64_t result = current_limit;
   if (average_usage > params.target ) {
      result = result * params.contract_rate;
   } else {
      result = result * params.expand_rate;
   }
   return std::min(std::max(result, params.max), params.max * params.max_multiplier);
}

void resource_limits_state_object::update_virtual_cpu_limit( const resource_limits_config_object& cfg ) {
   //idump((average_block_cpu_usage.average()));
   virtual_cpu_limit = update_elastic_limit(virtual_cpu_limit, average_block_cpu_usage.average(), cfg.cpu_limit_parameters);
   //idump((virtual_cpu_limit));
}

// 计算1分钟内块的CPU和NET的使用情况,用于计算下一个块最大可用的虚拟CPU和NET,通常在controller生成块的时候调用
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;

   });
}

到这里,有些小伙伴可能会有一个疑问,系统总资源是怎么计算出来的?

诚如上述,60s内的区块资源使用低于最大可用的10%,就(小比例)增加系统总资源,否则就(小比例)减少。

而virtual_block_cpu_limit和virtual_block_net_limit的总资源的初始值分别为block_cpu_limit和block_net_limit,也就是说,虚拟资源一开始等于实际资源,然后随着系统忙闲不断调整,最低值等于实际资源,最高值等于实际资源的1000倍。

void resource_limits_manager::initialize_database() {
   const auto& config = _db.create<resource_limits_config_object>([](resource_limits_config_object& config){
      // see default settings in the declaration
   });

   _db.create<resource_limits_state_object>([&config](resource_limits_state_object& state){
      // see default settings in the declaration

      // start the chain off in a way that it is "congested" aka slow-start
      state.virtual_cpu_limit = config.cpu_limit_parameters.max;
      state.virtual_net_limit = config.net_limit_parameters.max;
   });
}

账户CPU和NET属于可恢复资源,在EOS系统中,完全恢复周期为24h,也就是说,24h后账户的CPU和NET资源都会恢复。

static const uint32_t account_cpu_usage_average_window_ms  = 24*60*60*1000l;
static const uint32_t account_net_usage_average_window_ms  = 24*60*60*1000l;

虽然说CPU和NET的完全恢复周期为24h,但不代表你需要等24小时才能使用,其实EOS的资源是每时每刻都在恢复的。

比如现在距离你上笔交易正好是12h,上一笔交易后,NET的使用量是120KB,此时你可用的NET为( 1 - 12 / 24) * 120 = 60KB,NET的使用量也为60KB。

此时你发一笔交易,该交易需要消耗1KB的交易,那么交易成功后,NET的使用量为61KB。

请注意,通过get account获取到的CPU和NET的使用量都是上一笔交易后的状态,账户当前的可用资源信息必须通过交易来更新。

/**
       *  This class accumulates and exponential moving average based on inputs
       *  This accumulator assumes there are no drops in input data
       *
       *  The value stored is Precision times the sum of the inputs.
       */
      template<uint64_t Precision = config::rate_limiting_precision>
      struct exponential_moving_average_accumulator
      {
         static_assert( Precision > 0, "Precision must be positive" );
         static constexpr uint64_t max_raw_value = std::numeric_limits<uint64_t>::max() / Precision;

         exponential_moving_average_accumulator()
         : last_ordinal(0)
         , value_ex(0)
         , consumed(0)
         {
         }

         uint32_t   last_ordinal;  ///< The ordinal of the last period which has contributed to the average
         uint64_t   value_ex;      ///< The current average pre-multiplied by Precision
         uint64_t   consumed;       ///< The last periods average + the current periods contribution so far

         /**
          * return the average value
          */
         uint64_t average() const {
            return integer_divide_ceil(value_ex, Precision);
         }

         void add( uint64_t units, uint32_t ordinal, uint32_t window_size /* must be positive */ )
         {
            // check for some numerical limits before doing any state mutations
            EOS_ASSERT(units <= max_raw_value, rate_limiting_state_inconsistent, "Usage exceeds maximum value representable after extending for precision");
            EOS_ASSERT(std::numeric_limits<decltype(consumed)>::max() - consumed >= units, rate_limiting_state_inconsistent, "Overflow in tracked usage when adding usage!");

            auto value_ex_contrib = downgrade_cast<uint64_t>(integer_divide_ceil((uint128_t)units * Precision, (uint128_t)window_size));
            EOS_ASSERT(std::numeric_limits<decltype(value_ex)>::max() - value_ex >= value_ex_contrib, rate_limiting_state_inconsistent, "Overflow in accumulated value when adding usage!");

            if( last_ordinal != ordinal ) {
               FC_ASSERT( ordinal > last_ordinal, "new ordinal cannot be less than the previous ordinal" );
               if( (uint64_t)last_ordinal + window_size > (uint64_t)ordinal ) {
                  const auto delta = ordinal - last_ordinal; // clearly 0 < delta < window_size
                  const auto decay = make_ratio(
                          (uint64_t)window_size - delta,
                          (uint64_t)window_size
                  );

                  value_ex = value_ex * decay;
               } else {
                  value_ex = 0;
               }

               last_ordinal = ordinal;
               consumed = average();
            }

            consumed += units;
            value_ex += value_ex_contrib;
         }
      };

   }

   using usage_accumulator = impl::exponential_moving_average_accumulator<>;

    struct resource_usage_object : public chainbase::object<resource_usage_object_type, resource_usage_object> {
      OBJECT_CTOR(resource_usage_object)

      id_type id;
      account_name owner;

      usage_accumulator        net_usage;
      usage_accumulator        cpu_usage;

      uint64_t                 ram_usage = 0;
   };

在EOS中,交易都会消耗CPU和NET,下面是投票交易,大家留意第2次投票消耗的CPU和NET。

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io system voteproducer prods xxxxxxxxxxxxchainclubeos oraclegogogo eoscanadacom eoshenzhenio eoslaomaocom -p xxxxxxxxxxxx@active
1725682ms thread-0   main.cpp:429                  create_action        ] result: {"binargs":"a0a662fb519c856400000000000000000580a93a3aa2e94c43202932c94c83305540dd54ed4fd530552029a2465213315540196594a988cca5"} arg: {"code":"eosio","action":"voteproducer","args":{"voter":"xxxxxxxxxxxx","proxy":"","producers":["chainclubeos","eoscanadacom","eoshenzhenio","eoslaomaocom","oraclegogogo"]}} 
executed transaction: 0008ead46d05915c8eb129f234766481ba2cf672e20e96f94587b42cb1b59f52  152 bytes  3641 us
#         eosio <= eosio::voteproducer          {"voter":"xxxxxxxxxxxx","proxy":"","producers":["chainclubeos","eoscanadacom","eoshenzhenio","eoslao...
warning: transaction executed locally, but may not be confirmed by the network yet
ubuntu@ubuntu:~/eos/build/programs/cleos$ 
ubuntu@ubuntu:~/eos/build/programs/cleos$ 
ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get account xxxxxxxxxxxx
permissions: 
     owner     1:    1 EOS72pAJiLJVUcGypwoA57UUT6gsfwJtDBR83TL9PGaDSrgJDHLza
        active     1:    1 EOS5ANFfT6y5ubcCzPWGUYGUR6U13KKS5T3XwE32Uo7j8Ruarfi1x1 ************@active, 
memory: 
     quota:     7.953 KiB    used:     4.936 KiB  

net bandwidth: 
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:               351 bytes
     available:        1.906 MiB  
     limit:            1.906 MiB  

cpu bandwidth:
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             7.371 ms   
     available:          373 ms   
     limit:            380.4 ms   

producers:
     chainclubeos    eoscanadacom    eoshenzhenio    
     eoslaomaocom    oraclegogogo    

ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io system voteproducer prods xxxxxxxxxxxx chainclubeos oraclegogogo eoscanadacom eoshenzhenio eoslaomaocom -p xxxxxxxxxxxx@active
1750647ms thread-0   main.cpp:429                  create_action        ] result: {"binargs":"a0a662fb519c856400000000000000000580a93a3aa2e94c43202932c94c83305540dd54ed4fd530552029a2465213315540196594a988cca5"} arg: {"code":"eosio","action":"voteproducer","args":{"voter":"xxxxxxxxxxxx","proxy":"","producers":["chainclubeos","eoscanadacom","eoshenzhenio","eoslaomaocom","oraclegogogo"]}} 
executed transaction: 25271df8a7a1346125fd8c5a3e5119dbb9596dadb029810101a59881d7736751  152 bytes  5343 us
#         eosio <= eosio::voteproducer          {"voter":"xxxxxxxxxxxx","proxy":"","producers":["chainclubeos","eoscanadacom","eoshenzhenio","eoslao...
warning: transaction executed locally, but may not be confirmed by the network yet
ubuntu@ubuntu:~/eos/build/programs/cleos$ ./cleos -u https://api.oraclechain.io get account xxxxxxxxxxxx
permissions: 
     owner     1:    1 EOS72pAJiLJVUcGypwoA57UUT6gsfwJtDBR83TL9PGaDSrgJDHLza
        active     1:    1 EOS5ANFfT6y5ubcCzPWGUYGUR6U13KKS5T3XwE32Uo7j8Ruarfi1x1 ************@active, 
memory: 
     quota:     7.953 KiB    used:     4.936 KiB  

net bandwidth: 
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:               503 bytes
     available:        1.906 MiB  
     limit:            1.906 MiB  

cpu bandwidth:
     staked:          3.2157 EOS           (total stake delegated from account to self)
     delegated:       0.0000 EOS           (total staked delegated to account from others)
     used:             10.95 ms   
     available:        369.4 ms   
     limit:            380.4 ms   

producers:
     chainclubeos    eoscanadacom    eoshenzhenio    
     eoslaomaocom    oraclegogogo    

三、资源的计量

对CPU的计量主要是交易消耗的时间,对NET的计量主要是交易的大小。
对NET的计量比较直观易懂,这里重点介绍对CPU的计量。

对交易的计量主要是在transaction_context.cpp中完成的,即构造transaction_context类对象时开始计时,初始化( init()函数 )各钟参数,计算付费账户,交易处理结束后( finalize()函数 )停止计时,更新付费账户的资源使用情况。

   transaction_context::transaction_context( controller& c,
                                             const signed_transaction& t,
                                             const transaction_id_type& trx_id,
                                             fc::time_point s )
   :control(c)
   ,trx(t)
   ,id(trx_id)
   ,undo_session(c.db().start_undo_session(true))
   ,trace(std::make_shared<transaction_trace>())
   ,start(s)
   ,net_usage(trace->net_usage)
   ,pseudo_start(s)
   {
      trace->id = id;
      executed.reserve( trx.total_actions() );
      FC_ASSERT( trx.transaction_extensions.size() == 0, "we don't support any extensions yet" );
   }

void transaction_context::exec() {
      FC_ASSERT( is_initialized, "must first initialize" );

      if( apply_context_free ) {
         for( const auto& act : trx.context_free_actions ) {
            trace->action_traces.emplace_back();
            dispatch_action( trace->action_traces.back(), act, true );
         }
      }

      if( delay == fc::microseconds() ) {
         for( const auto& act : trx.actions ) {
            trace->action_traces.emplace_back();
            dispatch_action( trace->action_traces.back(), act );
         }
      } else {
         schedule_transaction();
      }
   }

void transaction_context::init(uint64_t initial_net_usage )
   {
      FC_ASSERT( !is_initialized, "cannot initialize twice" );
      const static int64_t large_number_no_overflow = std::numeric_limits<int64_t>::max()/2;

      const auto& cfg = control.get_global_properties().configuration;
      auto& rl = control.get_mutable_resource_limits_manager();

      net_limit = rl.get_block_net_limit();

      objective_duration_limit = fc::microseconds( rl.get_block_cpu_limit() );
      _deadline = start + objective_duration_limit;

      // Possibly lower net_limit to the maximum net usage a transaction is allowed to be billed
      if( cfg.max_transaction_net_usage <= net_limit ) {
         net_limit = cfg.max_transaction_net_usage;
         net_limit_due_to_block = false;
      }

      // Possibly lower objective_duration_limit to the maximum cpu usage a transaction is allowed to be billed
      if( cfg.max_transaction_cpu_usage <= objective_duration_limit.count() ) {
         objective_duration_limit = fc::microseconds(cfg.max_transaction_cpu_usage);
         billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
         _deadline = start + objective_duration_limit;
      }

      // Possibly lower net_limit to optional limit set in the transaction header
      uint64_t trx_specified_net_usage_limit = static_cast<uint64_t>(trx.max_net_usage_words.value) * 8;
      if( trx_specified_net_usage_limit > 0 && trx_specified_net_usage_limit <= net_limit ) {
         net_limit = trx_specified_net_usage_limit;
         net_limit_due_to_block = false;
      }

      // Possibly lower objective_duration_limit to optional limit set in transaction header
      if( trx.max_cpu_usage_ms > 0 ) {
         auto trx_specified_cpu_usage_limit = fc::milliseconds(trx.max_cpu_usage_ms);
         if( trx_specified_cpu_usage_limit <= objective_duration_limit ) {
            objective_duration_limit = trx_specified_cpu_usage_limit;
            billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
            _deadline = start + objective_duration_limit;
         }
      }

      if( billed_cpu_time_us > 0 )
         validate_cpu_usage_to_bill( billed_cpu_time_us, false ); // Fail early if the amount to be billed is too high

      // Record accounts to be billed for network and CPU usage
      // 一个action有很多authorization
      for( const auto& act : trx.actions ) {
         for( const auto& auth : act.authorization ) {
            bill_to_accounts.insert( auth.actor );
         }
      }
      validate_ram_usage.reserve( bill_to_accounts.size() );

      // Update usage values of accounts to reflect new time
      rl.update_account_usage( bill_to_accounts, block_timestamp_type(control.pending_block_time()).slot );

      // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed
      int64_t account_net_limit = large_number_no_overflow;
      int64_t account_cpu_limit = large_number_no_overflow;
      for( const auto& a : bill_to_accounts ) {
         auto net_limit = rl.get_account_net_limit(a);
         if( net_limit >= 0 )
            account_net_limit = std::min( account_net_limit, net_limit );
         auto cpu_limit = rl.get_account_cpu_limit(a);
         if( cpu_limit >= 0 )
            account_cpu_limit = std::min( account_cpu_limit, cpu_limit );
      }

      eager_net_limit = net_limit;

      // Possible lower eager_net_limit to what the billed accounts can pay plus some (objective) leeway
      auto new_eager_net_limit = std::min( eager_net_limit, static_cast<uint64_t>(account_net_limit + cfg.net_usage_leeway) );
      if( new_eager_net_limit < eager_net_limit ) {
         eager_net_limit = new_eager_net_limit;
         net_limit_due_to_block = false;
      }

      // Possibly limit deadline if the duration accounts can be billed for (+ a subjective leeway) does not exceed current delta
      if( (fc::microseconds(account_cpu_limit) + leeway) <= (_deadline - start) ) {
         _deadline = start + fc::microseconds(account_cpu_limit) + leeway;
         billing_timer_exception_code = leeway_deadline_exception::code_value;
      }

      billing_timer_duration_limit = _deadline - start;

      // Check if deadline is limited by caller-set deadline (only change deadline if billed_cpu_time_us is not set)
      if( billed_cpu_time_us > 0 || deadline < _deadline ) {
         _deadline = deadline;
         deadline_exception_code = deadline_exception::code_value;
      } else {
         deadline_exception_code = billing_timer_exception_code;
      }

      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

      if( initial_net_usage > 0 )
         add_net_usage( initial_net_usage );  // Fail early if current net usage is already greater than the calculated limit

      checktime(); // Fail early if deadline has already been exceeded

      is_initialized = true;
   }

   void transaction_context::finalize() {
      FC_ASSERT( is_initialized, "must first initialize" );
      const static int64_t large_number_no_overflow = std::numeric_limits<int64_t>::max()/2;

      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) );
            }
         }
      }

      auto& rl = control.get_mutable_resource_limits_manager();
      for( auto a : validate_ram_usage ) {
         rl.verify_account_ram_usage( a );
      }

      // Calculate the new highest network usage and CPU time that all of the billed accounts can afford to be billed
      int64_t account_net_limit = large_number_no_overflow;
      int64_t account_cpu_limit = large_number_no_overflow;
      for( const auto& a : bill_to_accounts ) {
         auto net_limit = rl.get_account_net_limit(a);
         if( net_limit >= 0 )
            account_net_limit = std::min( account_net_limit, net_limit );
         auto cpu_limit = rl.get_account_cpu_limit(a);
         if( cpu_limit >= 0 )
            account_cpu_limit = std::min( account_cpu_limit, cpu_limit );
      }

      // Possibly lower net_limit to what the billed accounts can pay
      if( static_cast<uint64_t>(account_net_limit) <= net_limit ) {
         net_limit = static_cast<uint64_t>(account_net_limit);
         net_limit_due_to_block = false;
      }

      // Possibly lower objective_duration_limit to what the billed accounts can pay
      if( account_cpu_limit <= objective_duration_limit.count() ) {
         objective_duration_limit = fc::microseconds(account_cpu_limit);
         billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
      }

      net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes)

      eager_net_limit = net_limit;
      check_net_usage();

      auto now = fc::time_point::now();
      trace->elapsed = now - start;

      if( billed_cpu_time_us == 0 ) {
         const auto& cfg = control.get_global_properties().configuration;
         billed_cpu_time_us = std::max( (now - pseudo_start).count(), static_cast<int64_t>(cfg.min_transaction_cpu_usage) );
      }

      validate_cpu_usage_to_bill( billed_cpu_time_us );

      // 为什么所有账户都需要买单,一样的CPU和NET消耗???
      rl.add_transaction_usage( bill_to_accounts, static_cast<uint64_t>(billed_cpu_time_us), net_usage,
                                block_timestamp_type(control.pending_block_time()).slot ); // Should never fail
   }

到了这里,有些朋友可能会产生一个疑问:EOS有21个出块节点,每个出块节点的性能是不一样的,同样的交易,在不同的出块节点执行,消耗的CPU时间是不一样的,那要怎么保证计量的统一性?

答案是这样的,对交易消耗的资源的计量由将交易打包到区块的出块节点决定,该出块节点将交易打包到区块时,会开一张收据,这张收据记录交易消耗的CPU时间和NET带宽,其它出块节点(和全节点)都认这个收据。

/**
    * When a transaction is referenced by a block it could imply one of several outcomes which
    * describe the state-transition undertaken by the block producer.
    */

   struct transaction_receipt_header {
      enum status_enum {
         executed  = 0, ///< succeed, no error handler executed
         soft_fail = 1, ///< objectively failed (not executed), error handler executed
         hard_fail = 2, ///< objectively failed and error handler objectively failed thus no state change
         delayed   = 3, ///< transaction delayed/deferred/scheduled for future execution
         expired   = 4  ///< transaction expired and storage space refuned to user
      };

      transaction_receipt_header():status(hard_fail){}
      transaction_receipt_header( status_enum s ):status(s){}

      friend inline bool operator ==( const transaction_receipt_header& lhs, const transaction_receipt_header& rhs ) {
         return std::tie(lhs.status, lhs.cpu_usage_us, lhs.net_usage_words) == std::tie(rhs.status, rhs.cpu_usage_us, rhs.net_usage_words);
      }

      fc::enum_type<uint8_t,status_enum>   status;
      uint32_t                             cpu_usage_us; ///< total billed CPU usage (microseconds)
      fc::unsigned_int                     net_usage_words; ///<  total billed NET usage, so we can reconstruct resource state when skipping context free data... hard failures...
   };

   struct transaction_receipt : public transaction_receipt_header {

      transaction_receipt():transaction_receipt_header(){}
      transaction_receipt( transaction_id_type tid ):transaction_receipt_header(executed),trx(tid){}
      transaction_receipt( packed_transaction ptrx ):transaction_receipt_header(executed),trx(ptrx){}

      fc::static_variant<transaction_id_type, packed_transaction> trx;

      digest_type digest()const {
         digest_type::encoder enc;
         fc::raw::pack( enc, status );
         fc::raw::pack( enc, cpu_usage_us );
         fc::raw::pack( enc, net_usage_words );
         if( trx.contains<transaction_id_type>() )
            fc::raw::pack( enc, trx.get<transaction_id_type>() );
         else
            fc::raw::pack( enc, trx.get<packed_transaction>().packed_digest() );
         return enc.result();
      }
   };

四、资源的管理

EOS对CPU、NET和RAM资源进行统一管理,对系统可用资源和账户可用资源集中记账。

// 将交易消耗的CPU和NET资源计入账户,并且加到块消耗的CPU和NET资源内,通常在交易验证后调用
void resource_limits_manager::add_transaction_usage(const flat_set<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>();

   for( const auto& a : accounts ) {

      const auto& usage = _db.get<resource_usage_object,by_owner>( a );
      int64_t unused;
      int64_t net_weight;
      int64_t cpu_weight;
      get_account_limits( a, unused, net_weight, cpu_weight );

      _db.modify( usage, [&]( auto& bu ){
          bu.net_usage.add( net_usage, time_slot, config.account_net_usage_average_window );
          bu.cpu_usage.add( cpu_usage, time_slot, config.account_cpu_usage_average_window );
      });

      if( cpu_weight >= 0 && state.total_cpu_weight > 0 ) {
         uint128_t window_size = config.account_cpu_usage_average_window;
         auto virtual_network_capacity_in_window = (uint128_t)state.virtual_cpu_limit * window_size;
         auto cpu_used_in_window                 = ((uint128_t)usage.cpu_usage.value_ex * window_size) / (uint128_t)config::rate_limiting_precision;

         uint128_t user_weight     = (uint128_t)cpu_weight;
         uint128_t all_user_weight = state.total_cpu_weight;

         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

         EOS_ASSERT( cpu_used_in_window <= max_user_use_in_window,
                     tx_cpu_usage_exceeded,
                     "authorizing account '${n}' has insufficient cpu resources for this transaction",
                     ("n", name(a))
                     ("cpu_used_in_window",cpu_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );
      }

      if( net_weight >= 0 && state.total_net_weight > 0) {

         uint128_t window_size = config.account_net_usage_average_window;
         auto virtual_network_capacity_in_window = (uint128_t)state.virtual_net_limit * window_size;
         auto net_used_in_window                 = ((uint128_t)usage.net_usage.value_ex * window_size) / (uint128_t)config::rate_limiting_precision;

         uint128_t user_weight     = (uint128_t)net_weight;
         uint128_t all_user_weight = state.total_net_weight;

         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

         EOS_ASSERT( net_used_in_window <= max_user_use_in_window,
                     tx_net_usage_exceeded,
                     "authorizing account '${n}' has insufficient net resources for this transaction",
                     ("n", name(a))
                     ("net_used_in_window",net_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );

      }
   }

   // 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" );
}

// 更新RAM的使用量
void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
   if (ram_delta == 0) {
      return;
   }

   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );

   EOS_ASSERT( ram_delta <= 0 || UINT64_MAX - usage.ram_usage >= (uint64_t)ram_delta, transaction_exception,
              "Ram usage delta would overflow UINT64_MAX");
   EOS_ASSERT(ram_delta >= 0 || usage.ram_usage >= (uint64_t)(-ram_delta), transaction_exception,
              "Ram usage delta would underflow UINT64_MAX");

   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
   });
}

// 更新各账户的资源限制,通常在controller生成块的时候调用
void resource_limits_manager::process_account_limit_updates() {
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

   // convenience local lambda to reduce clutter
   auto update_state_and_value = [](uint64_t &total, int64_t &value, int64_t pending_value, const char* debug_which) -> void {
      if (value > 0) {
         EOS_ASSERT(total >= value, rate_limiting_state_inconsistent, "underflow when reverting old value to ${which}", ("which", debug_which));
         total -= value;
      }

      if (pending_value > 0) {
         EOS_ASSERT(UINT64_MAX - total >= pending_value, rate_limiting_state_inconsistent, "overflow when applying new value to ${which}", ("which", debug_which));
         total += pending_value;
      }

      value = pending_value;
   };

   const auto& state = _db.get<resource_limits_state_object>();
   _db.modify(state, [&](resource_limits_state_object& rso){
      while(!by_owner_index.empty()) {
         const auto& itr = by_owner_index.lower_bound(boost::make_tuple(true));
         if (itr == by_owner_index.end() || itr->pending!= true) {
            break;
         }

         const auto& actual_entry = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, itr->owner));
         _db.modify(actual_entry, [&](resource_limits_object& rlo){
            update_state_and_value(rso.total_ram_bytes,  rlo.ram_bytes,  itr->ram_bytes, "ram_bytes");
            update_state_and_value(rso.total_cpu_weight, rlo.cpu_weight, itr->cpu_weight, "cpu_weight");
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
         });

         multi_index.remove(*itr);
      }
   });
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容