鏈碼容器啓動過程
每個實例化之後的鏈碼都會以容器的形式啓動起來,下面舉一個byfn.sh
啓動的例子:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
a908ea5fa0ee dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab "chaincode -peer.add…" 42 hours ago Up 42 hours dev-peer1.org2.example.com-mycc-1.0
9495cf13bc81 dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9 "chaincode -peer.add…" 42 hours ago Up 42 hours dev-peer0.org1.example.com-mycc-1.0
d7d5c7912f71 dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b "chaincode -peer.add…" 42 hours ago Up 42 hours dev-peer0.org2.example.com-mycc-1.0
容器啓動以後執行的命令是:
chaincode -peer.address [peer地址]
該命令會執行鏈碼的main
函數,一般來說鏈碼的main
函數都是下面的樣子:
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
主要是執行了shim.Start()
方法,這是鏈碼容器啓動的關鍵函數,今天就來解析一個這個函數到底做了一下什麼事。
梳理一下鏈碼啓動的整個過程:
- 鏈碼安裝
- 鏈碼實例化
- 實例化模擬提案中由
peer
節點執行ProcessProposal()
- 其中有個環節會啓動鏈碼容器(這裏暫時不展開討論,涉及到的代碼較多)
- 鏈碼容器啓動時執行命令
chaincode -peer.address [peer地址]
- 該命令啓動後執行鏈碼源代碼的
main
函數 main
函數中調用shim.Start()
方法完成鏈碼的啓動
- 實例化模擬提案中由
shim.Start 方法
看看這個方法具體做了什麼吧,在core/chaincode/shim/chaincode.go
的132行
// chaincodes.
func Start(cc Chaincode) error {
// 做一個鏈碼日誌的設置
SetupChaincodeLogging()
// 從環境變量中獲取chaincodeName,如下
// CORE_CHAINCODE_ID_NAME=mycc:1.0
chaincodename := viper.GetString("chaincode.id.name")
if chaincodename == "" {
return errors.New("error chaincode id not provided")
}
// 創建一個工廠
err := factory.InitFactories(factory.GetDefaultOpts())
if err != nil {
return errors.WithMessage(err, "internal error, BCCSP could not be initialized with default options")
}
// 設置streamGetter爲userChaincodeStreamGetter
// 即將用戶鏈碼以流的方式讀取進來
if streamGetter == nil {
streamGetter = userChaincodeStreamGetter
}
// 執行上面設置的streamGetter,參數傳一個chaincodename
stream, err := streamGetter(chaincodename)
if err != nil {
return err
}
// 這個函數很重要,與peer節點聊天,即與peer節點通信
// 鏈碼容器通過這個函數與安裝到的peer節點進行通信
err = chatWithPeer(chaincodename, stream, cc)
return err
}
Start()
方法主體邏輯很簡單,它由幾個重要的函數構成:
SetupChaincodeLogging()
,設置日誌InitFactories()
,創建工廠userChaincodeStreamGetter()
,將用戶鏈碼以流的方式拿到chatWithPeer()
,與peer節點通信
設置日誌的函數我們就不看了,主要是從環境變量中獲取一些日誌設置。下面就來看下剩下的三個函數究竟做了什麼
InitFactories()
先看下這個函數的參數GetDefaultOpts()
,在bccsp/factory/opts.go
的20行:
// GetDefaultOpts offers a default implementation for Opts
// returns a new instance every time
func GetDefaultOpts() *FactoryOpts {
return &FactoryOpts{
ProviderName: "SW",
SwOpts: &SwOpts{
HashFamily: "SHA2",
SecLevel: 256,
Ephemeral: true,
},
}
}
它返回了一個FactoryOpts
對象實例,可以看到該對象主要是一些加密有關的字段,這裏不做展開了。這樣可以猜想,InitFactories()
方法,也是做了一些鏈碼設置加密操作的一些內容,這裏就不展開看了,有興趣的可以去看下,代碼在bccsp/factory/nopkcs11.go
userChaincodeStreamGetter()
接下來就是userChaincodeStreamGetter()
方法了,在core/chaincode/shim/chaincode.go
的82行:
//the non-mock user CC stream establishment func
func userChaincodeStreamGetter(name string) (PeerChaincodeStream, error) {
// 這一行就是讀取鏈碼容器啓動執行的命令的那個參數的,拿到peer地址
flag.StringVar(&peerAddress, "peer.address", "", "peer address")
if viper.GetBool("peer.tls.enabled") {
// 下面是一些有關tls的幾個操作
// 獲取tls密鑰地址,在用戶安裝鏈碼的時候指定
keyPath := viper.GetString("tls.client.key.path")
// 獲取tls證書地址
certPath := viper.GetString("tls.client.cert.path")
// 讀取祕鑰數據
data, err1 := ioutil.ReadFile(keyPath)
if err1 != nil {
err1 = errors.Wrap(err1, fmt.Sprintf("error trying to read file content %s", keyPath))
chaincodeLogger.Errorf("%+v", err1)
return nil, err1
}
key = string(data)
// 讀取證書數據
data, err1 = ioutil.ReadFile(certPath)
if err1 != nil {
err1 = errors.Wrap(err1, fmt.Sprintf("error trying to read file content %s", certPath))
chaincodeLogger.Errorf("%+v", err1)
return nil, err1
}
cert = string(data)
}
flag.Parse()
chaincodeLogger.Debugf("Peer address: %s", getPeerAddress())
// 與peer節點建立連接
clientConn, err := newPeerClientConnection()
if err != nil {
err = errors.Wrap(err, "error trying to connect to local peer")
chaincodeLogger.Errorf("%+v", err)
return nil, err
}
來看下newPeerClientConnection()
方法,在core/chaincode/shim/chaincode.go
的308行:
func newPeerClientConnection() (*grpc.ClientConn, error) {
// 拿到peerAddress,那是一個全局變量,如果是空會從環境變量中拿
// 如果還是拿不到就會報錯
var peerAddress = getPeerAddress()
// 設置一些與peer節點的keepalive參數
kaOpts := &comm.KeepaliveOptions{
ClientInterval: time.Duration(1) * time.Minute,
ClientTimeout: time.Duration(20) * time.Second,
}
// 根據是否設置了tls,傳入不同的參數,執行NewClientConnectionWithAddres方法
if viper.GetBool("peer.tls.enabled") {
return comm.NewClientConnectionWithAddress(peerAddress, true, true,
comm.InitTLSForShim(key, cert), kaOpts)
}
return comm.NewClientConnectionWithAddress(peerAddress, true, false, nil, kaOpts)
}
來看下NewClientConnectionWithAddres()
方法,在core/comm/connection.go
的206行:
// NewClientConnectionWithAddress Returns a new grpc.ClientConn to the given address
func NewClientConnectionWithAddress(peerAddress string, block bool, tslEnabled bool,
creds credentials.TransportCredentials, ka *KeepaliveOptions) (*grpc.ClientConn, error) {
// 參數:1.peer地址 2.是否阻塞等待dial返回 3.是否開啓tls 4.tls配置(不開則傳nil) 5.keepalive參數
// 創建一個grpc的撥號參數
var opts []grpc.DialOption
// 設置keepalive部分參數
if ka != nil {
opts = ClientKeepaliveOptions(ka)
} else {
// set to the default options
opts = ClientKeepaliveOptions(DefaultKeepaliveOptions)
}
// 設置tls部分參數
if tslEnabled {
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithInsecure())
}
// 設置block參數
if block {
opts = append(opts, grpc.WithBlock())
}
// 設置最大接收消息大小,最大發送消息大小
opts = append(opts, grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(MaxRecvMsgSize),
grpc.MaxCallSendMsgSize(MaxSendMsgSize),
))
// 設置一個超時上下文
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
// grpc撥號連接
conn, err := grpc.DialContext(ctx, peerAddress, opts...)
if err != nil {
return nil, err
}
// 返回連接
return conn, err
}
回到userChaincodeStreamGetter()
中:
// 剛剛看到這裏
clientConn, err := newPeerClientConnection()
if err != nil {
err = errors.Wrap(err, "error trying to connect to local peer")
chaincodeLogger.Errorf("%+v", err)
return nil, err
}
chaincodeLogger.Debugf("os.Args returns: %s", os.Args)
// 創建chaincodeSupport客戶端,就一個字段clientConn
// chaincodeSupport服務在peer端有啓動
chaincodeSupportClient := pb.NewChaincodeSupportClient(clientConn)
// 註冊chaincodeSupport客戶端
stream, err := chaincodeSupportClient.Register(context.Background())
if err != nil {
return nil, errors.WithMessage(err, fmt.Sprintf("error chatting with leader at address=%s", getPeerAddress()))
}
return stream, nil
}
chaincodeSupportClient.Register()
部分源代碼在protos/peer/chaincode_shim.pb.go
的1016行:
type ChaincodeSupportClient interface {
Register(ctx context.Context, opts ...grpc.CallOption) (ChaincodeSupport_RegisterClient, error)
}
type chaincodeSupportClient struct {
cc *grpc.ClientConn
}
func NewChaincodeSupportClient(cc *grpc.ClientConn) ChaincodeSupportClient {
return &chaincodeSupportClient{cc}
}
func (c *chaincodeSupportClient) Register(ctx context.Context, opts ...grpc.CallOption) (ChaincodeSupport_RegisterClient, error) {
stream, err := c.cc.NewStream(ctx, &_ChaincodeSupport_serviceDesc.Streams[0], "/protos.ChaincodeSupport/Register", opts...)
if err != nil {
return nil, err
}
x := &chaincodeSupportRegisterClient{stream}
return x, nil
}
type ChaincodeSupport_RegisterClient interface {
Send(*ChaincodeMessage) error
Recv() (*ChaincodeMessage, error)
grpc.ClientStream
}
type chaincodeSupportRegisterClient struct {
grpc.ClientStream
}
chaincodeSupportRegisterClient
發送和接收的消息類型是ChaincodeMessage
,這裏提一下這個消息類型,後續會經常看到這個類型,它是鏈碼容器與peer
節點傳輸的消息類型。
userChaincodeStreamGetter()
方法到這裏就結束了。
chatWithPeer()
最後一個方法了,這個方法是一個大頭,userChaincodeStreamGetter()
方法已經完成了與peer
節點的通信了,它返回了一個stram
對象,是一個接口PeerChaincodeStream
,只提供了三個方法:
// PeerChaincodeStream interface for stream between Peer and chaincode instance.
type PeerChaincodeStream interface {
Send(*pb.ChaincodeMessage) error
Recv() (*pb.ChaincodeMessage, error)
CloseSend() error
}
將stream
傳給chatWithPeer
作爲參數
if streamGetter == nil {
streamGetter = userChaincodeStreamGetter
}
stream, err := streamGetter(chaincodename)
if err != nil {
return err
}
// cc即爲鏈碼對象,實現了Init和Invoke接口
err = chatWithPeer(chaincodename, stream, cc)
看下chatWithPeer()
吧,在core/chaincode/shim/chaincode.go
322行:
func chatWithPeer(chaincodename string, stream PeerChaincodeStream, cc Chaincode) error {
// Create the shim handler responsible for all control logic
handler := newChaincodeHandler(stream, cc)
defer stream.CloseSend()
第一步就是創建一個ChaincodeHandler
對象,這是一個很重要的對象,鏈碼容器與peer
節點的交互本質上就是兩邊的ChaincodeHandler
在通過GRPC
通信不斷地發送和介紹消息。看下newChaincodeHandler()
方法,在core/chaincode/shim/handler.go
的166行:
// Handler handler implementation for shim side of chaincode.
type Handler struct {
// 需要鎖來保護鏈碼,防止peer的併發請求
sync.Mutex
// 序列化鎖,在serialSend時用
serialLock sync.Mutex
To string
// grpc流,就是userChaincodeStreamGetter()創建的那個stream
ChatStream PeerChaincodeStream
// 鏈碼對象
cc Chaincode
// handler狀態,有created,ready,established三種狀態
state state
// 鏈碼信息響應通道
responseChannel map[string]chan pb.ChaincodeMessage
}
// NewChaincodeHandler returns a new instance of the shim side handler.
func newChaincodeHandler(peerChatStream PeerChaincodeStream, chaincode Chaincode) *Handler {
v := &Handler{
ChatStream: peerChatStream,
cc: chaincode,
}
v.responseChannel = make(map[string]chan pb.ChaincodeMessage)
v.state = created
return v
}
回到chatWithPeer()
:
// ....
// Send the ChaincodeID during register.
// 創建ChaincodeID對象
chaincodeID := &pb.ChaincodeID{Name: chaincodename}
payload, err := proto.Marshal(chaincodeID)
if err != nil {
return errors.Wrap(err, "error marshalling chaincodeID during chaincode registration")
}
// Register on the stream
chaincodeLogger.Debugf("Registering.. sending %s", pb.ChaincodeMessage_REGISTER)
// 這裏就開始發送第一個流信息了
// 類型是ChaincodeMessage_REGISTER,payload主要就是包含chaincodeName
// 主要是用於註冊,serialSend函數很簡單,主要就是調用stream流的send方法發送消息
if err = handler.serialSend(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTER, Payload: payload}); err != nil {
return errors.WithMessage(err, "error sending chaincode REGISTER")
}
// holds return values from gRPC Recv below
type recvMsg struct {
msg *pb.ChaincodeMessage
err error
}
msgAvail := make(chan *recvMsg, 1)
errc := make(chan error)
// 這個函數就是從grpc中接收對端消息了
receiveMessage := func() {
in, err := stream.Recv()
msgAvail <- &recvMsg{in, err}
}
// 下面這塊邏輯就是死循環不斷地從grpc中接收消息
go receiveMessage()
for {
select {
case rmsg := <-msgAvail:
// 從msgAvail中拿取接收到的消息
switch {
case rmsg.err == io.EOF:
// 接收到EOF表示對端關閉了,這邊就退出結束並退出
err = errors.Wrapf(rmsg.err, "received EOF, ending chaincode stream")
chaincodeLogger.Debugf("%+v", err)
return err
case rmsg.err != nil:
// err不爲空表示接收出錯了,返回err
err := errors.Wrap(rmsg.err, "receive failed")
chaincodeLogger.Errorf("Received error from server, ending chaincode stream: %+v", err)
return err
case rmsg.msg == nil:
// 接收到空的msg了,返回err
err := errors.New("received nil message, ending chaincode stream")
chaincodeLogger.Debugf("%+v", err)
return err
default:
// 默認情況是正常接收
chaincodeLogger.Debugf("[%s]Received message %s from peer", shorttxid(rmsg.msg.Txid), rmsg.msg.Type)
// 處理對端發過來的消息
err := handler.handleMessage(rmsg.msg, errc)
if err != nil {
err = errors.WithMessage(err, "error handling message")
return err
}
// 處理完以後重新起一個協程去監聽,這裏是不是搞一個協程池好一點?
go receiveMessage()
// 回到主循環繼續從grpc中接收消息
}
// 如果handleMessage後續發送過程出錯了,會往errc中發送err
// 可以看到handler.handleMessage第二個參數是errc
case sendErr := <-errc:
if sendErr != nil {
err := errors.Wrap(sendErr, "error sending")
return err
}
}
}
}
chatWithPeer()
方法到這裏就結束了,主要就是不斷地發送消息,接收消息,處理接收到的消息,繼續發送消息的過程,這也就是我之前講爲什麼鏈碼與peer
節點交互的過程本質上是兩個ChaincodeHandler
之間在不斷收發處理消息的過程,這個消息的類型就是ChaincodeMessage
類型。先來簡單看下這個類型:
type ChaincodeMessage struct {
// 消息類型
Type ChaincodeMessage_Type
// 時間戳
Timestamp *timestamp.Timestamp
// payload
Payload []byte
// 交易ID
Txid string
// 簽名提案
Proposal *SignedProposal
// 鏈碼事件
ChaincodeEvent *ChaincodeEvent
// channel id
ChannelId string
XXX_NoUnkeyedLiteral struct{}
XXX_unrecognized []byte
XXX_sizecache int32
}
因此,到這裏,後續的工作就是分析鏈碼容器與peer
節點兩側的ChaincodeHandler
具體是怎麼處理消息的就好了。
chatWithPeer()
中,鏈碼側發送了第一個消息給peer
節點,類型爲ChaincodeMessage_REGISTER
,下面看下peer
節點這邊做了什麼
Peer節點處理流程
peer
節點的handler
的具體實現在core/chaincode/handler.go
中,注意鏈碼側的handler
實現在core/chaincode/shim/handler.go
中。具體的處理流程函數在402行ProcessStream()
中。
我們暫時不關心
peer
節點中的handler
流程是如何起來的。不過我追蹤了一下這個函數的調用,好像也是在處理鏈碼實例化提案的時候起來了。也就是說,在
peer
節點執行鏈碼實例化的過程中,不僅僅啓動了鏈碼容器,也啓動了本地與鏈碼容器進行通信的handler
,都是在執行ChaincodeSupport.Launch()
方法的時候啓動的。
上述結論不一定是對的,但是我們暫時不關心,後續有機會可以解析一下這塊的邏輯,總之我們找到了peer
節點的handler
的處理流程,下面就先來看一下吧:
func (h *Handler) ProcessStream(stream ccintf.ChaincodeStream) error {
// 執行完之後註銷
defer h.deregister()
h.mutex.Lock()
h.streamDoneChan = make(chan struct{})
h.mutex.Unlock()
defer close(h.streamDoneChan)
h.chatStream = stream
h.errChan = make(chan error, 1)
// keepalive通道
var keepaliveCh <-chan time.Time
if h.Keepalive != 0 {
ticker := time.NewTicker(h.Keepalive)
defer ticker.Stop()
keepaliveCh = ticker.C
}
// ===============================
// 下面這塊幾乎和鏈碼側的處理流程一模一樣
type recvMsg struct {
msg *pb.ChaincodeMessage
err error
}
msgAvail := make(chan *recvMsg, 1)
receiveMessage := func() {
in, err := h.chatStream.Recv()
msgAvail <- &recvMsg{in, err}
}
go receiveMessage()
for {
select {
case rmsg := <-msgAvail:
switch {
// Defer the deregistering of the this handler.
case rmsg.err == io.EOF:
chaincodeLogger.Debugf("received EOF, ending chaincode support stream: %s", rmsg.err)
return rmsg.err
case rmsg.err != nil:
err := errors.Wrap(rmsg.err, "receive failed")
chaincodeLogger.Errorf("handling chaincode support stream: %+v", err)
return err
case rmsg.msg == nil:
err := errors.New("received nil message, ending chaincode support stream")
chaincodeLogger.Debugf("%+v", err)
return err
default:
err := h.handleMessage(rmsg.msg)
if err != nil {
err = errors.WithMessage(err, "error handling message, ending stream")
chaincodeLogger.Errorf("[%s] %+v", shorttxid(rmsg.msg.Txid), err)
return err
}
go receiveMessage()
}
case sendErr := <-h.errChan:
err := errors.Wrapf(sendErr, "received error while sending message, ending chaincode support stream")
chaincodeLogger.Errorf("%s", err)
return err
case <-keepaliveCh:
// 發送一個keepalive消息
h.serialSendAsync(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_KEEPALIVE})
continue
}
}
}
大體上和鏈碼側的處理邏輯幾乎相同,最終也是走到了handleMessage()
方法中,看下這個方法,在core/chaincode/handler.go
的178行:
// handleMessage is called by ProcessStream to dispatch messages.
func (h *Handler) handleMessage(msg *pb.ChaincodeMessage) error {
chaincodeLogger.Debugf("[%s] Fabric side handling ChaincodeMessage of type: %s in state %s", shorttxid(msg.Txid), msg.Type, h.state)
// 判斷是不是心跳消息,如果是就什麼都不做
if msg.Type == pb.ChaincodeMessage_KEEPALIVE {
return nil
}
// 這裏的state的賦值和鏈碼側的不一樣
// 鏈碼側的賦值用的string類型,而peer這邊用的int類型
// handler在創建的時候沒有給state賦值,state就是默認零值0
// 而Created狀態對應的就是0,Established是1,Ready是2
switch h.state {
case Created:
// 因此,在peerHandler接收到第一條消息時,會走這個case
return h.handleMessageCreatedState(msg)
case Ready:
return h.handleMessageReadyState(msg)
default:
return errors.Errorf("handle message: invalid state %s for transaction %s", h.state, msg.Txid)
}
}
看下handleMessageCreatedState()
方法,因爲走的是這個case,在core/chaincode/handler.go
的195行:
func (h *Handler) handleMessageCreatedState(msg *pb.ChaincodeMessage) error {
switch msg.Type {
// 之前在看鏈碼側時,鏈碼側發送的第一個消息的類型的就是ChaincodeMessage_REGISTER
case pb.ChaincodeMessage_REGISTER:
h.HandleRegister(msg)
default:
// handleMessageCreatedState只接收ChaincodeMessage_REGISTER這個消息類型
return fmt.Errorf("[%s] Fabric side handler cannot handle message (%s) while in created state", msg.Txid, msg.Type)
}
return nil
}
看下HandleRegister()
方法,因爲走的是這個case,在core/chaincode/handler.go
的508行:
// handleRegister is invoked when chaincode tries to register.
func (h *Handler) HandleRegister(msg *pb.ChaincodeMessage) {
chaincodeLogger.Debugf("Received %s in state %s", msg.Type, h.state)
// 從消息中獲取payload,主要就是鏈碼ID
chaincodeID := &pb.ChaincodeID{}
err := proto.Unmarshal(msg.Payload, chaincodeID)
if err != nil {
chaincodeLogger.Errorf("Error in received %s, could NOT unmarshal registration info: %s", pb.ChaincodeMessage_REGISTER, err)
return
}
// Now register with the chaincodeSupport
h.chaincodeID = chaincodeID
// 將鏈碼註冊到peer節點上
err = h.Registry.Register(h)
if err != nil {
// 該函數在註冊成功後發送,如果err不爲空會報錯
h.notifyRegistry(err)
return
}
// 解析鏈碼名字,包括鏈碼name,version,以及鏈碼id
h.ccInstance = ParseName(h.chaincodeID.Name)
chaincodeLogger.Debugf("Got %s for chaincodeID = %s, sending back %s", pb.ChaincodeMessage_REGISTER, chaincodeID, pb.ChaincodeMessage_REGISTERED)
// serialSend順序發送數據,即往鏈碼側發送消息了
// 消息類型爲ChaincodeMessage_REGISTERED標識已經註冊
if err := h.serialSend(&pb.ChaincodeMessage{Type: pb.ChaincodeMessage_REGISTERED}); err != nil {
chaincodeLogger.Errorf("error sending %s: %s", pb.ChaincodeMessage_REGISTERED, err)
h.notifyRegistry(err)
return
}
// 將當前handler的狀態改爲Established
h.state = Established
chaincodeLogger.Debugf("Changed state to established for %+v", h.chaincodeID)
// 這個函數一會看一下,它最終還會將handler的狀態更新爲ready
h.notifyRegistry(nil)
}
上述流程很簡單,主要就是將鏈碼註冊到peer
節點上,併發送ChaincodeMessage_REGISTERED
消息給鏈碼側,同時更新handler
的狀態爲Established
,最後調用h.notifyRegistry(nil)
將handler
的狀態置爲Ready
。
有兩個方法看一下,一個是Register()
方法用於註冊:
func (r *HandlerRegistry) Register(h *Handler) error {
r.mutex.Lock()
defer r.mutex.Unlock()
key := h.chaincodeID.Name
if r.handlers[key] != nil {
chaincodeLogger.Debugf("duplicate registered handler(key:%s) return error", key)
return errors.Errorf("duplicate chaincodeID: %s", h.chaincodeID.Name)
}
// This chaincode was not launched by the peer but is attempting
// to register. Only allowed in development mode.
if r.launching[key] == nil && !r.allowUnsolicitedRegistration {
return errors.Errorf("peer will not accept external chaincode connection %v (except in dev mode)", h.chaincodeID.Name)
}
// 將handler註冊到map中,key就是chaincodeID
r.handlers[key] = h
chaincodeLogger.Debugf("registered handler complete for chaincode %s", key)
return nil
}
還有一個是notifyRegistry()
方法:
// notifyRegistry will send ready on registration success and
// update the launch state of the chaincode in the handler registry.
func (h *Handler) notifyRegistry(err error) {
if err == nil {
err = h.sendReady()
}
if err != nil {
h.Registry.Failed(h.chaincodeID.Name, err)
chaincodeLogger.Errorf("failed to start %s", h.chaincodeID)
return
}
h.Registry.Ready(h.chaincodeID.Name)
}
// sendReady sends READY to chaincode serially (just like REGISTER)
func (h *Handler) sendReady() error {
chaincodeLogger.Debugf("sending READY for chaincode %+v", h.chaincodeID)
ccMsg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_READY}
// if error in sending tear down the h
if err := h.serialSend(ccMsg); err != nil {
chaincodeLogger.Errorf("error sending READY (%s) for chaincode %+v", err, h.chaincodeID)
return err
}
h.state = Ready
chaincodeLogger.Debugf("Changed to state ready for chaincode %+v", h.chaincodeID)
return nil
}
因此,在HandleRegister()
方法最後一行執行h.notifyRegistry(nil)
以後,因爲err==nil
,會執行sendReady()
方法,並最終再發送一個ChaincodeMessage_READY
消息給鏈碼側,同時將handler
自身的state
改爲Ready
。
如果err!=nil
,會輸出一個錯誤日誌,表示這次失敗。
到這裏peer
節點handler
的處理流程就執行完了,到最後發送了兩個消息給鏈碼側:
- 首先
peer
節點接收到鏈碼側發送來的ChaincodeMessage_REGISTER
以後,響應一個ChaincodeMessage_REGISTERED
消息,更新handler
的狀態爲Established
。 - 之後再發送一個
ChaincodeMessage_READY
消息給鏈碼側,更新handler
的狀態爲Ready
鏈碼側的處理流程
我們回到鏈碼側的handleMessage()
方法:
func (handler *Handler) handleMessage(msg *pb.ChaincodeMessage, errc chan error) error {
if msg.Type == pb.ChaincodeMessage_KEEPALIVE {
chaincodeLogger.Debug("Sending KEEPALIVE response")
// 鏈碼側接收到keepalive以後,與peer節點不同,他會迴應一個消息給peer節點
// 而peer節點收到keepalive以後是什麼都不做,因爲它有一個定時器在定期發送keepalive
// 這裏採用異步發送的方式,不關心是否出錯,因爲可能下次keepalive能接收到
handler.serialSendAs而peer節點收到keepalive以後是什麼都不做ync(msg, nil)
return nil
}
chaincodeLogger.Debugf("[%s] Handling ChaincodeMessage of type: %s(state:%s)", shorttxid(msg.Txid), msg.Type, handler.state)
var err error
// 判斷handelr的狀態
switch handler.state {
case ready:
err = handler.handleReady(msg, errc)
case established:
err = handler.handleEstablished(msg, errc)
case created:
// 鏈碼側的handler在創建的時候,state就是created
// 直到接收到消息之前,該狀態一直都是created,因此走這個case
err = handler.handleCreated(msg, errc)
default:
err = errors.Errorf("[%s] Chaincode handler cannot handle message (%s) with payload size (%d) while in state: %s", msg.Txid, msg.Type, len(msg.Payload), handler.state)
}
if err != nil {
// 如果err不爲空,表示處理出錯
// 則會發送一個ChaincodeMessage_ERROR消息給peer節點,並返回錯誤
payload := []byte(err.Error())
errorMsg := &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid}
handler.serialSend(errorMsg)
return err
}
return nil
}
看下handleCreated()
方法,在core/chaincode/shim/handler.go
的820行:
//handle created state
func (handler *Handler) handleCreated(msg *pb.ChaincodeMessage, errc chan error) error {
if msg.Type == pb.ChaincodeMessage_REGISTERED {
// peer節點發送來的消息類型就是ChaincodeMessage_REGISTERED
handler.state = established
return nil
}
return errors.Errorf("[%s] Chaincode handler cannot handle message (%s) with payload size (%d) while in state: %s", msg.Txid, msg.Type, len(msg.Payload), handler.state)
}
這裏peer
節點發送來的消息類型就是ChaincodeMessage_REGISTERED
類型(peer
節點發送的第一條消息),鏈碼側收到以後,將狀態改爲established
。
之後,鏈碼側會收到peer
節點發送來的第二條消息,因爲當前鏈碼側handler
狀態爲established
,因此走handleEstablished()
這個方法,在core/chaincode/shim/handler.go
的811行:
//handle established state
func (handler *Handler) handleEstablished(msg *pb.ChaincodeMessage, errc chan error) error {
if msg.Type == pb.ChaincodeMessage_READY {
handler.state = ready
return nil
}
return errors.Errorf("[%s] Chaincode handler cannot handle message (%s) with payload size (%d) while in state: %s", msg.Txid, msg.Type, len(msg.Payload), handler.state)
}
這裏peer
節點發送來的消息類型就是ChaincodeMessage_READY
類型(peer
節點發送的第二條消息),鏈碼側收到以後,將狀態改爲ready
。
至此到這裏,鏈碼側與peer
節點的handler
的狀態就都是ready
了
handleReady
不妨再看看,鏈碼側與peer
節點的handler
的handleReady
都接受哪些類型。
鏈碼側
代碼在core/chaincode/shim/handler.go
的774行
//handle ready state
func (handler *Handler) handleReady(msg *pb.ChaincodeMessage, errc chan error) error {
switch msg.Type {
case pb.ChaincodeMessage_RESPONSE:
if err := handler.sendChannel(msg); err != nil {
chaincodeLogger.Errorf("[%s] error sending %s (state:%s): %s", shorttxid(msg.Txid), msg.Type, handler.state, err)
return err
}
chaincodeLogger.Debugf("[%s] Received %s, communicated (state:%s)", shorttxid(msg.Txid), msg.Type, handler.state)
return nil
case pb.ChaincodeMessage_ERROR:
if err := handler.sendChannel(msg); err != nil {
chaincodeLogger.Errorf("[%s] error sending %s (state:%s): %s", shorttxid(msg.Txid), msg.Type, handler.state, err)
}
chaincodeLogger.Debugf("[%s] Error Received %s, communicated (state:%s)", shorttxid(msg.Txid), msg.Type, handler.state)
//we don't return error on ERROR
return nil
case pb.ChaincodeMessage_INIT:
chaincodeLogger.Debugf("[%s] Received %s, initializing chaincode", shorttxid(msg.Txid), msg.Type)
// Call the chaincode's Run function to initialize
handler.handleInit(msg, errc)
return nil
case pb.ChaincodeMessage_TRANSACTION:
chaincodeLogger.Debugf("[%s] Received %s, invoking transaction on chaincode(state:%s)", shorttxid(msg.Txid), msg.Type, handler.state)
// Call the chaincode's Run function to invoke transaction
handler.handleTransaction(msg, errc)
return nil
}
return errors.Errorf("[%s] Chaincode handler cannot handle message (%s) with payload size (%d) while in state: %s", msg.Txid, msg.Type, len(msg.Payload), handler.state)
}
主要處理四種類型:
ChaincodeMessage_RESPONSE
ChaincodeMessage_ERROR
ChaincodeMessage_INIT
ChaincodeMessage_TRANSACTION
前兩個主要是往handler
的responseChannel
發送數據,發送 response 或者是 error
ChaincodeMessage_INIT
主要是處理實例化的,實例化的時候,peer
側會發送ChaincodeMessage_INIT
類型的消息過來,最終會調用鏈碼的Init
方法
ChaincodeMessage_TRANSACTION
主要是處理鏈碼調用的,最終會調用鏈碼的Invoke
方法。
看下handleInit()
方法,在core/chaincode/shim/handler.go
的177行:
// handleInit handles request to initialize chaincode.
func (handler *Handler) handleInit(msg *pb.ChaincodeMessage, errc chan error) {
// The defer followed by triggering a go routine dance is needed to ensure that the previous state transition
// is completed before the next one is triggered. The previous state transition is deemed complete only when
// the beforeInit function is exited. Interesting bug fix!!
go func() {
var nextStateMsg *pb.ChaincodeMessage
defer func() {
// 異步地發送處理結果給peer節點
// nextStateMsg再之後的處理流程中會被複制,包括出錯或是成功
handler.triggerNextState(nextStateMsg, errc)
}()
// 錯誤處理函數
errFunc := func(err error, payload []byte, ce *pb.ChaincodeEvent, errFmt string, args ...interface{}) *pb.ChaincodeMessage {
if err != nil {
// Send ERROR message to chaincode support and change state
if payload == nil {
payload = []byte(err.Error())
}
chaincodeLogger.Errorf(errFmt, args...)
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid, ChaincodeEvent: ce, ChannelId: msg.ChannelId}
}
return nil
}
// 獲取peer節點傳過來的參數,是一個chaincodeInput結構體
input := &pb.ChaincodeInput{}
unmarshalErr := proto.Unmarshal(msg.Payload, input)
if nextStateMsg = errFunc(unmarshalErr, nil, nil, "[%s] Incorrect payload format. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
// Call chaincode's Run
// Create the ChaincodeStub which the chaincode can use to callback
// 創建stub對象
stub := new(ChaincodeStub)
// 執行stub的初始化,將提案中的信息抽取出來賦值到這個對象中
err := stub.init(handler, msg.ChannelId, msg.Txid, input, msg.Proposal)
if nextStateMsg = errFunc(err, nil, stub.chaincodeEvent, "[%s] Init get error response. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
// 執行鏈碼的Init方法
res := handler.cc.Init(stub)
chaincodeLogger.Debugf("[%s] Init get response status: %d", shorttxid(msg.Txid), res.Status)
if res.Status >= ERROR {
// 出錯處理
err = errors.New(res.Message)
if nextStateMsg = errFunc(err, []byte(res.Message), stub.chaincodeEvent, "[%s] Init get error response. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
}
resBytes, err := proto.Marshal(&res)
if err != nil {
payload := []byte(err.Error())
chaincodeLogger.Errorf("[%s] Init marshal response error [%s]. Sending %s", shorttxid(msg.Txid), err, pb.ChaincodeMessage_ERROR)
nextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent}
return
}
// Send COMPLETED message to chaincode support and change state
// 設置ChaincodeMessage_COMPLETED消息,表示完成
// 最終會調用defer中的triggerNextState方法異步發送給peer
nextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelId}
chaincodeLogger.Debugf("[%s] Init succeeded. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_COMPLETED)
}()
}
看下handleTransaction()
方法,在core/chaincode/shim/handler.go
的238行:
// handleTransaction Handles request to execute a transaction.
func (handler *Handler) handleTransaction(msg *pb.ChaincodeMessage, errc chan error) {
go func() {
//better not be nil
var nextStateMsg *pb.ChaincodeMessage
defer func() {
handler.triggerNextState(nextStateMsg, errc)
}()
errFunc := func(err error, ce *pb.ChaincodeEvent, errStr string, args ...interface{}) *pb.ChaincodeMessage {
if err != nil {
payload := []byte(err.Error())
chaincodeLogger.Errorf(errStr, args...)
return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: payload, Txid: msg.Txid, ChaincodeEvent: ce, ChannelId: msg.ChannelId}
}
return nil
}
// 獲取用戶傳遞的參數,由peer傳過來
input := &pb.ChaincodeInput{}
unmarshalErr := proto.Unmarshal(msg.Payload, input)
if nextStateMsg = errFunc(unmarshalErr, nil, "[%s] Incorrect payload format. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
// Call chaincode's Run
// Create the ChaincodeStub which the chaincode can use to callback
stub := new(ChaincodeStub)
err := stub.init(handler, msg.ChannelId, msg.Txid, input, msg.Proposal)
if nextStateMsg = errFunc(err, stub.chaincodeEvent, "[%s] Transaction execution failed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
// 調用鏈碼的Invoke
res := handler.cc.Invoke(stub)
// Endorser will handle error contained in Response.
resBytes, err := proto.Marshal(&res)
if nextStateMsg = errFunc(err, stub.chaincodeEvent, "[%s] Transaction execution failed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_ERROR.String()); nextStateMsg != nil {
return
}
// Send COMPLETED message to chaincode support and change state
chaincodeLogger.Debugf("[%s] Transaction completed. Sending %s", shorttxid(msg.Txid), pb.ChaincodeMessage_COMPLETED)
nextStateMsg = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_COMPLETED, Payload: resBytes, Txid: msg.Txid, ChaincodeEvent: stub.chaincodeEvent, ChannelId: stub.ChannelId}
}()
}
上述兩個方法處理邏輯基本相同,梳理一下流程:
- 獲取用戶傳遞的參數
- 創建並初始化stub對象
- 調用鏈碼的
Init
或者是Invoke
方法 - 異步發送
ChaincodeMessage_COMPLETED
消息給peer
節點
下面再回過頭來看下處理ChaincodeMessage_RESPONSE
和ChaincodeMessage_ERROR
的流程。看下sendChannel()
方法,在core/chaincode/shim/handler.go
的110行:
func (handler *Handler) sendChannel(msg *pb.ChaincodeMessage) error {
handler.Lock()
defer handler.Unlock()
// responseChannel map爲空則報錯
if handler.responseChannel == nil {
return errors.Errorf("[%s] Cannot send message response channel", shorttxid(msg.Txid))
}
// 以 ChannelId+Txid 作爲map的key
txCtxID := handler.getTxCtxId(msg.ChannelId, msg.Txid)
// 如果 map[key] 對應的channel爲 nil,則報錯
if handler.responseChannel[txCtxID] == nil {
return errors.Errorf("[%s] sendChannel does not exist", shorttxid(msg.Txid))
}
chaincodeLogger.Debugf("[%s] before send", shorttxid(msg.Txid))
// 往對應的channel中發送數據
handler.responseChannel[txCtxID] <- *msg
chaincodeLogger.Debugf("[%s] after send", shorttxid(msg.Txid))
return nil
}
我們想要知道responseChannel
中對應的 key,value
是在哪裏複製,追蹤了一下是在createChannel()
方法中,在core/chaincode/shim/handler.go
的95行:
func (handler *Handler) createChannel(channelID, txid string) (chan pb.ChaincodeMessage, error) {
handler.Lock()
defer handler.Unlock()
// responseChannel map爲空則報錯
if handler.responseChannel == nil {
return nil, errors.Errorf("[%s] cannot create response channel", shorttxid(txid))
}
// 以 ChannelId+Txid 作爲map的key
txCtxID := handler.getTxCtxId(channelID, txid)
// 如果key對應的value已經存在則報錯
if handler.responseChannel[txCtxID] != nil {
return nil, errors.Errorf("[%s] channel exists", shorttxid(txCtxID))
}
// 創建channel
c := make(chan pb.ChaincodeMessage)
// 賦值
handler.responseChannel[txCtxID] = c
// 返回相應的通道
return c, nil
}
看下這個函數在哪裏被調用了,有好幾處地方被調用了,挑一處比較重要的方法callPeerWithChaincodeMsg()
,在core/chaincode/shim/handler.go
的289行:
// callPeerWithChaincodeMsg sends a chaincode message (for e.g., GetState along with the key) to the peer for a given txid
// and receives the response.
func (handler *Handler) callPeerWithChaincodeMsg(msg *pb.ChaincodeMessage, channelID, txid string) (pb.ChaincodeMessage, error) {
// Create the channel on which to communicate the response from the peer
var respChan chan pb.ChaincodeMessage
var err error
// 這裏調用了createChannel,創建了channel
if respChan, err = handler.createChannel(channelID, txid); err != nil {
return pb.ChaincodeMessage{}, err
}
// 執行完以後刪除相應的通道
defer handler.deleteChannel(channelID, txid)
// 看下這個函數
return handler.sendReceive(msg, respChan)
}
其中有個sendReceive()
方法很重要,這個我們等下看,先看下callPeerWithChaincodeMsg()
方法在哪被調用了,我這裏貼一張圖過來
隨便找一處調用看一看,追溯回去以後發現,其實都是ChaincodeStub
的一些接口方法,比如GetState(),PutState()
等等。而這些方法一般在Inovke
或Init
調用。
到這裏就破案了,最後還有一個sendReceive()
方法再看下,在core/chaincode/shim/handler.go
的129行:
//sends a message and selects
func (handler *Handler) sendReceive(msg *pb.ChaincodeMessage, c chan pb.ChaincodeMessage) (pb.ChaincodeMessage, error) {
errc := make(chan error, 1)
// 異步發送消息,例如在GetState()方法調用好以後
// 生成一個消息,類型爲ChaincodeMessage_GET_STATE
handler.serialSendAsync(msg, errc)
//the serialsend above will send an err or nil
//the select filters that first error(or nil)
//and continues to wait for the response
//it is possible that the response triggers first
//in which case the errc obviously worked and is
//ignored
for {
select {
case err := <-errc:
if err == nil {
continue
}
//would have been logged, return false
return pb.ChaincodeMessage{}, err
case outmsg, val := <-c:
// c就是傳進來的respchan,是在createChannel時創建的
if !val {
return pb.ChaincodeMessage{}, errors.New("unexpected failure on receive")
}
return outmsg, nil
}
}
}
OK,到這裏以後整個處理邏輯就通了,以invoke
爲例:
peer
節點發送ChaincodeMessage_TRANSACTION
消息給鏈碼側- 鏈碼側收到以後,執行
handleTransaction()
方法,並最終調用鏈碼的Invoke()
方法 - 如果鏈碼的
Invoke()
中沒有調用其他操作賬本的方法,例如GetState(),PutState()
等等,那麼handleTransaction()
最終會返回一個ChaincodeMessage_COMPLETED
到peer
節點 - 如果有其他操作賬本的方法,只要調用了
createChannel()
- 鏈碼側創建一個以 channelID+txID 作爲 key 的響應通道
- 鏈碼側根據對應的方法設置對應的消息類型(例如
GetState()
方法類型爲ChaincodeMessage_GET_STATE
),發送到peer
節點,由peer
節點再做相應的處理 peer
節點處理完以後再發送一個ChaincodeMessage_RESPONSE
類型的消息給鏈碼側- 鏈碼側收到以後將這個消息發送到對應的響應通道中去
- 收到響應以後,鏈碼側邏輯回到
handleTransaction()
中,再返回一個ChaincodeMessage_COMPLETED
到peer
節點
鏈碼側的分析就到這裏了,下面看看peer
節點如何處理handleReady
的
peer節點
代碼在core/chaincode/handler.go
的205行:
func (h *Handler) handleMessageReadyState(msg *pb.ChaincodeMessage) error {
switch msg.Type {
// 如果是ChaincodeMessage_COMPLETED和ChaincodeMessage_ERROR類型則發送通知,這個方法一會再看
case pb.ChaincodeMessage_COMPLETED, pb.ChaincodeMessage_ERROR:
h.Notify(msg)
// 根據不同的消息類型做相應的處理邏輯
case pb.ChaincodeMessage_PUT_STATE:
go h.HandleTransaction(msg, h.HandlePutState)
case pb.ChaincodeMessage_DEL_STATE:
go h.HandleTransaction(msg, h.HandleDelState)
case pb.ChaincodeMessage_INVOKE_CHAINCODE:
go h.HandleTransaction(msg, h.HandleInvokeChaincode)
case pb.ChaincodeMessage_GET_STATE:
go h.HandleTransaction(msg, h.HandleGetState)
case pb.ChaincodeMessage_GET_STATE_BY_RANGE:
go h.HandleTransaction(msg, h.HandleGetStateByRange)
case pb.ChaincodeMessage_GET_QUERY_RESULT:
go h.HandleTransaction(msg, h.HandleGetQueryResult)
case pb.ChaincodeMessage_GET_HISTORY_FOR_KEY:
go h.HandleTransaction(msg, h.HandleGetHistoryForKey)
case pb.ChaincodeMessage_QUERY_STATE_NEXT:
go h.HandleTransaction(msg, h.HandleQueryStateNext)
case pb.ChaincodeMessage_QUERY_STATE_CLOSE:
go h.HandleTransaction(msg, h.HandleQueryStateClose)
case pb.ChaincodeMessage_GET_PRIVATE_DATA_HASH:
go h.HandleTransaction(msg, h.HandleGetPrivateDataHash)
case pb.ChaincodeMessage_GET_STATE_METADATA:
go h.HandleTransaction(msg, h.HandleGetStateMetadata)
case pb.ChaincodeMessage_PUT_STATE_METADATA:
go h.HandleTransaction(msg, h.HandlePutStateMetadata)
default:
return fmt.Errorf("[%s] Fabric side handler cannot handle message (%s) while in ready state", msg.Txid, msg.Type)
}
return nil
}
根據不同的消息類型,最終它們都執行了HandleTransaction()
方法,第一個參數是msg,第二個參數是指定的處理方法,各不相同。那麼下面來看下HandleTransaction()
方法,在core/chaincode/handler.go
的251行:
// HandleTransaction is a middleware function that obtains and verifies a transaction
// context prior to forwarding the message to the provided delegate. Response messages
// returned by the delegate are sent to the chat stream. Any errors returned by the
// delegate are packaged as chaincode error messages.
func (h *Handler) HandleTransaction(msg *pb.ChaincodeMessage, delegate handleFunc) {
chaincodeLogger.Debugf("[%s] handling %s from chaincode", shorttxid(msg.Txid), msg.Type.String())
if !h.registerTxid(msg) {
// 防止重複的TXID
return
}
startTime := time.Now()
var txContext *TransactionContext
var err error
// 獲取交易上下文
// 對ChaincodeMessage_INVOKE_CHAINCODE單獨處理,保證統一上下文
if msg.Type == pb.ChaincodeMessage_INVOKE_CHAINCODE {
txContext, err = h.getTxContextForInvoke(msg.ChannelId, msg.Txid, msg.Payload, "")
} else {
txContext, err = h.isValidTxSim(msg.ChannelId, msg.Txid, "no ledger context")
}
chaincodeName := h.chaincodeID.Name + ":" + h.chaincodeID.Version
meterLabels := []string{
"type", msg.Type.String(),
"channel", msg.ChannelId,
"chaincode", chaincodeName,
}
// 指標相關設置
h.Metrics.ShimRequestsReceived.With(meterLabels...).Add(1)
var resp *pb.ChaincodeMessage
if err == nil {
// 根據不同的類型做不同的處理操作,得到resp
resp, err = delegate(msg, txContext)
}
if err != nil {
// 出錯返回ChaincodeMessage_ERROR錯誤消息
err = errors.Wrapf(err, "%s failed: transaction ID: %s", msg.Type, msg.Txid)
chaincodeLogger.Errorf("[%s] Failed to handle %s. error: %+v", shorttxid(msg.Txid), msg.Type, err)
resp = &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_ERROR, Payload: []byte(err.Error()), Txid: msg.Txid, ChannelId: msg.ChannelId}
}
chaincodeLogger.Debugf("[%s] Completed %s. Sending %s", shorttxid(msg.Txid), msg.Type, resp.Type)
// 這裏刪除,對應最開始registerTxid中的ADD
h.ActiveTransactions.Remove(msg.ChannelId, msg.Txid)
// 異步發送響應
h.serialSendAsync(resp)
meterLabels = append(meterLabels, "success", strconv.FormatBool(resp.Type != pb.ChaincodeMessage_ERROR))
// 指標相關設置
h.Metrics.ShimRequestDuration.With(meterLabels...).Observe(time.Since(startTime).Seconds())
h.Metrics.ShimRequestsCompleted.With(meterLabels...).Add(1)
}
HandleTransaction()
方法主要是獲取當前交易的上下文,然後執行相應的消息類型的方法做相應的處理操作,並最終返回一個ChaincodeMessage_RESPONSE
類型的消息給鏈碼側。
再來看下Notify()
方法,在core/chaincode/handler.go
的544行:
func (h *Handler) Notify(msg *pb.ChaincodeMessage) {
// 獲取交易上下文
tctx := h.TXContexts.Get(msg.ChannelId, msg.Txid)
if tctx == nil {
chaincodeLogger.Debugf("notifier Txid:%s, channelID:%s does not exist for handling message %s", msg.Txid, msg.ChannelId, msg.Type)
return
}
chaincodeLogger.Debugf("[%s] notifying Txid:%s, channelID:%s", shorttxid(msg.Txid), msg.Txid, msg.ChannelId)
// 往交易上下文的ResponseNotifier中發送消息
tctx.ResponseNotifier <- msg
tctx.CloseQueryIterators()
}
主要起的是一個通知的作用,看下ResponseNotifier
這個變量在哪用到了,追溯到了handler.Execute()
方法,在core/chaincode/handler.go
的1240行:
func (h *Handler) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, msg *pb.ChaincodeMessage, timeout time.Duration) (*pb.ChaincodeMessage, error) {
chaincodeLogger.Debugf("Entry")
defer chaincodeLogger.Debugf("Exit")
txParams.CollectionStore = h.getCollectionStore(msg.ChannelId)
txParams.IsInitTransaction = (msg.Type == pb.ChaincodeMessage_INIT)
txctx, err := h.TXContexts.Create(txParams)
if err != nil {
return nil, err
}
defer h.TXContexts.Delete(msg.ChannelId, msg.Txid)
if err := h.setChaincodeProposal(txParams.SignedProp, txParams.Proposal, msg); err != nil {
return nil, err
}
h.serialSendAsync(msg)
var ccresp *pb.ChaincodeMessage
select {
// 這裏用到了,從這裏拿響應
case ccresp = <-txctx.ResponseNotifier:
// response is sent to user or calling chaincode. ChaincodeMessage_ERROR
// are typically treated as error
case <-time.After(timeout):
err = errors.New("timeout expired while executing transaction")
ccName := cccid.Name + ":" + cccid.Version
h.Metrics.ExecuteTimeouts.With("chaincode", ccName).Add(1)
case <-h.streamDone():
err = errors.New("chaincode stream terminated")
}
return ccresp, err
}
而如果我們追蹤這個Exucte()
方法,可以追溯到模擬提案中的callChaincode()
方法,這個在之前寫的文章Hyperledger Fabric從源碼分析背書提案過程中有提到。如果能夠追溯到這的朋友,恭喜你已經把整個背書模擬提案的流程基本上都搞清楚了!!!!
整體流程總結
總結一下吧,總結一下整體的模擬提案的流程,真的是太不容易了!!!
這裏假設鏈碼已經安裝了,我們從實例化開始
- 客戶端應用程序發送實例化模擬提案
peer
節點收到提案後,執行模擬提案peer
節點判斷鏈碼容器是否啓動,如果沒有啓動,則啓動鏈碼容器,調用shim.Start()
方法,並最終建立起鏈碼側與peer
節點兩端的handler
的GRPC
通信。- 鏈碼側發送
REGISTER
消息給peer
節點,當前鏈碼側狀態爲created
peer
節點當前狀態爲created
,收到REGISTER
消息以後,將其註冊到peer
節點的handler
上,發送REGISTERED
消息給鏈碼側,同時更新peer
節點狀態爲Established
peer
節點再發送Ready
消息給鏈碼側,同時更新peer
節點狀態爲Ready
- 鏈碼側收到
REGISTERED
消息後,更新鏈碼側狀態爲Established
- 鏈碼側收到
Ready
消息後,更新鏈碼側狀態爲Ready
- 此時兩側狀態都是
Ready
狀態,互相通信
- 鏈碼側發送
peer
節點判斷提案是lscc
的deploy
請求,則發送INIT
消息給鏈碼側- 鏈碼側收到
INIT
消息以後,調用鏈碼的Init()
方法,並返回COMPLETED
消息 peer
節點收到COMPLETED
消息以後,執行Notify()
方法,往交易上下文的響應通道中發送響應數據callChaincode()
方法內部會最終會調用到一個Execute()
方法,監聽這個通道,並拿到響應數據- 拿到響應數據之後,進行背書,並最終將背書結果發送給客戶端應用程序
- 客戶端應用程序收到背書結果後,生成一筆交易,發送給排序節點
- 排序節點排序好之後,發送給記賬節點,記賬節點驗證無誤之後記賬,整個流程結束
看了好多天的源碼,能把自己看的解析心得分享出來很開心,希望大家看了解析之後也能對Fabric
模擬提案的整體過程有更深入的瞭解