java隨記

          堅(jiān)持就是勝利!

           

          Fabric 1.1源代碼分析之 Chaincode(鏈碼)初始化

          # Fabric 1.1源代碼分析之 Chaincode(鏈碼)初始化 #ChaincodeSupport(鏈碼支持服務(wù)端)

          ## 1、Endorser概述

          1、Endorser相關(guān)代碼分布在protos/peer/peer.pb.go和core/endorser目錄。

          * 在peer/node/start.go的serve() 方法中注冊(cè)了 endoser服務(wù)
          serverEndorser := endorser.NewEndorserServer(privDataDist, &endorser.SupportImpl{})
              libConf := library.Config{}
              if err = viperutil.EnhancedExactUnmarshalKey("peer.handlers", &libConf); err != nil {
                  return errors.WithMessage(err, "could not load YAML config")
              }
              authFilters := library.InitRegistry(libConf).Lookup(library.Auth).([]authHandler.Filter)
              auth := authHandler.ChainFilters(serverEndorser, authFilters...)
              // Register the Endorser server
              pb.RegisterEndorserServer(peerServer.Server(), auth)

          * protos/peer/peer.pb.go,EndorserServer接口定義。
          type EndorserServer interface {
              ProcessProposal(context.Context, *SignedProposal) (*ProposalResponse, error)
          }
          * core/endorser目錄:
          * endorser.go,EndorserServer接口實(shí)現(xiàn),即Endorser結(jié)構(gòu)體及方法,以及EndorserServer服務(wù)端 ProcessProposal處理流程。
          * endorser.go,Support接口定義及實(shí)現(xiàn) type SupportImpl struct(support.go)。

          ## 2、endorser中的EndorserServer接口實(shí)現(xiàn)方法
          * // ProcessProposal process the Proposal 處理客戶(hù)端傳過(guò)來(lái)的提案。peer chaincode instantiate初始化合約命令也是一種提案,最終服務(wù)端此處是入口
          ```go
          func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
              addr := util.ExtractRemoteAddress(ctx)
              endorserLogger.Debug("Entering: Got request from", addr)
              defer endorserLogger.Debugf("Exit: request from", addr)

              //0 -- check and validate 對(duì)提案進(jìn)行預(yù)處理,檢查消息有校性及其權(quán)限
              vr, err := e.preProcess(signedProp)
              if err != nil {
                  resp := vr.resp
                  return resp, err
              }

              prop, hdrExt, chainID, txid := vr.prop, vr.hdrExt, vr.chainID, vr.txid

              // obtaining once the tx simulator for this proposal. This will be nil
              // for chainless proposals
              // Also obtain a history query executor for history queries, since tx simulator does not cover history
              var txsim ledger.TxSimulator
              var historyQueryExecutor ledger.HistoryQueryExecutor
              if chainID != "" {
                  if txsim, err = e.s.GetTxSimulator(chainID, txid); err != nil {
                      return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
                  }
                  if historyQueryExecutor, err = e.s.GetHistoryQueryExecutor(chainID); err != nil {
                      return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
                  }
                  // Add the historyQueryExecutor to context
                  // TODO shouldn't we also add txsim to context here as well? Rather than passing txsim parameter
                  // around separately, since eventually it gets added to context anyways
                  ctx = context.WithValue(ctx, chaincode.HistoryQueryExecutorKey, historyQueryExecutor)

                  defer txsim.Done()
              }
              //this could be a request to a chainless SysCC

              // TODO: if the proposal has an extension, it will be of type ChaincodeAction;
              // if it's present it means that no simulation is to be performed because
              // we're trying to emulate a submitting peer. On the other hand, we need
              // to validate the supplied action before endorsing it

              /*1 -- simulate //如果是擴(kuò)展提案,可能是一個(gè)鏈碼操作 調(diào)用本文件中的simulateProposal()->callChaincode()->Execute()(core/endorser/support.go switch spec.(type) {   case *pb.ChaincodeDeploymentSpec:return chaincode.Execute(ctxt, cccid, spec))
              support.go中的邏輯判斷如果是初始化合約命令最終執(zhí)行 core/chaincode/chaincode_support.go中的 Execute方法
              */
              cd, res, simulationResult, ccevent, err := e.simulateProposal(ctx, chainID, txid, signedProp, prop, hdrExt.ChaincodeId, txsim)
              if err != nil {
                  return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
              }
              if res != nil {
                  if res.Status >= shim.ERROR {
                      endorserLogger.Errorf("[%s][%s] simulateProposal() resulted in chaincode %s response status %d for txid: %s", chainID, shorttxid(txid), hdrExt.ChaincodeId, res.Status, txid)
                      var cceventBytes []byte
                      if ccevent != nil {
                          cceventBytes, err = putils.GetBytesChaincodeEvent(ccevent)
                          if err != nil {
                              return nil, errors.Wrap(err, "failed to marshal event bytes")
                          }
                      }
                      pResp, err := putils.CreateProposalResponseFailure(prop.Header, prop.Payload, res, simulationResult, cceventBytes, hdrExt.ChaincodeId, hdrExt.PayloadVisibility)
                      if err != nil {
                          return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
                      }
          return pResp, &chaincodeError{res.Status, res.Message}
                  }
              }

              //2 -- endorse and get a marshalled ProposalResponse message
              var pResp *pb.ProposalResponse

              //TODO till we implement global ESCC, CSCC for system chaincodes
              //chainless proposals (such as CSCC) don't have to be endorsed
              if chainID == "" {
                  pResp = &pb.ProposalResponse{Response: res}
              } else {
                  pResp, err = e.endorseProposal(ctx, chainID, txid, signedProp, prop, res, simulationResult, ccevent, hdrExt.PayloadVisibility, hdrExt.ChaincodeId, txsim, cd)
                  if err != nil {
                      return &pb.ProposalResponse{Response: &pb.Response{Status: 500, Message: err.Error()}}, err
                  }
                  if pResp != nil {
                      if res.Status >= shim.ERRORTHRESHOLD {
                          endorserLogger.Debugf("[%s][%s] endorseProposal() resulted in chaincode %s error for txid: %s", chainID, shorttxid(txid), hdrExt.ChaincodeId, txid)
                          return pResp, &chaincodeError{res.Status, res.Message}
                      }
                  }
              }

              // Set the proposal response payload - it
              // contains the "return value" from the
              // chaincode invocation
              pResp.Response.Payload = res.Payload

              return pResp, nil
          }
          ```

          ## 2、鏈碼相關(guān)處理
          * 在core/endorser/support.go 文件中 Execute方法判斷是初始化還是執(zhí)行合約。
          ```go
          //Execute - execute proposal, return original response of chaincode
          func (s *SupportImpl) Execute(ctxt context.Context, cid, name, version, txid string, syscc bool, signedProp *pb.SignedProposal, prop *pb.Proposal, spec interface{}) (*pb.Response, *pb.ChaincodeEvent, error) {
              cccid := ccprovider.NewCCContext(cid, name, version, txid, syscc, signedProp, prop)

              switch spec.(type) {
              case *pb.ChaincodeDeploymentSpec:
               //初始化 core/chaincode/exectransaction.go Execute()
                  return chaincode.Execute(ctxt, cccid, spec)
              case *pb.ChaincodeInvocationSpec:
                  cis := spec.(*pb.ChaincodeInvocationSpec)
                  // decorate the chaincode input
                  decorators := library.InitRegistry(library.Config{}).Lookup(library.Decoration).([]decoration.Decorator)
                  cis.ChaincodeSpec.Input.Decorations = make(map[string][]byte)
                  cis.ChaincodeSpec.Input = decoration.Apply(prop, cis.ChaincodeSpec.Input, decorators...)
                  cccid.ProposalDecorations = cis.ChaincodeSpec.Input.Decorations
          //執(zhí)行合約 core/chaincode/chaincodeexec.go ExecuteChaincode
                  return chaincode.ExecuteChaincode(ctxt, cccid, cis.ChaincodeSpec.Input.Args)
              default:
                  panic("programming error, unkwnown spec type")
              }
          }

          ```

          ## 3、初始化鏈碼方法 core/chaincode/exectransaction.go 文件中的Execute()
          ```go
          //Execute - execute proposal, return original response of chaincode
          func Execute(ctxt context.Context, cccid *ccprovider.CCContext, spec interface{}) (*pb.Response, *pb.ChaincodeEvent, error) {
              var err error
              var cds *pb.ChaincodeDeploymentSpec
              var ci *pb.ChaincodeInvocationSpec

              //init will call the Init method of a on a chain
              cctyp := pb.ChaincodeMessage_INIT
              if cds, _ = spec.(*pb.ChaincodeDeploymentSpec); cds == nil {
                  if ci, _ = spec.(*pb.ChaincodeInvocationSpec); ci == nil {
                      panic("Execute should be called with deployment or invocation spec")
                  }
                  cctyp = pb.ChaincodeMessage_TRANSACTION
              }
          //調(diào)用 core/chaincode/upchaincode_sport.go中的launch方法
              _, cMsg, err := theChaincodeSupport.Launch(ctxt, cccid, spec)
              if err != nil {
                  return nil, nil, err
              }

              cMsg.Decorations = cccid.ProposalDecorations

              var ccMsg *pb.ChaincodeMessage
              ccMsg, err = createCCMessage(cctyp, cccid.ChainID, cccid.TxID, cMsg)
              if err != nil {
                  return nil, nil, errors.WithMessage(err, "failed to create chaincode message")
              }


          //判斷chaincode是否啟動(dòng),是否超時(shí),返回結(jié)果給客戶(hù)端
              resp, err := theChaincodeSupport.Execute(ctxt, cccid, ccMsg, theChaincodeSupport.executetimeout)
              if err != nil {
                  // Rollback transaction
                  return nil, nil, errors.WithMessage(err, "failed to execute transaction")
              } else if resp == nil {
                  // Rollback transaction
                  return nil, nil, errors.Errorf("failed to receive a response for txid (%s)", cccid.TxID)
              }

              if resp.ChaincodeEvent != nil {
                  resp.ChaincodeEvent.ChaincodeId = cccid.Name
                  resp.ChaincodeEvent.TxId = cccid.TxID
              }

              if resp.Type == pb.ChaincodeMessage_COMPLETED {
                  res := &pb.Response{}
                  unmarshalErr := proto.Unmarshal(resp.Payload, res)
                  if unmarshalErr != nil {
                      return nil, nil, errors.Wrap(unmarshalErr, fmt.Sprintf("failed to unmarshal response for txid (%s)", cccid.TxID))
                  }

                  // Success
                  return res, resp.ChaincodeEvent, nil
              } else if resp.Type == pb.ChaincodeMessage_ERROR {
                  // Rollback transaction
                  return nil, resp.ChaincodeEvent, errors.Errorf("transaction returned with failure: %s", string(resp.Payload))
              }

              //TODO - this should never happen ... a panic is more appropriate but will save that for future
              return nil, nil, errors.Errorf("receive a response for txid (%s) but in invalid state (%d)", cccid.TxID, resp.Type)
          }


          ```


          ## 4、 core/chaincode/upchaincode_sport.go 的Launch()
          ```go
          // Launch will launch the chaincode if not running (if running return nil) and will wait for handler of the chaincode to get into FSM ready state. 啟動(dòng)鏈碼成功后FSM狀態(tài)機(jī)推到ready狀態(tài)
          func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid *ccprovider.CCContext, spec interface{}) (*pb.ChaincodeID, *pb.ChaincodeInput, error) {
              //build the chaincode
              var cID *pb.ChaincodeID
              var cMsg *pb.ChaincodeInput

              var cds *pb.ChaincodeDeploymentSpec
              var ci *pb.ChaincodeInvocationSpec
              if cds, _ = spec.(*pb.ChaincodeDeploymentSpec); cds == nil {
                  if ci, _ = spec.(*pb.ChaincodeInvocationSpec); ci == nil {
                      panic("Launch should be called with deployment or invocation spec")
                  }
              }
              if cds != nil {
                  cID = cds.ChaincodeSpec.ChaincodeId
                  cMsg = cds.ChaincodeSpec.Input
              } else {
                  cID = ci.ChaincodeSpec.ChaincodeId
                  cMsg = ci.ChaincodeSpec.Input
              }

              canName := cccid.GetCanonicalName()
              chaincodeSupport.runningChaincodes.Lock()
              var chrte *chaincodeRTEnv
              var ok bool
              var err error
              //if its in the map, there must be a connected stream...nothing to do
              if chrte, ok = chaincodeSupport.chaincodeHasBeenLaunched(canName); ok {
                  if !chrte.handler.registered {
                      chaincodeSupport.runningChaincodes.Unlock()
                      err = errors.Errorf("premature execution - chaincode (%s) launched and waiting for registration", canName)
                      chaincodeLogger.Debugf("%+v", err)
                      return cID, cMsg, err
                  }
                  if chrte.handler.isRunning() {
                      if chaincodeLogger.IsEnabledFor(logging.DEBUG) {
                          chaincodeLogger.Debugf("chaincode is running(no need to launch) : %s", canName)
                      }
                      chaincodeSupport.runningChaincodes.Unlock()
                      return cID, cMsg, nil
                  }
                  chaincodeLogger.Debugf("Container not in READY state(%s)...send init/ready", chrte.handler.FSM.Current())
              } else {
                  //chaincode is not up... but is the launch process underway? this is
                  //strictly not necessary as the actual launch process will catch this
                  //(in launchAndWaitForRegister), just a bit of optimization for thundering
                  //herds 判斷鏈碼是否啟動(dòng)
                  if chaincodeSupport.launchStarted(canName) {
                      chaincodeSupport.runningChaincodes.Unlock()
                      err = errors.Errorf("premature execution - chaincode (%s) is being launched", canName)
                      return cID, cMsg, err
                  }
              }
              chaincodeSupport.runningChaincodes.Unlock()

              if cds == nil {
                  if cccid.Syscc {
                      return cID, cMsg, errors.Errorf("a syscc should be running (it cannot be launched) %s", canName)
                  }

                  if chaincodeSupport.userRunsCC {
                      chaincodeLogger.Error("You are attempting to perform an action other than Deploy on Chaincode that is not ready and you are in developer mode. Did you forget to Deploy your chaincode?")
                  }

                  var depPayload []byte

                  //hopefully we are restarting from existing image and the deployed transaction exists
                  //(this will also validate the ID from the LSCC if we're not using the config-tree approach)
                  depPayload, err = GetCDS(context, cccid.TxID, cccid.SignedProposal, cccid.Proposal, cccid.ChainID, cID.Name)
                  if err != nil {
                      return cID, cMsg, errors.WithMessage(err, fmt.Sprintf("could not get ChaincodeDeploymentSpec for %s", canName))
                  }
                  if depPayload == nil {
                      return cID, cMsg, errors.WithMessage(err, fmt.Sprintf("nil ChaincodeDeploymentSpec for %s", canName))
                  }

                  cds = &pb.ChaincodeDeploymentSpec{}

                  //Get lang from original deployment
                  err = proto.Unmarshal(depPayload, cds)
                  if err != nil {
                      return cID, cMsg, errors.Wrap(err, fmt.Sprintf("failed to unmarshal deployment transactions for %s", canName))
                  }
              }

              //from here on : if we launch the container and get an error, we need to stop the container

              //launch container if it is a System container or not in dev mode
              if (!chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) && (chrte == nil || chrte.handler == nil) {
                  //NOTE-We need to streamline code a bit so the data from LSCC gets passed to this thus
                  //avoiding the need to go to the FS. In particular, we should use cdsfs completely. It is
                  //just a vestige of old protocol that we continue to use ChaincodeDeploymentSpec for
                  //anything other than Install. In particular, instantiate, invoke, upgrade should be using
                  //just some form of ChaincodeInvocationSpec.
                  //
                  //But for now, if we are invoking we have gone through the LSCC path above. If instantiating
                  //or upgrading currently we send a CDS with nil CodePackage. In this case the codepath
                  //in the endorser has gone through LSCC validation. Just get the code from the FS.
                  if cds.CodePackage == nil {
                      //no code bytes for these situations
                      if !(chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) {
                          ccpack, err := ccprovider.GetChaincodeFromFS(cID.Name, cID.Version)
                          if err != nil {
                              return cID, cMsg, err
                          }

                          cds = ccpack.GetDepSpec()
                          chaincodeLogger.Debugf("launchAndWaitForRegister fetched %d bytes from file system", len(cds.CodePackage))
                      }
                  }
                  
                  //_platforms.go中的GenerateDockerBuild作為返回值作為core.go中的BuildSpecFactory()的實(shí)現(xiàn)_
                  builder := func() (io.Reader, error) { return platforms.GenerateDockerBuild(cds) }

                  err = chaincodeSupport.launchAndWaitForRegister(context, cccid, cds, &ccLauncherImpl{context, chaincodeSupport, cccid, cds, builder})
                  if err != nil {
                      chaincodeLogger.Errorf("launchAndWaitForRegister failed: %+v", err)
                      return cID, cMsg, err
                  }
              }

              if err == nil {
                  //launch will set the chaincode in Ready state
                  err = chaincodeSupport.sendReady(context, cccid, chaincodeSupport.ccStartupTimeout)
                  if err != nil {
                      err = errors.WithMessage(err, "failed to init chaincode")
                      chaincodeLogger.Errorf("%+v", err)
                      errIgnore := chaincodeSupport.Stop(context, cccid, cds)
                      if errIgnore != nil {
                          chaincodeLogger.Errorf("stop failed: %+v", errIgnore)
                      }
                  }
                  chaincodeLogger.Debug("sending init completed")
              }

              chaincodeLogger.Debug("LaunchChaincode complete")

              return cID, cMsg, err
          }


          ```
          * 經(jīng)過(guò)調(diào)用 launchAndWaitForRegister()->launch()->core/container/controller.go VMCProcess()
          因?yàn)閏ontroller.go中有好幾個(gè) do() 但是在launch中傳進(jìn)來(lái)的確是
          ```go
          sir := container.StartImageReq{CCID: ccid, Builder: ccl.builder, Args: args, Env: env, FilesToUpload: filesToUpload, PrelaunchFunc: preLaunchFunc}
          ```
          ## 所以下面的VMCProcess中的 req.do 是執(zhí)行的func (si StartImageReq) do(ctxt context.Context, v api.VM)VMCResp VMCReqIntf是接口定義
          ccLauncherImpl結(jié)構(gòu)體中的builder就是platforms.go中的GenerateDockerBuild()
              
          func VMCProcess(ctxt context.Context, vmtype string, req VMCReqIntf) (interface{}, error)

          VMCProcess中調(diào)用 了v.Start() //core.go中的接口vm中定義的方法,其實(shí)現(xiàn)在dockercontroller.go中

          ## 4、 core/container包
          * core/container包提供了對(duì)容器的操作
          core/container/api/core.go中提供接口及函數(shù)類(lèi)型定義
          ```go
          type BuildSpecFactory func() (io.Reader, error) //坑
          type PrelaunchFunc func() error
          type VM interface {
              Deploy(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, reader io.Reader) error
              Start(ctxt context.Context, ccid ccintf.CCID, args []string, env []string, filesToUpload map[string][]byte, builder BuildSpecFactory, preLaunchFunc PrelaunchFunc) error
              Stop(ctxt context.Context, ccid ccintf.CCID, timeout uint, dontkill bool, dontremove bool) error
              Destroy(ctxt context.Context, ccid ccintf.CCID, force bool, noprune bool) error
              GetVMName(ccID ccintf.CCID, format func(string) (string, error)) (string, error)
          }
          ```
          core/container/dockercontroller/dockercontroller.go提供了對(duì)上面接口的實(shí)現(xiàn),并且定義如下接口
          ```go
          type dockerClient interface {
              // CreateContainer creates a docker container, returns an error in case of failure
              CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error)
              // UploadToContainer uploads a tar archive to be extracted to a path in the
              // filesystem of the container.
              UploadToContainer(id string, opts docker.UploadToContainerOptions) error
              // StartContainer starts a docker container, returns an error in case of failure
              StartContainer(id string, cfg *docker.HostConfig) error
              // AttachToContainer attaches to a docker container, returns an error in case of
              // failure
              AttachToContainer(opts docker.AttachToContainerOptions) error
              // BuildImage builds an image from a tarball's url or a Dockerfile in the input
              // stream, returns an error in case of failure
              BuildImage(opts docker.BuildImageOptions) error
              // RemoveImageExtended removes a docker image by its name or ID, returns an
              // error in case of failure
              RemoveImageExtended(id string, opts docker.RemoveImageOptions) error
              // StopContainer stops a docker container, killing it after the given timeout
              // (in seconds). Returns an error in case of failure
              StopContainer(id string, timeout uint) error
              // KillContainer sends a signal to a docker container, returns an error in
              // case of failure
              KillContainer(opts docker.KillContainerOptions) error
              // RemoveContainer removes a docker container, returns an error in case of failure
              RemoveContainer(opts docker.RemoveContainerOptions) error
          }
          ```
          * 上面dockerClient接口實(shí)現(xiàn)類(lèi)通過(guò)方法
          // getClient returns an instance that implements dockerClient interface
          type getClient func() (dockerClient, error) 返回其實(shí)現(xiàn) 類(lèi)

          * dockercontroller.go中的Start方法實(shí)現(xiàn) Start方法控制了合約容器生成到啟動(dòng)的過(guò)程
          /Start starts a container using a previously created docker image
          func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID,
              args []string, env []string, filesToUpload map[string][]byte, builder container.BuildSpecFactory, prelaunchFunc container.PrelaunchFunc) error {
              imageID, err := vm.GetVMName(ccid, formatImageName)
              if err != nil {
                  return err
              }

              client, err := vm.getClientFnc()
              if err != nil {
                  dockerLogger.Debugf("start - cannot create client %s", err)
                  return err
              }

          //獲取容器名稱(chēng) 規(guī)則 peer節(jié)點(diǎn)ID+domainname+合約名+隨機(jī)數(shù)
              containerID, err := vm.GetVMName(ccid, nil)
              if err != nil {
                  return err
              }

              attachStdout := viper.GetBool("vm.docker.attachStdout")

              //stop,force remove if necessary
              dockerLogger.Debugf("Cleanup container %s", containerID)
              //根據(jù)最后兩位參數(shù)選擇調(diào)用 stopContainer或者killcontainer或者removecontainer
              vm.stopInternal(ctxt, client, containerID, 0, false, false)

              dockerLogger.Debugf("Start container %s", containerID)
              //創(chuàng)建合約容器 第一次布署合約創(chuàng)建容器都會(huì)失敗。err不會(huì)為空因?yàn)闆](méi)有合約容器鏡像
              err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout)
              if err != nil {
                  //if image not found try to create image and
                  //如果鏡像沒(méi)找到則重新生成dockerfile文件
                  if err == docker.ErrNoSuchImage {
                      if builder != nil {
                          dockerLogger.Debugf("start-could not find image <%s> (container id <%s>), because of <%s>..."+
                              "attempt to recreate image", imageID, containerID, err)
          //********此處builder()調(diào)用 的是core/chaincdoe/platforms/platforms.go中的GenerateDockerBuild()函數(shù)**********
                          //********產(chǎn)生一個(gè)DockerFile并寫(xiě)到reader中
                          reader, err1 := builder()
                          if err1 != nil {
                              dockerLogger.Errorf("Error creating image builder for image <%s> (container id <%s>), "+
                                  "because of <%s>", imageID, containerID, err1)
                          }
          //根據(jù)builder()產(chǎn)生的DockerFile生成一個(gè)合約鏡像文件.但是在/platforms/node/platform.go中會(huì)先根據(jù) ccenv鏡像先
                          //npm install 安裝鏈碼. 此處會(huì)從reader中的DockerFile 生成新的鏡像(繼承自fabric-baseimage)
                          if err1 = vm.deployImage(client, ccid, args, env, reader); err1 != nil {
                              return err1
                          }

                          dockerLogger.Debug("start-recreated image successfully")
                          //創(chuàng)建容器
                          if err1 = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout); err1 != nil {
                              dockerLogger.Errorf("start-could not recreate container post recreate image: %s", err1)
                              return err1
                          }
                      } else {
                          dockerLogger.Errorf("start-could not find image <%s>, because of %s", imageID, err)
                          return err
                      }
                  } else {
                      dockerLogger.Errorf("start-could not recreate container <%s>, because of %s", containerID, err)
                      return err
                  }
              }

              if attachStdout {
                  // Launch a few go-threads to manage output streams from the container.
                  // They will be automatically destroyed when the container exits
                  //core.yml配置文件如果vm.docker.attachStdout設(shè)置為true 則會(huì)輸出合約docker容器的日志信息,默認(rèn)關(guān)閉
                  attached := make(chan struct{})
                  r, w := io.Pipe()

                  go func() {
                      // AttachToContainer will fire off a message on the "attached" channel once the
                      // attachment completes, and then block until the container is terminated.
                      // The returned error is not used outside the scope of this function. Assign the
                      // error to a local variable to prevent clobbering the function variable 'err'.
                      err := client.AttachToContainer(docker.AttachToContainerOptions{
                          Container: containerID,
                          OutputStream: w,
                          ErrorStream: w,
                          Logs: true,
                          Stdout: true,
                          Stderr: true,
                          Stream: true,
                          Success: attached,
                      })

                      // If we get here, the container has terminated. Send a signal on the pipe
                      // so that downstream may clean up appropriately
                      _ = w.CloseWithError(err)
                  }()

                  go func() {
                      // Block here until the attachment completes or we timeout
                      select {
                      case <-attached:
                          // successful attach
                      case <-time.After(10 * time.Second):
                          dockerLogger.Errorf("Timeout while attaching to IO channel in container %s", containerID)
                          return
                      }

                      // Acknowledge the attachment? This was included in the gist I followed
                      // (http://bit.ly/2jBrCtM). Not sure it's actually needed but it doesn't
                      // appear to hurt anything.
                      attached <- struct{}{}

                      // Establish a buffer for our IO channel so that we may do readline-style
                      // ingestion of the IO, one log entry per line
                      is := bufio.NewReader(r)

                      // Acquire a custom logger for our chaincode, inheriting the level from the peer
                      containerLogger := flogging.MustGetLogger(containerID)
                      logging.SetLevel(logging.GetLevel("peer"), containerID)

                      for {
                          // Loop forever dumping lines of text into the containerLogger
                          // until the pipe is closed
                          line, err2 := is.ReadString('\n')
                          if err2 != nil {
                              switch err2 {
                              case io.EOF:
                                  dockerLogger.Infof("Container %s has closed its IO channel", containerID)
                              default:
                                  dockerLogger.Errorf("Error reading container output: %s", err2)
                              }

                              return
                          }

                          containerLogger.Info(line)
                      }
                  }()
              }

              // upload specified files to the container before starting it
              // this can be used for configurations such as TLS key and certs
              //容器啟動(dòng)前上傳指定文件到容器內(nèi)部 如ca證書(shū)文件tls證書(shū)文件
              if len(filesToUpload) != 0 {
                  // the docker upload API takes a tar file, so we need to first
                  // consolidate the file entries to a tar
                  payload := bytes.NewBuffer(nil)
                  gw := gzip.NewWriter(payload)
                  tw := tar.NewWriter(gw)

                  for path, fileToUpload := range filesToUpload {
                      cutil.WriteBytesToPackage(path, fileToUpload, tw)
                  }

                  // Write the tar file out
                  if err = tw.Close(); err != nil {
                      return fmt.Errorf("Error writing files to upload to Docker instance into a temporary tar blob: %s", err)
                  }

                  gw.Close()

                  err = client.UploadToContainer(containerID, docker.UploadToContainerOptions{
                      InputStream: bytes.NewReader(payload.Bytes()),
                      Path: "/",
                      NoOverwriteDirNonDir: false,
                  })

                  if err != nil {
                      return fmt.Errorf("Error uploading files to the container instance %s: %s", containerID, err)
                  }
              }
          //調(diào)用chaincode_support.go中preLaunchSetup()
              if prelaunchFunc != nil {
                  if err = prelaunchFunc(); err != nil {
                      return err
                  }
              }

              // start container with HostConfig was deprecated since v1.10 and removed in v1.2
              //啟動(dòng)容器
              err = client.StartContainer(containerID, nil)
              if err != nil {
                  dockerLogger.Errorf("start-could not start container: %s", err)
                  return err
              }

              dockerLogger.Debugf("Started container %s", containerID)
              return nil
          }


          ##5、 core/chaincode/platforms包
          * core/chaincode/platforms目錄,鏈碼的編寫(xiě)語(yǔ)言平臺(tái)實(shí)現(xiàn),如golang或java。
          platforms.go,Platform接口定義,及platforms相關(guān)工具函數(shù)。
          util目錄,Docker相關(guān)工具函數(shù)。
          java目錄,java語(yǔ)言平臺(tái)實(shí)現(xiàn)。
          node目錄,nodejs語(yǔ)言平臺(tái)實(shí)現(xiàn)。

          * Platform接口定義

          ```go
          type Platform interface {
          //驗(yàn)證ChaincodeSpec
          ValidateSpec(spec *pb.ChaincodeSpec) error
          //驗(yàn)證ChaincodeDeploymentSpec
          ValidateDeploymentSpec(spec *pb.ChaincodeDeploymentSpec) error
          //獲取部署Payload
          GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error)
          //生成Dockerfile
          GenerateDockerfile(spec *pb.ChaincodeDeploymentSpec) (string, error)
          //生成DockerBuild
          GenerateDockerBuild(spec *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error
          }
          //代碼在core/chaincode/platforms/platforms.go
          ```
          ### 5.1、platforms相關(guān)工具函數(shù)

          ```go
          //按鏈碼類(lèi)型構(gòu)造Platform接口實(shí)例,如golang.Platform{}
          func Find(chaincodeType pb.ChaincodeSpec_Type) (Platform, error)
          //調(diào)取platform.GetDeploymentPayload(spec),獲取部署Payload
          func GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error)
          //優(yōu)先獲取tls根證書(shū),如無(wú)則獲取tls證書(shū)
          func getPeerTLSCert() ([]byte, error)
          //調(diào)取platform.GenerateDockerfile(cds),創(chuàng)建Dockerfile
          func generateDockerfile(platform Platform, cds *pb.ChaincodeDeploymentSpec, tls bool) ([]byte, error)
          //調(diào)取platform.GenerateDockerBuild(cds, tw),創(chuàng)建DockerBuild
          func generateDockerBuild(platform Platform, cds *pb.ChaincodeDeploymentSpec, inputFiles InputFiles, tw *tar.Writer) error
          //調(diào)取generateDockerfile(platform, cds, cert != nil)
          func GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec) (io.Reader, error)
          //代碼在core/chaincode/platforms/platforms.go
          ```

          ### 5.2 platforms介紹
          * dockercontroller.go中的Start()里有build()方法調(diào)用 ,前文介紹過(guò)會(huì)調(diào)用platforms.GenerateDockerBuild()
          ```go
          func GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec) (io.Reader, error) {

              inputFiles := make(InputFiles)

              // ----------------------------------------------------------------------------------------------------
              // Determine our platform driver from the spec
              // ----------------------------------------------------------------------------------------------------
              //查找平臺(tái)相關(guān)實(shí)現(xiàn) Nodejs在 platforms/node/platform.go中 go在platforms/golang/platform.go中
              platform, err := _Find(cds.ChaincodeSpec.Type)
              if err != nil {
                  return nil, fmt.Errorf("Failed to determine platform type: %s", err)
              }

              // ----------------------------------------------------------------------------------------------------
              // Generate the Dockerfile specific to our context
              // ----------------------------------------------------------------------------------------------------
              //生成各平臺(tái)DockerFile(nodejs java go)
              dockerFile, err := _generateDockerfile(platform, cds)
              if err != nil {
                  return nil, fmt.Errorf("Failed to generate a Dockerfile: %s", err)
              }

              inputFiles["Dockerfile"] = dockerFile

              // ----------------------------------------------------------------------------------------------------
              // Finally, launch an asynchronous process to stream all of the above into a docker build context
              // ----------------------------------------------------------------------------------------------------
              input, output := io.Pipe()

              go func() {
                  gw := gzip.NewWriter(output)
                  tw := tar.NewWriter(gw)
                  //生成鏡像
                  err := _generateDockerBuild(platform, cds, inputFiles, tw)
                  if err != nil {
                      logger.Error(err)
                  }

                  tw.Close()
                  gw.Close()
                  output.CloseWithError(err)
              }()

              return input, nil
          }


          ```
          * platforms/node/platform.go GenerateDockerBuild函數(shù)

          func (nodePlatform *Platform) GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error {

              codepackage := bytes.NewReader(cds.CodePackage)
              binpackage := bytes.NewBuffer(nil)
              str :=""
              //此處是自己修改過(guò)的代碼,目的是如果環(huán)境變量里配置了 NODE_REGISTRY 則npm使用這個(gè)源
              var cusRegisry = os.Getenv("NODE_REGISTRY")
              if cusRegisry !=""{
                  str = "cp -R /chaincode/input/src/. /chaincode/output && cd /chaincode/output && npm config set registry "+cusRegisry+" && npm install --production"
              } else {
                  str = "cp -R /chaincode/input/src/. /chaincode/output && cd /chaincode/output && npm install --production"
              }

              fmt.Println("cmd........"+str)
          //把鏈碼傳到ccenv鏡像的容器里啟動(dòng)并安裝 nodejs合約模塊.網(wǎng)絡(luò)的原因,很慢。有些模塊還需要編譯二進(jìn)制文件,可能失敗(composer 合約是這樣)
              err := util.DockerBuild(util.DockerBuildOptions{
                  Cmd: fmt.Sprint(str),
                  InputStream: codepackage,
                  OutputStream: binpackage,
              })
              if err != nil {
                  return err
              }

              return cutil.WriteBytesToPackage("binpackage.tar", binpackage.Bytes(), tw)
          }

          ### 5.3、nodejs合約容器啟動(dòng)編譯流程圖
          * nodejs寫(xiě)合約的話(huà)會(huì)先啟動(dòng)ccenv鏡像,并在這個(gè)容器里編譯nodejs合約,完成后拿到編譯好的文件夾,再啟
          動(dòng)baseimage,并且把編譯好的文件放到usr/local/src下面.最后才是 npm start ...命令啟動(dòng).流程圖如下
          ![](nodejsdocker.png)

            posted on 2018-06-12 14:51 傻 瓜 閱讀(2968) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 雜項(xiàng)

            導(dǎo)航

            統(tǒng)計(jì)

            常用鏈接

            留言簿(7)

            我參與的團(tuán)隊(duì)

            隨筆分類(lèi)

            隨筆檔案

            文章分類(lèi)

            友情鏈接

            搜索

            積分與排名

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            主站蜘蛛池模板: 方山县| 民勤县| 浦北县| 南江县| 金堂县| 栾川县| 石棉县| 铅山县| 唐海县| 高陵县| 亳州市| 津南区| 阳江市| 昌黎县| 儋州市| 灵璧县| SHOW| 深圳市| 桦甸市| 正阳县| 桑日县| 甘肃省| 高碑店市| 张掖市| 五台县| 勃利县| 南雄市| 钟山县| 博白县| 惠州市| 宜黄县| 九江县| 东辽县| 临猗县| 措勤县| 华容县| 鸡东县| 富蕴县| 潼南县| 寻乌县| 临泽县|