EOS智能合约之push_transaction的天龙八“步”

本文深入探讨了EOSIO系统中交易(transaction)的执行过程,详细分析了从cleos命令行发起交易请求,到交易信息如何通过push_transaction写入区块的全过程。文章以创建账户为例,分解为八个步骤阐述交易信息如何在chain_plugin与producer_plugin间异步传递,并最终写入区块。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

eosio整个系统中,transaction占据着十分重要的位置。我们在区块链上的任何有效操作,都代表着有transaction被执行了。在执行的过程中,push_transaction是不可以被忽略的。例如我们创建账户的时候,会通过push_transaction写到区块信息中,我们进行转账也会push_transaction写到区块信息中,今天我们来看看push_transaction作为区块信息写入的入口,背后做了哪些操作,交易信息是如何写入到区块中的。

本文主要包含以下内容:

  • push_transaction的天龙八步

  • transaction信息写入区块的过程

1、push_transaction的天龙八步

我们平时在代码调试或者阅读的过程中,总免不了使用cleos命令行,比如我们创建账户就需要使用:

1cleos system newaccount eosio yourname pubkey  pubkey --stake-net "10.0000 EOS" --stake-cpu "10.0000 EOS" --buy-ram-bytes 1000

那么我们在这个命令输入之后都进行了哪些操作呢?在创建新用户的过程中,我们给新用户抵押了资源,购买了内存,在cleos的main.cpp中,我们以帮助新用户购买RAM为例,可以看到该命令调用了系统合约中的buyram方法,那么我们如何来一步步找到buyram这个action执行的地方呢,我们可以分为八步来看:

 1//第一步:设置cleos命令行中传入的参数
 2add_standard_transaction_options(createAccount);
 3//第二步:根据公钥、私钥等创建账户
 4auto create = create_newaccount(creator, account_name, owner_key, active_key);
 5//第三步:创建购买ram等其他操作的action,在这里我们可以看到调用了系统合约中的buyram方法
 6create_action(tx_permission.empty() ? vector<chain::permission_level>{{creator,config::active_name}} : get_account_permissions(tx_permission),
 7                        config::system_account_name, N(buyram), act_payload);
 8}
 9//第四步:send_action
10send_actions( { create, buyram, delegate } 
11//第五步:push_action
12auto result = push_actions( move(actions), extra_kcpu, compression);
13//第六步:将action写到transaction中并push_transaction
14fc::variant push_actions(std::vector<chain::action>&& actions, int32_t extra_kcpu, packed_transaction::compression_type compression = packed_transaction::none ) {
15   signed_transaction trx;
16   trx.actions = std::forward<decltype(actions)>(actions);
17
18   return push_transaction(trx, extra_kcpu, compression);
19}
20//第七步:通过回调,调用chain_plugin中的push_transaction
21call(push_txn_func, packed_transaction(trx, compression));
22//第八步:chain_plugin将transaction信息异步写入到区块中
23void read_write::push_transaction(const read_write::push_transaction_params& params, next_function<read_write::push_transaction_results> next) 
24{
25     //处理
26}

创建账户的时候是如此,其他的链上操作也基本类似,感兴趣的可以去一一查看,接下来我们要看看天龙八步中的第八步,交易信息是如何写入区块中的。

 

2、push_transaction背后的操作

我们通过以前的文章可以了解到,区块的生成是以producer_plugin为入口,而后在chain的controller中实际完成的,那么上面天龙八步中的第八步是如何将交易transaction信息异步发送至producer_plugin中的呢。我们在来看chain_plugin中的transaction,可以看到其中使用了incoming::methods::transaction_async异步调用的方式,一步步的走下去:

1app().get_method<incoming::methods::transaction_async>()(pretty_input, true, [this, next](const fc::static_variant<fc::exception_ptr, transaction_trace_ptr>& result) 
2//transaction_async的定义
3using transaction_async     = method_decl<chain_plugin_interface, void(const packed_transaction_ptr&, bool, next_function<transaction_trace_ptr>), first_provider_policy>;

可以看到这其实是一个插件的接口,具体可以参看method_decl,我们回头看transaction_async,通过get_method的方式将transaction信息异步发送至producer_plugin,那么get_method又是什么呢:

 1         /**
 2          * 获取对传入类型声明的方法的引用,第一次使用的时候将会重构这个方法,该方法也会绑定两个插件
 3          */
 4         template<typename MethodDecl>
 5         auto get_method() -> std::enable_if_t<is_method_decl<MethodDecl>::value, typename MethodDecl::method_type&>
 6         {
 7            using method_type = typename MethodDecl::method_type;
 8            auto key = std::type_index(typeid(MethodDecl));
 9            auto itr = methods.find(key);
10            if(itr != methods.end()) {
11               return *method_type::get_method(itr->second);
12            } else {
13               methods.emplace(std::make_pair(key, method_type::make_unique()));
14               return  *method_type::get_method(methods.at(key));
15            }
16         }

读到这里我们大概都会猜想的到,既然是两个插件之间的通信,想必producer_plugin中也有transaction_async相关的使用,果不其然,在producer_plugin我们可以找得到transaction_async及其使用的地方:

1//接收来自chain_plugin中的transaction的句柄
2incoming::methods::transaction_async::method_type::handle _incoming_transaction_async_provider;
3//在producer_plugin插件初始化的时候就绑定了_incoming_transaction_async_provider和方法,类似于回调的方式,当有get_method执行的时候,on_incoming_transaction_async也将会执行
4   my->_incoming_transaction_async_provider = app().get_method<incoming::methods::transaction_async>().register_provider([this](const packed_transaction_ptr& trx, bool persist_until_expired, next_function<transaction_trace_ptr> next) -> void {
5      return my->on_incoming_transaction_async(trx, persist_until_expired, next );
6   });

在以前的文章中提到,节点生产区块实在start_block中执行的,我们不再赘述,下面完整的(默克尔树太长,忽略)打印其中一个区块的信息,新入门eos开发的读者朋友们也可以参考下一个区块中到底包含有哪些信息:

 1{
 2    "id": "0000000000000000000000000000000000000000000000000000000000000000",
 3    "block_num": 58447,
 4    "header": {
 5        "timestamp": "2018-09-15T07:28:49.500",
 6        "producer": "eosio",
 7        "confirmed": 0,
 8        "previous": "0000e44e252e319484583568da419e4179a9d956198e933927f4b7806bb8a373",
 9        "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
10        "action_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
11        "schedule_version": 0,
12        "header_extensions": [],
13        "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
14    },
15    "dpos_proposed_irreversible_blocknum": 58447,
16    "dpos_irreversible_blocknum": 58446,
17    "bft_irreversible_blocknum": 0,
18    "pending_schedule_lib_num": 0,
19    "pending_schedule_hash": "828135c21a947b15cdbf4941ba09e1c9e0a80e88a157b0989e9b476b71a21c6b",
20    "pending_schedule": {
21        "version": 0,
22        "producers": []
23    },
24    "active_schedule": {
25        "version": 0,
26        "producers": [{
27            "producer_name": "eosio",
28            "block_signing_key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"
29        }]
30    },
31    "blockroot_merkle": {
         //默克尔树省略
32    },
33    "producer_to_last_produced": [["eosio",
34    58447]],
35    "producer_to_last_implied_irb": [["eosio",
36    58446]],
37    "block_signing_key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",
38    "confirm_count": [],
39    "confirmations": [],
40    "block": {
41        "timestamp": "2018-09-15T07:28:49.500",
42        "producer": "eosio",
43        "confirmed": 1,
44        "previous": "0000e44e252e319484583568da419e4179a9d956198e933927f4b7806bb8a373",
45        "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
46        "action_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
47        "schedule_version": 0,
48        "header_extensions": [],
49        "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne",
50        "transactions": [],
51        "block_extensions": []
52    },
53    "validated": false,
54    "in_current_chain": true
55}

在我们的chain_plugin执行完push_transaction之后,controller.cpp中也对应着push_transaction,当没有transaction信息到来的时候,下面的内容不会执行,而当有交易信息的时候,则将会交易信息写入到pending中(注意,我这里加了部分日志打印来确认):

1 if (!trx->implicit) {
2               transaction_receipt::status_enum s = (trx_context.delay == fc::seconds(0))
3                                                    ? transaction_receipt::executed
4                                                    : transaction_receipt::delayed;
5               trace->receipt = push_receipt(trx->packed_trx, s, trx_context.billed_cpu_time_us, trace->net_usage);
6               pending->_pending_block_state->trxs.emplace_back(trx);
7                strPending = fc::json::to_string(*pending->_pending_block_state);
8                dlog("contorller push_transaction pending state step3:${state}", ("state", strPending));
9            } 

在这些执行完成之后,我们可以看到pending的打印中将会多出transaction的相关信息,如下:

 1"transactions": [{
 2           "status": "executed",
 3            "cpu_usage_us": 953,
 4            "net_usage_words": 25,
 5            "trx": [1,
 6            {
 7                "signatures": ["SIG_K1_KVpVk3PeWTXqGmExT6Lf7TbbgmJsPXcmmF63UZrTjFxf9Q8mqnKtLrU2CcBeZH3KU6qps7g73HxPDrAsUHZcic9NUp7E6f"],
 8                "compression": "none",
 9                "packed_context_free_data": "",
10                "packed_trx": "cfb49c5b4de4d5cd608f00000000010000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed3232660000000000ea305500000819ab9cb1ca01000000010003e2f5c375717113f8cde854b8fabf0f8db01c02b9e197e13b8cf83100728f0b390100000001000000010003e2f5c375717113f8cde854b8fabf0f8db01c02b9e197e13b8cf83100728f0b390100000000"
11            }]

 

本文主要结合日志打印来分析交易信息是如何通过push_action写入到区块中的,以命令行创建用户为例,拆分为八步来讨论两个插件之间的异步交互,chain_plugin中的信息是如何发送至producer_plugin中的。

### 关于 `FA_TRANSACTION_HEADERS` 表中的 `TRANSACTION_TYPE_CODE` 在财务模块中,`FA_TRANSACTION_HEADERS` 是固定资产事务处理的核心表之一。此表记录了与资产相关的各种交易信息,其中字段 `TRANSACTION_TYPE_CODE` 定义了具体的事务类型[^1]。 #### 字段解释 - **`TRANSACTION_TYPE_CODE`**: 这是一个枚举类型的字段,用于表示特定的事务类别。常见的值可能包括但不限于: - ADDITION: 资产新增 - RETIREMENT: 资产退役 - TRANSFER: 资产转移 - ADJUSTMENT: 调整 这些代码的具体含义可以通过查询 Oracle E-Business Suite 的基础数据字典或者通过业务逻辑文档获取更多信息[^2]。 #### 查询示例 为了检索 `FA_TRANSACTION_HEADERS` 中的所有唯一事务类型及其描述,可以执行以下 SQL 查询: ```sql SELECT DISTINCT TRANSACTION_TYPE_CODE, DESCRIPTION FROM FA_TRANSACTION_HEADERS; ``` 如果需要进一关联其他上下文信息(例如组织单位),则可以根据需求加入额外的连接条件。例如,假设存在一个映射关系来绑定组织 ID (`ORG_ID`) 和其短码 (`SHORT_CODE`),可参考如下扩展查询: ```sql SELECT fth.TRANSACTION_TYPE_CODE, hou.SHORT_CODE AS ORGANIZATION_SHORT_CODE FROM FA_TRANSACTION_HEADERS fth JOIN HR_OPERATING_UNITS hou ON fth.ORGANIZATION_ID = hou.ORGANIZATION_ID; ``` 上述语句展示了如何将固定资产管理的数据与其他操作单元信息结合起来分析[^3]。 #### 使用 JPA 自定义查询 当采用 Java Persistence API (JPA) 实现动态访问数据库时,也可以创建类似的接口方法完成相同功能。下面提供了一个基于 Spring Data JPA 的实现例子: ```java public interface FaTransactionHeaderRepository extends CrudRepository<FaTransactionHeader, Long> { @Query(value = "SELECT transaction_type_code FROM fa_transaction_headers WHERE organization_id=:orgId", nativeQuery = true) List<String> findTransactionTypeCodesByOrg(@Param("orgId") Long orgId); } ``` 该片段允许开发者按需调取某个具体机构下的所有可用事务类型列表[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值