transaction_context.cpp 13.9 KB
Newer Older
D
Daniel Larimer 已提交
1
#include <eosio/chain/apply_context.hpp>
2
#include <eosio/chain/transaction_context.hpp>
D
Daniel Larimer 已提交
3
#include <eosio/chain/exceptions.hpp>
4
#include <eosio/chain/resource_limits.hpp>
5 6
#include <eosio/chain/generated_transaction_object.hpp>
#include <eosio/chain/transaction_object.hpp>
A
arhag 已提交
7
#include <eosio/chain/global_property_object.hpp>
D
Daniel Larimer 已提交
8 9 10

namespace eosio { namespace chain {

11 12
   transaction_context::transaction_context( controller& c,
                                             const signed_transaction& t,
13
                                             const transaction_id_type& trx_id )
14 15 16 17 18 19 20 21 22 23
   :control(c)
   ,trx(t)
   ,id(trx_id)
   ,undo_session(c.db().start_undo_session(true))
   ,trace(std::make_shared<transaction_trace>())
   ,net_usage(trace->net_usage)
   ,cpu_usage(trace->cpu_usage)
   {
      trace->id = id;
      executed.reserve( trx.total_actions() );
24 25 26 27 28 29 30
      is_input = false;
      apply_context_free = false;
   }

   void transaction_context::init(uint64_t initial_net_usage, uint64_t initial_cpu_usage )
   {
      FC_ASSERT( !is_initialized, "cannot initialize twice" );
31 32 33 34 35 36 37 38 39

      // Record accounts to be billed for network and CPU usage
      uint64_t determine_payers_cpu_cost = 0;
      for( const auto& act : trx.actions ) {
         for( const auto& auth : act.authorization ) {
            bill_to_accounts.insert( auth.actor );
            determine_payers_cpu_cost += config::determine_payers_cpu_overhead_per_authorization;
         }
      }
40
      validate_ram_usage.reserve( bill_to_accounts.size() );
D
Daniel Larimer 已提交
41

42
      // Calculate network and CPU usage limits and initial usage:
D
Daniel Larimer 已提交
43

44 45 46 47
      // Start with limits set in dynamic configuration
      const auto& cfg = control.get_global_properties().configuration;
      max_net = cfg.max_transaction_net_usage;
      max_cpu = cfg.max_transaction_cpu_usage;
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
      // Potentially lower limits to what is optionally set in the transaction header
      uint64_t trx_specified_net_usage_limit = static_cast<uint64_t>(trx.max_net_usage_words.value)*8;
      if( trx_specified_net_usage_limit > 0 )
         max_net = std::min( max_net, trx_specified_net_usage_limit );
      uint64_t trx_specified_cpu_usage_limit = static_cast<uint64_t>(trx.max_kcpu_usage.value)*1024;
      if( trx_specified_cpu_usage_limit > 0 )
         max_cpu = std::min( max_cpu, trx_specified_cpu_usage_limit );

      // Initial billing for network usage
      if( initial_net_usage > 0 )
         add_net_usage( initial_net_usage );

      // Initial billing for CPU usage known to be soon consumed
      add_cpu_usage( initial_cpu_usage
                     + static_cast<uint64_t>(cfg.base_per_transaction_cpu_usage)
                     + determine_payers_cpu_cost
                     + bill_to_accounts.size() * config::resource_processing_cpu_overhead_per_billed_account );
      // Fails early if current CPU usage is already greater than the current limit (which may still go lower).

      // Update usage values of accounts to reflect new time
69
      auto& rl = control.get_mutable_resource_limits_manager();
70
      rl.add_transaction_usage( bill_to_accounts, 0, 0, block_timestamp_type(control.pending_block_time()).slot );
71

72 73 74 75 76 77 78 79
      // Lower limits to what the billed accounts can afford to pay
      for( const auto& a : bill_to_accounts ) {
         auto net_limit = rl.get_account_net_limit(a);
         if( net_limit >= 0 )
            max_net = std::min( max_net, static_cast<uint64_t>(net_limit) ); // reduce max_net to the amount the account is able to pay
         auto cpu_limit = rl.get_account_cpu_limit(a);
         if( cpu_limit >= 0 )
            max_cpu = std::min( max_cpu, static_cast<uint64_t>(cpu_limit) ); // reduce max_cpu to the amount the account is able to pay
80 81
      }

82 83 84 85 86 87 88 89 90
      if( rl.get_block_net_limit() < max_net ) {
         max_net = rl.get_block_net_limit();
         net_limit_due_to_block = true;
      }
      if( rl.get_block_cpu_limit() < max_cpu ) {
         max_cpu = rl.get_block_cpu_limit();
         cpu_limit_due_to_block = true;
      }

91 92 93 94
      // Round down network and CPU usage limits so that comparison to actual usage is more efficient
      max_net = (max_net/8)*8;       // Round down to nearest multiple of word size (8 bytes)
      max_cpu = (max_cpu/1024)*1024; // Round down to nearest multiple of 1024
      if( initial_net_usage > 0 )
95
         check_net_usage();  // Fail early if current net usage is already greater than the calculated limit
96
      check_cpu_usage(); // Fail early if current CPU usage is already greater than the calculated limit
97 98 99 100 101 102 103 104 105

      is_initialized = true;
   }

   void transaction_context::init_for_implicit_trx( fc::time_point d, uint64_t initial_net_usage, uint64_t initial_cpu_usage )
   {
      published = control.pending_block_time();
      deadline = d;
      init( initial_net_usage, initial_cpu_usage );
106
   }
107

108 109 110 111 112 113 114 115 116 117 118
   void transaction_context::init_for_input_trx( fc::time_point d,
                                                 uint64_t packed_trx_unprunable_size,
                                                 uint64_t packed_trx_prunable_size    )
   {
      const auto& cfg = control.get_global_properties().configuration;

      uint64_t discounted_size_for_pruned_data = packed_trx_prunable_size;
      if( cfg.context_free_discount_net_usage_den > 0
          && cfg.context_free_discount_net_usage_num < cfg.context_free_discount_net_usage_den )
      {
         discounted_size_for_pruned_data *= cfg.context_free_discount_net_usage_num;
119 120
         discounted_size_for_pruned_data =  ( discounted_size_for_pruned_data + cfg.context_free_discount_net_usage_den - 1)
                                                                                    / cfg.context_free_discount_net_usage_den; // rounds up
121 122 123 124 125 126
      }

      uint64_t initial_net_usage = static_cast<uint64_t>(cfg.base_per_transaction_net_usage)
                                    + packed_trx_unprunable_size + discounted_size_for_pruned_data;

      if( trx.delay_sec.value > 0 ) {
127 128 129 130
          // If delayed, also charge ahead of time for the additional net usage needed to retire the delayed transaction
          // whether that be by successfully executing, soft failure, hard failure, or expiration.
         initial_net_usage += static_cast<uint64_t>(cfg.base_per_transaction_net_usage)
                               + static_cast<uint64_t>(config::transaction_id_net_usage);
131
      }
132 133 134 135 136

      published = control.pending_block_time();
      deadline = d;
      is_input = true;
      init( initial_net_usage, 0 );
137
   }
138

139 140
   void transaction_context::init_for_deferred_trx( fc::time_point d,
                                                    fc::time_point p )
141
   {
142 143
      published = p;
      deadline = d;
144 145
      trace->scheduled = true;
      apply_context_free = false;
146
      init( 0, 0 );
147 148 149
   }

   void transaction_context::exec() {
150 151
      FC_ASSERT( is_initialized, "must first initialize" );

152 153 154
      //trx.validate(); // Not needed anymore since overflow is prevented by using uint64_t instead of uint32_t
      control.validate_tapos( trx );
      control.validate_referenced_accounts( trx );
155

156 157 158 159
      if( is_input ) { /// signed transaction from user rather than a deferred transaction
         control.validate_expiration( trx );
         record_transaction( id, trx.expiration ); /// checks for dupes
      }
160

161 162
      if( apply_context_free ) {
         for( const auto& act : trx.context_free_actions ) {
163
            dispatch_action( act, true );
164 165 166 167 168
         }
      }

      if( delay == fc::microseconds() ) {
         for( const auto& act : trx.actions ) {
169
            dispatch_action( act );
170 171
         }
      } else {
172
         schedule_transaction();
D
Daniel Larimer 已提交
173 174
      }

175 176 177 178 179 180 181
      add_cpu_usage( validate_ram_usage.size() * config::ram_usage_validation_overhead_per_account );

      auto& rl = control.get_mutable_resource_limits_manager();
      for( auto a : validate_ram_usage ) {
         rl.verify_account_ram_usage( a );
      }

182 183 184 185 186
      net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes)
      cpu_usage = ((cpu_usage + 1023)/1024)*1024; // Round up to nearest multiple of 1024
      control.get_mutable_resource_limits_manager()
             .add_transaction_usage( bill_to_accounts, cpu_usage, net_usage,
                                     block_timestamp_type(control.pending_block_time()).slot );
187 188
   }

189
   void transaction_context::squash() {
D
Daniel Larimer 已提交
190 191 192
      undo_session.squash();
   }

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
   uint64_t transaction_context::add_action_cpu_usage( uint64_t u, bool context_free ) {
      const auto& cfg = control.get_global_properties().configuration;

      uint64_t discounted_cpu_usage = u;
      if( context_free && cfg.context_free_discount_cpu_usage_den > 0
          && cfg.context_free_discount_cpu_usage_num < cfg.context_free_discount_cpu_usage_den )
      {
         discounted_cpu_usage *= cfg.context_free_discount_cpu_usage_num;
         discounted_cpu_usage =  ( discounted_cpu_usage + cfg.context_free_discount_cpu_usage_den - 1)
                                                               / cfg.context_free_discount_cpu_usage_den; // rounds up
      }

      add_cpu_usage( discounted_cpu_usage );
      return discounted_cpu_usage;
   }

   uint64_t transaction_context::get_action_cpu_usage_limit( bool context_free )const {
      check_cpu_usage();
      uint64_t diff = max_cpu - cpu_usage;
      if( !context_free ) return diff;

      const auto& cfg = control.get_global_properties().configuration;
      uint64_t n = cfg.context_free_discount_cpu_usage_num;
      uint64_t d = cfg.context_free_discount_cpu_usage_den;

      if( d == 0 || n >= d ) return diff;

      if( n == 0 ) return std::numeric_limits<uint64_t>::max();

      return (diff * d)/n;
   }

225
   void transaction_context::check_net_usage()const {
226 227 228 229 230 231 232 233 234 235 236
      if( BOOST_UNLIKELY(net_usage > max_net) ) {
         if( BOOST_UNLIKELY( net_limit_due_to_block ) ) {
            EOS_THROW( tx_soft_net_usage_exceeded,
                       "not enough space left in block: ${actual_net_usage} > ${net_usage_limit}",
                       ("actual_net_usage", net_usage)("net_usage_limit", max_net) );
         } else {
            EOS_THROW( tx_net_usage_exceeded,
                       "net usage of transaction is too high: ${actual_net_usage} > ${net_usage_limit}",
                       ("actual_net_usage", net_usage)("net_usage_limit", max_net) );
         }
      }
237 238 239
   }

   void transaction_context::check_cpu_usage()const {
240 241 242 243 244 245 246 247 248 249 250
      if( BOOST_UNLIKELY(cpu_usage > max_cpu) ) {
         if( BOOST_UNLIKELY( cpu_limit_due_to_block ) ) {
            EOS_THROW( tx_soft_cpu_usage_exceeded,
                       "not enough CPU usage allotment left in block: ${actual_cpu_usage} > ${cpu_usage_limit}",
                       ("actual_cpu_usage", cpu_usage)("cpu_usage_limit", max_cpu) );
         } else {
            EOS_THROW( tx_cpu_usage_exceeded,
                       "cpu usage of transaction is too high: ${actual_cpu_usage} > ${cpu_usage_limit}",
                       ("actual_cpu_usage", cpu_usage)("cpu_usage_limit", max_cpu) );
         }
      }
251 252 253
   }

   void transaction_context::check_time()const {
254
      EOS_ASSERT( BOOST_LIKELY(fc::time_point::now() <= deadline), tx_deadline_exceeded, "deadline exceeded" );
255 256
   }

257 258 259 260 261 262 263 264
   void transaction_context::add_ram_usage( account_name account, int64_t ram_delta ) {
      auto& rl = control.get_mutable_resource_limits_manager();
      rl.add_pending_ram_usage( account, ram_delta );
      if( ram_delta > 0 ) {
         validate_ram_usage.insert( account );
      }
   }

D
Daniel Larimer 已提交
265
   void transaction_context::dispatch_action( const action& a, account_name receiver, bool context_free ) {
266 267 268
      apply_context  acontext( control, *this, a );
      acontext.context_free = context_free;
      acontext.receiver     = receiver;
269 270 271 272 273 274 275 276 277

      try {
         acontext.exec();
      } catch( const tx_cpu_usage_exceeded& e ) {
         add_action_cpu_usage( acontext.cpu_usage, context_free ); // Will update cpu_usage to latest value and throw appropriate exception
         FC_ASSERT(false, "should not have reached here" );
      } catch( ... ) {
         throw;
      }
D
Daniel Larimer 已提交
278 279

      fc::move_append(executed, move(acontext.executed) );
280

281
      trace->action_traces.emplace_back( move(acontext.trace) );
D
Daniel Larimer 已提交
282 283
   }

284
   void transaction_context::schedule_transaction() {
285 286 287 288 289 290 291 292
      // Charge ahead of time for the additional net usage needed to retire the delayed transaction
      // whether that be by successfully executing, soft failure, hard failure, or expiration.
      if( trx.delay_sec.value == 0 ) { // Do not double bill. Only charge if we have not already charged for the delay.
         const auto& cfg = control.get_global_properties().configuration;
         add_net_usage( static_cast<uint64_t>(cfg.base_per_transaction_net_usage)
                         + static_cast<uint64_t>(config::transaction_id_net_usage) ); // Will exit early if net usage cannot be payed.
      }

293 294
      auto first_auth = trx.first_authorizor();

295
      uint32_t trx_size = 0;
296 297 298
      const auto& cgto = control.db().create<generated_transaction_object>( [&]( auto& gto ) {
        gto.trx_id      = id;
        gto.payer       = first_auth;
299
        gto.sender      = account_name(); /// delayed transactions have no sender
300 301 302
        gto.sender_id   = transaction_id_to_sender_id( gto.trx_id );
        gto.published   = control.pending_block_time();
        gto.delay_until = gto.published + delay;
A
arhag 已提交
303
        gto.expiration  = gto.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window);
304
        trx_size = gto.set( trx );
305 306
      });

307
      add_ram_usage( cgto.payer, (config::billable_size_v<generated_transaction_object> + trx_size) );
308 309 310 311 312 313 314 315 316
   }

   void transaction_context::record_transaction( const transaction_id_type& id, fc::time_point_sec expire ) {
      try {
          control.db().create<transaction_object>([&](transaction_object& transaction) {
              transaction.trx_id = id;
              transaction.expiration = expire;
          });
      } catch ( ... ) {
317
          EOS_ASSERT( false, tx_duplicate,
318 319 320 321
                     "duplicate transaction ${id}", ("id", id ) );
      }
   } /// record_transaction

D
Daniel Larimer 已提交
322

D
Daniel Larimer 已提交
323
} } /// eosio::chain