Hyperledger Fabric從源碼分析鏈碼容器啓動過程

鏈碼容器啓動過程

每個實例化之後的鏈碼都會以容器的形式啓動起來,下面舉一個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()方法,這是鏈碼容器啓動的關鍵函數,今天就來解析一個這個函數到底做了一下什麼事。

梳理一下鏈碼啓動的整個過程:

  1. 鏈碼安裝
  2. 鏈碼實例化
    1. 實例化模擬提案中由peer節點執行ProcessProposal()
    2. 其中有個環節會啓動鏈碼容器(這裏暫時不展開討論,涉及到的代碼較多)
    3. 鏈碼容器啓動時執行命令chaincode -peer.address [peer地址]
    4. 該命令啓動後執行鏈碼源代碼的main函數
    5. 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.go322行:

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的處理流程就執行完了,到最後發送了兩個消息給鏈碼側:

  1. 首先peer節點接收到鏈碼側發送來的ChaincodeMessage_REGISTER以後,響應一個ChaincodeMessage_REGISTERED消息,更新handler的狀態爲Established
  2. 之後再發送一個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節點的handlerhandleReady都接受哪些類型。

鏈碼側

代碼在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

前兩個主要是往handlerresponseChannel發送數據,發送 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}
	}()
}

上述兩個方法處理邏輯基本相同,梳理一下流程:

  1. 獲取用戶傳遞的參數
  2. 創建並初始化stub對象
  3. 調用鏈碼的Init或者是Invoke方法
  4. 異步發送ChaincodeMessage_COMPLETED消息給peer節點

下面再回過頭來看下處理ChaincodeMessage_RESPONSEChaincodeMessage_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()等等。而這些方法一般在InovkeInit調用。

到這裏就破案了,最後還有一個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爲例:

  1. peer節點發送ChaincodeMessage_TRANSACTION消息給鏈碼側
  2. 鏈碼側收到以後,執行handleTransaction()方法,並最終調用鏈碼的Invoke()方法
  3. 如果鏈碼的Invoke()中沒有調用其他操作賬本的方法,例如GetState(),PutState()等等,那麼handleTransaction()最終會返回一個ChaincodeMessage_COMPLETEDpeer節點
  4. 如果有其他操作賬本的方法,只要調用了createChannel()
    1. 鏈碼側創建一個以 channelID+txID 作爲 key 的響應通道
    2. 鏈碼側根據對應的方法設置對應的消息類型(例如GetState()方法類型爲ChaincodeMessage_GET_STATE),發送到peer節點,由peer節點再做相應的處理
    3. peer節點處理完以後再發送一個ChaincodeMessage_RESPONSE類型的消息給鏈碼側
    4. 鏈碼側收到以後將這個消息發送到對應的響應通道中去
    5. 收到響應以後,鏈碼側邏輯回到handleTransaction()中,再返回一個ChaincodeMessage_COMPLETEDpeer節點

鏈碼側的分析就到這裏了,下面看看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從源碼分析背書提案過程中有提到。如果能夠追溯到這的朋友,恭喜你已經把整個背書模擬提案的流程基本上都搞清楚了!!!!

整體流程總結

總結一下吧,總結一下整體的模擬提案的流程,真的是太不容易了!!!

這裏假設鏈碼已經安裝了,我們從實例化開始

  1. 客戶端應用程序發送實例化模擬提案
  2. peer節點收到提案後,執行模擬提案
  3. peer節點判斷鏈碼容器是否啓動,如果沒有啓動,則啓動鏈碼容器,調用shim.Start()方法,並最終建立起鏈碼側與peer節點兩端的handlerGRPC通信。
    1. 鏈碼側發送REGISTER消息給peer節點,當前鏈碼側狀態爲created
    2. peer節點當前狀態爲created,收到REGISTER消息以後,將其註冊到peer節點的handler上,發送REGISTERED消息給鏈碼側,同時更新peer節點狀態爲Established
    3. peer節點再發送Ready消息給鏈碼側,同時更新peer節點狀態爲Ready
    4. 鏈碼側收到REGISTERED消息後,更新鏈碼側狀態爲Established
    5. 鏈碼側收到Ready消息後,更新鏈碼側狀態爲Ready
    6. 此時兩側狀態都是Ready狀態,互相通信
  4. peer節點判斷提案是lsccdeploy請求,則發送INIT消息給鏈碼側
  5. 鏈碼側收到INIT消息以後,調用鏈碼的Init()方法,並返回COMPLETED消息
  6. peer節點收到COMPLETED消息以後,執行Notify()方法,往交易上下文的響應通道中發送響應數據
  7. callChaincode()方法內部會最終會調用到一個Execute()方法,監聽這個通道,並拿到響應數據
  8. 拿到響應數據之後,進行背書,並最終將背書結果發送給客戶端應用程序
  9. 客戶端應用程序收到背書結果後,生成一筆交易,發送給排序節點
  10. 排序節點排序好之後,發送給記賬節點,記賬節點驗證無誤之後記賬,整個流程結束

看了好多天的源碼,能把自己看的解析心得分享出來很開心,希望大家看了解析之後也能對Fabric模擬提案的整體過程有更深入的瞭解

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