EOSIO源码分析 - 交易transaction投递执行流程

什么是交易 transaction

在前面我们讲什么是EOSIO的时候,说过EOSIO是一个依靠职能合约来执行任务的企业操作系统,EOSIO提供的是一个最基础的运行框架,合约负责的是真正的业务逻辑,外部业务,如DApp需要依靠自己的合约来完成自己的业务,那DApp是怎么和自己的职能合约进行交互呢?
答案是依靠EOSIO的交易消息transaction,所以简单来说transaction就是外部系统用来和在EOSIO中运行的职能合约进行交互的消息,是一种特殊的消息格式

Transaction的格式

Transaction相关的核心结构与变量如下

// 基类信息,记录了过期时间,引用块号,延迟执行等信息
struct transaction_header {
    
    
  time_point_sec         expiration;   ///< the time at which a transaction expires
  uint16_t               ref_block_num       = 0U; ///< specifies a block num in the last 2^16 blocks.
  uint32_t               ref_block_prefix    = 0UL; ///< specifies the lower 32 bits of the blockid at get_ref_blocknum
  fc::unsigned_int       max_net_usage_words = 0UL; /// upper limit on total network bandwidth (in 8 byte words) billed for this transaction
  uint8_t                max_cpu_usage_ms    = 0; /// upper limit on the total CPU time billed for this transaction
  fc::unsigned_int       delay_sec           = 0UL; /// number of seconds to delay this transaction for during which it may be canceled.
};

// 继承 transaction_header,记录交互的真正数据,actions
struct transaction : public transaction_header {
    
    
  vector<action>         context_free_actions;		// 不进行资源消耗的action
  vector<action>         actions;					// 真正的交易执行数据
  extensions_type        transaction_extensions;
};

// 签过名的交易,投递到EOSIO中的交易必须要进行签名
struct signed_transaction : public transaction
{
    
    
   vector<signature_type>    signatures;
   vector<bytes>             context_free_data; ///< for each context-free action, there is an entry here
};

// 对签名交易的序列化打包,存储,传输都是使用的该结构
struct packed_transaction : fc::reflect_init {
    
    
  vector<signature_type>                   signatures;
  fc::enum_type<uint8_t, compression_type> compression;
  bytes                                    packed_context_free_data;
  bytes                                    packed_trx;
};
// action基类
struct action_base {
    
    
   account_name             account;		//执行的账户名,也就是合约账户
   action_name              name;			//执行的合约动作名称
   vector<permission_level> authorization;	//权限
};

struct action : public action_base {
    
    		
   bytes                      data;			//动作参数,利用ABI打包出来的二进制参数
};

Transaction的投递过程

在前面讲解中,知道在EOSIO中,外部访问都是通过http接口来访问的,所以transaction的投递也不例外,具体的代码流程如下

// Http调用处理
// 这三个函数都可以投递交易,具体的调用可以参考在线文档
auto& _http_plugin = app().get_plugin<http_plugin>();
ro_api.set_shorten_abi_errors( !_http_plugin.verbose_errors() );
_http_plugin.add_api({
    
    
   CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202, http_params_types::params_required),
   CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202, http_params_types::params_required),
   CHAIN_RW_CALL_ASYNC(send_transaction, chain_apis::read_write::send_transaction_results, 202, http_params_types::params_required)
});

// 以push_transaction为例,最终业务逻辑在chain_plugin插件的push_transaction函数中
void read_write::push_transaction(const read_write::push_transaction_params& params, next_function<read_write::push_transaction_results> next) {
    
    
   try {
    
    
   	  // 创建abi解析器,并将解析出来的交易保存进入变量 input_trx_v0
   	  // 注意:在EOSIO后期版本中,解析交易的过程ABI完全没有用到,这里这样写,完全是为了兼容以前的版本
      packed_transaction_v0 input_trx_v0;
      auto resolver = make_resolver(this, abi_serializer::create_yield_function( abi_serializer_max_time ));
      packed_transaction_ptr input_trx;
      try {
    
    
         abi_serializer::from_variant(params, input_trx_v0, std::move( resolver ), abi_serializer::create_yield_function( abi_serializer_max_time ));
         input_trx = std::make_shared<packed_transaction>( std::move( input_trx_v0 ), true );
      } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction")
      // 如果交易解析成功,则将交易投递给接下来的处理函数,业务逻辑在producer_plugin中
      // 业务逻辑异步处理完成之后,将结果回调到result中,并且将最终的结果发送到客户端
      app().get_method<incoming::methods::transaction_async>()(input_trx, true,
            [this, token=trx_trace.get_token(), input_trx, next]
            (const std::variant<fc::exception_ptr, transaction_trace_ptr>& result) -> void {
    
    
            	// 进行结果回调处理
            } CATCH_AND_CALL(next);
         }
      });
   } catch ( boost::interprocess::bad_alloc& ) {
    
    
      chain_plugin::handle_db_exhaustion();	//异常处理
   } catch ( const std::bad_alloc& ) {
    
    
      chain_plugin::handle_bad_alloc();	//异常处理
   } CATCH_AND_CALL(next);	//异常处理
}
// 继续producer_plugin中的transaction投递
void on_incoming_transaction_async(const packed_transaction_ptr& trx, bool persist_until_expired, next_function<transaction_trace_ptr> next) {
    
    
  chain::controller& chain = chain_plug->chain();
  const auto max_trx_time_ms = _max_transaction_time_ms.load();
  fc::microseconds max_trx_cpu_usage = max_trx_time_ms < 0 ? fc::microseconds::maximum() : fc::milliseconds( max_trx_time_ms );

  // 利用线程池,进行签名解析,最核心的是要解出签名时用到的公钥key
  auto future = transaction_metadata::start_recover_keys( trx, _thread_pool->get_executor(),
         chain.get_chain_id(), fc::microseconds( max_trx_cpu_usage ), chain.configured_subjective_signature_length_limit() );

  boost::asio::post(_thread_pool->get_executor(), [self = this, future{
    
    std::move(future)}, persist_until_expired,
                                                   next{
    
    std::move(next)}, trx]() mutable {
    
    
     if( future.valid() ) {
    
    
        future.wait();
        // 签名解析完成时候,接下来将交易交给合约线程进行执行,注意这里合约执行实在单线程中执行的,为什么是单线程,后面我们再详细说明
        // 合约执行最终的处理实在 process_incoming_transaction_async 函数中
        app().post( priority::low, [self, future{
    
    std::move(future)}, persist_until_expired, next{
    
    std::move( next )}, trx{
    
    std::move(trx)}]() mutable {
    
    
           auto exception_handler = [&next, trx{
    
    std::move(trx)}](fc::exception_ptr ex) {
    
    
              fc_dlog(_trx_failed_trace_log, "[TRX_TRACE] Speculative execution is REJECTING tx: ${txid}, auth: ${a} : ${why} ",
                     ("txid", trx->id())("a",trx->get_transaction().first_authorizer())("why",ex->what()));
              next(ex);
           };
           try {
    
    
              auto result = future.get();
              if( !self->process_incoming_transaction_async( result, persist_until_expired, next ) ) {
    
    
                 if( self->_pending_block_mode == pending_block_mode::producing ) {
    
    
                    self->schedule_maybe_produce_block( true );
                 } else {
    
    
                    self->restart_speculative_block();
                 }
              }
           } CATCH_AND_CALL(exception_handler);
        } );
     }
  });
}

在上面的流程中,是交易从api接口到执行的流程,依次经过

  • 交易反序列化
  • 投递给解签线程进行签名解析
  • 解签完成之后最终投递给执行线程
  • 执行完成之后返回结果给客户端
  • 如果节点正在生产中,交易是需要暂存执行的

Transaction的执行过程

注意篇幅相关,我们只分析解释核心代码段的核心部分

bool process_incoming_transaction_async(const transaction_metadata_ptr& trx, bool persist_until_expired, next_function<transaction_trace_ptr> next) {
    
    
  bool exhausted = false;
  chain::controller& chain = chain_plug->chain();
  try {
    
    
     const auto& id = trx->id();
     // 判断交易是否过期,重复
     // 注意:如果当前系统正处于生产块的过程中,则投递过来的交易必须要暂存起来,等生产完成之后再执行,这个过程牵扯到块的生产与同步,后面我们再讲
     if( !chain.is_building_block()) {
    
    
        _unapplied_transactions.add_incoming( trx, persist_until_expired, next );
        return true;
     }
     // 调用chain.push_transaction函数进行交易执行
     auto trace = chain.push_transaction( trx, deadline, trx->billed_cpu_time_us, false, sub_bill );
     if( trace->except ) {
    
    
        // 如果交易执行失败了
        // 注意:这里失败的判断,是要区分他为社么失败的,如果是执行超时空,并且是由于块生产引起的,是要重新运行的,这个也和块的同步有关
        if( exception_is_exhausted( *trace->except, deadline_is_subjective )) {
    
    
           _unapplied_transactions.add_incoming( trx, persist_until_expired, next );
           if( _pending_block_mode == pending_block_mode::producing ) {
    
    
           } else {
    
    
           }
           exhausted = block_is_exhausted();
        } else {
    
    
           _subjective_billing.subjective_bill_failure( first_auth, trace->elapsed, fc::time_point::now() );
           auto e_ptr = trace->except->dynamic_copy_exception();
           send_response( e_ptr );
        }
     } else {
    
    
        //交易执行成功
        //注意:这里如果交易是API投递过来的要暂存,这个和后面块同步有关,特别注意参数persist_until_expired
        if( persist_until_expired && !_disable_persist_until_expired ) {
    
    
           // if this trx didnt fail/soft-fail and the persist flag is set, store its ID so that we can
           // ensure its applied to all future speculative blocks as well.
           // No need to subjective bill since it will be re-applied
           _unapplied_transactions.add_persisted( trx );
        } else {
    
    
           // if db_read_mode SPECULATIVE then trx is in the pending block and not immediately reverted
           _subjective_billing.subjective_bill( trx->id(), expire, first_auth, trace->elapsed,
                                                chain.get_read_mode() == chain::db_read_mode::SPECULATIVE );
        }
        // 消息广播,结果返回
        send_response( trace );
     }
  } catch ( ... ) {
    
    
    // 异常处理
  } CATCH_AND_CALL(send_response);
  return !exhausted;
}

transaction_trace_ptr push_transaction( ... )
{
    
    
   transaction_trace_ptr trace;
   try {
    
    
      auto start = fc::time_point::now();
      // 构建交易执行的上下文,并且设置相关的参数
      transaction_checktime_timer trx_timer(timer);
      transaction_context trx_context(self, *trx->packed_trx(), std::move(trx_timer), start);
      if ((bool)subjective_cpu_leeway && pending->_block_status == controller::block_status::incomplete) {
    
    
         trx_context.leeway = *subjective_cpu_leeway;
      }
      trx_context.deadline = deadline;
      trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time;
      trx_context.billed_cpu_time_us = billed_cpu_time_us;
      trx_context.subjective_cpu_bill_us = subjective_cpu_bill_us;
      trace = trx_context.trace;

      auto handle_exception =[&](const auto& e)
      {
    
    
         trace->error_code = controller::convert_exception_to_error_code( e );
         trace->except = e;
         trace->except_ptr = std::current_exception();
         trace->elapsed = fc::time_point::now() - trx_context.start;
      };

      try {
    
    
         const transaction& trn = trx->packed_trx()->get_transaction();
         // 如果当前此笔交易需要鉴权,则要进行权限相关的鉴定操作
         // 注意:一般来说外部投递过来的交易都是需要鉴权的,但是如果在重播行为中,交易是不需要重播的
         if( check_auth ) {
    
    
            authorization.check_authorization(
                    trn.actions,
                    trx->recovered_keys(),
                    {
    
    },
                    trx_context.delay,
                    [&trx_context](){
    
     trx_context.checktime(); },
                    false
            );
         }
         // 调用exec函数,执行交易
         trx_context.exec();
         trx_context.finalize(); // Automatically rounds up network and CPU usage in trace and bills payers if successful

		 // 注意这里有个隐式交易的概念
		 // 外部投递过来的交易都是显示的
		 // onblock交易,还有合约中投递过来的交易等都是隐式的,隐式交易是不需要参与同步的,它一般只存在于内存中,onblock和块的同步,生产相关后面再讲
         if (!trx->implicit) {
    
    
            transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0))
                                                 ? transaction_receipt::executed
                                                 : transaction_receipt::delayed;
            trace->receipt = push_receipt(*trx->packed_trx(), s, trx_context.billed_cpu_time_us, trace->net_usage);
            trx->billed_cpu_time_us = trx_context.billed_cpu_time_us;
            std::get<building_block>(pending->_block_stage)._pending_trx_metas.emplace_back(trx);
         } else {
    
    
            transaction_receipt_header r;
            r.status = transaction_receipt::executed;
            r.cpu_usage_us = trx_context.billed_cpu_time_us;
            r.net_usage_words = trace->net_usage / 8;
            trace->receipt = r;
         }

         fc::move_append( std::get<building_block>(pending->_block_stage)._action_receipt_digests,
                          std::move(trx_context.executed_action_receipt_digests) );

		 // 对于执行成功的交易
		 // 如果当前节点不是生产节点,是不能执行,需要回滚的
		 // 如果执行成功了,特别需要注意它执行了trx_context.squash(),这个函数类似git的提交,和交易回滚密切相关,最终执行chainbase的squash()
		 // 如果执行失败了,这里并没有调用trx_context.undo(),实际上它在chainbase中利用析构调用了,当然再次调用trx_context.undo()也是可以的,具体的流程可以去细读chainbase的代码
         if ( read_mode != db_read_mode::SPECULATIVE && pending->_block_status == controller::block_status::incomplete ) {
    
    
            //this may happen automatically in destructor, but I prefere make it more explicit
            trx_context.undo();
         } else {
    
    
            restore.cancel();
            trx_context.squash();
         }
         return trace;
      } catch( ... ) {
    
    
         // 进行异常处理
      }
      return trace;
   } FC_CAPTURE_AND_RETHROW((trace))
} /// push_transaction

// 合约继续在exec中执行
// 从处理过程可以看出交易是按照action一个个执行,所以action是执行的最小单位
void transaction_context::exec() {
    
    
   const transaction& trx = packed_trx.get_transaction();
   if( apply_context_free ) {
    
    
      // 派发context_free_actions
      for( const auto& act : trx.context_free_actions ) {
    
    
         schedule_action( act, act.account, true, 0, 0 );
      }
   }

   if( delay == fc::microseconds() ) {
    
    
      //如果交易不延迟执行,则立即派阀
      for( const auto& act : trx.actions ) {
    
    
         schedule_action( act, act.account, false, 0, 0 );
      }
   }

   // 执行
   auto& action_traces = trace->action_traces;
   uint32_t num_original_actions_to_execute = action_traces.size();
   for( uint32_t i = 1; i <= num_original_actions_to_execute; ++i ) {
    
    
      execute_action( i, 0 );
   }
   // 如果需要延迟执行,暂存交易
   if( delay != fc::microseconds() ) {
    
    
      schedule_transaction();
   }
}

// 按照单个action的顺序依次执行,最终会调用到如下函数
void apply_context::exec_one()
{
    
    
   auto start = fc::time_point::now();
   try {
    
    
      try {
    
    
         receiver_account = &db.get<account_metadata_object,by_name>( receiver );
         if( !(context_free && control.skip_trx_checks()) ) {
    
    
            privileged = receiver_account->is_privileged();
            auto native = control.find_apply_handler( receiver, act->account, act->name );
            // 如果是内部系统合约,则执行native操作
            if( native ) {
    
    
               if( trx_context.enforce_whiteblacklist && control.is_producing_block() ) {
    
    
                  control.check_contract_list( receiver );
                  control.check_action_list( act->account, act->name );
               }
               (*native)( *this );
            }
			// 如果是普通合约,则需要在合约wasm虚拟机中执行
            if( ( receiver_account->code_hash != digest_type() ) &&
                  (  !( act->account == config::system_account_name
                        && act->name == "setcode"_n
                        && receiver == config::system_account_name )
                     || control.is_builtin_activated( builtin_protocol_feature_t::forward_setcode )
                  )
            ) {
    
    
               // 判断黑白名单,是否可以执行
               if( trx_context.enforce_whiteblacklist && control.is_producing_block() ) {
    
    
                  control.check_contract_list( receiver );
                  control.check_action_list( act->account, act->name );
               }
               // 调用虚拟机执行
               try {
    
    
                  control.get_wasm_interface().apply( receiver_account->code_hash, receiver_account->vm_type, receiver_account->vm_version, *this );
               } catch( const wasm_exit& ) {
    
    }
            }
         }
      } FC_RETHROW_EXCEPTIONS( warn, "pending console output: ${console}", ("console", _pending_console_output) )

      if( control.is_builtin_activated( builtin_protocol_feature_t::action_return_value ) ) {
    
    
         act_digest =   generate_action_digest(
                           [this](const char* data, uint32_t datalen) {
    
    
                              return trx_context.hash_with_checktime<digest_type>(data, datalen);
                           },
                           *act,
                           action_return_value
                        );
      } else {
    
    
         act_digest = digest_type::hash(*act);
      }
   } catch ( const std::bad_alloc& ) {
    
    
       ... //异常处理
   }
}

上面的代码,分析了在系统接收到具体的交易之后,接下来怎么执行,总结来说,经历过以下大的步骤

  • 构建上下文执行环境
  • 交易鉴权,分析该账户有没有权限来执行这笔交易
  • 交易又分为隐式交易和非隐式交易
  • 合约又分为系统合约和非系统合约
  • 执行的过程如果发生了异常,数据库会发生回滚
  • 最终具体的操作是按照action单元在wasm虚拟机中执行的

总结

交易的执行的流程非常复杂,大的过程分为虚拟机内执行和虚拟机之外执行,此文主要分析了在虚拟机之外执行,在虚拟机之外执行涉及了加密与解密,序列化与反序列化,块的生产与同步,chainbase的回滚与确认等相关结束点,同时看完之后我们又有了以下疑问

  • 合约为什么在单线程中执行?能不能在多线程执行
  • 交易是如何进行鉴权的,eosio的账号系统是个什么样子
  • 节点在生产中,暂存起来的交易接下来会做如何处理
  • chainbase是如何进行数据回滚的,是按照交易回滚的,还是按照什么原则进行回滚的
  • 合约为什么会出现系统合约与非系统合约,这个系统合约是个什么回事
  • onblock的具体流程是什么,块是如何进行生产的
  • ABI的作用是什么?Action的参数是如何传递给虚拟机的
  • 如果我们要给进行交易gas收费,如何改动代码

随着代码的深入,我们的疑问也就愈多,多读代码, 我们来一起学习提高,加油,努力

猜你喜欢

转载自blog.csdn.net/whg1016/article/details/128642550
今日推荐