Invoke流程
client端处理流程
在peer/chaincode/invoke.go
func invokeCmd(cf *ChaincodeCmdFactory) *cobra.Command {
chaincodeInvokeCmd = &cobra.Command{
Use: "invoke",
Short: fmt.Sprintf("Invoke the specified %s.", chainFuncName),
Long: fmt.Sprintf("Invoke the specified %s. It will try to commit the endorsed transaction to the network.", chainFuncName),
ValidArgs: []string{"1"},
RunE: func(cmd *cobra.Command, args []string) error {
return chaincodeInvoke(cmd, args, cf) //处理函数
},
}
return chaincodeInvokeCmd
}
函数的调用流程为chaincodeInvoke chaincodeInvokeOrQuery ChaincodeInvokeOrQuery
func ChaincodeInvokeOrQuery(spec *pb.ChaincodeSpec,
cID string, invoke bool,
signer msp.SigningIdentity,
endorserClient pb.EndorserClient,
bc common.BroadcastClient) (*pb.ProposalResponse, error) {
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}
if customIDGenAlg != common.UndefinedParamValue {
invocation.IdGenerationAlg = customIDGenAlg
}
creator, err := signer.Serialize()
funcName := "invoke"
if !invoke {
funcName = "query"
}
// 返回一个给定序列化身份的方案
var prop *pb.Proposal
prop, _, err = putils.CreateProposalFromCIS(pcommon.HeaderType_ENDORSER_TRANSACTION, cID, invocation, creator)
// 返回了一个给出提案消息和签名身份的签名提案
var signedProp *pb.SignedProposal
signedProp, err = putils.GetSignedProposal(prop, signer)
// 处理提案,通过grpc发送给endorser处理
var proposalResp *pb.ProposalResponse
proposalResp, err = endorserClient.ProcessProposal(context.Background(), signedProp)
if invoke {
if proposalResp != nil {
// 组装一个签名的交易(这是一个信封信息)
env, err := putils.CreateSignedTx(prop, signer, proposalResp)
if err != nil {
return proposalResp, fmt.Errorf("Could not assemble transaction, err %s", err)
}
// 广播发送信封给orderer
if err = bc.Send(env); err != nil {
return proposalResp, fmt.Errorf("Error sending transaction %s: %s", funcName, err)
}
}
}
return proposalResp, nil
}
调用grpc发送给endorser处理
func (c *endorserClient) ProcessProposal(ctx context.Context, in *SignedProposal, opts ...grpc.CallOption) (*ProposalResponse, error) {
out := new(ProposalResponse)
// 调用grpc发送给endorser处理
err := grpc.Invoke(ctx, "/protos.Endorser/ProcessProposal", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
endorser执行chaincode,基于读取和写入的key生成读写操作集,向客户端返回提案结果,包括读写操作集。
然后客户端把交易提交到order,交易内容包含来自提案结果的读写操作集
在peer/common/orderclient.go中定义了广播给orderer的接口
type BroadcastClient interface {
//Send data to orderer
Send(env *cb.Envelope) error
Close() error
}
func (s *broadcastClient) Send(env *cb.Envelope) error {
//发送给orderer
if err := s.client.Send(env); err != nil {
return fmt.Errorf("Could not send :%s)", err)
}
//同步等待消息返回
err := s.getAck()
return err
}
type AtomicBroadcast_BroadcastClient interface {
Send(*common.Envelope) error //发送给order的接口
Recv() (*BroadcastResponse, error)
grpc.ClientStream
}
func (x *atomicBroadcastDeliverClient) Send(m *common.Envelope) error {
return x.ClientStream.SendMsg(m)
}
发送到orderer后,orderer将排完序的交易封装到区块中去,见后面的流程
endorser处理流程
在protos/peer/peer.pb.go中注册了处理函数
func _Endorser_ProcessProposal_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SignedProposal)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(EndorserServer).ProcessProposal(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/protos.Endorser/ProcessProposal",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(EndorserServer).ProcessProposal(ctx, req.(*SignedProposal))
}
return interceptor(ctx, in, info, handler)
}
endorser接收到invoke进行实际的处理,代码在core/endorser/endorser.go
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
// 检查消息是否合法
//获取chainId
// chainID不为空
//获取Ledger
lgr := peer.GetLedger(chainID)
if _, err := lgr.GetTransactionByID(txid)
//检查ACL - 我们验证此提案符合链条的政策
if err = e.checkACL(signedProp, chdr, shdr, hdrExt)
接下来的流程就是比较重要了
// 模拟
cd, res, simulationResult, ccevent, err := e.simulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim)
if chainID == "" {//没有chain的提案,如CSCC不需要认可
pResp = &pb.ProposalResponse{Response: res}
} else {
// 认可并获得编组的ProposalResponse消息
pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)
}
simulateProposal 方法处理模拟的主要动作包含
//为链码检查ESCC和VSCC(验证用途的系统合约程序)
err = e.checkEsccAndVscc(prop);
//执行提议获得模拟结果
res, ccevent, err = e.callChaincode(ctx, chainID, version, txid, signedProp, prop, cis, cid, txsim)
callChaincode 最终调用core/chaincode/chaincodeexec.go的ExecuteChaincode,主要做了如下事情
//创建链码调用规范
spec, err = createCIS(cccid.Name, args)
//执行提案,返回链码的原始响应
res, ccevent, err = Execute(ctxt, cccid, spec)
再来详细看下执行提案的函数Execute
//1、调用一个链上的Init方法
cctyp := pb.ChaincodeMessage_INIT
//2、如果不运行,启动将启动链码(如果运行返回为零),并且将等待链码的处理程序进入FSM就绪状态。
cID, cMsg, err := theChaincodeSupport.Launch(ctxt, cccid, spec)
//3、封装链码消息
var ccMsg *pb.ChaincodeMessage
ccMsg, err = createCCMessage(cctyp, cccid.TxID, cMsg)
//4、执行事务并等待它完成,直到超时值。
resp, err := theChaincodeSupport.Execute(ctxt, cccid, ccMsg, theChaincodeSupport.executetimeout)
执行提案的第2步Launch的处理流程
//如果在map上,必须有一个连接的流
chrte, ok = chaincodeSupport.chaincodeHasBeenLaunched(canName)
// 如果尚未运行,launchAndWaitForRegister将启动容器。 如果找不到,使用targz创建图像
cLang := cds.ChaincodeSpec.Type
err = chaincodeSupport.launchAndWaitForRegister(context, cccid, cds, cLang, builder)
看下launchAndWaitForRegister的处理
//如果不在map中,加入map
notfy := chaincodeSupport.preLaunchSetup(canName)
//创建容器,完成注册过程
resp, err := container.VMCProcess(ipcCtxt, vmtype, sir)
执行提案的第4步Execute
//完整性检查,此时链码必然运行
chrte, ok := chaincodeSupport.chaincodeHasBeenLaunched(canName)
//发送执行消息
if notfy, err = chrte.handler.sendExecuteMessage(ctxt, cccid.ChainID, msg, cccid.SignedProposal, cccid.Proposal);
//如果sendExecuteMessage成功,需要删除事务上下文
chrte.handler.deleteTxContext(msg.Txid)
endorser处理完后,消息返回给客户端,消息再发送到orderer
order的处理流程(sbft)
sbft接收消息的流程在函数createConsensusStack
backend, err := backend.NewBackend(sbft.config.Peers, conn, persist)
NewBackend的最后会启动一个协程
c.persistence = persist
c.queue = make(chan Executable)
go c.run()
协程的工作就是读队列
func (b *Backend) run() {
for {
//queue就是chan Executable,从队列中获取消息,否则阻塞
e := <-b.queue
//执行消息
e.Execute(b)
}
}
func (m *msgEvent) Execute(backend *Backend) {
// 每个chainId对应一个处理函数
backend.consensus[m.chainId].Receive(m.msg, m.src)
}
Receive工作函数接收到消息,进行处理
当一个primary节点p收到一个客户端请求,就会开始三段协议的执行过程
func (s *SBFT) Receive(m *Msg, src uint64) {
log.Debugf("replica %d: received message from %d: %s", s.id, src, m)
if h := m.GetHello(); h != nil {
s.handleHello(h, src)
return
} else if req := m.GetRequest(); req != nil {
//首先,primary节点会多播该请求到所有的其它服务节点
s.handleRequest(req, src)
return
} else if vs := m.GetViewChange(); vs != nil {
s.handleViewChange(vs, src)
return
} else if nv := m.GetNewView(); nv != nil {
s.handleNewView(nv, src)
return
}
if s.testBacklogMessage(m, src) {
log.Debugf("replica %d: message for future seq, storing for later", s.id)
s.recordBacklogMsg(m, src)
return
}
//pbft算法使用三段提交协议
s.handleQueueableMessage(m, src)
}
pbft算法使用三段提交协议,查看handleQueueableMessage函数
func (s *SBFT) handleQueueableMessage(m *Msg, src uint64) {
if pp := m.GetPreprepare(); pp != nil {
s.handlePreprepare(pp, src)
return
} else if p := m.GetPrepare(); p != nil {
s.handlePrepare(p, src)
return
} else if c := m.GetCommit(); c != nil {
s.handleCommit(c, src)
return
//如果在每次请求执行完成之后就生成证据,那么代价是及其昂贵的,因此,这些证据是阶段性的生成的,当一个请求的序列号被一些常数整除的时候(例如100),该请求执行完即可以生成证据。我们把这些请求执行完后的节点状态称为 checkpoint,把具有证据的 checkpoint 称为 stable checkpoint。
} else if c := m.GetCheckpoint(); c != nil {
s.handleCheckpoint(c, src)
return
}
三段提交协议:pre-prepare,prepare,commit。pre-prepare和 prepare 阶段被用来给在同一个 view
内发送的请求排序,即使提出请求顺序的 primary 阶段是恶意的;prepare 和 commit 阶段被用来保证跨 view
提交的请求的最终顺序。view的含义是以同一个节点为primary的为一个view。具体参考:
https://blockchain.iethpay.com/pbft.html
来查看invoke动作的消息处理函数
func (s *SBFT) handleRequest(req *Request, src uint64) {
//是主节点才会执行发送给其他共识节点
if s.isPrimary() && s.activeView {
//通过验证后,批量发送给共识节点
//当 primary 节点正在执行协议过程中的消息数量大于一个给定的数值时,primary 节点再接收到新的请求时,不会立马开始新的执行过程,它会把新收到的消息缓冲起来,稍后,缓冲区内的消息会作为一个 group 多播出去,有效的减小了 CPU 的负荷和消息的拥堵。
s.maybeSendNextBatch()
}
maybeSendNextBatch函数的最后
//进入三段提交协议的第一阶段,向view内的backup广播消息
s.sendPreprepare(batch, committers)
s.broadcast(&Msg{&Msg_Preprepare{m}})
orderer处理流程(kafka)
kafka部分定义了两个接口
// Broadcast receives a stream of messages from a client for ordering
// Broadcast主要接收Peer的数据并在Orderer里面生成一系列数据块
func (s *server) Broadcast(srv ab.AtomicBroadcast_BroadcastServer) error {
logger.Debugf("Starting new Broadcast handler")
return s.bh.Handle(srv)
}
// Deliver sends a stream of blocks to a client after ordering
// peer从orderer获取数据的接口
func (s *server) Deliver(srv ab.AtomicBroadcast_DeliverServer) error {
logger.Debugf("Starting new Deliver handler")
return s.dh.Handle(srv)
当客户端推数据过来,orderer处理主函数
func (bh *handlerImpl) Handle(srv ab.AtomicBroadcast_BroadcastServer) error {
// 推送消息到kafka
if !support.Enqueue(msg) {...}
// 最后向客户端返回一个成功
err = srv.Send(&ab.BroadcastResponse{Status: cb.Status_SUCCESS})
func (x *atomicBroadcastBroadcastServer) Send(m *BroadcastResponse) error {
return x.ServerStream.SendMsg(m)
}
Enqueue接受消息并在接受时返回true,或者在关闭时返回false。由drainQueue goroutine调用,在调用广播处理程序的Handle()函数时生成。
func (ch *chainImpl) Enqueue(env *cb.Envelope) bool {
if ch.halted {
return false
}
logger.Debugf("[channel: %s] Enqueueing envelope...", ch.support.ChainID())
// 发送KafkaMessage_Regular消息
if err := ch.producer.Send(ch.partition, utils.MarshalOrPanic(newRegularMessage(utils.MarshalOrPanic(env)))); err != nil {
return false
}
return !ch.halted // If ch.halted has been set to true while sending, we should return false
}
当收到一条新的KafkaMessage_Regular消息
case *ab.KafkaMessage_Regular:
env := new(cb.Envelope)
if err := proto.Unmarshal(msg.GetRegular().Payload, env); err != nil { //解析数据
continue
}
// 获取可生成下一个区块的数据
batches, committers, ok := ch.support.BlockCutter().Ordered(env)
if ok && len(batches) == 0 && timer == nil {
//重新设置下一个区块生成定时器
timer = time.After(ch.batchTimeout)
continue
}
// If !ok, batches == nil, so this will be skipped
for i, batch := range batches {
block := ch.support.CreateNextBlock(batch) //创建一个新的数据块
encodedLastOffsetPersisted = utils.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: in.Offset})
//将新的区块数据写入到ledger中(RAM 或者FILE)
ch.support.WriteBlock(block, committers[i], encodedLastOffsetPersisted)
ch.lastCutBlock++
logger.Debug("Batch filled, just cut block", ch.lastCutBlock)
}
if len(batches) > 0 {
timer = nil
}
}
浙公网安备 33010602011771号