Fabric 1.1源代码分析之 Chaincode(链码)初始化

# Fabric 1.1源代码分析之 Chaincode(链码)初始化 #ChaincodeSupport(链码支持服务端)

## 1、Endorser概述

1、Endorser相关代码分布在protos/peer/peer.pb.go和core/endorser目录。

* 在peer/node/start.go的serve() 方法中注册了 endoser服务
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接口实现,即Endorser结构体及方法,以及EndorserServer服务端 ProcessProposal处理流程。
* endorser.go,Support接口定义及实现 type SupportImpl struct(support.go)。

## 2、endorser中的EndorserServer接口实现方法
* // ProcessProposal process the Proposal 处理客户端传过来的提案。peer chaincode instantiate初始化合约命令也是一种提案,最终服务端此处是入口
```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 对提案进行预处理,检查消息有校性及其权限
    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 //如果是扩展提案,可能是一个链码操作 调用本文件中的simulateProposal()->callChaincode()->Execute()(core/endorser/support.go switch spec.(type) {   case *pb.ChaincodeDeploymentSpec:return chaincode.Execute(ctxt, cccid, spec))
    support.go中的逻辑判断如果是初始化合约命令最终执行 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、链码相关处理
* 在core/endorser/support.go 文件中 Execute方法判断是初始化还是执行合约。
```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
//执行合约 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
    }
//调用 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是否启动,是否超时,返回结果给客户端
    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. 启动链码成功后FSM状态机推到ready状态
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 判断链码是否启动
         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()的实现_
        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
}


```
* 经过调用 launchAndWaitForRegister()->launch()->core/container/controller.go VMCProcess()
因为controller.go中有好几个 do() 但是在launch中传进来的确是
```go
sir := container.StartImageReq{CCID: ccid, Builder: ccl.builder, Args: args, Env: env, FilesToUpload: filesToUpload, PrelaunchFunc: preLaunchFunc}
```
## 所以下面的VMCProcess中的 req.do 是执行的func (si StartImageReq) do(ctxt context.Context, v api.VM)VMCResp VMCReqIntf是接口定义
ccLauncherImpl结构体中的builder就是platforms.go中的GenerateDockerBuild()
    
func VMCProcess(ctxt context.Context, vmtype string, req VMCReqIntf) (interface{}, error)

VMCProcess中调用 了v.Start() //core.go中的接口vm中定义的方法,其实现在dockercontroller.go中

## 4、 core/container包
* core/container包提供了对容器的操作
core/container/api/core.go中提供接口及函数类型定义
```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提供了对上面接口的实现,并且定义如下接口
```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接口实现类通过方法
// getClient returns an instance that implements dockerClient interface
type getClient func() (dockerClient, error) 返回其实现 类

* dockercontroller.go中的Start方法实现 Start方法控制了合约容器生成到启动的过程
/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
    }

//获取容器名称 规则 peer节点ID+domainname+合约名+随机数
    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)
    //根据最后两位参数选择调用 stopContainer或者killcontainer或者removecontainer
    vm.stopInternal(ctxt, client, containerID, 0, false, false)

    dockerLogger.Debugf("Start container %s", containerID)
    //创建合约容器 第一次布署合约创建容器都会失败。err不会为空因为没有合约容器镜像
    err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout)
    if err != nil {
        //if image not found try to create image and
        //如果镜像没找到则重新生成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()调用 的是core/chaincdoe/platforms/platforms.go中的GenerateDockerBuild()函数**********
                //********产生一个DockerFile并写到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)
                }
//根据builder()产生的DockerFile生成一个合约镜像文件.但是在/platforms/node/platform.go中会先根据 ccenv镜像先
                //npm install 安装链码. 此处会从reader中的DockerFile 生成新的镜像(继承自fabric-baseimage)
                if err1 = vm.deployImage(client, ccid, args, env, reader); err1 != nil {
                    return err1
                }

                dockerLogger.Debug("start-recreated image successfully")
                //创建容器
                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设置为true 则会输出合约docker容器的日志信息,默认关闭
        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
    //容器启动前上传指定文件到容器内部 如ca证书文件tls证书文件
    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)
        }
    }
//调用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
    //启动容器
    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目录,链码的编写语言平台实现,如golang或java。
platforms.go,Platform接口定义,及platforms相关工具函数。
util目录,Docker相关工具函数。
java目录,java语言平台实现。
node目录,nodejs语言平台实现。

* Platform接口定义

```go
type Platform interface {
//验证ChaincodeSpec
ValidateSpec(spec *pb.ChaincodeSpec) error
//验证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相关工具函数

```go
//按链码类型构造Platform接口实例,如golang.Platform{}
func Find(chaincodeType pb.ChaincodeSpec_Type) (Platform, error)
//调取platform.GetDeploymentPayload(spec),获取部署Payload
func GetDeploymentPayload(spec *pb.ChaincodeSpec) ([] byte, error)
//优先获取tls根证书,如无则获取tls证书
func getPeerTLSCert() ([] byte, error)
//调取platform.GenerateDockerfile(cds),创建Dockerfile
func generateDockerfile(platform Platform, cds *pb.ChaincodeDeploymentSpec, tls bool) ([] byte, error)
//调取platform.GenerateDockerBuild(cds, tw),创建DockerBuild
func generateDockerBuild(platform Platform, cds *pb.ChaincodeDeploymentSpec, inputFiles InputFiles, tw *tar.Writer) error
//调取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()方法调用 ,前文介绍过会调用platforms.GenerateDockerBuild()
```go
func GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec) (io.Reader, error) {

    inputFiles := make(InputFiles)

     // ----------------------------------------------------------------------------------------------------
     // Determine our platform driver from the spec
     // ----------------------------------------------------------------------------------------------------
     //查找平台相关实现 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
     // ----------------------------------------------------------------------------------------------------
     //生成各平台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函数

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

    codepackage := bytes.NewReader(cds.CodePackage)
    binpackage := bytes.NewBuffer(nil)
    str :=""
    //此处是自己修改过的代码,目的是如果环境变量里配置了 NODE_REGISTRY 则npm使用这个源
    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镜像的容器里启动并安装 nodejs合约模块.网络的原因,很慢。有些模块还需要编译二进制文件,可能失败(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合约容器启动编译流程图
* nodejs写合约的话会先启动ccenv镜像,并在这个容器里编译nodejs合约,完成后拿到编译好的文件夹,再启
动baseimage,并且把编译好的文件放到usr/local/src下面.最后才是 npm start ...命令启动.流程图如下
![]( nodejsdocker.png)

猜你喜欢

转载自blog.csdn.net/foolone/article/details/80540340
今日推荐