hyperledger fabric1.0總體架構與記帳邏輯架構的分析

一、關於邏輯架構的一些問題

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)的運行時架構:數據庫

三、fabric1.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是無效的。

相關文章
相關標籤/搜索