1. 程式人生 > >eos原始碼賞析(二十):EOS智慧合約之push_transaction的天龍八“步”

eos原始碼賞析(二十):EOS智慧合約之push_transaction的天龍八“步”

很久沒談《天龍八部》了。

eosio整個系統中,transaction佔據著十分重要的位置。我們在區塊鏈上的任何有效操作,都代表著有transaction被執行了。在執行的過程中,push_transaction是不可以被忽略的。例如我們建立賬戶的時候,會通過push_transaction寫到區塊資訊中,我們進行轉賬也會push_transaction寫到區塊資訊中,今天我們來看看push_transaction作為區塊資訊寫入的入口,背後做了哪些操作,交易資訊是如何寫入到區塊中的。

本文主要包含以下內容:

  • push_transaction的天龍八步
  • transaction資訊寫入區塊的過程

1、push_transaction的天龍八步
我們平時在程式碼除錯或者閱讀的過程中,總免不了使用cleos命令列,比如我們建立賬戶就需要使用:

cleos 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中的。

如果你覺得我的文章對你有一定的幫助,請點選文章末尾的喜歡該作者。

如果你對eos開發感興趣,歡迎關注本公眾號,一起學習eos開發。

這裡寫圖片描述
微信公眾號
有任何疑問或者指教請新增本人個人微信,當然有對eos開發感興趣或者金庸粉的也可以新增一起交流,備註eos開發或金庸。
這裡寫圖片描述

個人微訊號