resource_limits.cpp 17.9 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 83 84 85 86 87 88 89 90 91 92
void resource_limits_manager::update_account_usage(const flat_set<account_name>& accounts, uint32_t time_slot ) {
   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 );
      _db.modify( usage, [&]( auto& bu ){
          bu.net_usage.add( 0, time_slot, config.account_net_usage_average_window );
          bu.cpu_usage.add( 0, time_slot, config.account_cpu_usage_average_window );
      });
   }
}

93
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 ) {
94 95 96 97 98 99
   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 );
100 101 102 103 104
      int64_t unused;
      int64_t net_weight;
      int64_t cpu_weight;
      get_account_limits( a, unused, net_weight, cpu_weight );

105
      _db.modify( usage, [&]( auto& bu ){
106 107
          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 );
108 109
      });

110
      if( cpu_weight >= 0 && state.total_cpu_weight > 0 ) {
111
         uint128_t window_size = config.account_cpu_usage_average_window;
112 113
         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;
114

115
         uint128_t user_weight     = (uint128_t)cpu_weight;
116 117 118 119
         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;

120
         EOS_ASSERT( cpu_used_in_window <= max_user_use_in_window,
121
                     tx_cpu_usage_exceeded,
122
                     "authorizing account '${n}' has insufficient cpu resources for this transaction",
123 124 125
                     ("n", name(a))
                     ("cpu_used_in_window",cpu_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );
126 127
      }

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

         uint128_t window_size = config.account_net_usage_average_window;
131 132
         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;
133

134
         uint128_t user_weight     = (uint128_t)net_weight;
135
         uint128_t all_user_weight = state.total_net_weight;
136

137 138
         auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;

139
         EOS_ASSERT( net_used_in_window <= max_user_use_in_window,
140
                     tx_net_usage_exceeded,
141
                     "authorizing account '${n}' has insufficient net resources for this transaction",
142 143 144 145
                     ("n", name(a))
                     ("net_used_in_window",net_used_in_window)
                     ("max_user_use_in_window",max_user_use_in_window) );

146
      }
147
   }
148 149 150 151 152 153 154 155 156

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

159
void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
160 161 162 163
   if (ram_delta == 0) {
      return;
   }

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

166 167 168 169 170
   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");

171 172
   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
B
Bart Wyatt 已提交
173
   });
174
}
B
Bart Wyatt 已提交
175

176 177 178 179
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 );
180

181 182
   if( ram_bytes >= 0 ) {
      EOS_ASSERT( usage.ram_usage <= ram_bytes, ram_usage_exceeded,
D
Daniel Larimer 已提交
183 184
                  "account ${account} has insufficient ram; needs ${needs} bytes has ${available} bytes",
                  ("account", account)("needs",usage.ram_usage)("available",ram_bytes)              );
185 186 187
   }
}

188 189 190 191
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;
}

192

193
bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
194
   //const auto& usage = _db.get<resource_usage_object,by_owner>( account );
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
   /*
    * 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();
218

219 220 221 222 223 224 225 226
   bool decreased_limit = false;

   if( ram_bytes >= 0 ) {

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

      /*
      if( limits.ram_bytes < 0 ) {
227 228 229 230
         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));
      }
231
      */
232 233
   }

234 235 236 237 238
   _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;
   });
239 240

   return decreased_limit;
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
}

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

258
void resource_limits_manager::process_account_limit_updates() {
259 260 261
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

262 263 264 265 266 267
   // 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;
      }
268

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

274 275
      value = pending_value;
   };
276

277
   const auto& state = _db.get<resource_limits_state_object>();
278
   _db.modify(state, [&](resource_limits_state_object& rso){
279 280 281 282 283 284 285 286 287 288
      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 已提交
289
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
290
         });
291

292 293
         multi_index.remove(*itr);
      }
294 295
   });
}
B
Bart Wyatt 已提交
296

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
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;

   });
312

313 314
}

315 316 317 318 319 320 321 322 323 324
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;
}

325 326 327 328 329 330 331 332 333 334 335 336
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;
}

K
Kayan 已提交
337 338
int64_t resource_limits_manager::get_account_cpu_limit( const account_name& name, bool elastic ) const {
   auto arl = get_account_cpu_limit_ex(name, elastic);
339 340 341
   return arl.available;
}

K
Kayan 已提交
342
account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name, bool elastic) const {
343

344 345
   const auto& state = _db.get<resource_limits_state_object>();
   const auto& usage = _db.get<resource_usage_object, by_owner>(name);
346 347
   const auto& config = _db.get<resource_limits_config_object>();

B
Bart Wyatt 已提交
348 349
   int64_t cpu_weight, x, y;
   get_account_limits( name, x, y, cpu_weight );
350

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

355
   account_resource_limit arl;
356

357
   uint128_t window_size = config.account_cpu_usage_average_window;
358

K
Kayan 已提交
359
   uint128_t virtual_cpu_capacity_in_window = (uint128_t)(elastic ? state.virtual_cpu_limit : config.cpu_limit_parameters.max) * window_size;
360 361
   uint128_t user_weight     = (uint128_t)cpu_weight;
   uint128_t all_user_weight = (uint128_t)state.total_cpu_weight;
362

363 364
   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);
365

366
   if( max_user_use_in_window <= cpu_used_in_window )
367 368
      arl.available = 0;
   else
369
      arl.available = impl::downgrade_cast<int64_t>(max_user_use_in_window - cpu_used_in_window);
370

371 372
   arl.used = impl::downgrade_cast<int64_t>(cpu_used_in_window);
   arl.max = impl::downgrade_cast<int64_t>(max_user_use_in_window);
373
   return arl;
374 375
}

K
Kayan 已提交
376 377
int64_t resource_limits_manager::get_account_net_limit( const account_name& name, bool elastic) const {
   auto arl = get_account_net_limit_ex(name, elastic);
378
   return arl.available;
379 380
}

K
Kayan 已提交
381
account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name, bool elastic) const {
382 383 384
   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 已提交
385

386 387
   int64_t net_weight, x, y;
   get_account_limits( name, x, net_weight, y );
A
arhag 已提交
388

389
   if( net_weight < 0 || state.total_net_weight == 0) {
390 391 392
      return { -1, -1, -1 };
   }

393
   account_resource_limit arl;
394

395
   uint128_t window_size = config.account_net_usage_average_window;
396

K
Kayan 已提交
397
   uint128_t virtual_network_capacity_in_window = (uint128_t)(elastic ? state.virtual_net_limit : config.net_limit_parameters.max) * window_size;
398 399
   uint128_t user_weight     = (uint128_t)net_weight;
   uint128_t all_user_weight = (uint128_t)state.total_net_weight;
400

401

402
   auto max_user_use_in_window = (virtual_network_capacity_in_window * user_weight) / all_user_weight;
403
   auto net_used_in_window  = impl::integer_divide_ceil((uint128_t)usage.net_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);
404

405
   if( max_user_use_in_window <= net_used_in_window )
406 407
      arl.available = 0;
   else
408
      arl.available = impl::downgrade_cast<int64_t>(max_user_use_in_window - net_used_in_window);
409

410 411
   arl.used = impl::downgrade_cast<int64_t>(net_used_in_window);
   arl.max = impl::downgrade_cast<int64_t>(max_user_use_in_window);
412
   return arl;
413 414
}

415
} } } /// eosio::chain::resource_limits