eos原始碼賞析(二十三):默克爾樹在EOS中的應用(上)
前面文章中在分析push_transactioneos原始碼賞析(二十):EOS智慧合約之push_transaction的天龍八“步”以及區塊簽名eos原始碼賞析(二十一):EOS智慧合約之區塊簽名的天龍八“步”的時候都提到了默克爾樹,受限於篇幅未做具體分析。今天我們來談談默克爾樹在eos中的應用。擬分為上下兩篇,上篇主要分為以下內容:
默克爾樹簡介
eos中如何構建默克爾樹
1、默克爾樹簡介
由於側重點不同,我們不再進行一一解釋,我們嘗試用通俗的語言來解釋下如何構建一個Merkle樹。讀過《笑傲江湖》原著或者看過《笑傲江湖》影視劇的朋友們對華山派應該都不陌生,華山派由於種種原因分成了氣宗和劍宗兩派,而我們所熟知的“君子劍”嶽不群便是氣宗的代表人物,而老前輩風清揚是劍宗的代表人物,下面以氣宗和劍宗的代表武功來說明如何構建一個默克爾樹:
Merkle Tree可以看做Hash List的泛化(Hash List可以看作一種特殊的Merkle Tree,即樹高為2的多叉Merkle Tree)。在最底層,和雜湊列表一樣,我們把資料分成小的資料塊,這裡我們選取了華山派氣宗和劍宗的代表武功紫霞神功、無雙無對寧氏一劍、獨孤九劍、衝靈劍法(不要問我為什麼選這個,就是覺得好聽),往上一層我們分別對這四種武功進行一次hash,在eos中也就是使用sha256中的hash轉換為64位的資料。但是往上走,並不是直接去運算根雜湊,而是把相鄰的兩個雜湊合併成一個字串,然後運算這個字串的雜湊,例如劍宗中的獨孤九劍和衝靈劍法不是直接hash,而是將兩者的hash合併成一個字串再進行hash,在往上依舊如此。這樣便形成了華山派的hash值。
那麼有的朋友可能會問:玉女十九劍這個武功是劍宗還是氣宗分不清怎麼辦,我們是該把它當成氣宗還是劍宗呢?我們可以把它單獨拿出來進行hash,也就是最底層的資料塊出現奇數的情況下,可以這樣進行構建默克爾樹:
如果最底層武功數量是奇數,那到最後必然出現一個單身雜湊,這種情況就直接對玉女十九劍進行雜湊運算,所以也能得到它的子雜湊。於是往上推,依然是一樣的方式,可以得到數目更少的新一級雜湊,最終必然形成一棵倒掛的樹,到了樹根的這個位置,這一代就剩下一個根雜湊了,我們把它叫做 Merkle Root,也就是我們的華山派的hash值。
2、eos中如何構建默克爾樹
我們知道在eos中最重要的因素無非區塊(block)、事物(transaction)、動作(action),通過閱讀原始碼我們會發現,在每一次transaction執行的過程中都會對transaction和action進行默克爾樹的構建,我們來一步步看一下。
當transaction被打包到區塊中之後會有一個區塊資訊的確認,即:finalize_block,我們來看:
1void finalize_block()
2 {
3 //該方法開始對一些資源資訊進行確認操作
4 //我們接下來加了部分日誌列印,方便觀察對比
5 std::string strPending = "";
6 strPending = fc::json::to_string(*pending->_pending_block_state);
7 dlog("contorller before set action merkle:${state}", ("state", strPending));
8 set_action_merkle();
9 strPending = fc::json::to_string(*pending->_pending_block_state);
10 dlog("contorller after set action and before set trx merkle:${state}", ("state", strPending));
11 set_trx_merkle();
12 strPending = fc::json::to_string(*pending->_pending_block_state);
13 dlog("contorller after set trx merkle:${state}", ("state", strPending));
14
15 auto p = pending->_pending_block_state;
16 p->id = p->header.id();
17
18 create_block_summary(p->id);
19
20 } FC_CAPTURE_AND_RETHROW() }
在這裡面我們看到了set_action_merkle以及set_trx_merkle對action以及transaction進行默克爾樹的構建,繼續來看:
1 void set_action_merkle() {
2 vector<digest_type> action_digests;
3 dlog("contorller set_action_merkle size:${size}", ("size", pending->_actions.size()));
4 action_digests.reserve( pending->_actions.size() );
5 for( const auto& a : pending->_actions )
6 action_digests.emplace_back( a.digest() );
7
8 pending->_pending_block_state->header.action_mroot = merkle( move(action_digests) );
9 }
10
11 void set_trx_merkle() {
12 vector<digest_type> trx_digests;
13 const auto& trxs = pending->_pending_block_state->block->transactions;
14 dlog("contorller set_trx_merkle size:${size}", ("size", trxs.size()));
15 trx_digests.reserve( trxs.size() );
16 for( const auto& a : trxs )
17 trx_digests.emplace_back( a.digest() );
18
19 pending->_pending_block_state->header.transaction_mroot = merkle( move(trx_digests) );
20 }
不管是set_action_merkle還是set_trx_merkle最後都呼叫了Merkle方法,即先獲取action或者transaction的摘要資訊,然後進行默克爾樹的構建,我們來看Merkle函式,在Merkle.cpp中:
1digest_type merkle(vector<digest_type> ids) {
2 if( 0 == ids.size() ) { return digest_type(); }
3
4 while( ids.size() > 1 ) {
5 if( ids.size() % 2 )
6 ids.push_back(ids.back());
7
8 for (int i = 0; i < ids.size() / 2; i++) {
9 ids[i] = digest_type::hash(make_canonical_pair(ids[2 * i], ids[(2 * i) + 1]));
10 }
11
12 ids.resize(ids.size() / 2);
13 }
針對武功個數是奇數還是偶數分別進行了hash,最終形成了默克爾樹的構建,我這裡以建立使用者名稱為例,根據日誌的列印對其進行跟蹤對比結果,先執行命令列如下:
1cleos create account eosio merkletest yourpubkey yourpubkey
在構建默克爾樹之前和之後的對比結果如下:
1//構建默克爾樹之前
2 "header": {
3 "timestamp": "2018-10-10T12:32:30.500",
4 "producer": "eosio",
5 "confirmed": 0,
6 "previous": "0000014735eeda35fd8c2e303ceda4ff8fd5c67fbb032187c75eeb319daec123",
7 "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
8 "action_mroot": "0000000000000000000000000000000000000000000000000000000000000000",
9 "schedule_version": 0,
10 "header_extensions": [],
11 "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
12 }
13//構建默克爾樹之後
14"header": {
15 "timestamp": "2018-10-10T12:32:30.500",
16 "producer": "eosio",
17 "confirmed": 0,
18 "previous": "0000014735eeda35fd8c2e303ceda4ff8fd5c67fbb032187c75eeb319daec123",
19 "transaction_mroot": "12399917b8b8a3d7eb05aa58dd87aec2bbbda139e770627332da9e6f5efd7d88",
20 "action_mroot": "2b4c51c20fb35268628e8f2c5171549fa5bcb947079ed82a7f2d38c7481f8c4e",
21 "schedule_version": 0,
22 "header_extensions": [],
23 "producer_signature": "SIG_K1_111111111111111111111111111111111111111111111111111111111111111116uk5ne"
24 }
通過對比可以發現transaction_mroot及action_mroot發生了相應的變化,至此eos中構建默克爾樹的流程也已經完成。
本文簡單的介紹了默克爾樹的基本概念,以《笑傲江湖》華山派為例介紹默克爾樹的構建,以及eos中transaction和action的默克爾樹的構建,關於默克爾樹在eos中的具體使用,我們慢慢再談。
如果你覺得我的文章對你有一定的幫助,請點選文章末尾的喜歡該作者。
如果你對eos開發感興趣,歡迎關注本公眾號,一起學習eos開發。
微信公眾號
有任何疑問或者指教請新增本人個人微信,當然有對eos開發感興趣或者金庸粉的也可以新增一起交流,備註eos開發或金庸。
個人微訊號