解析 deferred_transaction 的原理

eosio::transaction out;

out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );

out.delay_sec = refund_delay;

cancel_deferred( from ); // TODO: Remove this line when replacing deferred trxs is fixed

out.send( from, from, true );

 

先声明一个transaction. Push action之后,设置延迟的时间就可以了, cancel_deferred是用来取消一个deferred_transaction的。 一个deferred_transaction 通过send_id 和 contract_account来作为唯一标识符, payer 为该 deferred_trx RAM资源消耗的支付者(即该 trx 所占的内存,执行完后返回), deferred_trx 的 CPU NET 由当前 trx 中 actions 的 author 支付。 最后用send函数就可以执行一个延迟交易了

//eosiolib/transaction.hpp

void send(const uint128_t& sender_id, account_name payer, bool replace_existing = false) const {

   auto serialize = pack(*this);

   send_deferred(sender_id, payer, serialize.data(), serialize.size(), replace_existing);

}

 

send函数将数据打包后调用wasm提供的接口。

//libraries/chain/wasm_interface.cpp

void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr data, size_t data_len, uint32_t replace_existing) {

   try {

      transaction trx;

      fc::raw::unpack(data, data_len, trx);

      //调用apply_context的函数。

      context.schedule_deferred_transaction(sender_id, payer, std::move(trx), replace_existing);

   } FC_RETHROW_EXCEPTIONS(warn, "data as hex: ${data}", ("data", fc::to_hex(data, data_len)))

}

 

void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing ) {

  //deferred_transaction 不需要有上下文无关的action。

   EOS_ASSERT( trx.context_free_actions.size() == 0, cfa_inside_generated_tx, "context free actions are not currently allowed in generated transactions" );

  //给trx添加必要的参数

   trx.expiration = control.pending_block_time() + fc::microseconds(999'999); // Rounds up to nearest second (makes expiration check unnecessary)

   trx.set_reference_block(control.head_block_id()); // No TaPoS check necessary

   control.validate_referenced_accounts( trx );

 

   // Charge ahead of time for the additional net usage needed to retire the deferred transaction

   // whether that be by successfully executing, soft failure, hard failure, or expiration.

   const auto& cfg = control.get_global_properties().configuration;

   trx_context.add_net_usage( static_cast(cfg.base_per_transaction_net_usage)

                               + static_cast(config::transaction_id_net_usage) ); // Will exit early if net usage cannot be payed.

 

   auto delay = fc::seconds(trx.delay_sec);

 

   if( !control.skip_auth_check() && !privileged ) { // Do not need to check authorization if replayng irreversible block or if contract is privileged

      if( payer != receiver ) {

         require_authorization(payer); /// uses payer's storage

      }

 

      // if a contract is deferring only actions to itself then there is no need

      // to check permissions, it could have done everything anyway.

      bool check_auth = false;

      for( const auto& act : trx.actions ) {

         if( act.account != receiver ) {

            check_auth = true;

            break;

         }

      }

      if( check_auth ) {

         control.get_authorization_manager()

                .check_authorization( trx.actions,

                                      {},

                                      {{receiver, config::eosio_code_name}},

                                      delay,

                                      std::bind(&transaction_context::checktime, &this->trx_context),

                                      false

                                    );

      }

   }

 

   uint32_t trx_size = 0;

   auto& d = control.db();

 //当deferred_transaction不存在时,往数据库添加,存在时,如果不允许替换则中断,否则更新。

   if ( auto ptr = d.find(boost::make_tuple(receiver, sender_id)) ) {

      EOS_ASSERT( replace_existing, deferred_tx_duplicate, "deferred transaction with the same sender_id and payer already exists" );

 

      // TODO: Remove the following subjective check when the deferred trx replacement RAM bug has been fixed with a hard fork.

      EOS_ASSERT( !control.is_producing_block(), subjective_block_production_exception,

                  "Replacing a deferred transaction is temporarily disabled." );

 

      // TODO: The logic of the next line needs to be incorporated into the next hard fork.

      // trx_context.add_ram_usage( ptr->payer, -(config::billable_size_v + ptr->packed_trx.size()) );

 

      d.modify( *ptr, [&]( auto& gtx ) {

            gtx.sender      = receiver;

            gtx.sender_id   = sender_id;

            gtx.payer       = payer;

            gtx.published   = control.pending_block_time();

            gtx.delay_until = gtx.published + delay;

            gtx.expiration  = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window);

 

            trx_size = gtx.set( trx );

         });

   } else {

      d.create( [&]( auto& gtx ) {

            gtx.trx_id      = trx.id();

            gtx.sender      = receiver;

            gtx.sender_id   = sender_id;

            gtx.payer       = payer;

            gtx.published   = control.pending_block_time();

            gtx.delay_until = gtx.published + delay;

            gtx.expiration  = gtx.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window);

 

            trx_size = gtx.set( trx );

         });

   }

 

   trx_context.add_ram_usage( payer, (config::billable_size_v + trx_size) );

}

 

好了, 数据库已经有deferred_transaction的数据了,那么是在什么时候执行它呢。先看看generated_transaction_object 用的是什么索引。

using generated_transaction_multi_index = chainbase::shared_multi_index_container<

      generated_transaction_object,

      indexed_by<

         ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER(generated_transaction_object, generated_transaction_object::id_type, id)>,

         ordered_unique< tag, BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, transaction_id_type, trx_id)>,

         ordered_unique< tag,

            composite_key< generated_transaction_object,

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, expiration),

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)

            >

         >,

         ordered_unique< tag,

            composite_key< generated_transaction_object,

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, time_point, delay_until),

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, generated_transaction_object::id_type, id)

            >

         >,

         ordered_unique< tag,

            composite_key< generated_transaction_object,

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, account_name, sender),

               BOOST_MULTI_INDEX_MEMBER( generated_transaction_object, uint128_t, sender_id)

            >

         >

      >

   >;

 

看看哪里引用了这个索引。通过查找我们会在controller.cpp发现get_scheduled_transactions函数。

//获取所有delay transaction的索引

vector controller::get_scheduled_transactions() const {

   const auto& idx = db().get_index();

 

   vector result;

 

   static const size_t max_reserve = 64;

   result.reserve(std::min(idx.size(), max_reserve));

 

   auto itr = idx.begin();

   while( itr != idx.end() && itr->delay_until <= pending_block_time() ) {

      result.emplace_back(itr->trx_id);

      ++itr;

   }

   return result;

}

 

这个函数会在producer_plugin使用。在start_block生产区块的时候执行delay_transaction 并打包进块里。

if (_pending_block_mode == pending_block_mode::producing) {

   auto& blacklist_by_id = _blacklisted_transactions.get();

   auto& blacklist_by_expiry = _blacklisted_transactions.get();

   auto now = fc::time_point::now();

   while (!blacklist_by_expiry.empty() && blacklist_by_expiry.begin()->expiry <= now) {

      blacklist_by_expiry.erase(blacklist_by_expiry.begin());

   }

   //获取delay transaction的id

   auto scheduled_trxs = chain.get_scheduled_transactions();

 

   for (const auto& trx : scheduled_trxs) {

      if (block_time <= fc::time_point::now()) exhausted = true;

      if (exhausted) {

         break;

      }

 

      // configurable ratio of incoming txns vs deferred txns

      while (_incoming_trx_weight >= 1.0 && orig_pending_txn_size && _pending_incoming_transactions.size()) {

         auto e = _pending_incoming_transactions.front();

         _pending_incoming_transactions.pop_front();

         --orig_pending_txn_size;

         _incoming_trx_weight -= 1.0;

         on_incoming_transaction_async(std::get<0>(e), std::get<1>(e), std::get<2>(e));

      }

 

      if (block_time <= fc::time_point::now()) {

         exhausted = true;

         break;

      }

 

      if (blacklist_by_id.find(trx) != blacklist_by_id.end()) {

         continue;

      }

 

      try {

         auto deadline = fc::time_point::now() + fc::milliseconds(_max_transaction_time_ms);

         bool deadline_is_subjective = false;

         if (_max_transaction_time_ms < 0 || (_pending_block_mode == pending_block_mode::producing && block_time < deadline)) {

            deadline_is_subjective = true;

            deadline = block_time;

         }

         //执行

         auto trace = chain.push_scheduled_transaction(trx, deadline);

         if (trace->except) {

            if (failure_is_subjective(*trace->except, deadline_is_subjective)) {

               exhausted = true;

            } else {

               auto expiration = fc::time_point::now() + fc::seconds(chain.get_global_properties().configuration.deferred_trx_expiration_window);

               // this failed our configured maximum transaction time, we don't want to replay it add it to a blacklist

               _blacklisted_transactions.insert(transaction_id_with_expiry{trx, expiration});

            }

         }

      } catch ( const guard_exception& e ) {

         app().get_plugin().handle_guard_exception(e);

         return start_block_result::failed;

      } FC_LOG_AND_DROP();

 

      _incoming_trx_weight += _incoming_defer_ratio;

      if (!orig_pending_txn_size) _incoming_trx_weight = 0.0;

   }

}

 

push_scheduled_transaction 的逻辑跟push_transaction 类似, 这里就不再赘述了。

值得一提的是,通过合约建立的延时交易,在block上只会存他的transaction_id。 不会存详情。 通过push_transaction的会把整个transaction都存在block上不会只存transaction_id。

你可能感兴趣的:(Eos)