1. 程式人生 > >eos原始碼賞析(二十三):默克爾樹在EOS中的應用(上)

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開發或金庸。

在這裡插入圖片描述

                                                                   個人微訊號