resource_limits.cpp 19.6 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 11
static_assert( config::rate_limiting_precision > 0, "config::rate_limiting_precision must be positive" );

12
static uint64_t update_elastic_limit(uint64_t current_limit, uint64_t average_usage, const elastic_limit_parameters& params) {
B
Bart Wyatt 已提交
13
   uint64_t result = current_limit;
14
   if (average_usage > params.target ) {
B
Bart Wyatt 已提交
15 16 17 18
      result = result * params.contract_rate;
   } else {
      result = result * params.expand_rate;
   }
19
   return std::min(std::max(result, params.max), params.max * params.max_multiplier);
B
Bart Wyatt 已提交
20 21
}

22 23 24 25 26 27 28 29 30
void elastic_limit_parameters::validate()const {
   // At the very least ensure parameters are not set to values that will cause divide by zero errors later on.
   // Stricter checks for sensible values can be added later.
   FC_ASSERT( periods > 0, "elastic limit parameter 'periods' cannot be zero" );
   FC_ASSERT( contract_rate.denominator > 0, "elastic limit parameter 'contract_rate' is not a well-defined ratio" );
   FC_ASSERT( expand_rate.denominator > 0,   "elastic limit parameter 'expand_rate' is not a well-defined ratio" );
}


B
Bart Wyatt 已提交
31
void resource_limits_state_object::update_virtual_cpu_limit( const resource_limits_config_object& cfg ) {
32
   //idump((average_block_cpu_usage.average()));
B
Bart Wyatt 已提交
33
   virtual_cpu_limit = update_elastic_limit(virtual_cpu_limit, average_block_cpu_usage.average(), cfg.cpu_limit_parameters);
34
   //idump((virtual_cpu_limit));
B
Bart Wyatt 已提交
35 36 37 38 39 40
}

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

41
void resource_limits_manager::add_indices() {
42 43 44 45 46 47
   _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>();
}

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

53
   _db.create<resource_limits_state_object>([&config](resource_limits_state_object& state){
54
      // see default settings in the declaration
55 56 57 58

      // 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;
59 60 61 62
   });
}

void resource_limits_manager::initialize_account(const account_name& account) {
63 64 65 66 67
   _db.create<resource_limits_object>([&]( resource_limits_object& bl ) {
      bl.owner = account;
   });

   _db.create<resource_usage_object>([&]( resource_usage_object& bu ) {
68 69 70 71
      bu.owner = account;
   });
}

72
void resource_limits_manager::set_block_parameters(const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ) {
73 74
   cpu_limit_parameters.validate();
   net_limit_parameters.validate();
75 76 77 78 79 80 81
   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;
   });
}

82
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 ) {
83 84 85 86 87 88 89
   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 );
90 91 92 93 94
      int64_t unused;
      int64_t net_weight;
      int64_t cpu_weight;
      get_account_limits( a, unused, net_weight, cpu_weight );

95
      _db.modify( usage, [&]( auto& bu ){
96 97
          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 );
98 99
      });

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

105
         uint128_t user_weight     = cpu_weight;
106 107 108 109
         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;

110
         EOS_ASSERT( cpu_used_in_window <= max_user_use_in_window,
111
                     tx_cpu_usage_exceeded,
112
                     "authorizing account '${n}' has insufficient cpu resources for this transaction",
113 114 115
                     ("n", name(a))
                     ("cpu_used_in_window",cpu_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );
116 117
      }

118
      if( net_weight >= 0 && state.total_net_weight > 0) {
119 120 121 122 123

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

124
         uint128_t user_weight     = net_weight;
125
         uint128_t all_user_weight = state.total_net_weight;
126

127 128
         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

129
         EOS_ASSERT( net_used_in_window <= max_user_use_in_window,
130
                     tx_net_usage_exceeded,
131
                     "authorizing account '${n}' has insufficient net resources for this transaction",
132 133 134 135
                     ("n", name(a))
                     ("net_used_in_window",net_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );

136
      }
137
   }
138 139 140 141 142 143 144 145 146

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

149
void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
150 151 152 153
   if (ram_delta == 0) {
      return;
   }

154
   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );
B
Bart Wyatt 已提交
155

156 157 158 159 160
   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");

161 162
   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
B
Bart Wyatt 已提交
163
   });
164
}
B
Bart Wyatt 已提交
165

166 167 168 169
void resource_limits_manager::verify_account_ram_usage( const account_name account )const {
   int64_t ram_bytes; int64_t net_weight; int64_t cpu_weight;
   get_account_limits( account, ram_bytes, net_weight, cpu_weight );
   const auto& usage  = _db.get<resource_usage_object,by_owner>( account );
170

171 172 173 174
   if( ram_bytes >= 0 ) {
      EOS_ASSERT( usage.ram_usage <= ram_bytes, ram_usage_exceeded,
                  "account ${account} has insufficient ram bytes; needs ${available} has ${needs}",
                  ("account", account)("available",usage.ram_usage)("needs",ram_bytes)              );
175 176 177
   }
}

178 179 180 181
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;
}

182

183
bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
184
   const auto& usage = _db.get<resource_usage_object,by_owner>( account );
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
   /*
    * 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();
208

209 210 211 212 213 214 215 216
   bool decreased_limit = false;

   if( ram_bytes >= 0 ) {

      decreased_limit = ( (limits.ram_bytes < 0) || (ram_bytes < limits.ram_bytes) );

      /*
      if( limits.ram_bytes < 0 ) {
217 218 219 220
         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));
      }
221
      */
222 223
   }

224 225 226 227 228 229
   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;
   });
230 231

   return decreased_limit;
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
}

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 已提交
248

249
void resource_limits_manager::process_account_limit_updates() {
250 251 252
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

253 254 255 256 257 258
   // 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;
      }
259

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

265 266
      value = pending_value;
   };
267

268
   const auto& state = _db.get<resource_limits_state_object>();
269
   _db.modify(state, [&](resource_limits_state_object& rso){
270 271 272 273 274 275 276 277 278 279
      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 已提交
280
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
281
         });
282

283 284
         multi_index.remove(*itr);
      }
285 286
   });
}
B
Bart Wyatt 已提交
287

288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
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;

   });
303

304 305
}

306 307 308 309 310 311 312 313 314 315
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;
}

316 317 318 319 320 321 322 323 324 325 326 327 328
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 {
329

330 331
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
332 333 334 335 336 337
   const auto& config = _db.get<resource_limits_config_object>();

   int64_t unused;
   int64_t cpu_weight;
   get_account_limits( name, unused, unused, cpu_weight );

D
Daniel Larimer 已提交
338
   if( cpu_weight < 0 || state.total_cpu_weight == 0 ) {
339 340 341 342 343
      return -1;
   }

   uint128_t window_size = config.account_cpu_usage_average_window;

344
   auto virtual_cpu_capacity_in_window = state.virtual_cpu_limit * window_size;
345 346 347
   uint128_t user_weight     = cpu_weight;
   uint128_t all_user_weight = state.total_cpu_weight;

348
   auto max_user_use_in_window = (virtual_cpu_capacity_in_window * user_weight) / all_user_weight;
349 350 351 352 353 354 355
   auto cpu_used_in_window  = (usage.cpu_usage.value_ex * window_size) / config::rate_limiting_precision;

   if( max_user_use_in_window <= cpu_used_in_window ) return 0;

   return max_user_use_in_window - cpu_used_in_window;

/*
356 357
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
A
arhag 已提交
358 359 360 361 362 363

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

   if( cpu_weight < 0 ) {
364 365 366
      return -1;
   }

367 368 369
   auto total_cpu_weight = state.total_cpu_weight;
   if( total_cpu_weight == 0 ) total_cpu_weight = 1;

370 371
   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;
372

A
arhag 已提交
373
   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * cpu_weight) / (uint128_t)total_cpu_weight;
374

A
arhag 已提交
375
   if( usable_capacity_ex < consumed_ex ) {
376 377 378 379
      return 0;
   }

   return (int64_t)((usable_capacity_ex - consumed_ex) / (uint128_t)config::rate_limiting_precision);
380
   */
381 382
}

383
account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name ) const {
384
   const auto& config = _db.get<resource_limits_config_object>();
385 386
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
A
arhag 已提交
387 388 389 390 391

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

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

396
   account_resource_limit arl;
397

398
   uint128_t window_size = config.account_cpu_usage_average_window;
399

400 401 402
   auto virtual_cpu_capacity_in_window = state.virtual_cpu_limit * window_size;
   uint128_t user_weight     = cpu_weight;
   uint128_t all_user_weight = state.total_cpu_weight;
403

404 405 406
   wdump((cpu_weight));

   auto max_user_use_in_window = (uint128_t(virtual_cpu_capacity_in_window) * user_weight) / all_user_weight;
407
   auto cpu_used_in_window  = (usage.cpu_usage.value_ex * window_size) / config::rate_limiting_precision;
408

409 410 411 412
   if( max_user_use_in_window <= cpu_used_in_window ) 
      arl.available = 0;
   else
      arl.available = max_user_use_in_window - cpu_used_in_window;
413

414
   arl.used = cpu_used_in_window;
415
   arl.max = max_user_use_in_window;
416 417

   return arl;
418 419
}

420
int64_t resource_limits_manager::get_account_net_limit( const account_name& name ) const {
421 422
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
423
   const auto& config = _db.get<resource_limits_config_object>();
A
arhag 已提交
424

425
   int64_t unused;
A
arhag 已提交
426
   int64_t net_weight;
427
   get_account_limits( name, unused, net_weight, unused );
A
arhag 已提交
428 429

   if( net_weight < 0 ) {
430 431 432
      return -1;
   }

433 434 435 436 437 438 439 440 441 442 443 444
   uint128_t window_size = config.account_net_usage_average_window;

   auto virtual_network_capacity_in_window = state.virtual_net_limit * window_size;
   uint128_t user_weight     = 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;
   auto net_used_in_window  = (usage.net_usage.value_ex * window_size) / config::rate_limiting_precision;

   if( max_user_use_in_window <= net_used_in_window ) return 0;

   return max_user_use_in_window - net_used_in_window;
445 446
}

447
account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name ) const {
448 449 450
   const auto& config = _db.get<resource_limits_config_object>();
   const auto& state  = _db.get<resource_limits_state_object>();
   const auto& usage  = _db.get<resource_usage_object, by_owner>(name);
A
arhag 已提交
451 452 453 454 455

   int64_t x;
   int64_t net_weight;
   get_account_limits( name, x, net_weight, x );

456
   if( net_weight < 0 || state.total_net_weight == 0) {
457 458 459
      return { -1, -1, -1 };
   }

460
   account_resource_limit arl;
461

462
   uint128_t window_size = config.account_net_usage_average_window;
463

464 465 466
   auto virtual_network_capacity_in_window = state.virtual_net_limit * window_size;
   uint128_t user_weight     = net_weight;
   uint128_t all_user_weight = state.total_net_weight;
467

468 469
   auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;
   auto net_used_in_window  = (usage.net_usage.value_ex * window_size) / config::rate_limiting_precision;
470

471 472 473 474
   if( max_user_use_in_window <= net_used_in_window ) 
      arl.available = 0;
   else
      arl.available = max_user_use_in_window - net_used_in_window;
475

476
   arl.used = net_used_in_window;
477
   arl.max = max_user_use_in_window;
478
   return arl;
479 480
}

481

482
} } } /// eosio::chain::resource_limits