區塊鏈 + 大資料:EOS儲存

來源:互聯網
上載者:User

談到區塊鏈的儲存,我們很容易聯想到它的鏈式儲存結構,然而區塊鏈從比特幣發展到今日當紅的EOS,技術形態已經演化了10年之久。目前的EOS的儲存除了確認結構的鏈式儲存以外,在狀態儲存方面有了很大的進步,尤其是引入了MongoDB plugin以後,可以將功能有限的狀態庫搭上大資料的班車。本文將全面介紹EOS的儲存技術。

EOS 儲存,Merkle Tree,mongodb,chainbase,源碼學習,context_free_actions

EOS的鏈式儲存結構

EOS的區塊資料結構如下:

field explanation
timestamp 時間戳記
producer 生產者
confirmed 生產者確認數
previous 鏈式結構前一個區塊的id
transaction_mroot 交易默克爾樹根
action_mroot 動作默克爾樹根
schedule_version 生產者版本排序號
new_producers 下一個生產者
header_extensions 區塊頭擴充欄位
producer_signature 區塊簽名,由生產者簽名
transactions 塊打包交易內容,是數組結構,可以多個
block_extensions 區塊擴充欄位
id 當前塊id
block_num 當前塊高度
ref_block_prefix 引用區塊的區塊頭
Merkle Tree

默克爾樹的演化路線是 Hash => Hash Tree => Merkle Tree ,他們都是為解決資料一致性而存在的,具體的含義如下:

  • Hash 是我們都熟知的技術了,它可以為一個檔案或其他資料產生一個Hash值,我們在下載一個檔案時,通常會在下載頁面看到這個檔案的Hash值以及該Hash值的演算法,下載完畢以後,我們可以在本地對整個檔案進行同樣的Hash演算法得到Hash值,然後與網頁上的Hash值進行對比,如果相同,則說明檔案完整,是網頁上的源檔案,如果不匹配則說明檔案損壞,被修改或者不完整。
  • 仍舊是檔案完整性的校正需求,當這個檔案特別大的時候,對這個檔案進行Hash演算法是能耗巨大的,所以可以將檔案切割成很多的小塊,每一個小塊都有一個Hash,然後將所有小塊的Hash值拼在一起再次進行Hash演算法得到的就是Root Hash。這樣一來,我們在下載大檔案的時候,會先下載一個包含Root Hash的Hash list,通過校正Root Hash可以確定Hash list的正確性,確定Hash list正確以後,再逐個下載小塊檔案並逐一驗證Hash,當發現某個小塊Hash不匹配的時候,就可以單獨重新下載該小塊即可,而不必重新下載全部。Hash list的結構實際上是一個Root Hash 為根,小塊Hashs為葉子節點的樹高為2的Hash Tree。
  • Merkle Tree實際上是對Hash List的最佳化,它極大的提高了效能。它的結構是一個二叉樹(也可以是多叉樹,效能最佳化的關鍵點是它的高度是大於等於2的),每個節點最多隻有兩個子節點,只有分葉節點是根據小塊檔案做的Hash,每兩個相鄰的分葉節點的父節點是由這兩個Hash做的父Hash,如果分葉節點的總數是單數,則會剩餘一個,逐級而上,最終會有一個的根節點,這個根節點就是Merkle Root。這樣以來,我們在下載大檔案的時候,會首先下載一個Merkle Tree,從最左下分葉節點進行校正,逐級而上,將整個Merkle Tree校正完畢。這裡面不同於上面Hash Tree的是,只要最左下相鄰的兩個分葉節點的Hash值與他們的父節點的Hash通過了匹配,則可以立即開始下載這兩個分葉節點對應的檔案塊,並行地,再校正其他分葉節點,這就提高了效能,不必校正完整的Merkle Tree之後再下載檔案。
Merkle Tree 與 區塊鏈

上面的區塊資料結構中包含了兩個與Merkle Tree相關的欄位:

  • transaction_mroot,一個區塊中的transactions欄位可以包含多筆交易,區塊中的transaction_mroot是所有該區塊內打包的交易的Merkle Root,可以用來校正其中的每筆交易的正確性。如果該區塊中不包含任何交易,則該欄位的值為0000000000000000000000000000000000000000000000000000000000000000。節點同步資料的時候,會先將交易的Merkle Tree下載並通過Merkle Root來校正,而不是將所有的交易主體全部下載下來,這樣可以節省輕節點的資料量。
  • action_mroot,建立一個基於所有分發的action的根,在區塊內接收交易時進行評估。用在輕用戶端的校正,功能同上。

action_mroot是始終有值的,哪怕transaction_mroot是0,這是因為出塊本身也是一個action動作onblock,這個動作調用的是system合約的onblock函數。TODO:源碼分析

/***  At the start of each block we notify the system contract with a transaction that passes in*  the block header of the prior block (which is currently our head block)*/signed_transaction get_on_block_transaction(){  action on_block_act;  on_block_act.account = config::system_account_name;  on_block_act.name = N(onblock);  on_block_act.authorization = vector<permission_level>{{config::system_account_name, config::active_name}};  on_block_act.data = fc::raw::pack(self.head_block_header());  signed_transaction trx;  trx.actions.emplace_back(std::move(on_block_act));  trx.set_reference_block(self.head_block_id());  trx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to nearest second to avoid appearing expired  return trx;}
context_free_actions

通過對eosio.null賬戶的nouce動作,可以將無簽名的資料打包進入context_free_action欄位,結果區塊資訊如下:

evsward@evsward-TM1701:~/work/src/github.com/eos/tutorials/bios-boot-tutorial$ cleos --wallet-url http://127.0.0.1:6666 --url http://127.0.0.1:8000 get block 440{  "timestamp": "2018-08-14T08:47:09.000",  "producer": "eosio",  "confirmed": 0,  "previous": "000001b760e4a6610d122c5aa5d855aa49e29f3052ac3e40b9e1ef78e0f1fd02",  "transaction_mroot": "32cb43abd7863f162f4d8f3ab9026623ea99d3f8261d2c8b4d8bf920ab97e3d1",  "action_mroot": "09afeaf40d6988a14e9e92817d2ccf4023b280075c99f13782a6535ccc58cbb0",  "schedule_version": 0,  "new_producers": null,  "header_extensions": [],  "producer_signature": "SIG_K1_K2eFDzbxCg3hmQzpzPuLYmiesrciPmTHdeNsQDyFgcHUMFeMC3PntXTqiup5VuNmyb7qmH18FBdMuNKsc7jgCm1TSPFbaj",  "transactions": [{      "status": "executed",      "cpu_usage_us": 290,      "net_usage_words": 16,      "trx": {        "id": "d74843749d1e255f13572b7a3b95af9ddd6df23d1d0ad19d88e1496091d4be2b",        "signatures": [          "SIG_K1_KVzwg3QRH6ZmempNsvAxpPQa42hF4tDpV5cqwqo7EY4oSU7NMrEFwG7gdSDCnUHHhmH1EwtVAmV1z9bqtTvvQNSXiSgaWG"        ],        "compression": "none",        "packed_context_free_data": "",        "context_free_data": [],        "packed_trx": "8497725bb601973ea96f0000000100408c7a02ea3055000000000085269d000706686168616861010082c95865ea3055000000000000806b010082c95865ea305500000000a8ed3232080000000000d08cf200",        "transaction": {          "expiration": "2018-08-14T08:49:08",          "ref_block_num": 438,          "ref_block_prefix": 1873362583,          "max_net_usage_words": 0,          "max_cpu_usage_ms": 0,          "delay_sec": 0,          "context_free_actions": [{              "account": "eosio.null",              "name": "nonce",              "authorization": [],              "data": "06686168616861"            }          ],          "actions": [{              "account": "eosiotesta1",              "name": "hi",              "authorization": [{                  "actor": "eosiotesta1",                  "permission": "active"                }              ],              "data": {                "user": "yeah"              },              "hex_data": "0000000000d08cf2"            }          ],          "transaction_extensions": []        }      }    }  ],  "block_extensions": [],  "id": "000001b8d299602b289a9194bd698476c5d39c5ad88235460908e9d43d04edc8",  "block_num": 440,  "ref_block_prefix": 2492570152}

正常的actions的內容是hi智能合約的調用,而context_free_action中包含了無簽名的data資料,是已做數字摘要後的形態。源碼中的操作:

// lets also push a context free action, the multi chain test will then also include a context free action("context_free_actions", fc::variants({    fc::mutable_variant_object()       ("account", name(config::null_account_name))       ("name", "nonce")       ("data", fc::raw::pack(v))    }) );
EOS的StateDB

我們來設想一個情境:

A賬戶轉賬給B賬戶100個SYS,如何查看A賬戶的餘額?

對於不知道以上動作何時發生的我們來講,我們要如何做呢:

  • 首先是從頭掃描區塊內的交易,交易內的action,直到找到A賬戶被建立的action所對應的區塊號。
  • 從這個區塊號開始繼續掃描,要將所有A賬戶的轉賬,包括收入和支出的所有action記錄下來並統計。
  • 算出A的當前餘額。

以上步驟很容易出錯且繁瑣,每一次的餘額查詢都要重複這些操作實在是毫無意義,因此StateDB就誕生了,這個庫顧名思義就是用來儲存狀態資料的,如果有了StateDB,上面的情境的解決辦法就是:

  • 從A賬戶被第一次收入SYS開始,為A賬戶在StateDB中建立一個table,儲存A賬戶的餘額,每當A賬戶發生轉賬的action,都會同步更新StateDB中相關table中A賬戶的餘額的值,當我們需要知道A賬戶的餘額時,我們可以直接尋找這個餘額state即可。
測試案例

這裡為大家提供一個測試方法,也是我的命令history:

cleos create keycleos wallet import 5JA9oDotJHoKnjUV6NrAMx4g5gWTCVCRLybTnG1XVU3EKGZZeNYcleos wallet import --private-key 5JA9oDotJHoKnjUV6NrAMx4g5gWTCVCRLybTnG1XVU3EKGZZeNYcleos wallet keyscleos wallet import --private-key 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3cleos wallet keyscleos create account eosio usertesta1 EOS761KfjhYy3FSypZGG5hePrR8K2wzmw75JCDXgypKt2DLXZoZPWcleos create account eosio eosio.token EOS761KfjhYy3FSypZGG5hePrR8K2wzmw75JCDXgypKt2DLXZoZPWcleos set contract eosio.token work/src/github.com/eos/build/contracts/eosio.token/cleos push action eosio.token create '["eosio","100000000.0000 SYS"]'cleos push action eosio.token issue cleos push action eosio.token issue '["usertesta1","10000000.0000 SYS"]' -p eosio.tokencleos push action eosio.token issue '["usertesta1","10000000.0000 SYS"]' -p eosiocleos get currency balance eosio.token usertesta1cleos get table eosio.token usertesta1 accountscleos get table eosio.token eosio accounts

可以看到當我想獲得usertesta1賬戶的餘額時,是通過查詢StateDB的table來擷取的,而不是最開始的那種掃塊的笨方法。

鏈式儲存和StateDB儲存的區別
  • 鏈式儲存,儲存的是固定結構的資料:Block=> Block Header/ transactions=>actions,一個action的結構例子:
{    "account": "eosiotesta1",    "name": "hi",    "authorization": [{      "actor": "eosiotesta1",      "permission": "active"    }],    "data": {    "user": "yeah"    },    "hex_data": "0000000000d08cf2"}

這個例子中,我們調用了hello合約的hi函數,data傳入的格式是hi函數中自訂的,所以在鏈式儲存中,留給我們發揮的空間也即在此。

  • StateDB,儲存的是一個最終要記錄的狀態,這個狀態資料必須是有意義的,是有人關心的,無關緊要的資料請不要放在StateDB中去,所以StateDB是可以增刪改查的,就像一個普通資料庫那樣,在合約中通過multi_index來操作,具體請參照文章EOS技術研究:合約與資料庫互動

很多人搞不明白為什麼區塊鏈不可篡改,卻在StateDB中好像可以修改還能刪除?

其實不是這樣的,鏈式儲存的內容會將所有的動作action全部記錄下來,是所有的過程資料,是流水帳,中繼資料,這些資料一旦上鏈是不可修改,不可刪除的。而StateDB只是為了儲存一個狀態資訊,這個狀態資訊的修改與刪除並不影響區塊鏈的不可篡改的特性。

目前StateDB的主流實現方式是將它放在記憶體中,而當有些人對StateDB的認識有偏差造成濫用的時候,會引發記憶體過載,因此一方面我們要非常清楚的理解StateDB的含義,一方面EOSIO協助我們提供了一個mongodb-plugin外掛程式來同步StateDB資料。

mongodb安裝
  • 下載tgz安裝包
  • 解壓安裝到/usr/local/bin(或者其他某路徑)
  • sudo mkdir /data/db
普通模式
  • sudo mongod
  • mongo
服務模式

我們也可以使用ubuntu系統的服務模式。

曾經我們要定義一個系統啟動時自啟動服務的方式是在/etc/init.d 目錄下寫一個指令碼來執行,現在在ubuntu的服務模式下,我們可以丟棄那種方式,服務模式的命令是service,而現在的ubuntu系統推崇使用的systemctl命令,他倆的使用方法的區別就在於參數的順序。

  • 定義一個設定檔mongod.conf
  • 定義一個服務檔案,放置在/etc/systemd/system/

    sudo vi /etc/systemd/system/mongodb.service

[Unit]Description=High-performance, schema-free document-oriented databaseAfter=network.target [Service]User=mongodbExecStart=['mongod' command location] --quiet --config /etc/mongod.conf [Install]WantedBy=multi-user.target
  • 尋找服務狀態

    systemctl list-unit-files

  • 查詢mongodb服務的啟用狀態

    systemctl is-enabled mongodb

  • 啟用系統自啟動服務

    sudo systemctl enable mongodb

  • 啟動mongodb服務

    sudo systemctl start mongodb

  • 查詢mongodb服務狀態

    sudo systemctl status mongodb

  • 停止mongodb服務

    sudo systemctl stop mongodb

偵錯模式

IDE選擇CLion,EOS源碼下載最新的,保證本地可以使用指令碼編譯通過,安裝了相關依賴包,然後在CLion中匯入EOS,CLion會自動識別CMakeList.txt檔案產生makefile檔案並make編譯執行。編譯時間可能會遇到錯誤,一般來講要麼是環境依賴沒有配置好,要麼就是CMakeList.txt要有修改,例如mongodb-plugin匯入時要在總開關配置上開啟。

set(BUILD_MONGO_DB_PLUGIN "true")

全部編譯成功以後,會自動識別出可以debug的target,與EOS中配備CMakeList.txt的模組一一對應。

安裝Mongo Explorer外掛程式

上面我們介紹了MongoDB的安裝方法,以及啟動nodeos時的配置方法(除了上文提到的總開關,當然要在config.ini檔案末尾設定上plugin = eosio::mongo_db_plugin,這部分內容演練多次,這裡不再贅述。)鏈啟動開始出塊以後,會同步到mongodb中去(注意要預先啟動mongod守護進程,可以理解為服務端),通過mongo命令接入可使用mongo命令查詢資料,但這樣很不方便。可以在CLion中安裝mongo-plugin,配置好效果如下:

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.