# 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)