openblockchain是IBM開源的blockchain項目,具體安裝流程之前已經介紹過,具體請看http://blog.csdn.net/pangjiuzala/article/details/50897819。
解壓後會發現在obc-peer根目錄下出現一個main.go文件,其中主要功能是生成obc-peer命令,核心代碼集中在openchain中的。接下來,將首先從chaincode代碼分析開始,包含了如圖下圖所示的幾個核心文件。
chaincode.go
chaincode.go位於shim包中,
var chaincodeLogger = logging.MustGetLogger("chaincode")
//調用go-logging中logging庫的MustGetLogger函數對shim package進行記錄,相當於日誌文件
//其中傳遞的參數爲當前go文件
MustGetLogger
MustGetLogger位於logger.go文件中,具體代碼如下
// GetLogger 創建和返回一個基於模塊名稱的Logger對象
func GetLogger(module string) (*Logger, error) {
return &Logger{Module: module}, nil
}
// MustGetLogger 與GetLogger相似,但是當logger不能被創建時會出現
//錯亂,它簡化了安全初始化全局記錄器
func MustGetLogger(module string) *Logger {
logger, err := GetLogger(module)
if err != nil {
panic("logger: " + module + ": " + err.Error())
}
return logger
}
定義chaincode接口,它是一個供chaincode開發者需要實現標準回調接口
type Chaincode interface {
// run方法在每一筆交易初始化的時候被調用
Run(stub *ChaincodeStub, function string, args []string) ([]byte, error)
// Query函數以只讀方式查詢chaincode狀態
Query(stub *ChaincodeStub, function string, args []string) ([]byte, error)
}
Start(cc Chaincode)
啓動chaincode引導程序的入口節點
func Start(cc Chaincode) error {
viper.SetEnvPrefix("OPENCHAIN")
viper.AutomaticEnv()
replacer := strings.NewReplacer(".", "_")
//替換前綴爲openchain的文件中的.爲_
viper.SetEnvKeyReplacer(replacer)
flag.StringVar(&peerAddress, "peer.address", "", "peer address")
flag.Parse()
chaincodeLogger.Debug("Peer address: %s", getPeerAddress())
// 使用同步檢驗建立與client的連接
clientConn, err := newPeerClientConnection()
if err != nil {
chaincodeLogger.Error(fmt.Sprintf("Error trying to connect to local peer: %s", err))
return fmt.Errorf("Error trying to connect to local peer: %s", err)
}
chaincodeLogger.Debug("os.Args returns: %s", os.Args)
chaincodeSupportClient := pb.NewChaincodeSupportClient(clientConn)
err = chatWithPeer(chaincodeSupportClient, cc)
//啓動chiancodeSuppportClient
return err
}
getPeerAddress()
獲取peer地址
func getPeerAddress() string {
if peerAddress != "" {
return peerAddress
}
if peerAddress = viper.GetString("peer.address"); peerAddress == "" {
//假如被docker容器包含,返回主機地址
peerAddress = "172.17.42.1:30303"
}
return peerAddress
}
newPeerClientConnection()
創建peer 客戶端連接
func newPeerClientConnection() (*grpc.ClientConn, error) {
//調用google.golang.org中grpc的ClinetConn方法
var opts []grpc.DialOption
if viper.GetBool("peer.tls.enabled") {
//viper來源於github.com/spf13/viper,一個應用程序的配置系統
var sn string
if viper.GetString("peer.tls.server-host-override") != "" {
sn = viper.GetString("peer.tls.server-host-override")
}
var creds credentials.TransportAuthenticator
//credenetials包實現了各種支持g RPC庫調用的憑證
if viper.GetString("peer.tls.cert.file") != "" {
var err error
creds, err = credentials.NewClientTLSFromFile(viper.GetString("peer.tls.cert.file"), sn)
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
} else {
creds = credentials.NewClientTLSFromCert(nil, sn)
//NewClientTLSFromCert從客戶端輸入的證書中構造了TLS
}
//append中grpc調用的方法在grpc根目錄下下clientconn.go文件中定義方//法
opts = append(opts, grpc.WithTransportCredentials(creds))
}
///WithTransportCredentials返回配置了一個連接級別的安全憑據(例//如,TLS/ SSL)的撥號操作。
opts = append(opts, grpc.WithTimeout(1*time.Second))
//WithTimeout返回配置客戶端撥號超時連接的撥號操作
opts = append(opts, grpc.WithBlock())
//WithBlock返回一個撥號選項,它將持續調用一個撥號塊直到底層的連接已經建立。沒有這一點,在後臺發生將會立即返回撥打和服務器連接。
opts = append(opts, grpc.WithInsecure())
conn, err := grpc.Dial(getPeerAddress(), opts...)
if err != nil {
return nil, err
}
return conn, err
}
chatWithPeer()
使用peer進行通信
func chatWithPeer(chaincodeSupportClient pb.ChaincodeSupportClient, cc Chaincode) error {
// 通過peer驗證創建流
stream, err := chaincodeSupportClient.Register(context.Background())
if err != nil {
return fmt.Errorf("Error chatting with leader at address=%s: %s", getPeerAddress(), err)
}
//創建傳遞給鏈碼的鏈碼存根
//stub := &ChaincodeStub{}
// Create the shim handler responsible for all control logic
handler = newChaincodeHandler(getPeerAddress(), stream, cc)
defer stream.CloseSend()
// Send the ChaincodeID during register.
chaincodeID := &pb.ChaincodeID{Name: viper.GetString("chaincode.id.name")}
payload, err := proto.Marshal(chaincodeID)
if err != nil {
return fmt.Errorf("Error marshalling chaincodeID during chaincode registration: %s", err)
}
// 流寄存器
chaincodeLogger.Debug("Registering.. sending %s", pb.ChaincodeMessage_REGISTER)
handler.serialSend(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload})
waitc := make(chan struct{})
go func() {
defer close(waitc)
msgAvail := make(chan *pb.ChaincodeMessage)
var nsInfo *nextStateInfo
var in *pb.ChaincodeMessage
recv := true
for {
in = nil
err = nil
nsInfo = nil
if recv {
recv = false
go func() {
var in2 *pb.ChaincodeMessage
in2, err = stream.Recv()
msgAvail <- in2
}()
}
select {
case in = <-msgAvail:
if err == io.EOF {
chaincodeLogger.Debug("Received EOF, ending chaincode stream, %s", err)
return
} else if err != nil {
chaincodeLogger.Error(fmt.Sprintf("Received error from server: %s, ending chaincode stream", err))
return
} else if in == nil {
err = fmt.Errorf("Received nil message, ending chaincode stream")
chaincodeLogger.Debug("Received nil message, ending chaincode stream")
return
}
chaincodeLogger.Debug("[%s]Received message %s from shim", shortuuid(in.Uuid), in.Type.String())
recv = true
case nsInfo = <-handler.nextState:
in = nsInfo.msg
if in == nil {
panic("nil msg")
}
chaincodeLogger.Debug("[%s]Move state message %s", shortuuid(in.Uuid), in.Type.String())
}
// 調用 FSM.handleMessage(),fsm即狀態機
err = handler.handleMessage(in)
if err != nil {
err = fmt.Errorf("Error handling message: %s", err)
return
}
if nsInfo != nil && nsInfo.sendToCC {
chaincodeLogger.Debug("[%s]send state message %s", shortuuid(in.Uuid), in.Type.String())
if err = handler.serialSend(in); err != nil {
err = fmt.Errorf("Error sending %s: %s", in.Type.String(), err)
return
}
}
}
}()
<-waitc
return err
}
GetState
GetState被調用後,將從總帳中獲取chaincode狀態記錄
func (stub *ChaincodeStub) GetState(key string) ([]byte, error) {
return handler.handleGetState(key, stub.UUID)
PutState
相對於GetState,PutState被調用後,將會把chaincode狀態記錄到總賬中
func (stub *ChaincodeStub) PutState(key string, value []byte) error {
return handler.handlePutState(key, value, stub.UUID)
}
DelState
從總賬中刪除chaincode狀態記錄
func (stub *ChaincodeStub) DelState(key string) error {
return handler.handleDelState(key, stub.UUID)
}
StateRangeQueryIterator
StateRangeQueryIterator是一個迭代器,遍歷一定範圍內的以鍵值對形式記錄的狀態
type StateRangeQueryIterator struct {
handler *Handler
uuid string
response *pb.RangeQueryStateResponse
currentLoc int
}
StateRangeQueryIterator
chaincode 調用RangeQueryState來查詢一定範圍內鍵的狀態。假設startKey和endKey按詞彙順序排序,將返回一個迭代器,可用於遍歷startKey和endKey之間的所有鍵,並且迭代器返回順序是隨機的。
func (stub *ChaincodeStub) RangeQueryState(startKey, endKey string) (*StateRangeQueryIterator, error) {
response, err := handler.handleRangeQueryState(startKey, endKey, stub.UUID)
if err != nil {
return nil, err
}
return &StateRangeQueryIterator{handler, stub.UUID, response, 0}, nil
}
HasNext()
如果迭代器的查詢範圍包含附加鍵和值,hasnext將返回true
func (iter *StateRangeQueryIterator) HasNext() bool {
if iter.currentLoc < len(iter.response.KeysAndValues) || iter.response.HasMore {
return true
}
return false
}
Next()
Next將返回下一個在迭代器查詢範圍內的鍵和值
func (iter *StateRangeQueryIterator) Next() (string, []byte, error) {
if iter.currentLoc < len(iter.response.KeysAndValues) {
keyValue := iter.response.KeysAndValues[iter.currentLoc]
iter.currentLoc++
return keyValue.Key, keyValue.Value, nil
} else if !iter.response.HasMore {
return "", nil, errors.New("No such key")
} else {
response, err := iter.handler.handleRangeQueryStateNext(iter.response.ID, iter.uuid)
//返回迭代器的響應id和uuid
if err != nil {
return "", nil, err
}
iter.currentLoc = 0
iter.response = response
keyValue := iter.response.KeysAndValues[iter.currentLoc]
iter.currentLoc++
return keyValue.Key, keyValue.Value, nil
}
}
Close()
當讀取過程完成從迭代器中釋放資源後,調用Close函數關閉範圍查詢迭代器
func (iter *StateRangeQueryIterator) Close() error {
_, err := iter.handler.handleRangeQueryStateClose(iter.response.ID, iter.uuid)
return err
}
InvokeChaincode()
通過一個chaincode調用中執行對另一個chaincode的調用
func (stub *ChaincodeStub) InvokeChaincode(chaincodeName string, function string, args []string) ([]byte, error) {
return handler.handleInvokeChaincode(chaincodeName, function, args, stub.UUID)
}
QueryChaincode
當一個chaincode調用中執行對另一個chaincode的查詢操作時調用
QueryChaincode
func (stub *ChaincodeStub) QueryChaincode(chaincodeName string, function string, args []string) ([]byte, error) {
return handler.handleQueryChaincode(chaincodeName, function, args, stub.UUID)
}
GetRows
接下來的方法是對數據庫建表以及查詢的一些功能函數,這裏就不一一贅述,以GetRows方法爲例介紹
GetRows返回基於部分key的行,例如 ,下給出下表
| A | B | C | D |
當A,C和D是key的時候,GetRow方法可以被|A,C|調用,來返回所有包含A,C以及其他例如D的值的行。當然,只包含A時也可以獲取C以及D的值
func (stub *ChaincodeStub) GetRows(tableName string, key []Column) (<-chan Row, error) {
keyString, err := buildKeyString(tableName, key)
if err != nil {
return nil, err
}
iter, err := stub.RangeQueryState(keyString+"1", keyString+":")
if err != nil {
return nil, fmt.Errorf("Error fetching rows: %s", err)
}
defer iter.Close()
rows := make(chan Row)
go func() {
for iter.HasNext() {
_, rowBytes, err := iter.Next()
if err != nil {
close(rows)
}
var row Row
err = proto.Unmarshal(rowBytes, &row)
//調用proto的Unmarshal函數,實現對數據的散集
if err != nil {
close(rows)
}
rows <- row
}
close(rows)
}()
return rows, nil
}
chaincode.pb.go
chaincode.pb.go是從chaincode.proto文件中產生的,它包含了一些頂端消息
- 列的定義
- 表信息
- 行信息
- 列信息
ColumnDefinition
type ColumnDefinition struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Type ColumnDefinition_Type `protobuf:"varint,2,opt,name=type,enum=shim.ColumnDefinition_Type" json:"type,omitempty"`
Key bool `protobuf:"varint,3,opt,name=key" json:"key,omitempty"`
}
Table
type Table struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
//protobuf以bytes爲單位,opt未操作
//json中傳遞的是表名,可以忽略空表情況
ColumnDefinitions []*ColumnDefinition `protobuf:"bytes,2,rep,name=columnDefinitions" json:"columnDefinitions,omitempty"`
}
Column
type Column struct {
// Types that are valid to be assigned to Value:
// *Column_String_
// *Column_Int32
// *Column_Int64
// *Column_Uint32
// *Column_Uint64
// *Column_Bytes
// *Column_Bool
Value isColumn_Value `protobuf_oneof:"value"`
}
Row
type Row struct {
Columns []*Column `protobuf:"bytes,1,rep,name=columns" json:"columns,omitempty"`
}
shim包下handler.go
Handler Struct
Handler實現shim包中的一面
type Handler struct {
sync.RWMutex
// RWMutex提供了四個方法:
// func (*RWMutex) Lock 寫鎖定
// func (*RWMutex) Unlock 寫解鎖
// func (*RWMutex) RLock 讀鎖定
// func (*RWMutex) RUnlock 讀解鎖
To string
ChatStream PeerChaincodeStream
FSM *fsm.FSM
cc Chaincode
// 多個查詢(和一個事務)具有不同的UUID可以並行爲執行chaincode
// responseChannel是其上響應由shim到chaincodeStub連通的通道。
responseChannel map[string]chan pb.ChaincodeMessage
// 跟蹤哪些是的UUID交易,這是查詢,以決定是否允許獲取/把狀態並調用chaincode。
isTransaction map[string]bool
//isTransaction的key爲String類型,value爲bool類型
nextState chan *nextStateInfo
}
PeerChaincodeStream接口定義了Peer和chaincode實例之間的流。
type PeerChaincodeStream interface {
Send(*pb.ChaincodeMessage) error
Recv() (*pb.ChaincodeMessage, error)
}
markIsTransaction
markIsTransaction函數標誌着UUID作爲交易或查詢,
爲true的時候代表交易,爲false的時候代表查詢
func (handler *Handler) markIsTransaction(uuid string, isTrans bool) bool {
if handler.isTransaction == nil {
return false
}
handler.Lock()
defer handler.Unlock()
handler.isTransaction[uuid] = isTrans
return true
}
exectransaction.go
Execute
執行交易或查詢
func Execute(ctxt context.Context, chain *ChaincodeSupport, t *pb.Transaction) ([]byte, error) {
var err error
// 得到一個處理賬本至標記TX的開始/結束
ledger, ledgerErr := ledger.GetLedger()
if ledgerErr != nil {
return nil, fmt.Errorf("Failed to get handle to ledger (%s)", ledgerErr)
}
if secHelper := chain.getSecHelper(); nil != secHelper {
var err error
t, err = secHelper.TransactionPreExecution(t)
// 注意,t被現在解密並且是原始輸入t的深克隆
if nil != err {
return nil, err
}
}
if t.Type == pb.Transaction_CHAINCODE_NEW {
_, err := chain.DeployChaincode(ctxt, t)
if err != nil {
return nil, fmt.Errorf("Failed to deploy chaincode spec(%s)", err)
}
//啓動並等待準備就緒
markTxBegin(ledger, t)
_, _, err = chain.LaunchChaincode(ctxt, t)
if err != nil {
markTxFinish(ledger, t, false)
return nil, fmt.Errorf("%s", err)
}
markTxFinish(ledger, t, true)
} else if t.Type == pb.Transaction_CHAINCODE_EXECUTE || t.Type == pb.Transaction_CHAINCODE_QUERY {
//將發動(如有必要,並等待就緒)
cID, cMsg, err := chain.LaunchChaincode(ctxt, t)
if err != nil {
return nil, fmt.Errorf("Failed to launch chaincode spec(%s)", err)
}
//這裏應該生效,因爲它上面的生效...
chaincode := cID.Name
if err != nil {
return nil, fmt.Errorf("Failed to stablish stream to container %s", chaincode)
}
// 當getTimeout調用被創建的事務塊需要註釋下一行,並取消註釋
timeout := time.Duration(30000) * time.Millisecond
//timeout, err := getTimeout(cID)
if err != nil {
return nil, fmt.Errorf("Failed to retrieve chaincode spec(%s)", err)
}
var ccMsg *pb.ChaincodeMessage
if t.Type == pb.Transaction_CHAINCODE_EXECUTE {
ccMsg, err = createTransactionMessage(t.Uuid, cMsg)
if err != nil {
return nil, fmt.Errorf("Failed to transaction message(%s)", err)
}
} else {
ccMsg, err = createQueryMessage(t.Uuid, cMsg)
if err != nil {
return nil, fmt.Errorf("Failed to query message(%s)", err)
}
}
markTxBegin(ledger, t)
resp, err := chain.Execute(ctxt, chaincode, ccMsg, timeout, t)
if err != nil {
// 交易回滾
markTxFinish(ledger, t, false)
return nil, fmt.Errorf("Failed to execute transaction or query(%s)", err)
} else if resp == nil {
// 交易回滾
markTxFinish(ledger, t, false)
return nil, fmt.Errorf("Failed to receive a response for (%s)", t.Uuid)
} else {
if resp.Type == pb.ChaincodeMessage_COMPLETED || resp.Type == pb.ChaincodeMessage_QUERY_COMPLETED {
// Success
markTxFinish(ledger, t, true)
return resp.Payload, nil
} else if resp.Type == pb.ChaincodeMessage_ERROR || resp.Type == pb.ChaincodeMessage_QUERY_ERROR {
// Rollback transaction
markTxFinish(ledger, t, false)
return nil, fmt.Errorf("Transaction or query returned with failure: %s", string(resp.Payload))
}
markTxFinish(ledger, t, false)
return resp.Payload, fmt.Errorf("receive a response for (%s) but in invalid state(%d)", t.Uuid, resp.Type)
}
} else {
err = fmt.Errorf("Invalid transaction type %s", t.Type.String())
}
return nil, err
}
ExecuteTransactions
由一個執行數組中的一個上的交易將返回錯誤陣列之一的每個交易。如果執行成功,數組元素將是零。返回狀態的哈希值
func ExecuteTransactions(ctxt context.Context, cname ChainName, xacts []*pb.Transaction) ([]byte, []error) {
var chain = GetChain(cname)
if chain == nil {
// 我們不應該到調到這裏來,但在其他方面一個很好的提醒,以更好地處理
panic(fmt.Sprintf("[ExecuteTransactions]Chain %s not found\n", cname))
}
errs := make([]error, len(xacts)+1)
for i, t := range xacts {
_, errs[i] = Execute(ctxt, chain, t)
}
ledger, hasherr := ledger.GetLedger()
var statehash []byte
if hasherr == nil {
statehash, hasherr = ledger.GetTempStateHash()
}
errs[len(errs)-1] = hasherr
return statehash, errs
}
chaincode下handler.go
//負責處理對Peer's 側的chaincode流的管理
type Handler struct {
sync.RWMutex
ChatStream PeerChaincodeStream
FSM *fsm.FSM
ChaincodeID *pb.ChaincodeID
//此處理管理解密部署TX的副本,沒有編碼
deployTXSecContext *pb.Transaction
chaincodeSupport *ChaincodeSupport
registered bool
readyNotify chan bool
//是對TX UUID的要麼調用或查詢TX(解密)的映射。每個TX將被添加之前執行並完成時,執行刪除操作
txCtxs map[string]*transactionContext
uuidMap map[string]bool
//跟蹤哪些是UUID查詢的;雖然shim保持包含這個,但它不能被信任
isTransaction map[string]bool
//用來發送並確保後的狀態轉換完成,
nextState chan *nextStateInfo
}
chaincode就分析到這裏,其中有不正確的地方還望讀者批評指正。接下來將分析另一個重要的部分Ledger,敬請期待哦