跟原力一起玩转EOS源码-Push Transaction机制

EOS源码备忘-Push Transaction机制

这里我们讨论EOS Push Transaction 的逻辑,这块EOS与Eosforce实现有一些区别,我们会着重点出。 关于wasm相关的内容我们会有一片专门的文档分析。

我们这里通常将Transaction译做交易,其实这里应该是事务的意思。

  1. Transaction与Action
    在EOS中Transaction与Action是最重要的几个类型, 在EOS中,所有的链上行为都是Action,Transaction是一系列Action组成的事务。

EOS中使用继承体系划分trx与action结构,关系图如下:

transaction_header <- transaction <- signed_transaction <- deferred_transaction

                    |

            packed_transaction

1.1 Action

我们这里先看一下Action的声明:

// 权限结构structpermission_level{account_name actor; permission_name permission; }; …structaction{account_name account; action_name name;// 执行所需的权限vector authorization; bytes data; …// 打包成二进制templateTdata_as()const{ … } };

Action没有什么特别的内容,但要注意:

!> 在EOS中一个transaction中包含很多个action,而在Eosforce中一个trx只能包括一个action。

1.2 Transaction

下面我们分析一下transaction,这里简写为trx。

首先看下

/**

*  The transaction header contains the fixed-sized data

*  associated with each transaction. It is separated from

*  the transaction body to facilitate partial parsing of

*  transactions without requiring dynamic memory allocation.

*

*  All transactions have an expiration time after which they

*  may no longer be included in the blockchain. Once a block

*  with a block_header::timestamp greater than expiration is

*  deemed irreversible, then a user can safely trust the transaction

*  will never be included.

*

*  Each region is an independent blockchain, it is included as routing

*  information for inter-blockchain communication. A contract in this

*  region might generate or authorize a transaction intended for a foreign

*  region.

*/structtransaction_header{time_point_sec        expiration;///< trx超时时间uint16_tref_block_num      =0U;// 包含trx的block num 注意这个值是后2^16个块中uint32_tref_block_prefix    =0UL;// blockid的低32位fc::unsigned_int      max_net_usage_words =0UL;// 网络资源上限uint8_tmax_cpu_usage_ms    =0;// cpu资源上限fc::unsigned_int      delay_sec          =0UL;/// 延迟交易的延迟时间/**

  * @return the absolute block number given the relative ref_block_num

  * 计算ref_block_num

  */block_num_typeget_ref_blocknum( block_num_type head_blocknum )const{return((head_blocknum/0xffff)*0xffff) + head_blocknum%0xffff;      }voidset_reference_block(constblock_id_type& reference_block );boolverify_reference_block(constblock_id_type& reference_block )const;voidvalidate()const;  };

transaction_header包含一个trx中固定长度的数据,这里之所以要单独提出来主要是为了优化。

transaction视为交易体数据,这里主要是存储这个trx包含的action。

/**

*  A transaction consits of a set of messages which must all be applied or

*  all are rejected. These messages have access to data within the given

*  read and write scopes.

*/

// 在EOS中一个交易中 action要么全部执行,要么都不执行

struct transaction : public transaction_header {

  vector<action>        context_free_actions;

  vector<action>        actions;

  extensions_type        transaction_extensions;

  // 获取trx id

  transaction_id_type        id()const;

  digest_type                sig_digest( const chain_id_type& chain_id, const vector<bytes>& cfd = vector<bytes>() )const;

...

};

注意这里的context_free_actions,这里指上下文无关的Action,具体信息可以参见这里: https://medium.com/@bytemaster/eosio-development-update-272198df22c1https://github.com/EOSIO/eos/issues/1387。 如果一个Action执行时只依赖与transaction的数据,而不依赖与链上的状态,这样的action可以并发的执行。

另外一个值得注意的是trx id:

transaction_id_type transaction::id()const{ digest_type::encoder enc; fc::raw::pack( enc, *this);returnenc.result();}

!> Eosforce不同

在Eosforce中为了添加手续费信息,trx与EOS结构不同,主要是增加了fee, 在transaction中:

struct transaction : public transaction_header {

  vector<action>        context_free_actions;

  vector<action>        actions;

  extensions_type        transaction_extensions;

  asset                  fee; // EOSForce 增加的手续费,在客户端push trx时需要写入

  transaction_id_type        id()const;

  digest_type                sig_digest( const chain_id_type& chain_id, const vector<bytes>& cfd = vector<bytes>() )const;

  flat_set<public_key_type>  get_signature_keys( const vector<signature_type>& signatures,

                                                const chain_id_type& chain_id,

                                                const vector<bytes>& cfd = vector<bytes>(),

                                                bool allow_duplicate_keys = false,

                                                bool use_cache = true )const;

  uint32_t total_actions()const { return context_free_actions.size() + actions.size(); }

  account_name first_authorizor()const {

    for( const auto& a : actions ) {

        for( const auto& u : a.authorization )

          return u.actor;

    }

    return account_name();

  }

};

https://eosforce.github.io/Documentation/#/zh-cn/eosforce_client_develop_guild 这篇文档里也有说明。

这里计算trx id时完全使用trx的数据,这意味着,如果是两个trx数据完全一致,特别的他们在一个区块中,那么这两个trx的id就会是一样的。

1.3 signed_transaction

一个trx签名之后会得到一个signed_transaction,

structsigned_transaction:publictransaction { …vector signatures;// 签名vector context_free_data;// 上下文无关的action所使用的数据// 签名constsignature_type&sign(constprivate_key_type& key,constchain_id_type& chain_id);signature_typesign(constprivate_key_type& key,constchain_id_type& chain_id)const; flat_set get_signature_keys(constchain_id_type& chain_id,boolallow_duplicate_keys =false,booluse_cache =true)const; };

signed_transaction包含签名数据和上下文无关的action所使用的数据,

这里要谈一下context_free_data,可以参见 https://github.com/EOSIO/eos/commit/a41b4d56b5cbfd0346de34b0e03819f72e834041 ,之前我们看过context_free_actions, 在上下文无关的action中可以去从context_free_data获取数据,可以参见在api_tests.cpp中的测试用例:

… {// back to normal actionactionact1(pl, da); signed_transaction trx; trx.context_free_actions.push_back(act); trx.context_free_data.emplace_back(fc::raw::pack(100));// verify payload matches context free datatrx.context_free_data.emplace_back(fc::raw::pack(200)); trx.actions.push_back(act1);// attempt to access non context free apifor(uint32_ti =200; i <=211; ++i) { trx.context_free_actions.clear(); trx.context_free_data.clear(); cfa.payload = i; cfa.cfd_idx =1;actioncfa_act({}, cfa); trx.context_free_actions.emplace_back(cfa_act); trx.signatures.clear(); set_transaction_headers(trx); sigs = trx.sign(get_private_key(N(testapi),“active”), control->get_chain_id()); BOOST_CHECK_EXCEPTION(push_transaction(trx), unaccessible_api, [](constfc::exception& e) {returnexpect_assert_message(e,“only context free api’s can be used in this context”); } ); }…

这里可以作为context_free_action的一个例子,在test_api.cpp中的合约会调用void test_action::test_cf_action()函数:

// 这个是测试context_free_action的action

void test_action::test_cf_action() {

eosio::action act = eosio::get_action( 0, 0 );

cf_action cfa = act.data_as<cf_action>();

if ( cfa.payload == 100 ) {

  // verify read of get_context_free_data, also verifies system api access

  // 测试在合约中通过 get_context_free_data 获取 context_free_data

  int size = get_context_free_data( cfa.cfd_idx, nullptr, 0 );

  eosio_assert( size > 0, "size determination failed" );

  eosio::bytes cfd( static_cast<size_t>(size) );

  size = get_context_free_data( cfa.cfd_idx, &cfd[0], static_cast<size_t>(size) );

  eosio_assert(static_cast<size_t>(size) == cfd.size(), "get_context_free_data failed" );

  uint32_t v = eosio::unpack<uint32_t>( &cfd[0], cfd.size() );

  eosio_assert( v == cfa.payload, "invalid value" );

  // 以下是测试一些功能

  // verify crypto api access

  checksum256 hash;

  char test[] = "test";

  ...

  // verify context_free_system_api

  eosio_assert( true, "verify eosio_assert can be called" );

  // 下面是测试一些在上下文无关action中不能使用的功能

} else if ( cfa.payload == 200 ) {

  // attempt to access non context free api, privileged_api

  is_privileged(act.name);

  eosio_assert( false, "privileged_api should not be allowed" );

} else if ( cfa.payload == 201 ) {

  // attempt to access non context free api, producer_api

  get_active_producers( nullptr, 0 );

  eosio_assert( false, "producer_api should not be allowed" );



...

} else if ( cfa.payload == 211 ) {

  send_deferred( N(testapi), N(testapi), "hello", 6 );

  eosio_assert( false, "transaction_api should not be allowed" );

}

}

接下来我们来看一看packed_transaction,通过这个类我们可以将trx打包,这样可以最大的节省空间,关于它的功能,会在下面使用的提到。

  1. Transaction的接收和转发流程
    了解Transaction类定义之后,我们先来看一下trx在EOS系统中的接收和转发流程,确定发起trx的入口, 在EOS中,大部分trx都是由用户所操纵的客户端发向同步节点,再通过同步网络发送给超级节点,超级节点会把trx打包进块,这里我们梳理一下这里的逻辑,

首先,关于客户端提交trx的流程,可以参见 https://eosforce.github.io/Documentation/#/zh-cn/eosforce_client_develop_guild , 我们这里从node的角度看是怎么处理收到的trx的。

对于一个节点,trx可能是其他节点同步过来的,也可能是客户端通过api请求的,我们先看看api:

EOS中通过http_plugin插件响应http请求,这里我们只看处理逻辑,在chain_api_plugin.cpp中注册的这两个:

voidchain_api_plugin::plugin_startup() { ilog(“starting chain_api_plugin”); my.reset(newchain_api_plugin_impl(app().get_plugin().chain()));autoro_api = app().get_plugin().get_read_only_api();autorw_api = app().get_plugin().get_read_write_api(); app().get_plugin().add_api({ … CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results,202), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results,202) });}

最终实际调用的是这里:

// 调用流程 push_transactions -> push_recurse -> push_transactionvoidread_write::push_transaction(constread_write::push_transaction_params& params, next_function next) {try{autopretty_input =std::make_shared();autoresolver = make_resolver(this, abi_serializer_max_time);try{// 这里在使用 packed_transaction 解包abi_serializer::from_variant(params, *pretty_input, resolver, abi_serializer_max_time); } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception,“Invalid packed transaction”)// 这里调用 incoming::methods::transaction_async 函数app().get_method()(pretty_input,true, [this, next](constfc::static_variant& result) ->void{ …// 返回返回值, 略去}); }catch( boost::interprocess::bad_alloc& ) { raise(SIGUSR1); } CATCH_AND_CALL(next);}

注意这里的 persist_until_expired 参数,我们在 EOS源码备忘-Block Produce机制 这篇文档中分析过。 incoming::methods::transaction_async注册的是on_incoming_transaction_async函数:

my->_incoming_transaction_async_provider = app().get_method().register_provider([this](constpacked_transaction_ptr& trx,boolpersist_until_expired, next_function next) ->void{returnmy->on_incoming_transaction_async(trx, persist_until_expired, next ); });

on_incoming_transaction_async如下:

voidon_incoming_transaction_async(constpacked_transaction_ptr& trx,boolpersist_until_expired, next_function next){ chain::controller& chain = app().get_plugin().chain();if(!chain.pending_block_state()) { _pending_incoming_transactions.emplace_back(trx, persist_until_expired, next);return; }autoblock_time = chain.pending_block_state()->header.timestamp.to_time_point();// 返回结果的回调autosend_response = [this, &trx, &next](constfc::static_variant& response) { next(response);if(response.conta

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值