text: #include <eosio/chain/exceptions.hpp>
  #include <eosio/chain/resource_limits.hpp>
  #include <eosio/chain/resource_limits_private.hpp>
  #include <eosio/chain/transaction_metadata.hpp>
  #include <eosio/chain/transaction.hpp>
  #include <boost/tuple/tuple_io.hpp>
  #include <eosio/chain/database_utils.hpp>
  #include <eosio/chain/memory_db.hpp>
  #include <eosio/chain/config_on_chain.hpp>
  #include <algorithm>
  
  namespace eosio { namespace chain { namespace resource_limits {
  
  using resource_index_set = index_set<
     resource_limits_index,
     resource_usage_index,
     resource_limits_state_index,
     resource_limits_config_index
  >;
  
  static_assert( config::rate_limiting_precision > 0, "config::rate_limiting_precision must be positive" );
  
  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 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.
     EOS_ASSERT( periods > 0, resource_limit_exception, "elastic limit parameter 'periods' cannot be zero" );
     EOS_ASSERT( contract_rate.denominator > 0, resource_limit_exception, "elastic limit parameter 'contract_rate' is not a well-defined ratio" );
     EOS_ASSERT( expand_rate.denominator > 0, resource_limit_exception, "elastic limit parameter 'expand_rate' is not a well-defined ratio" );
  }
  
  
  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));
  }
  
  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);
  }
  
  void resource_limits_manager::add_indices() {
     resource_index_set::add_indices(_db);
  }
  
  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;
     });
  }
  
  void resource_limits_manager::add_to_snapshot( const snapshot_writer_ptr& snapshot ) const {
     resource_index_set::walk_indices([this, &snapshot]( auto utils ){
        snapshot->write_section<typename decltype(utils)::index_t::value_type>([this]( auto& section ){
           decltype(utils)::walk(_db, [this, &section]( const auto &row ) {
              section.add_row(row, _db);
           });
        });
     });
  }
  
  void resource_limits_manager::read_from_snapshot( const snapshot_reader_ptr& snapshot ) {
     resource_index_set::walk_indices([this, &snapshot]( auto utils ){
        snapshot->read_section<typename decltype(utils)::index_t::value_type>([this]( auto& section ) {
           bool more = !section.empty();
           while(more) {
              decltype(utils)::create(_db, [this, &section, &more]( auto &row ) {
                 more = section.read_row(row, _db);
              });
           }
        });
     });
  }
  
  void resource_limits_manager::initialize_account(const account_name& account) {
     _db.create<resource_limits_object>([&]( resource_limits_object& bl ) {
        bl.owner = account;
     });
  
     _db.create<resource_usage_object>([&]( resource_usage_object& bu ) {
        bu.owner = account;
     });
  }
  
  void resource_limits_manager::set_block_parameters(const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ) {
     cpu_limit_parameters.validate();
     net_limit_parameters.validate();
     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;
     });
  }
  
  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 );
        });
     }
  }
  
  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" );
  }
  
  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;
     });
  }
  
  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 );
  
     if( ram_bytes >= 0 ) {
        EOS_ASSERT( usage.ram_usage <= static_cast<uint64_t>(ram_bytes), ram_usage_exceeded,
                    "account ${account} has insufficient ram; needs ${needs} bytes has ${available} bytes",
                    ("account", account)("needs",usage.ram_usage)("available",ram_bytes)              );
     }
  }
  
  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;
  }
  
  
  bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) {
     //const auto& usage = _db.get<resource_usage_object,by_owner>( account );
     /*
      * 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();
  
     bool decreased_limit = false;
  
     if( ram_bytes >= 0 ) {
  
        decreased_limit = ( (limits.ram_bytes < 0) || (ram_bytes < limits.ram_bytes) );
  
        /*
        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));
        }
        */
     }
  
     _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;
     });
  
     return decreased_limit;
  }
  
  inline int64_t get_account_ram_limit( database& db, const account_name& name ) {
     // every account can use 8k ram free default
     const int64_t init_ram_size = get_num_config_on_chain(db, config::res_typ::free_ram_per_account, 8*1024);
     // if is account by system use ram unlimit,
     // if is a common account with -1 ram limit use init limit
     if(    name == config::system_account_name
         || name == N(force.ram) // keep for some func
         || name == N(force.cpu) // keep for some func
         || name == N(force.net) // keep for some func
         || name == N(force.test) // keep for some func
         || name == config::token_account_name
         || name == config::msig_account_name
         || name == config::bios_account_name ) {
        return -1;
     }
  
     memory_db::vote4ram_info vote_info;
     const auto ok = memory_db{db}.get(
           config::system_account_name,
           config::system_account_name,
           N(vote4ramsum),
           name, vote_info);
  
     if(!ok) {
        return init_ram_size;
     }
  
     // default is 100 eos for 10kb
     const int64_t ram_rent = get_num_config_on_chain(db, config::res_typ::ram_rent_b_per_eos, 10240);
     const int64_t staked   = vote_info.staked.get_amount();
  
     // 1 eos for 1 kb, note because staked cannot too much by limit
     const auto res = ((staked * ram_rent) / 1000000);
  
     return res + init_ram_size;
  }
  
  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 = get_account_ram_limit( _db, account );
        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 = get_account_ram_limit( _db, account );
        net_weight = buo.net_weight;
        cpu_weight = buo.cpu_weight;
     }
  }
  
  
  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 >= static_cast<uint64_t>(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 >= static_cast<uint64_t>(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);
        }
     });
  }
  
  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;
  
     });
  
  }
  
  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;
  }
  
  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, bool elastic ) const {
     auto arl = get_account_cpu_limit_ex(name, elastic);
     return arl.available;
  }
  
  account_resource_limit resource_limits_manager::get_account_cpu_limit_ex( const account_name& name, bool elastic) 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;
  
     uint128_t virtual_cpu_capacity_in_window = (uint128_t)(elastic ? state.virtual_cpu_limit : config.cpu_limit_parameters.max) * 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;
  }
  
  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);
     return arl.available;
  }
  
  account_resource_limit resource_limits_manager::get_account_net_limit_ex( const account_name& name, bool elastic) const {
     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);
  
     int64_t net_weight, x, y;
     get_account_limits( name, x, net_weight, y );
  
     if( net_weight < 0 || state.total_net_weight == 0) {
        return { -1, -1, -1 };
     }
  
     account_resource_limit arl;
  
     uint128_t window_size = config.account_net_usage_average_window;
  
     uint128_t virtual_network_capacity_in_window = (uint128_t)(elastic ? state.virtual_net_limit : config.net_limit_parameters.max) * window_size;
     uint128_t user_weight     = (uint128_t)net_weight;
     uint128_t all_user_weight = (uint128_t)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  = impl::integer_divide_ceil((uint128_t)usage.net_usage.value_ex * window_size, (uint128_t)config::rate_limiting_precision);
  
     if( max_user_use_in_window <= net_used_in_window )
        arl.available = 0;
     else
        arl.available = impl::downgrade_cast<int64_t>(max_user_use_in_window - net_used_in_window);
  
     arl.used = impl::downgrade_cast<int64_t>(net_used_in_window);
     arl.max = impl::downgrade_cast<int64_t>(max_user_use_in_window);
     return arl;
  }
  
  } } } /// eosio::chain::resource_limits
  