java随记

坚持就是胜利!

 

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)

    posted on 2018-06-12 14:51 傻 瓜 阅读(2918) 评论(0)  编辑  收藏 所属分类: 杂项


    只有注册用户登录后才能发表评论。


    网站导航:
     

    导航

    统计

    常用链接

    留言簿(7)

    我参与的团队

    随笔分类

    随笔档案

    文章分类

    友情链接

    搜索

    积分与排名

    最新评论

    阅读排行榜

    评论排行榜