IBM openblockchain學習(二)--chaincode源碼分析

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是其上響應由shimchaincodeStub連通的通道。
    responseChannel map[string]chan pb.ChaincodeMessage
    // 跟蹤哪些是的UUID交易,這是查詢,以決定是否允許獲取/把狀態並調用chaincodeisTransaction map[string]bool
    //isTransactionkeyString類型,valuebool類型
    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,敬請期待哦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章