IBM HyperLedger fabric 详解
生活随笔
收集整理的這篇文章主要介紹了
IBM HyperLedger fabric 详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
fabric源于IBM,初衷為了服務于工業生產,IBM將44,000行代碼開源,是了不起的貢獻,讓我們可以有機會如此近的去探究區塊鏈的原理,但畢竟IBM是從自身利益和客戶利益出發的,并不是毫無目的的去做這項公益事業,我們在看fabric的同時要有一種審慎的思維:區塊鏈不一定非得這樣,它跟比特幣最本質的非技術區別在哪里。我們先來大致了解一下fabric的關鍵術語(因為一些詞匯用英文更準確,我就不硬翻譯了)。
1. Terminology
2. Architecture
架構核心邏輯有三條:Membership、Blockchain和Chaincode。
2.1 Membership Services
這項服務用來管理節點身份、隱私、confidentiality 和 auditability。在一個 non-permissioned的區塊鏈網絡里,參與者不要求授權,所有的節點被視作一樣,都可以去submit一個transaction,去把這些交易存到區塊(blocks)中。那Membership Service是要將一個 non-permissioned的區塊鏈網絡變成一個permissioned的區塊鏈網絡,憑借著Public Key Infrastructure (PKI)、去中心和一致性。
2.2 Blockchain Services
Blockchain services使用建立在HTTP/2上的P2P協議來管理分布式賬本。提供最有效的哈希算法來維護world state的副本。采取可插拔的方式來根據具體需求來設置共識協議,比如PBFT,Raft,PoW和PoS等等。
2.3 Chaincode Services
Chaincode services 會提供一種安全且輕量級的沙盒運行模式,來在VP節點上執行chaincode邏輯。這里使用Container環境,里面的base鏡像都是經過簽名驗證的安全鏡像,包括OS層和開發chaincode的語言、runtime和SDK層,目前支持Go、Jave和Nodejs開發語言。
2.4 Events
在blockchain網絡里,VP節點和chaincode會發送events來觸發一些監聽動作。比如chaincode是用戶代碼,它可以產生用戶事件。
2.5 API 和 CLI
提供REST API,允許注冊用戶、查詢blockchain和發送transactions。一些針對chaincode的API,可以用來執行transactions和查詢交易結果。對于開發者,可以通過CLI快速去測試chaincode,或者去查詢交易狀態。
3. Topology
分布式網絡的拓撲結構是非常值得研究的。在這個世界里散布著眾多參與者,不同角色,不同利益體,各種各樣的情況處理象征著分布式網絡里的規則和法律,無規則不成方圓。在區塊鏈網絡里,有Membership service,有VP節點,NVP節點,一個或多個應用,它們形成一個chain,然后會有多個chain,每一個chain都有各自的安全要求和操作需求。
3.1 單個VP節點網絡
最簡單的網絡就是只包含一個VP節點,因此就省去了共識部分。
3.2 多個VP節點網絡
多個VP和NVP參與的網絡才是有價值和實際意義的。NVP節點分擔VP節點的工作壓力,承擔處理API請求和events的工作。
而對于VP節點,VP節點間會組成一個網狀網絡來傳播信息。一個NVP節點如果被允許的話可以與鄰近的一個VP節點相連。NVP節點是可以省略的,如果Application可以直接和VP節點通訊。
3.3 Multichain
還會存在一個網絡里多條chain的情況,各個chain的意圖不一樣。
4. Protocol
fabric是用gRPC來做P2P通訊的,是一個雙向流消息傳遞。使用?Protocol Buffer來序列化要傳遞的數據結構。
4.1 Message
message分四種:Discovery,Transaction,Synchronization 和 Consensus。每一種信息下還會包含更多的子信息,由payload指出。
payload是一個不透明的字節數組,它包含著一些對象,比如 Transaction 或者 Response。例如,如果 type 是 CHAIN_TRANSACTION,那么 payload 就是一個 Transaction的對象。
message Message { ? ?enum Type { ? ? ? ? UNDEFINED = 0;
? ? ? ? DISC_HELLO = 1; ? ? ? ? DISC_DISCONNECT = 2; ? ? ? ? DISC_GET_PEERS = 3; ? ? ? ? DISC_PEERS = 4; ? ? ? ? DISC_NEWMSG = 5;
? ? ? ? CHAIN_STATUS = 6; ? ? ? ? CHAIN_TRANSACTION = 7; ? ? ? ? CHAIN_GET_TRANSACTIONS = 8; ? ? ? ? CHAIN_QUERY = 9;
? ? ? ? SYNC_GET_BLOCKS = 11; ? ? ? ? SYNC_BLOCKS = 12; ? ? ? ? SYNC_BLOCK_ADDED = 13;
? ? ? ? SYNC_STATE_GET_SNAPSHOT = 14; ? ? ? ? SYNC_STATE_SNAPSHOT = 15; ? ? ? ? SYNC_STATE_GET_DELTAS = 16; ? ? ? ? SYNC_STATE_DELTAS = 17;
? ? ? ? RESPONSE = 20; ? ? ? ? CONSENSUS = 21; ? ? } ? ? Type type = 1; ? ? bytes payload = 2; ? ? google.protobuf.Timestamp timestamp = 3; }
4.1.1 Discovery Messages
一個新啟動的節點,如果CORE_PEER_DISCOVERY_ROOTNODE(ROOTNODE是指網絡中其它任意一個節點的IP)被指定了,它就會開始運行discovery協議。而ROOTNODE就作為最一開始的發現節點,然后通過ROOTNODE節點進而發現全網中所有的節點。discovery協議信息是DISC_HELLO,它的payload是一個HelloMessage對象,同時包含信息發送節點的信息:
message HelloMessage { ? PeerEndpoint peerEndpoint = 1; ? uint64 blockNumber = 2; } message PeerEndpoint { ? ? PeerID ID = 1; ? ? string address = 2; ? ? enum Type { ? ? ? UNDEFINED = 0; ? ? ? VALIDATOR = 1; ? ? ? NON_VALIDATOR = 2; ? ? } ? ? Type type = 3; ? ? bytes pkiID = 4; }
message PeerID { ? ? string name = 1; }
在這個剛加入節點完成DISC_HELLO這輪消息傳遞后,接下來回周期性的發送DISC_GET_PEERS來發現其它加入網絡中的節點。為了回復DISC_GET_PEERS,一個節點會發送DISC_PEERS。
4.1.2 Synchronization Messages
Synchronization 協議是接著上面所說的discovery協議開始的,當一個節點發現它的block的狀態跟其它節點不一致時,就會觸發同步。該節點會廣播(broadcast)三種信息:SYNC_GET_BLOCKS , SYNC_STATE_GET_SNAPSHOT 或者? SYNC_STATE_GET_DELTAS,同時對應接收到三種信息:SYNC_BLOCKS , SYNC_STATE_SNAPSHOT 或者 SYNC_STATE_DELTAS。
目前fabric嵌入的共識算法是pbft。
SYNC_GET_BLOCKS?會請求一系列連續的block,發送的數據結構中payload將是一個SyncBlockRange對象。
message SyncBlockRange { ? ? uint64 correlationId = 1; ? ? uint64 start = 2; ? ? uint64 end = 3; }
接收的節點會回復SYNC_BLOCKS,它的payload是一個SyncBlocks對象:
message SyncBlocks { ? ? SyncBlockRange range = 1; ? ? repeated Block blocks = 2; }
start和end表示起始和結束的block。例如start=3, end=5,代表了block 3,4,5;start=5, end=3,代表了block 5,4,3。
SYNC_STATE_GET_SNAPSHOT?會請求當前world state的一個snapshot,該信息的payload是一個SyncStateSnapshotRequest對象:
message SyncStateSnapshotRequest { ? ? uint64 correlationId = 1; }
correlationId是發出請求的peer用來追蹤對應的該信息的回復。收到該消息的peer會回復SYNC_STATE_SNAPSHOT,它的payload是一個SyncStateSnapshot對象:
message SyncStateSnapshot { ? ? bytes delta = 1; ? ? uint64 sequence = 2; ? ? uint64 blockNumber = 3; ? ? SyncStateSnapshotRequest request = 4; }
SYNC_STATE_GET_DELTAS?默認Ledger會包含500個transition deltas。delta(j)表示block(i)和block(j)之間的狀態轉變(i = j -1)。
4.1.3 Consensus Messages
Consensus framework會將接收到的CHAIN_TRANSACTION轉變成CONSENSUS,然后廣播給所有的VP節點。
4.1.4 Transaction Messages
在fabric中的交易有三種:Deploy, Invoke 和 Query。Deploy將指定的chaincode安裝到chain上,Invoke和Query會調用已經部署好的chaincode的函數。
4.2 Ledger
Ledger主要包含兩塊:blockchain和world state。blockchain就是一系列連在一起的block,用來記錄歷史交易。world state是一個key-value數據庫,當交易執行后,chaincode會將state存在里面。
4.2.1 Blockchain
blockchain是指由一些block連成的list,每一個block都包含上一個block的hash。一個block還會包含一些交易列表以及執行所有這些交易后world state的一個hash。
message Block { ? version = 1; ? google.protobuf.Timestamp timestamp = 2; ? bytes transactionsHash = 3; ? bytes stateHash = 4; ? bytes previousBlockHash = 5; ? bytes consensusMetadata = 6; ? NonHashData nonHashData = 7; }
message BlockTransactions { ? repeated Transaction transactions = 1; }
那上一個block的hash是如何計算的呢:
4.2.2 World State
一個peer的world state是所有部署的chaincodes的狀態(state)的集合。一個chaincode的狀態由鍵值對(key-value)的集合來描述。我們期望網絡里的節點擁有一致的world state,所以會通過計算world state的 crypto-hash 來進行比較,但是將會消耗比較昂貴的算力,為此我們需要設計一個高效率的計算方法。比如引入Bucket-tree來實現world state的組織。
world state中的key的表示為{chaincodeID, ckey},我們可以這樣來描述key, key = chaincodeID+nil+cKey。
world state的key-value會存到一個hash表中,這個hash表有預先定義好數量(numBuckets)的buckets組成。一個 hash function 會來定義哪個桶包含哪個key。這些buckets都將作為merkle-tree的葉子節點,編號最小的bucket作為這個merkle-tree最左面的葉子節點。倒數第二層的構建,從左開始每maxGroupingAtEachLevel(預先定義好數量)這么多的葉子節點為一組聚在一起,形成N組,每一組都會插入一個節點作為所包含葉子節點的父節點,這樣就形成了倒數第二層。要注意的是,最末層的父節點(就是剛剛描述的插入的節點)可能會有少于maxGroupingAtEachLevel的孩子節點。按照這樣的方法不斷構建更高一層,直到根節點被構建出來。
舉一個例子,{numBuckets=10009 and maxGroupingAtEachLevel=10},它形成的tree的每一次包含的節點數目如下:
4.3 Consensus Framework
consensus framework包含了三個package:consensus、controller和helper。
4.3.1 Executor 接口
在源碼中我們會經常看 executor 相關的代碼,這個借口下的方法可以做到:? 開始批量交易、執行交易、提交與回滾交易
4.3.2 Ledger 接口
type Ledger interface { ? ? ReadOnlyLedger ? ? UtilLedger ? ? WritableLedger }
ReadOnlyLedger接口用來查詢 ledger 的本地備份,不做修改,函數有:
1、GetBlockchainSize() (uint64, error),這個函數在源碼里常見,返回了ledger的長度 2、GetBlock(id uint64) (block *pb.Block, err error) 3、GetCurrentStateHash() (stateHash []byte, err error),返回 ledger 當前狀態的hash
4.3.3 helper 包
helper包可以幫助VP節點建立與其他peer之間的通信和消息處理,helper.HandleMessage,這個函數會處理四種消息類型,
pb.Message_CONSENSUS? pb.Message_CHAIN_TRANSACTION? pb.Message_CHAIN_QUERY? others
4.4 Chaincode
chaincode是一段應用級的代碼,交易邏輯就在里面,fabric是用Docker容器來運行chaincode的。一旦chaincode容器被啟動,它就會通過gRPC與啟動這個chaincode的VP(Validating Peer)節點連接。
上面4.1提到的四種消息中有一種叫transaction message,包含Deploy, Invoke 和 Query。指的就是與chaincode相關的交易信息。chaincode需要實現三個函數,Init,Invoke 和 Query。Init是構造函數,它只在部署交易時被執行,Query函數用來讀取狀態。Invoke來進行交易的發生。
chaincode容器被部署時,會向對應的peer進行注冊,注冊之后,VP節點就會通知chaincode容器調用Init函數。其實peer跟chaincode容器之間是隔著一個shim層的,chaincode容器的shim層會接收來自peer的信息,根據信息調用chaincode相應的函數,如Invoke。
5. What we can do
5.1 Asset Management 資產管理
這是一個在fabric上實現的一個chaincode demo,用來模擬數字資產的管理。chaincode一共有四個函數:init(user), assign(asset, user), transfer(asset, user), query(asset)。
在chaincode被部署時,init(user)就會被自動調用。設想一下,? 1. Alice是這個chaincode的部署者;? 2. Alice想要將管理員這個角色分配給Bob;? 3. 之后Alice會獲得Bob的一個TCert,我們叫這個證書BobCert;? 4. Alice構建一條deploy交易,并將交易的元數據設置到BobCert;? 5. Alice將這個交易提交到fabric網絡中。
這樣Bob就會被賦予管理員角色,這就是init函數要做的。接下來看一下assign:
1、Bob成為了chaincode的管理員 2、Bob想要將資產‘Picasso’分配給Charlie 3、Bob會獲得Charlie的一個TCert,我們叫這個證書CharlieCert 4、Bob構建一個invoke交易,來調用assign這個函數,參數是 (‘Picasso’, Base64(DER(CharlieCert))) 5、Bob提交這個交易到fabric網絡中
transfer函數:? 1. Charlie成為了資產‘Picasso’的擁有者了? 2. Charlie想要將‘Picasso’的所有權轉交給Dave? 3. Charlie獲得Dave的一個TCert,我們叫這個證書為DaveCert? 4. Charlie構建一個invoke交易,來調用transfer函數,參數為(‘Picasso’, Base64(DER(DaveCert)))? 5. Charlie提交交易到fabric網絡中
query函數用來查詢資產的擁有者。
完成整套邏輯,需要我們寫的chaincode的代碼只有三百行。像在transfer的實現中,我們需要首先判斷這個發起人的身份,確保只有資產所有者才能轉移自己的資產,然后全網公證資產的轉移,任何一方都無法篡改和抵賴。
6. Defect
其實fabric還存在著諸多的缺陷,畢竟目前還是一個襁褓中的嬰兒。? 例如memberserice與現有CA系統的集成,數據庫部分也欠缺。? 其實這里有一個開放性的命題,大家不妨一起想想,可以在博客下面的評論中留言,或許會碰撞出一些火花:VP(validating peer)節點是網絡的實質性參與者,可以提出交易,并就交易達成一致,然后執行交易,但在fabric中有一個節點叫NVP(not-validating peer)節點,它只與某一個VP節點相連,不能參與交易執行和一致性達成,只能為它所連的VP節點分擔API處理部分和事件部分的壓力,但可以去查詢網絡產生的ledger,有人說這樣的設計可以有助于監管者加入進來,監管者只需查詢生成的ledger,而不需參與交易,也有人說NVP節點引入是為了降低VP節點的計算壓力,將一些外圍的操作讓NVP節點來做。
7. Contribution
Implement SYNC_BLOCK_ADDED handler
我的一個同事實現了SYNC_BLOCK_ADDED消息的handler,這樣在noops共識模式下,當一個block被加到(mined/added)ledger時,NVP節點就可以處理這條消息了,并將最新加入的block存在它自己的ledger中。
SYNC_BLOCK_ADDED message 對應的callback是beforeBlockAdded(core/peer/handler.go),官方代碼如下:
func (d *Handler) beforeBlockAdded(e *fsm.Event) { ? ? peerLogger.Debugf("Received message: %s", e.Event) ? ? msg, ok := e.Args[0].(*pb.Message) ? ? if !ok { ? ? ? ? e.Cancel(fmt.Errorf("Received unexpected message type")) ? ? ? ? return ? ? } ? ? // Add the block and any delta state to the ledger ? ? _ = msg }
這里并沒有去獲取和處理block的信息,我們需要加入如下:
if ValidatorEnabled() { ? ? ?e.Cancel(fmt.Errorf("VP shouldn't receive SYNC_BLOCK_ADDED")) ? ? ?return ?} ? ? // Add the block and any delta state to the ledger - ? _ = msg ?blockState := &pb.BlockState{} ?err := proto.Unmarshal(msg.Payload, blockState) ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error unmarshalling BlockState: %s", err)) ? ? ?return ?} ?coord := d.Coordinator ?blockHeight := coord.GetBlockchainSize() ?if blockHeight <= 0 { ? ? ?e.Cancel(fmt.Errorf("No genesis block is made")) ? ? ?return ?} ?curBlock, err := coord.GetBlockByNumber(blockHeight -1) ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error fetching block #%d, %s", blockHeight -1, err)) ? ? ?return ?} ?hash, err := curBlock.GetHash() ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error hashing latest block")) ? ? ?return ?} ?if bytes.Compare(hash, blockState.Block.PreviousBlockHash) != 0 { ? ? ?e.Cancel(fmt.Errorf("PreviousBlockHash of received block doesnot match hash of current block")) ? ? ?return ?} ?coord.PutBlock(blockHeight, blockState.Block) ?delta := &statemgmt.StateDelta{} ?if err := delta.Unmarshal(blockState.StateDelta); nil != err { ? ? ?e.Cancel(fmt.Errorf("Received a corrupt state delta")) ? ? ?return ?} ?coord.ApplyStateDelta(msg, delta) ?if coord.CommitStateDelta(msg) != nil { ? ? ?e.Cancel(fmt.Errorf("Played state forward, hashes matched, but failed to commit, invalidated state")) ? ? ?return ?} ?peerLogger.Infof("Blockchain height grows into %d", coord.GetBlockchainSize())
Enable statetransfer for HELLO message
我們還發現當一個NVP節點剛加入網絡時,它會發送一個DISC_HELLO message,隨后從其他節點接收一個包含那個節點的blockchain信息的DISC_HELLO message,不過官方代碼并沒有給出NVP依據這些返回信息同步自己狀態的實現。NVP正在網絡中實施自己的狀態同步時,一個新的block被mine,NVP卻不能把這個新的block加入到自己的chain中。所以目前就出現了一個棘手的情況:當新的NVP節點剛加入網絡時,通過HELLO message獲取其他節點的blockchain信息開始同步自己的狀態,這肯定需要一定的時間來完成,但與此同時,網絡里的交易還在繼續,新的block會被不斷的mined,雖然NVP能接收到SYNC_BLOCK_ADDED,并擁有處理它的handler,但是這時候卻不能將新的block信息加入到自己的chain中,因為hash不匹配,畢竟NVP節點并沒有完成一開始的同步。
1. Terminology
- Transaction? 它一條request,用來在ledger上執行一個function,這個function是用chaincode來實現的
- Transactor? 發出transaction的實體,比如它可能是一個客戶端應用
- Ledger ?Legder可以理解為一串經過加密的block鏈條,每一個block包含著transactions和當前world state等信息
- World State? world state是一組變量的集合,包含著transactions的執行結果
- Chaincode? 這是一段應用層面的代碼(又叫smart contract,智能合約),它存儲在ledger上,作為transaction的一部分。也就是說chaincode來運行transaction,然后運行結果可能會修改world state
- Validating Peer? 參與者之一,它是一種在網絡里負責執行一致性協議、確認交易和維護賬本的計算機節點
- Nonvalidating Peer??它相當于一個代理節點,用來連接transactor和鄰近的VP(Validating Peer)節點。一個NVP節點不會去執行transactions但是回去驗證它們。同時它也會承擔起事件流server和提供REST services的角色
- Permissioned Ledger??這是一個要求每一個實體和節點都要成為網絡成員的blockchain網絡,所有匿名節點都不被允許連接
- Privacy??用來保護和隱蔽chain transactors的身份,當網絡成員要檢查交易時,如果沒有特權的話,是無法追蹤到交易的transactor
- Confidentiality??這個特性使得交易內容不是對所有人可見,只開放給利益相關者
- Auditability??將blockchain用于商業用途需要遵守規則,方便監管者調查交易記錄
2. Architecture
架構核心邏輯有三條:Membership、Blockchain和Chaincode。
2.1 Membership Services
這項服務用來管理節點身份、隱私、confidentiality 和 auditability。在一個 non-permissioned的區塊鏈網絡里,參與者不要求授權,所有的節點被視作一樣,都可以去submit一個transaction,去把這些交易存到區塊(blocks)中。那Membership Service是要將一個 non-permissioned的區塊鏈網絡變成一個permissioned的區塊鏈網絡,憑借著Public Key Infrastructure (PKI)、去中心和一致性。
2.2 Blockchain Services
Blockchain services使用建立在HTTP/2上的P2P協議來管理分布式賬本。提供最有效的哈希算法來維護world state的副本。采取可插拔的方式來根據具體需求來設置共識協議,比如PBFT,Raft,PoW和PoS等等。
2.3 Chaincode Services
Chaincode services 會提供一種安全且輕量級的沙盒運行模式,來在VP節點上執行chaincode邏輯。這里使用Container環境,里面的base鏡像都是經過簽名驗證的安全鏡像,包括OS層和開發chaincode的語言、runtime和SDK層,目前支持Go、Jave和Nodejs開發語言。
2.4 Events
在blockchain網絡里,VP節點和chaincode會發送events來觸發一些監聽動作。比如chaincode是用戶代碼,它可以產生用戶事件。
2.5 API 和 CLI
提供REST API,允許注冊用戶、查詢blockchain和發送transactions。一些針對chaincode的API,可以用來執行transactions和查詢交易結果。對于開發者,可以通過CLI快速去測試chaincode,或者去查詢交易狀態。
3. Topology
分布式網絡的拓撲結構是非常值得研究的。在這個世界里散布著眾多參與者,不同角色,不同利益體,各種各樣的情況處理象征著分布式網絡里的規則和法律,無規則不成方圓。在區塊鏈網絡里,有Membership service,有VP節點,NVP節點,一個或多個應用,它們形成一個chain,然后會有多個chain,每一個chain都有各自的安全要求和操作需求。
3.1 單個VP節點網絡
最簡單的網絡就是只包含一個VP節點,因此就省去了共識部分。
3.2 多個VP節點網絡
多個VP和NVP參與的網絡才是有價值和實際意義的。NVP節點分擔VP節點的工作壓力,承擔處理API請求和events的工作。
而對于VP節點,VP節點間會組成一個網狀網絡來傳播信息。一個NVP節點如果被允許的話可以與鄰近的一個VP節點相連。NVP節點是可以省略的,如果Application可以直接和VP節點通訊。
3.3 Multichain
還會存在一個網絡里多條chain的情況,各個chain的意圖不一樣。
4. Protocol
fabric是用gRPC來做P2P通訊的,是一個雙向流消息傳遞。使用?Protocol Buffer來序列化要傳遞的數據結構。
4.1 Message
message分四種:Discovery,Transaction,Synchronization 和 Consensus。每一種信息下還會包含更多的子信息,由payload指出。
payload是一個不透明的字節數組,它包含著一些對象,比如 Transaction 或者 Response。例如,如果 type 是 CHAIN_TRANSACTION,那么 payload 就是一個 Transaction的對象。
message Message { ? ?enum Type { ? ? ? ? UNDEFINED = 0;
? ? ? ? DISC_HELLO = 1; ? ? ? ? DISC_DISCONNECT = 2; ? ? ? ? DISC_GET_PEERS = 3; ? ? ? ? DISC_PEERS = 4; ? ? ? ? DISC_NEWMSG = 5;
? ? ? ? CHAIN_STATUS = 6; ? ? ? ? CHAIN_TRANSACTION = 7; ? ? ? ? CHAIN_GET_TRANSACTIONS = 8; ? ? ? ? CHAIN_QUERY = 9;
? ? ? ? SYNC_GET_BLOCKS = 11; ? ? ? ? SYNC_BLOCKS = 12; ? ? ? ? SYNC_BLOCK_ADDED = 13;
? ? ? ? SYNC_STATE_GET_SNAPSHOT = 14; ? ? ? ? SYNC_STATE_SNAPSHOT = 15; ? ? ? ? SYNC_STATE_GET_DELTAS = 16; ? ? ? ? SYNC_STATE_DELTAS = 17;
? ? ? ? RESPONSE = 20; ? ? ? ? CONSENSUS = 21; ? ? } ? ? Type type = 1; ? ? bytes payload = 2; ? ? google.protobuf.Timestamp timestamp = 3; }
4.1.1 Discovery Messages
一個新啟動的節點,如果CORE_PEER_DISCOVERY_ROOTNODE(ROOTNODE是指網絡中其它任意一個節點的IP)被指定了,它就會開始運行discovery協議。而ROOTNODE就作為最一開始的發現節點,然后通過ROOTNODE節點進而發現全網中所有的節點。discovery協議信息是DISC_HELLO,它的payload是一個HelloMessage對象,同時包含信息發送節點的信息:
message HelloMessage { ? PeerEndpoint peerEndpoint = 1; ? uint64 blockNumber = 2; } message PeerEndpoint { ? ? PeerID ID = 1; ? ? string address = 2; ? ? enum Type { ? ? ? UNDEFINED = 0; ? ? ? VALIDATOR = 1; ? ? ? NON_VALIDATOR = 2; ? ? } ? ? Type type = 3; ? ? bytes pkiID = 4; }
message PeerID { ? ? string name = 1; }
如果一個節點接收到DISC_HELLO信息,發現里面的block height高于自己目前的block height,它會立即發送一個同步協議來與全網同步自己的狀態(mark:但是在源碼層面似乎并沒有實現同步這個邏輯)。
在這個剛加入節點完成DISC_HELLO這輪消息傳遞后,接下來回周期性的發送DISC_GET_PEERS來發現其它加入網絡中的節點。為了回復DISC_GET_PEERS,一個節點會發送DISC_PEERS。
4.1.2 Synchronization Messages
Synchronization 協議是接著上面所說的discovery協議開始的,當一個節點發現它的block的狀態跟其它節點不一致時,就會觸發同步。該節點會廣播(broadcast)三種信息:SYNC_GET_BLOCKS , SYNC_STATE_GET_SNAPSHOT 或者? SYNC_STATE_GET_DELTAS,同時對應接收到三種信息:SYNC_BLOCKS , SYNC_STATE_SNAPSHOT 或者 SYNC_STATE_DELTAS。
目前fabric嵌入的共識算法是pbft。
SYNC_GET_BLOCKS?會請求一系列連續的block,發送的數據結構中payload將是一個SyncBlockRange對象。
message SyncBlockRange { ? ? uint64 correlationId = 1; ? ? uint64 start = 2; ? ? uint64 end = 3; }
接收的節點會回復SYNC_BLOCKS,它的payload是一個SyncBlocks對象:
message SyncBlocks { ? ? SyncBlockRange range = 1; ? ? repeated Block blocks = 2; }
start和end表示起始和結束的block。例如start=3, end=5,代表了block 3,4,5;start=5, end=3,代表了block 5,4,3。
SYNC_STATE_GET_SNAPSHOT?會請求當前world state的一個snapshot,該信息的payload是一個SyncStateSnapshotRequest對象:
message SyncStateSnapshotRequest { ? ? uint64 correlationId = 1; }
correlationId是發出請求的peer用來追蹤對應的該信息的回復。收到該消息的peer會回復SYNC_STATE_SNAPSHOT,它的payload是一個SyncStateSnapshot對象:
message SyncStateSnapshot { ? ? bytes delta = 1; ? ? uint64 sequence = 2; ? ? uint64 blockNumber = 3; ? ? SyncStateSnapshotRequest request = 4; }
SYNC_STATE_GET_DELTAS?默認Ledger會包含500個transition deltas。delta(j)表示block(i)和block(j)之間的狀態轉變(i = j -1)。
4.1.3 Consensus Messages
Consensus framework會將接收到的CHAIN_TRANSACTION轉變成CONSENSUS,然后廣播給所有的VP節點。
4.1.4 Transaction Messages
在fabric中的交易有三種:Deploy, Invoke 和 Query。Deploy將指定的chaincode安裝到chain上,Invoke和Query會調用已經部署好的chaincode的函數。
4.2 Ledger
Ledger主要包含兩塊:blockchain和world state。blockchain就是一系列連在一起的block,用來記錄歷史交易。world state是一個key-value數據庫,當交易執行后,chaincode會將state存在里面。
4.2.1 Blockchain
blockchain是指由一些block連成的list,每一個block都包含上一個block的hash。一個block還會包含一些交易列表以及執行所有這些交易后world state的一個hash。
message Block { ? version = 1; ? google.protobuf.Timestamp timestamp = 2; ? bytes transactionsHash = 3; ? bytes stateHash = 4; ? bytes previousBlockHash = 5; ? bytes consensusMetadata = 6; ? NonHashData nonHashData = 7; }
message BlockTransactions { ? repeated Transaction transactions = 1; }
那上一個block的hash是如何計算的呢:
- 用 protocol buffer 序列化block的信息
- 用 SHA3 SHAKE256 算法將序列化后的block信息哈希成一個512字節的輸出
4.2.2 World State
一個peer的world state是所有部署的chaincodes的狀態(state)的集合。一個chaincode的狀態由鍵值對(key-value)的集合來描述。我們期望網絡里的節點擁有一致的world state,所以會通過計算world state的 crypto-hash 來進行比較,但是將會消耗比較昂貴的算力,為此我們需要設計一個高效率的計算方法。比如引入Bucket-tree來實現world state的組織。
world state中的key的表示為{chaincodeID, ckey},我們可以這樣來描述key, key = chaincodeID+nil+cKey。
world state的key-value會存到一個hash表中,這個hash表有預先定義好數量(numBuckets)的buckets組成。一個 hash function 會來定義哪個桶包含哪個key。這些buckets都將作為merkle-tree的葉子節點,編號最小的bucket作為這個merkle-tree最左面的葉子節點。倒數第二層的構建,從左開始每maxGroupingAtEachLevel(預先定義好數量)這么多的葉子節點為一組聚在一起,形成N組,每一組都會插入一個節點作為所包含葉子節點的父節點,這樣就形成了倒數第二層。要注意的是,最末層的父節點(就是剛剛描述的插入的節點)可能會有少于maxGroupingAtEachLevel的孩子節點。按照這樣的方法不斷構建更高一層,直到根節點被構建出來。
舉一個例子,{numBuckets=10009 and maxGroupingAtEachLevel=10},它形成的tree的每一次包含的節點數目如下:
4.3 Consensus Framework
consensus framework包含了三個package:consensus、controller和helper。
- consensus.Communicator用來發送消息給其他的VP節點
- consensus.Executor用于交易的啟動、執行和回滾,還有preview、commit
- controller指定被VP節點使用的consensus plugin
- helper用來幫助consensus plugin與stack交互,例如維護message handler
4.3.1 Executor 接口
在源碼中我們會經常看 executor 相關的代碼,這個借口下的方法可以做到:? 開始批量交易、執行交易、提交與回滾交易
4.3.2 Ledger 接口
type Ledger interface { ? ? ReadOnlyLedger ? ? UtilLedger ? ? WritableLedger }
ReadOnlyLedger接口用來查詢 ledger 的本地備份,不做修改,函數有:
1、GetBlockchainSize() (uint64, error),這個函數在源碼里常見,返回了ledger的長度 2、GetBlock(id uint64) (block *pb.Block, err error) 3、GetCurrentStateHash() (stateHash []byte, err error),返回 ledger 當前狀態的hash
4.3.3 helper 包
helper包可以幫助VP節點建立與其他peer之間的通信和消息處理,helper.HandleMessage,這個函數會處理四種消息類型,
pb.Message_CONSENSUS? pb.Message_CHAIN_TRANSACTION? pb.Message_CHAIN_QUERY? others
4.4 Chaincode
chaincode是一段應用級的代碼,交易邏輯就在里面,fabric是用Docker容器來運行chaincode的。一旦chaincode容器被啟動,它就會通過gRPC與啟動這個chaincode的VP(Validating Peer)節點連接。
上面4.1提到的四種消息中有一種叫transaction message,包含Deploy, Invoke 和 Query。指的就是與chaincode相關的交易信息。chaincode需要實現三個函數,Init,Invoke 和 Query。Init是構造函數,它只在部署交易時被執行,Query函數用來讀取狀態。Invoke來進行交易的發生。
chaincode容器被部署時,會向對應的peer進行注冊,注冊之后,VP節點就會通知chaincode容器調用Init函數。其實peer跟chaincode容器之間是隔著一個shim層的,chaincode容器的shim層會接收來自peer的信息,根據信息調用chaincode相應的函數,如Invoke。
5. What we can do
5.1 Asset Management 資產管理
這是一個在fabric上實現的一個chaincode demo,用來模擬數字資產的管理。chaincode一共有四個函數:init(user), assign(asset, user), transfer(asset, user), query(asset)。
在chaincode被部署時,init(user)就會被自動調用。設想一下,? 1. Alice是這個chaincode的部署者;? 2. Alice想要將管理員這個角色分配給Bob;? 3. 之后Alice會獲得Bob的一個TCert,我們叫這個證書BobCert;? 4. Alice構建一條deploy交易,并將交易的元數據設置到BobCert;? 5. Alice將這個交易提交到fabric網絡中。
這樣Bob就會被賦予管理員角色,這就是init函數要做的。接下來看一下assign:
1、Bob成為了chaincode的管理員 2、Bob想要將資產‘Picasso’分配給Charlie 3、Bob會獲得Charlie的一個TCert,我們叫這個證書CharlieCert 4、Bob構建一個invoke交易,來調用assign這個函數,參數是 (‘Picasso’, Base64(DER(CharlieCert))) 5、Bob提交這個交易到fabric網絡中
transfer函數:? 1. Charlie成為了資產‘Picasso’的擁有者了? 2. Charlie想要將‘Picasso’的所有權轉交給Dave? 3. Charlie獲得Dave的一個TCert,我們叫這個證書為DaveCert? 4. Charlie構建一個invoke交易,來調用transfer函數,參數為(‘Picasso’, Base64(DER(DaveCert)))? 5. Charlie提交交易到fabric網絡中
query函數用來查詢資產的擁有者。
完成整套邏輯,需要我們寫的chaincode的代碼只有三百行。像在transfer的實現中,我們需要首先判斷這個發起人的身份,確保只有資產所有者才能轉移自己的資產,然后全網公證資產的轉移,任何一方都無法篡改和抵賴。
6. Defect
其實fabric還存在著諸多的缺陷,畢竟目前還是一個襁褓中的嬰兒。? 例如memberserice與現有CA系統的集成,數據庫部分也欠缺。? 其實這里有一個開放性的命題,大家不妨一起想想,可以在博客下面的評論中留言,或許會碰撞出一些火花:VP(validating peer)節點是網絡的實質性參與者,可以提出交易,并就交易達成一致,然后執行交易,但在fabric中有一個節點叫NVP(not-validating peer)節點,它只與某一個VP節點相連,不能參與交易執行和一致性達成,只能為它所連的VP節點分擔API處理部分和事件部分的壓力,但可以去查詢網絡產生的ledger,有人說這樣的設計可以有助于監管者加入進來,監管者只需查詢生成的ledger,而不需參與交易,也有人說NVP節點引入是為了降低VP節點的計算壓力,將一些外圍的操作讓NVP節點來做。
7. Contribution
Implement SYNC_BLOCK_ADDED handler
我的一個同事實現了SYNC_BLOCK_ADDED消息的handler,這樣在noops共識模式下,當一個block被加到(mined/added)ledger時,NVP節點就可以處理這條消息了,并將最新加入的block存在它自己的ledger中。
SYNC_BLOCK_ADDED message 對應的callback是beforeBlockAdded(core/peer/handler.go),官方代碼如下:
func (d *Handler) beforeBlockAdded(e *fsm.Event) { ? ? peerLogger.Debugf("Received message: %s", e.Event) ? ? msg, ok := e.Args[0].(*pb.Message) ? ? if !ok { ? ? ? ? e.Cancel(fmt.Errorf("Received unexpected message type")) ? ? ? ? return ? ? } ? ? // Add the block and any delta state to the ledger ? ? _ = msg }
這里并沒有去獲取和處理block的信息,我們需要加入如下:
if ValidatorEnabled() { ? ? ?e.Cancel(fmt.Errorf("VP shouldn't receive SYNC_BLOCK_ADDED")) ? ? ?return ?} ? ? // Add the block and any delta state to the ledger - ? _ = msg ?blockState := &pb.BlockState{} ?err := proto.Unmarshal(msg.Payload, blockState) ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error unmarshalling BlockState: %s", err)) ? ? ?return ?} ?coord := d.Coordinator ?blockHeight := coord.GetBlockchainSize() ?if blockHeight <= 0 { ? ? ?e.Cancel(fmt.Errorf("No genesis block is made")) ? ? ?return ?} ?curBlock, err := coord.GetBlockByNumber(blockHeight -1) ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error fetching block #%d, %s", blockHeight -1, err)) ? ? ?return ?} ?hash, err := curBlock.GetHash() ?if err != nil { ? ? ?e.Cancel(fmt.Errorf("Error hashing latest block")) ? ? ?return ?} ?if bytes.Compare(hash, blockState.Block.PreviousBlockHash) != 0 { ? ? ?e.Cancel(fmt.Errorf("PreviousBlockHash of received block doesnot match hash of current block")) ? ? ?return ?} ?coord.PutBlock(blockHeight, blockState.Block) ?delta := &statemgmt.StateDelta{} ?if err := delta.Unmarshal(blockState.StateDelta); nil != err { ? ? ?e.Cancel(fmt.Errorf("Received a corrupt state delta")) ? ? ?return ?} ?coord.ApplyStateDelta(msg, delta) ?if coord.CommitStateDelta(msg) != nil { ? ? ?e.Cancel(fmt.Errorf("Played state forward, hashes matched, but failed to commit, invalidated state")) ? ? ?return ?} ?peerLogger.Infof("Blockchain height grows into %d", coord.GetBlockchainSize())
Enable statetransfer for HELLO message
我們還發現當一個NVP節點剛加入網絡時,它會發送一個DISC_HELLO message,隨后從其他節點接收一個包含那個節點的blockchain信息的DISC_HELLO message,不過官方代碼并沒有給出NVP依據這些返回信息同步自己狀態的實現。NVP正在網絡中實施自己的狀態同步時,一個新的block被mine,NVP卻不能把這個新的block加入到自己的chain中。所以目前就出現了一個棘手的情況:當新的NVP節點剛加入網絡時,通過HELLO message獲取其他節點的blockchain信息開始同步自己的狀態,這肯定需要一定的時間來完成,但與此同時,網絡里的交易還在繼續,新的block會被不斷的mined,雖然NVP能接收到SYNC_BLOCK_ADDED,并擁有處理它的handler,但是這時候卻不能將新的block信息加入到自己的chain中,因為hash不匹配,畢竟NVP節點并沒有完成一開始的同步。
總結
以上是生活随笔為你收集整理的IBM HyperLedger fabric 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两步搞定Hyperledger主打区块链
- 下一篇: 优秀的API接口设计原则及方法