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 90 91
   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 ){
92 93
          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 );
94 95
      });

96 97 98 99 100
      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),
101
                     tx_cpu_usage_exceeded,
102 103 104 105 106 107 108 109 110 111 112 113 114 115
                     "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),
116
                     tx_net_usage_exceeded,
117 118 119 120 121 122 123 124
                     "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)
         );
      }
125
   }
126 127 128 129 130 131 132 133 134

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

137
void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) {
138 139 140 141
   if (ram_delta == 0) {
      return;
   }

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

144 145 146 147 148
   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");

149 150
   _db.modify( usage, [&]( auto& u ) {
     u.ram_usage += ram_delta;
B
Bart Wyatt 已提交
151
   });
152
}
B
Bart Wyatt 已提交
153

154 155 156 157
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 );
158

159 160 161 162
   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)              );
163 164 165
   }
}

166 167 168 169
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;
}

170

171
bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
172
   const auto& usage = _db.get<resource_usage_object,by_owner>( account );
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
   /*
    * 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();
196

197 198 199 200 201 202 203 204
   bool decreased_limit = false;

   if( ram_bytes >= 0 ) {

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

      /*
      if( limits.ram_bytes < 0 ) {
205 206 207 208
         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));
      }
209
      */
210 211
   }

212 213 214 215 216 217
   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;
   });
218 219

   return decreased_limit;
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
}

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

237
void resource_limits_manager::process_account_limit_updates() {
238 239 240
   auto& multi_index = _db.get_mutable_index<resource_limits_index>();
   auto& by_owner_index = multi_index.indices().get<by_owner>();

241 242 243 244 245 246
   // 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;
      }
247

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

253 254
      value = pending_value;
   };
255

256
   const auto& state = _db.get<resource_limits_state_object>();
257
   _db.modify(state, [&](resource_limits_state_object& rso){
258 259 260 261 262 263 264 265 266 267
      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 已提交
268
            update_state_and_value(rso.total_net_weight, rlo.net_weight, itr->net_weight, "net_weight");
269
         });
270

271 272
         multi_index.remove(*itr);
      }
273 274
   });
}
B
Bart Wyatt 已提交
275

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
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;

   });
291

292 293
}

294 295 296 297 298 299 300 301 302 303
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;
}

304 305 306 307 308 309 310 311 312 313 314 315 316
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 {
317 318 319 320 321 322 323
   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;
   }

324 325 326
   auto total_cpu_weight = state.total_cpu_weight;
   if( total_cpu_weight == 0 ) total_cpu_weight = 1;

327 328
   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;
329

330
   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.cpu_weight) / (uint128_t)total_cpu_weight;
331

332
   //idump((virtual_capacity_ex)(total_cpu_weight)(limits.cpu_weight)(usable_capacity_ex)(consumed_ex)(config::rate_limiting_precision) );
333 334 335 336 337 338 339
   if (usable_capacity_ex < consumed_ex) {
      return 0;
   }

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

340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name ) const {
   const auto& cfg = _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);
   const auto& limits = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, name));
   if (limits.cpu_weight < 0) {
      return { -1, -1, -1 };
   }

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

   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;

   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.cpu_weight) / (uint128_t)total_cpu_weight;

357
   wdump((cfg.cpu_limit_parameters.target));
358 359 360 361 362 363 364 365 366 367 368 369 370 371
   uint128_t real_capacity_ex = (uint128_t)cfg.cpu_limit_parameters.target * (uint128_t)config::rate_limiting_precision;
   uint128_t guaranteed_capacity_ex = (uint128_t)(real_capacity_ex * limits.cpu_weight) / (uint128_t)total_cpu_weight;

   uint128_t blocks_per_day = 86400 * 1000 / config::block_interval_ms;

   if (usable_capacity_ex < consumed_ex) {
      consumed_ex = usable_capacity_ex;
   }
   return { (int64_t)(std::min(usable_capacity_ex - consumed_ex, real_capacity_ex) / (uint128_t)config::rate_limiting_precision),
         (int64_t)(std::min(usable_capacity_ex, real_capacity_ex) / (uint128_t)config::rate_limiting_precision),
         (int64_t)(guaranteed_capacity_ex * blocks_per_day / cfg.cpu_limit_parameters.periods / (uint128_t)config::rate_limiting_precision)
         };
}

372
int64_t resource_limits_manager::get_account_net_limit( const account_name& name ) const {
373 374 375 376 377 378 379 380 381
   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;
382 383 384 385

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

386
   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.net_weight) / (uint128_t)total_net_weight; // max
387 388 389 390 391 392 393 394 395

   if (usable_capacity_ex < consumed_ex) {
      return 0;
   }

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

}

396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name ) const {
   const auto& cfg = _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);
   const auto& limits = _db.get<resource_limits_object, by_owner>(boost::make_tuple(false, name));
   if (limits.net_weight < 0) {
      return { -1, -1, -1 };
   }

   auto total_net_weight = state.total_net_weight;
   if( total_net_weight == 0 ) total_net_weight = 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;

   uint128_t usable_capacity_ex = (uint128_t)(virtual_capacity_ex * limits.net_weight) / (uint128_t)total_net_weight; // max

   uint128_t real_capacity_ex = (uint128_t)cfg.net_limit_parameters.target * (uint128_t)config::rate_limiting_precision;
   uint128_t guaranteed_capacity_ex = (uint128_t)(real_capacity_ex * limits.net_weight) / (uint128_t)total_net_weight;

   uint128_t blocks_per_day = 86400 * 1000 / config::block_interval_ms;

   if (usable_capacity_ex < consumed_ex) {
      consumed_ex = usable_capacity_ex;
   }
   return { (int64_t)(std::min(usable_capacity_ex - consumed_ex, real_capacity_ex) / (uint128_t)config::rate_limiting_precision),
         (int64_t)(std::min(usable_capacity_ex, real_capacity_ex) / (uint128_t)config::rate_limiting_precision),
         (int64_t)(guaranteed_capacity_ex * blocks_per_day / cfg.net_limit_parameters.periods / (uint128_t)config::rate_limiting_precision)
         };
}

427

428
} } } /// eosio::chain::resource_limits