最近發現不少人投資了 EOS,卻並不關心 EOS 目前的開發進度和技術細節,若是你投資了 EOS, 還有必定的技術基礎,那就更應該關心 EOS 的開發狀況了,本期《區塊鏈100講》咱們就從 EOS 的源代碼提及。git
爲了理解EOS區塊鏈的基本原理,首先須要瞭解這它的數據結構和關鍵算法,數據結構和算法是構成整個骨架的基本元素,對於深刻理解EOS相當重要。下文從一些基本概念開始,逐漸深刻底層,一步步探究其中邏輯與思路。github
基本概念算法
name數據庫
name在EOS裏能夠理解爲標識符,這與通常意義的英文單詞name不一樣,name的長度是13個字節。源代碼中全部形式爲xxx_name的名稱均可以理解爲此類型,通常的,xxx表示其功能或者目的,_name代表這是一個標識符。設計模式
sha256緩存
sha256是一個長度爲32字節的數據結構,一般用16進製表示,能夠理解由32個表示16進制的字符隨機組成的字符串,這樣的數據結構一般用來表示校驗碼或者標識符(ID)。ID與name的區別在於name是人類可讀的,而ID只是一串隨機的字符序列。安全
scope網絡
scope實際是一個上層設計的概念,並不屬於底層實現的範疇,只是相關scope的說明並很少,在這裏略做解釋。scop能夠理解爲權限,也就是規定當前這個操做所要求的受權列表,scope能夠包含零個,一個,或者多個受權要求。session
merkle數據結構
merkle是指默克爾樹,這是一種利用Hash算法老是輸出固定長度的數這一特性而設計的樹型數據結構,它的特色是消耗空間小,利用Hash的特色能夠用很小的存儲空間來驗證大量數據的簽名,而不須要把數據所有保存下來,就能夠保證數據的完整性和一致性。有關默克爾樹的詳細解釋可參看區塊鏈100講:梅克爾樹保障區塊鏈數據不可篡改,想換根就要砍樹!
digest
digest中文含義是摘要,屬於密碼學範疇。對於EOS中的區塊來講,摘要就是對整個區塊數據作Hash運算獲得的結果,因爲Hash的結果長度固定,並且單向不可逆,徹底符合摘要的要求。下面是源代碼中對digest函數的定義:
digest_type block_header::digest()const { return digest_type::hash(*this); }
block_id vs block_num
不少人把區塊id和區塊序號混爲一談,其實它們是不同的概念,區塊id是一個與區塊內容有關的標識,而區塊序號僅僅是從0計數的區塊編號。不爲人知的是,這兩個概念之間在底層實現上是有關係的,請看下面的代碼:
block_id_type signed_block_header::id()const { block_id_type result = fc::sha256::hash(*this); result._hash[0] &= 0xffffffff00000000; result._hash[0] += fc::endian_reverse_u32(block_num()); // store the block num in the ID, 160 bits is plenty for the hash return result; }
不難看出id實際是由兩部分組成:對區塊內容作Hash運算,而後與區塊序號進行拼接而獲得的。因此id實際包含了區塊序號,經過id能夠計算得出序號。
action vs message
值得指出的是,目前在EOS中message和action的概念是同樣的,實際上action是自3.0之後的新規範,message的概念已經能夠拋棄,因爲3.0版本發佈到目前爲止只有幾天的年齡,互聯網上的大部分文字和材料依然使用message的概念,爲了緩解讀者的困擾,在此特作說明,下文均採用action代替message。
區塊
區塊是EOS和其它全部區塊鏈技術的核心數據結構,要掌握任何一個區塊鏈技術,首先須要理解的就是區塊的設計。
區塊的目的
在EOS的源代碼中,設計者給出了區塊這個結構的主要目的:
/** * The block_summary defines the set of transactions that were successfully applied as they * are organized into cycles and shards. A shard contains the set of transactions IDs which * are either user generated transactions or code-generated transactions. * * * The primary purpose of a block is to define the order in which messages are processed. * * The secodnary purpose of a block is certify that the messages are valid according to * a group of 3rd party validators (producers). * * The next purpose of a block is to enable light-weight proofs that a transaction occured * and was considered valid. * * The next purpose is to enable code to generate messages that are certified by the * producers to be authorized. * * A block is therefore defined by the ordered set of executed and generated transactions, * and the merkle proof is over set of messages delivered as a result of executing the * transactions. * * A message is defined by { receiver, code, function, permission, data }, the merkle * tree of a block should be generated over a set of message IDs rather than a set of * transaction ids. */ struct signed_block_summary : public signed_block_header { ... };
翻譯成中文就是:區塊的
主要目的是定義處理消息的順序。
次要目的是經過第三方生產者驗證消息的有效性。
第三個目的是提供輕量級的交易驗證機制。
第四個目的是受權給智能合約代碼,以產生新的消息。
區塊的結構
所以區塊是由按順序組織的交易來構成的。區塊像是一個容器,它自頂向下依次包含如下這些結構:
Block: 區塊 => regions: 區域 => cycles: 週期 => shards: 片區 => transactions: 交易 => actions: 操做
然而從區塊的直接設計來看,它的定義就是按順序構成的交易集合:
/** * This structure contains the set of signed transactions referenced by the * block summary. This inherits from block_summary/signed_block_header and is * what would be logged to disk to enable the regeneration of blockchain state. * * The transactions are grouped to mirror the cycles in block_summary, generated * transactions are not included. */ struct signed_block : public signed_block_summary { digest_type calculate_transaction_merkle_root()const; vector<signed_transaction> input_transactions; /// this is loaded and indexed into map<id,trx> that is referenced by summary };
這與上面表述的結構不一致,如何理解呢?實際上,從signed_block的定義不難看出,這個區塊的定義「繼承」了signed_block_summary的定義,而signed_block_summary的定義就是上述的自上而下的包含結構,因此兩種區塊的不一樣表示並不衝突,能夠理解爲包含結構是底層結構的一種映射,實際的區塊僅僅須要定義交易的前後順序便可,而cycle,shard這些概念是爲了有機的組織必定數量的交易而設計出來的,是交易組合的不一樣視圖和抽象,目的是爲了更方便高效的執行運算,包括區塊的驗證和簽名等等。
區塊的存儲形式 - 區塊日誌
區塊日誌是存儲區塊的二進制文件,區塊日誌的特性是隻能從末尾追加(append only),區塊日誌包含兩類文件:
區塊文件,結構以下:
+---------+----------------+---------+----------------+-----+------------+-------------------+ | Block 1 | Pos of Block 1 | Block 2 | Pos of Block 2 | ... | Head Block | Pos of Head Block |
+---------+----------------+---------+----------------+-----+------------+-------------------+
區塊文件包含區塊的內容已經每一個區塊的位置信息。區塊位置信息是固定的8字節寬度,這樣便於在連續讀取區塊的時候,按照讀一個區塊,向後跳躍8個字節,讀一個區塊,向後跳躍8個字節的模式快速加載區塊內容。
索引文件,結構以下:
+----------------+----------------+-----+-------------------+
| Pos of Block 1 | Pos of Block 2 | ... | Pos of Head Block |
+----------------+----------------+-----+-------------------+
區塊索引的目的在於提供一個基於區塊序號的快速隨機搜索方法,若是一直區塊序號,使用索引文件能夠快速定位目標區塊在區塊文件中的具體位置。索引文件不是必須的,沒有索引文件區塊鏈仍然能夠運行,索引文件的主要做用是經過少許空間換取速度提高。索引文件能夠經過順序讀取區塊文件來從新構建。
若是讀者運行過eos全節點的eosiod程序,能夠發現這個程序啓動時會建立上述兩個文件,以下圖所示
blockchain@syphml11:~/eos/build/programs/eosd/data-dir/blocks$ ls -lh total 137M -rw-rw-r-- 1 blockchain blockchain 6.9M Jan 29 18:29 blocks.index -rw-rw-r-- 1 blockchain blockchain 130M Jan 29 18:29 blocks.log
區塊中的關鍵算法
咱們如今瞭解了區塊的結構,而結構的設計是爲了運算的目的,下面咱們來介紹與區塊相關的關鍵算法。
計算交易的默克爾樹根
這樣的表述或許有些過於術語化,對於這一步運算,讀者須要理解的是對於一個區塊的主要運算實際就是作Hash驗證,而默克爾樹就是爲了提升驗證效率而設計的數據結構,這個樹型結構的根部是一個Hash值,這個Hash值先後是否一致就代表區塊數據的有效性,這裏須要強調的是因爲交易的id已經包含了交易內容的Hash值,因此在計算默克爾樹的時候並不須要從新計算交易的Hash值,只須要經過交易的id計算默克爾樹的各個結點便可。源代碼以下:
digest_type signed_block::calculate_transaction_merkle_root()const { vector<digest_type> ids; ids.reserve(input_transactions.size()); for( const auto& t : input_transactions ) ids.emplace_back( t.id() ); return merkle( std::move(ids) ); }
實際上,在不一樣視圖下的默克爾樹的計算原理都相似,下面分別是在shard和region這兩個視圖下的默克爾樹計算:
void shard_trace::calculate_root() { static const size_t GUESS_ACTS_PER_TX = 10; vector<digest_type> action_roots; action_roots.reserve(transaction_traces.size() * GUESS_ACTS_PER_TX); for (const auto& tx :transaction_traces) { for (const auto& at: tx.action_traces) { digest_type::encoder enc; fc::raw::pack(enc, at.receiver); fc::raw::pack(enc, at.act.account); fc::raw::pack(enc, at.act.name); fc::raw::pack(enc, at.act.data); fc::raw::pack(enc, at.region_id); fc::raw::pack(enc, at.cycle_index); fc::raw::pack(enc, at.data_access); action_roots.emplace_back(enc.result()); } } shard_root = merkle(action_roots); } digest_type block_trace::calculate_action_merkle_root()const { vector<digest_type> shard_roots; shard_roots.reserve(1024); for(const auto& rt: region_traces ) { for(const auto& ct: rt.cycle_traces ) { for(const auto& st: ct.shard_traces) { shard_roots.emplace_back(st.shard_root); } } } return merkle(shard_roots); }
上面這段代碼值得注意的是shard視圖下的默克爾樹構建,shard之下已是交易了,這一層的運算EOS採起的是直接對交易內容中的每個action進行打包,再構建默克爾樹,因此這一層面的計算不是基於id的,而是基於內容的。
上文提到,區塊的基本組成是交易,交易的基本單位是操做。下面咱們來介紹交易的數據結構。
交易的數據結構
交易狀態
當一筆交易被某個區塊引用時,區塊生產者針對這筆交易會作出相應的操做,而操做的不一樣結果會致使這筆交易的不一樣狀態,交易一共有三種狀態:
成功執行
軟失敗:執行未成功,可是異常處理機制成功捕獲錯誤而且執行
硬失敗:執行未成功,異常處理機制也執行失敗
交易數據結構視圖
+-----------+--------------+------------+--------+----+----------+----------+----+-----------+ | block num | block prefix | expiration | region | id | action 1 | action 2 |... | signature |
+-----------+--------------+------------+--------+----+----------+----------+----+-----------+
+------------------head ------------------------+--------------------body-------------------+
交易頭
交易頭的長度是固定的,能夠避免解析時的動態內存分配,便於快速解析交易部分信息。值得指出的是,全部的交易都有一個期限,這個期限限定了一個交易必須在規定時間內被歸入區塊鏈,若是咱們發現一個交易的時限已通過去,就能夠放心的放棄這個交易,由於全部生產者都不會將它歸入任何區塊。
除了佔用4個字節的期限,交易頭還包含2個字節的區塊序號和4個字節的區塊前綴,以及2個字節的區域(region)標識。交易頭的總長度是12個字節。
交易體
交易體包含了3部份內容:
交易ID
交易操做
交易簽名
交易ID是經過對交易內容自己通過Hash運算得出,因此每一個交易的ID是與其內容一一對應的。交易的主體是由操做構成的,關於操做的細節,本文的後面部分會有詳細解讀。一個交易在歸入區塊以前必須含有簽名,用以驗證交易的合法性,除此以外,交易體也包含了簽名的摘要,一樣用於交易的驗證機制。
延遲型交易
交易分爲兩種類型:
一種是帳戶發起的普通交易;
一種是由代碼生成的自動交易,自動交易能夠設置一個延遲時間,這樣的交易叫延遲型交易,這種交易不會當即被執行,而是等到設定時間到時纔會被執行。
交易中的關鍵算法
在交易這一視圖層的算法相對簡單,不涉及複雜的運算和策略,其中值得一提的是從簽名中提取公鑰的計算。這個過程並不複雜,能夠總結爲下面三個環節:
遍歷交易的全部簽名
經過簽名和簽名摘要(digest)抽取公鑰
把公鑰和簽名組成的配對放入緩存,以便下次快捷訪問
如下是這部分計算的源代碼:
flat_set<public_key_type> signed_transaction::get_signature_keys( const chain_id_type& chain_id )const{ try { using boost::adaptors::transformed; constexpr size_t recovery_cache_size = 100000; static recovery_cache_type recovery_cache; const digest_type digest = sig_digest(chain_id); flat_set<public_key_type> recovered_pub_keys; for(const signature_type& sig : signatures) { recovery_cache_type::index<by_sig>::type::iterator it = recovery_cache.get<by_sig>().find(sig); if(it == recovery_cache.get<by_sig>().end() || it->trx_id != id()) { public_key_type recov = public_key_type(sig, digest); recovery_cache.emplace_back( cached_pub_key{id(), recov, sig} ); //could fail on dup signatures; not a problem recovered_pub_keys.insert(recov); continue; } recovered_pub_keys.insert(it->pub_key); } while(recovery_cache.size() > recovery_cache_size) recovery_cache.erase(recovery_cache.begin()); return recovered_pub_keys; } FC_CAPTURE_AND_RETHROW() }
EOS區塊鏈中的交易是由一個個操做組成的,操做能夠理解成一個可以更改區塊鏈全局狀態的方法,操做的順序是肯定的,一個交易內的操做要麼所有執行成功,要麼都不執行,這與交易的本意(transaction)是一致的。操做是區塊鏈的最底層邏輯,至關於區塊鏈這個大腦的神經元,區塊鏈的智能最終也是經過一個個操做的組合來實現的。從EOS的源代碼中不難看出,做者對於操做的設計是下了一番功夫的。
操做的設計原則
在源代碼中,EOS的做者給出瞭如下幾大設計原則:
獨立原則
操做自己須包含足以解釋操做含義的信息,而不須要依賴區塊鏈提供的上下文信息來幫助詮釋操做。因此,即使一個操做的當前狀態能夠經過區塊鏈上的數據推導得出,咱們也須要將狀態信息歸入操做數據中,以便每一個操做是容易理解的。這個原則體現的是區塊的可解釋性,這一點很是重要,這個底層的設計原則將影響整個區塊鏈的使用效率。
餘額可計算原則
一個帳戶當前的餘額計算,僅僅依賴於與這個帳戶相關的信息即可得出,而不須要解析整個區塊鏈才能得到。這個原則針對的是比特幣的設計,因爲比特幣的餘額計算須要掃描區塊鏈中的全部交易才能精準的計算出一個帳戶的餘額,這使得一個很是基礎的計算落地起來都變得至關繁瑣,EOS的這個設計目的在於提高運算效率。
明確費用原則
區塊鏈的交易費用隨時間變化而變化,因此,一個簽名過的交易須明確的認同這個交易所須要支付的費用,這個費用是在交易造成以前就已經設定而且明確好了的,這一點也很是重要,由於明確的費用協議才能保證餘額的正確計算。
明確受權原則
每一個操做須包含足夠的受權信息以標明是哪個帳戶擁有受權這個操做的權力,這種明確受權的設計思路帶來幾個好處:
便於集中管理
能夠優化受權管理
便於並行處理
**關聯帳戶原則 **
每一個操做須包含足夠的足夠關聯帳戶信息,以保證這個操做可以遍歷全部相關聯的帳戶,也就是這個操做可以影響的全部帳戶,這個原則的目的一樣是爲了確保帳戶的餘額可以獲得及時和準確的運算。
操做的設計思路
操做的來源
一個操做能夠經過兩種途徑產生:
由一個帳號產生,經過簽名來受權,即顯性方式。
由代碼生成,即隱形方式。
底層邏輯
操做的設計遵循React Flux設計模式(https://github.com/facebook/flux/tree/master/examples/flux-concepts),簡單的說就是每個操做將會被賦予一個名稱,而後被分發給一個或者多個handler。在EOS環境中,每一個操做對應的handler是經過scope和name來定義的,默認的handler也能夠再次將操做分發給下一級的多個handler。因此,每一個EOS應用能夠實現本身的handler,當操做被分發到這個應用時,相應的handler的代碼就會被執行。
操做的設計思路中另外一重要概念是受權。每個操做的執行都必須確保具有了指定帳戶的受權。受權經過許可(permission)的方式聲明,對於一個受權動做,帳戶能夠要求任意數量的許可,許可的驗證是獨立於代碼執行的,只有全部規定的許可被成功驗證以後,對應的代碼纔可以被執行。咱們能感覺到這裏面設計者對安全性的追求是很極致的,設計者將安全特性深深的嵌入了區塊鏈的底層設計邏輯,同時又不讓安全機制成爲性能和結構的累贅,讓它自成體系,獨立管理。
操做的數據結構
操做的數據結構定義以下:
struct action { account_name account; action_name name; vector<permission_level> authorization; bytes data; ... };
理解了操做的設計思路的讀者必定會驚呼:居然如此簡潔!
一個操做僅由四個部分構成:
帳戶:操做的來源
名稱:操做的標識
受權:執行操做的許可列表
數據:執行操做須要用到的信息
這樣簡潔的結構不須要過多解釋了。
操做的關鍵算法
在操做這個視圖層,比較複雜的算法邏輯是與受權相關的部分,這部份內容最好的資料是EOS的官方文檔,讀者能夠查看有關這部份內容的中文翻譯:https://github.com/BlockchainTranslator/EOS/blob/master/wiki/Accounts-%26-Permissions.md
讀者如今應該對EOS區塊鏈內核的底層結構和基本邏輯有了瞭解,上述內容是內核的基本組成部分,如同一個機器的零部件,然而,要讓機器運轉起來,還須要把這些零部件串起來的控制中心,而鏈控制器(chain_controller)就是這個控制中心。下面咱們來詳細介紹EOS的鏈控制器。
鏈控制器的基本功能
首先,咱們須要理解鏈控制器存在的主要目的是做爲外部世界與區塊鏈內核之間的交互界面,因此它有着承上啓下的做用,承上爲支撐區塊鏈與外部的交互,啓下爲管理區塊鏈內部的狀態變動。因此,從理解的不一樣角度,鏈控制器能夠被理解爲如下兩個概念:
從外部視角,鏈控制器是一個數據庫,這與鏈外對區塊鏈的理解是一致的,從狹義的角度看,區塊鏈就像一個不可更改的數據庫,而鏈控制器能夠看作這個數據庫的管家,外部世界不用直接與區塊鏈底層通訊,而是經過這個管家來與區塊鏈交互。
從內部視角,鏈控制器是一個協調者,區塊鏈內部各個部件均獨立運做,這與上述的設計原則是一致的,這樣的話,各個部件之間如何調度與協調,就須要一個有全局視角的角色來統一管理,這就是鏈控制器的第二個做用。
用做者的原話來講,鏈控制器的目的是以一種能夠擴展的方式來跟蹤區塊鏈的狀態變化。
鏈控制器的基本要素
爲了維護可靠的區塊鏈狀態,鏈控制器管理着兩個不用類型的數據庫:
區塊日誌
前文已經介紹過了。它的做用是存儲已經通過驗證的不可逆轉的區塊鏈主幹。
塊數據庫
這是一個結構化數據庫,關於塊數據庫的詳細解讀,將會在另外的文章裏面說明,在這裏讀者只須要明白這是一個帶索引的能夠增刪改查的數據庫。它的做用是維護和管理未經驗證的區塊鏈主幹與分支兩個子數據庫,主幹數據庫存儲的是當前長度最長的一條鏈,而分支存儲的是由於不一樣分叉而形成的分支鏈,隨着時間的推移,主鏈是有可能被分支替代的,每當一個新的區塊要寫入時,EOS都會從新計算各分支和主幹的長度,而且決定要不要切換主幹。最後,執行錯誤或者驗證不經過的區塊最終會被拋棄,並從數據庫中清理出去。
除了存儲部分,鏈控制器還需精確維護一個消息機制,以保證對區塊鏈中的其餘成員廣播最新的區塊狀態變動,這個消息機制是區塊鏈網絡可以正常和自主運行的基礎。
事務處理機制
熟悉數據庫的讀者必定知道,對於數據庫的寫入操做,維持一個事務的原子性是很關鍵的,也就是說,對於一批寫入操做,要麼全都成功執行,要麼都不執行,必定不能出現只執行了其中一部分的狀況,由於這樣將致使數據的不一致性,這樣的錯誤是致命的。EOS中對於塊數據庫的寫操做採用一樣的設計原則,事務的使用主要體如今兩個層面:
區塊 整個區塊的內容是一個總體,要麼全都寫入,要麼都不寫入
交易 一個交易是一個總體,其中的操做要麼都執行,要麼都不執行
當事務中的某個步驟出現錯誤時,整個事務就會啓動回滾機制,整個區塊鏈將會恢復到這個事務發生以前的狀態。
鏈控制器中的關鍵過程
鏈控制器有着統籌全局,協調資源的做用,它管理的過程當中最重要的有三個:
鏈控制器的初始化
鏈控制器的初始化是區塊鏈的起點,它的核心過程以下所示:
/** initialize the controller */chain_controller::chain_controller( const chain_controller::controller_config& cfg ):_db( cfg.shared_memory_dir, (cfg.read_only ? database::read_only : database::read_write), cfg.shared_memory_size), _block_log(cfg.block_log_dir) { _initialize_indexes(); for (auto& f : cfg.applied_irreversible_block_callbacks) applied_irreversible_block.connect(f); contracts::chain_initializer starter(cfg.genesis); starter.register_types(*this, _db); // Behave as though we are applying a block during chain initialization (it's the genesis block!) with_applying_block([&] { _initialize_chain(starter); }); _spinup_db(); _spinup_fork_db(); if (_block_log.read_head() && head_block_num() < _block_log.read_head()->block_num()) replay(); } /// chain_controller::chain_controller
它包含了6個環節:
初始化數據庫索引
與須要廣播的對象創建鏈接,以便每次新區塊的加入能夠被區塊鏈網絡收聽到
初始化創世區塊的配置,創世區塊指的是區塊鏈中的第一個區塊,是以後全部區塊的老祖宗,其名所以而來
初始化區塊鏈,詳見下文
啓動數據庫
若是區塊日誌中已經有內容,那麼從新播放全部歷史區塊
區塊鏈的初始化
上面提到的區塊鏈的初始化過程的核心代碼以下所示:
/* *initialize the blockchain、 */void chain_controller::_initialize_chain(contracts::chain_initializer& starter) { try { if (!_db.find<global_property_object>()) { _db.with_write_lock([this, &starter] { auto initial_timestamp = starter.get_chain_start_time(); FC_ASSERT(initial_timestamp != time_point(), "Must initialize genesis timestamp." ); FC_ASSERT( block_timestamp_type(initial_timestamp) == initial_timestamp, "Genesis timestamp must be divisible by config::block_interval_ms" ); // Create global properties const auto& gp = _db.create<global_property_object>([&starter](global_property_object& p) { p.configuration = starter.get_chain_start_configuration(); p.active_producers = starter.get_chain_start_producers(); }); _db.create<dynamic_global_property_object>([&](dynamic_global_property_object& p) { p.time = initial_timestamp; p.recent_slots_filled = uint64_t(-1); p.virtual_net_bandwidth = gp.configuration.max_block_size * (config::blocksize_average_window_ms / config::block_interval_ms ); p.virtual_act_bandwidth = gp.configuration.max_block_acts * (config::blocksize_average_window_ms / config::block_interval_ms ); }); // Initialize block summary index for (int i = 0; i < 0x10000; i++) _db.create<block_summary_object>([&](block_summary_object&) {}); auto acts = starter.prepare_database(*this, _db); // create a block for our genesis transaction to send to applied_irreversible_block below signed_block block{}; block.producer = config::system_account_name; block_trace btrace{block}; btrace.region_traces.emplace_back(); auto& rtrace = btrace.region_traces.back(); rtrace.cycle_traces.emplace_back(); auto& ctrace = rtrace.cycle_traces.back(); ctrace.shard_traces.emplace_back(); auto& strace = ctrace.shard_traces.back(); signed_transaction genesis_setup_transaction; // not actually signed, signature checking is skipped genesis_setup_transaction.actions = move(acts); block.input_transactions.emplace_back(genesis_setup_transaction); ilog( "applying genesis transaction" ); with_skip_flags(skip_scope_check | skip_transaction_signatures | skip_authority_check | received_block | genesis_setup, [&](){ transaction_metadata tmeta( genesis_setup_transaction ); transaction_trace ttrace = __apply_transaction( tmeta ); strace.append(ttrace); }); // TODO: Should we write this genesis block instead of faking it on startup? strace.calculate_root(); applied_block(btrace); applied_irreversible_block(block); ilog( "done applying genesis transaction" ); }); } } FC_CAPTURE_AND_RETHROW() }
上面這段代碼不難理解,它描述了這樣一個初始化過程:
肯定區塊鏈建立時間
初始化全局變量
初始化區塊概要索引
建立創世區塊(genesis)
將創世區塊寫入區塊鏈
計算創世區塊的默克爾樹根
廣播新區塊(創世區塊)的加入
區塊的創造
區塊的創造過程相對比較繁瑣,下面是核心部分的源代碼:
// verify that the block signer is in the current set of active producers. shared_ptr<fork_item> new_head = _fork_db.push_block(new_block); //If the head block from the longest chain does not build off of the current head, we need to switch forks. if (new_head->data.previous != head_block_id()) { //If the newly pushed block is the same height as head, we get head back in new_head //Only switch forks if new_head is actually higher than head if (new_head->data.block_num() > head_block_num()) { wlog("Switching to fork: ${id}", ("id",new_head->data.id())); auto branches = _fork_db.fetch_branch_from(new_head->data.id(), head_block_id()); // pop blocks until we hit the forked block while (head_block_id() != branches.second.back()->data.previous) pop_block(); // push all blocks on the new fork for (auto ritr = branches.first.rbegin(); ritr != branches.first.rend(); ++ritr) { ilog("pushing blocks from fork ${n} ${id}", ("n",(*ritr)->data.block_num())("id",(*ritr)->data.id())); optional<fc::exception> except; try { auto session = _db.start_undo_session(true); _apply_block((*ritr)->data, skip); session.push(); } catch (const fc::exception& e) { except = e; } if (except) { wlog("exception thrown while switching forks ${e}", ("e",except->to_detail_string())); // remove the rest of branches.first from the fork_db, those blocks are invalid while (ritr != branches.first.rend()) { _fork_db.remove((*ritr)->data.id()); ++ritr; } _fork_db.set_head(branches.second.front()); // pop all blocks from the bad fork while (head_block_id() != branches.second.back()->data.previous) pop_block(); // restore all blocks from the good fork for (auto ritr = branches.second.rbegin(); ritr != branches.second.rend(); ++ritr) { auto session = _db.start_undo_session(true); _apply_block((*ritr)->data, skip); session.push(); } throw *except; } } return true; //swithced fork } else return false; // didn't switch fork }
這個過程能夠理解爲:
檢查區塊生產者的合法性
若是當前主幹已經不是最長鏈,須要切換到最長的分叉鏈上切換過程以下:彈出必定數量的區塊,直到碰到分叉處--將分叉中的新區塊寫入主幹數據庫--切換後的分叉成爲主幹--若是上述過程失敗,則恢復狀態
若是不須要切換分支,則往主幹數據庫中寫入區塊
返回結果,以代表此次寫入是否形成了分支切換
交易的寫入
交易的寫入過程相對簡單一些,源代碼以下所示:
// record a txtransaction_trace chain_controller::__apply_transaction( transaction_metadata& meta ) { transaction_trace result(meta.id); for (const auto &act : meta.trx.actions) { apply_context context(*this, _db, act, meta); context.exec(); fc::move_append(result.action_traces, std::move(context.results.applied_actions)); fc::move_append(result.deferred_transactions, std::move(context.results.generated_transactions)); fc::move_append(result.canceled_deferred, std::move(context.results.canceled_deferred)); } uint32_t act_usage = result.action_traces.size(); for (auto &at: result.action_traces) { at.region_id = meta.region_id; at.cycle_index = meta.cycle_index; if (at.receiver == config::system_account_name && at.act.account == config::system_account_name && at.act.name == N(setcode)) { act_usage += config::setcode_act_usage; } } update_usage(meta, act_usage); record_transaction(meta.trx); return result; }
這個過程能夠總結爲:
遍歷交易中的全部操做,對於每一個操做:獲取操做的相關信息,而且執行操做;記錄此次操做執行
更新因此次操做而消耗的資源統計數據
記錄交易的內容
返回結果
但願從底層代碼的解讀中,讀者能以一個更爲深刻的角度來理解EOS的設計思路,也但願這樣解釋角度能夠幫助讀者在EOS平臺上搭建新的應用或者改造EOS的行爲以便符合新的需求,有了這些認知,EOS在讀者面前或許再也不披着神祕面紗,更重要的是,讀者如今有了一個入手的突破點,從這裏開始,能夠逐步的將EOS納爲己用,創造出更精彩的應用。
(注:本文內容僅爲提供瞭解EOS的資料,不作投資建議)
內容來源於:區塊鏈兄弟
原文發佈於簡書
做者:王雲波
延伸閱讀:區塊鏈100講:梅克爾樹保障區塊鏈數據不可篡改,想換根就要砍樹!
崗位招聘