1)CLI客戶端和peer節點之間是如何溝通的?javascript
2)Peer節點之間如何數據傳輸處理,與cli和peer之間的方式有何不一樣?java
3)數據什麼時候進入orderer節點,orderer節點是如何處理的?(0.6裏面就是共識這塊怎麼處理)git
4)鏈碼(CC)是如何與節點或cli或rest api交互的,怎麼實現的?github
舊版本(0.6)的運行時架構:docker
新版本(1.0)的運行時架構:數據庫
Fabric帳本邏輯架構json
Fabric 1.0中的帳本分爲3種:api
區塊鏈數據,這是用文件系統存儲在Committer節點上的。區塊鏈中存儲了Transaction的讀寫集。架構
爲了檢索區塊鏈的方便,因此用LevelDB對其中的Transaction進行了索引。併發
ChainCode操做的實際數據存儲在State Database中,這是一個Key Value的數據庫,默認採用的LevelDB,如今1.0也支持使用CouchDB做爲State Database。
當執行a向b轉帳10元,咱們在cli中執行的命令爲:
peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/cacerts/ca.example.com-cert.pem -C mychannel -n devincc -c '{"Args":["invoke","a","b","10"]}'
這個過程是這樣的:
其中peer chaincode invoke代表這是一個Transaction調用。-c '{"Args":["invoke","a","b","10"]}'中的」invoke」說明調用的是example02.go中的invoke函數,具體函數咱們能夠看看到底實現了什麼功能:
// Transaction makes payment of X units from A to B func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response { var A, B string // Entities var Aval, Bval int // Asset holdings var X int // Transaction value var err error if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 3") } A = args[0] B = args[1] // Get the state from the ledger // TODO: will be nice to have a GetAllState call to ledger Avalbytes, err := stub.GetState(A) if err != nil { return shim.Error("Failed to get state") } if Avalbytes == nil { return shim.Error("Entity not found") } Aval, _ = strconv.Atoi(string(Avalbytes)) Bvalbytes, err := stub.GetState(B) if err != nil { return shim.Error("Failed to get state") } if Bvalbytes == nil { return shim.Error("Entity not found") } Bval, _ = strconv.Atoi(string(Bvalbytes)) // Perform the execution X, err = strconv.Atoi(args[2]) if err != nil { return shim.Error("Invalid transaction amount, expecting a integer value") } Aval = Aval - X Bval = Bval + X fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // Write the state back to the ledger err = stub.PutState(A, []byte(strconv.Itoa(Aval))) if err != nil { return shim.Error(err.Error()) } err = stub.PutState(B, []byte(strconv.Itoa(Bval))) if err != nil { return shim.Error(err.Error()) } return shim.Success(nil) }
其中主要的4個關於StateDatabase調用是:
Avalbytes, err := stub.GetState(A) Bvalbytes, err := stub.GetState(B) err = stub.PutState(A, []byte(strconv.Itoa(Aval))) err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
1.客戶端SDK把'{"Args":["invoke","a","b","10"]}'這些參數發送到endorser peer節點,
2.endorser peer會與ChainCode的docker實例通訊,併爲其提供模擬的State Database的讀寫集,也就是說ChainCode會執行完邏輯,可是並不會在stub.PutState的時候寫數據庫。
3.endorser把這些讀寫集連同簽名返回給Client SDK。
4.SDK再把讀寫集發送給Orderer節點,Orderer節點是進行共識的排序節點,在測試的狀況下,只啓動一個orderer節點,沒有容錯。在生產環境,要進行Crash容錯,須要啓用Zookeeper和Kafka。在1.0中移除了拜占庭容錯,沒有0.6的PBFT,也沒有傳說中的SBFT,不得不說是一個遺憾。
5.Orderer節點只是負責排序和打包工做,處理的結果是一個Batch的Transactions,也就是一個Block,這個Block的產生有兩種狀況,一種狀況是Transaction不少,Block的大小達到了設定的大小,而另外一種狀況是Transaction不多,沒有達到設定的大小,那麼Orderer就會等,等到大小足夠大或者超時時間。這些設置是在configtx.yaml中設定的。
6.打包好的一堆Transactions會發送給Committer Peer提交節點,
7.提交節點收到Orderer節點的數據後,會先進行VSCC校驗,檢查Block的數據是否正確。接下來是對每一個Transaction的驗證,主要是驗證Transaction中的讀寫數據集是否與State Database的數據版本一致。驗證完Block中的全部Transactions後,提交節點會把吧Block寫入區塊鏈。而後把全部驗證經過的Transaction的讀寫集中的寫的部分寫入State Database。另外對於區塊鏈,自己是文件系統,不是數據庫,全部也會有把區塊中的數據在LevelDB中創建索引。
查詢a帳戶的cli命令是:
peer chaincode query -C mychannel -n devincc -c '{"Args":["query","a"]}'
這樣系統會調用ChainCode中的invoke函數,可是傳入的function name是query。也就是會執行以下代碼:
} else if function == "query" { // the old "Query" is now implemtned in invoke return t.query(stub, args) } // query callback representing the query of a chaincode func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response { var A string // Entities var err error if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting name of the person to query") } A = args[0] // Get the state from the ledger Avalbytes, err := stub.GetState(A) if err != nil { jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}" return shim.Error(jsonResp) } jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}" fmt.Printf("Query Response:%s\n", jsonResp) return shim.Success(Avalbytes) }
咱們能夠看到,咱們只是調用了stub.GetState(A),並無寫操做,那麼會像前面說的Transaction同樣那麼複雜嗎?答案是不會。由於調用調用的是peer query,在代碼中,只有invoke的時候纔會執行Transaction步驟中的四、五、六、7.可是若是咱們使用peer invoke,那麼會怎麼樣呢?好比以下的命令:
peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/cacerts/ca.example.com-cert.pem -C mychannel -n mycc -c '{"Args":["query","a"]}'
那麼從代碼上來看,雖然咱們是一個查詢,卻會以Transaction的生命週期來處理。
Fabric不支持對同一個數據的併發事務處理,也就是說,若是咱們同時運行了a->b 10元,b->a 10元,那麼只會第一條Transaction成功,而第二條失敗。由於在Committer節點進行讀寫集版本驗證的時候,第二條Transaction會驗證失敗。這是我徹底沒法接受的一點!
Fabric是異步的系統,在Endorser的時候a->b 10元,b->a 10元都會返回給SDK成功,而第二條Transaction在Committer驗證失敗後不進行State Database的寫入,可是並不會通知Client SDK,因此必須使用EventHub通知Client或者Client從新查詢才能知道是否寫入成功。
無論在提交節點對事務的讀寫數據版本驗證是否經過,由於Block已經在Orderer節點生成了,因此Block是被整塊寫入區塊鏈的,而在State Database不會寫入,因此會在Transaction以外的地方標識該Transaction是無效的。