resource_limits.cpp 15.0 KB
Newer Older
1
#include <eosio/chain/exceptions.hpp>
2
#include <eosio/chain/resource_limits.hpp>
3 4 5
#include <eosio/chain/resource_limits_private.hpp>
#include <eosio/chain/transaction_metadata.hpp>
#include <eosio/chain/transaction.hpp>
B
Bart Wyatt 已提交
6 7
#include <algorithm>

8
namespace eosio { namespace chain { namespace resource_limits {
B
Bart Wyatt 已提交
9

10
static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) {
B
Bart Wyatt 已提交
11
   uint64_t result = current_limit;
12
   if (average_usage > params.target ) {
B
Bart Wyatt 已提交
13 14 15 16 17
      result = result * params.contract_rate;
   } else {
      result = result * params.expand_rate;
   }

18
   return std::min(std::max(result, params.max), params.max * params.max_multiplier);
B
Bart Wyatt 已提交
19 20 21 22 23 24 25 26 27 28
}

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

void resource_limits_state_object::update_virtual_net_limit( const resource_limits_config_object& cfg ) {
   virtual_net_limit = update_elastic_limit(virtual_net_limit, average_block_net_usage.average(), cfg.net_limit_parameters);
}

29
void resource_limits_manager::add_indices() {
30 31 32 33 34 35
   _db.add_index<resource_limits_index>();
   _db.add_index<resource_usage_index>();
   _db.add_index<resource_limits_state_index>();
   _db.add_index<resource_limits_config_index>();
}

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

41
   _db.create<resource_limits_state_object>([&config](resource_limits_state_object& state){
42
      // see default settings in the declaration
43 44 45 46

      // 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;
47 48 49 50
   });
}

void resource_limits_manager::initialize_account(const account_name& account) {
51 52 53 54 55
   _db.create<resource_limits_object>([&]( resource_limits_object& bl ) {
      bl.owner = account;
   });

   _db.create<resource_usage_object>([&]( resource_usage_object& bu ) {
56 57 58 59
      bu.owner = account;
   });
}

60 61 62 63 64 65 66 67
void resource_limits_manager::set_block_parameters(const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ) {
   const auto& config = _db.get<resource_limits_config_object>();
   _db.modify(config, [&](resource_limits_config_object& c){
      c.cpu_limit_parameters = cpu_limit_parameters;
      c.net_limit_parameters = net_limit_parameters;
   });
}

68
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 ) {
69 70 71 72 73 74 75 76 77
   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;

   for( const auto& a : accounts ) {

      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 ){
78 79
          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 );
80 81
      });

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
      if (limits.cpu_weight >= 0) {
         uint128_t  consumed_cpu_ex = usage.cpu_usage.consumed * config::rate_limiting_precision;
         uint128_t  capacity_cpu_ex = state.virtual_cpu_limit * config::rate_limiting_precision;

         EOS_ASSERT( state.total_cpu_weight > 0 && (consumed_cpu_ex * state.total_cpu_weight) <= (limits.cpu_weight * capacity_cpu_ex),
                     tx_resource_exhausted,
                     "authorizing account '${n}' has insufficient cpu resources for this transaction",
                     ("n",                    name(a))
                     ("consumed",             (double)consumed_cpu_ex/(double)config::rate_limiting_precision)
                     ("cpu_weight",           limits.cpu_weight)
                     ("virtual_cpu_capacity", (double)state.virtual_cpu_limit )
                     ("total_cpu_weight",     state.total_cpu_weight)
         );
      }

      if (limits.net_weight >= 0) {
         uint128_t  consumed_net_ex = usage.net_usage.consumed * config::rate_limiting_precision;
         uint128_t  capacity_net_ex = state.virtual_net_limit * config::rate_limiting_precision;

         EOS_ASSERT( state.total_net_weight > 0 && (consumed_net_ex * state.total_net_weight) <= (limits.net_weight * capacity_net_ex),
                     tx_resource_exhausted,
                     "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)
                     ("virtual_net_capacity", (double)state.virtual_net_limit )
                     ("total_net_weight",     state.total_net_weight)
         );
      }
111
   }
112 113 114 115 116 117 118 119 120

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

123 124 125 126 127
void resource_limits_manager::add_pending_account_ram_usage( const account_name account, int64_t ram_delta ) {
   if (ram_delta == 0) {
      return;
   }

128 129
   const auto& limits = _db.get<resource_limits_object,by_owner>( boost::make_tuple(false, account));
   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );
B
Bart Wyatt 已提交
130

131 132
   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
B
Bart Wyatt 已提交
133 134
   });

135
   FC_ASSERT( usage.ram_usage >= 0, "cannot have negative usage!" );
136

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

142 143 144 145
   if( limits.ram_bytes >= 0 && usage.ram_usage > limits.ram_bytes ) {
      tx_resource_exhausted e(FC_LOG_MESSAGE(error, "account ${a} has insufficient ram bytes", ("a", account)));
      e.append_log(FC_LOG_MESSAGE(error, "needs ${d} has ${m}", ("d",usage.ram_usage)("m",limits.ram_bytes)));
      throw e;
146 147 148
   }
}

149 150 151 152
int64_t resource_limits_manager::get_account_ram_usage( const account_name& name )const {
   return _db.get<resource_usage_object,by_owner>( name ).ram_usage;
}

153

154
void resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
155
   const auto& usage = _db.get<resource_usage_object,by_owner>( account );
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
   /*
    * Since we need to delay these until the next resource limiting boundary, these are created in a "pending"
    * state or adjusted in an existing "pending" state.  The chain controller will collapse "pending" state into
    * the actual state at the next appropriate boundary.
    */
   auto find_or_create_pending_limits = [&]() -> const resource_limits_object& {
      const auto* pending_limits = _db.find<resource_limits_object, by_owner>( boost::make_tuple(true, account) );
      if (pending_limits == nullptr) {
         const auto& limits = _db.get<resource_limits_object, by_owner>( boost::make_tuple(false, account));
         return _db.create<resource_limits_object>([&](resource_limits_object& pending_limits){
            pending_limits.owner = limits.owner;
            pending_limits.ram_bytes = limits.ram_bytes;
            pending_limits.net_weight = limits.net_weight;
            pending_limits.cpu_weight = limits.cpu_weight;
            pending_limits.pending = true;
         });
      } else {
         return *pending_limits;
      }
   };

   // update the users weights directly
   auto& limits = find_or_create_pending_limits();
179 180 181 182 183 184 185 186 187 188

   if (ram_bytes >= 0) {
      if (limits.ram_bytes < 0 ) {
         EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "converting unlimited account would result in overcommitment [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes));
      } else {
         EOS_ASSERT(ram_bytes >= usage.ram_usage, wasm_execution_error, "attempting to release committed ram resources [commit=${c}, desired limit=${l}]", ("c", usage.ram_usage)("l", ram_bytes));
      }

   }

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
   auto old_ram_bytes = limits.ram_bytes;
   _db.modify( limits, [&]( resource_limits_object& pending_limits ){
      pending_limits.ram_bytes = ram_bytes;
      pending_limits.net_weight = net_weight;
      pending_limits.cpu_weight = cpu_weight;
   });
}

void resource_limits_manager::get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight ) const {
   const auto* pending_buo = _db.find<resource_limits_object,by_owner>( boost::make_tuple(true, account) );
   if (pending_buo) {
      ram_bytes  = pending_buo->ram_bytes;
      net_weight = pending_buo->net_weight;
      cpu_weight = pending_buo->cpu_weight;
   } else {
      const auto& buo = _db.get<resource_limits_object,by_owner>( boost::make_tuple( false, account ) );
      ram_bytes  = buo.ram_bytes;
      net_weight = buo.net_weight;
      cpu_weight = buo.cpu_weight;
   }
}

B
Bart Wyatt 已提交
211

212
void resource_limits_manager::process_account_limit_updates() {
213 214 215
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

216 217 218 219 220 221
   // 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;
      }
222

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

228 229
      value = pending_value;
   };
230

231
   const auto& state = _db.get<resource_limits_state_object>();
232
   _db.modify(state, [&](resource_limits_state_object& rso){
233 234 235 236 237 238 239 240 241 242
      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");
B
Brian Johnson 已提交
243
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
244
         });
245

246 247
         multi_index.remove(*itr);
      }
248 249
   });
}
B
Bart Wyatt 已提交
250

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
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;

   });
266

267 268
}

269 270 271 272 273 274 275 276 277 278
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;
}

uint64_t resource_limits_manager::get_virtual_block_net_limit() const {
   const auto& state = _db.get<resource_limits_state_object>();
   return state.virtual_net_limit;
}

279 280 281 282 283 284 285 286 287 288 289 290 291
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 {
292 293 294 295 296 297 298
   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));
   if (limits.cpu_weight < 0) {
      return -1;
   }

299 300 301
   auto total_cpu_weight = state.total_cpu_weight;
   if( total_cpu_weight == 0 ) total_cpu_weight = 1;

302 303
   uint128_t consumed_ex = (uint128_t)usage.cpu_usage.consumed * (uint128_t)config::rate_limiting_precision;
   uint128_t virtual_capacity_ex = (uint128_t)state.virtual_cpu_limit * (uint128_t)config::rate_limiting_precision;
304
   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.cpu_weight) / (uint128_t)total_cpu_weight;
305 306 307 308 309 310 311 312

   if (usable_capacity_ex < consumed_ex) {
      return 0;
   }

   return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision);
}

313
int64_t resource_limits_manager::get_account_net_limit( const account_name& name ) const {
314 315 316 317 318 319 320 321 322
   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));
   if (limits.net_weight < 0) {
      return -1;
   }

   uint128_t consumed_ex = (uint128_t)usage.net_usage.consumed * (uint128_t)config::rate_limiting_precision;
   uint128_t virtual_capacity_ex = (uint128_t)state.virtual_net_limit * (uint128_t)config::rate_limiting_precision;
323 324 325 326 327

   auto total_net_weight = state.total_net_weight;
   if( total_net_weight == 0 ) total_net_weight = 1;

   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.net_weight) / (uint128_t)total_net_weight;
328 329 330 331 332 333 334 335 336 337

   if (usable_capacity_ex < consumed_ex) {
      return 0;
   }

   return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision);

}


338
} } } /// eosio::chain::resource_limits