fabric1.4链码实例化源码解析

链码容器启动

peer节点背书提案的入口方法 endorser.goProcessProposal()方法

func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
    // start time for computing elapsed time metric for successfully endorsed proposals
    // 获取peer节点处理提案的开始时间
    startTime := time.Now()
    // Peer节点收到提案的数量+1
    e.Metrics.ProposalsReceived.Add(1)

    // 从上下文中获取发起提案的地址
    addr := util.ExtractRemoteAddress(ctx)
    endorserLogger.Debug("Entering: request from", addr)

提案预处理

对提案进行模拟执行

endorser.goSimulateProposal() 方法

func (e *Endorser) SimulateProposal(txParams *ccprovider.TransactionParams, cid *pb.ChaincodeID) (ccprovider.ChaincodeDefinition, *pb.Response, []byte, *pb.ChaincodeEvent, error) {
    endorserLogger.Debugf("[%s][%s] Entry chaincode: %s", txParams.ChannelID, shorttxid(txParams.TxID), cid)
    defer endorserLogger.Debugf("[%s][%s] Exit", txParams.ChannelID, shorttxid(txParams.TxID))
    // we do expect the payload to be a ChaincodeInvocationSpec
    // if we are supporting other payloads in future, this be glaringly point
    // as something that should change
    cis, err := putils.GetChaincodeInvocationSpec(txParams.Proposal)
    if err != nil {
        return nil, nil, nil, nil, err
    }

    var cdLedger ccprovider.ChaincodeDefinition
    var version string

    if !e.s.IsSysCC(cid.Name) {
        cdLedger, err = e.s.GetChaincodeDefinition(cid.Name, txParams.TXSimulator)
        if err != nil {
            return nil, nil, nil, nil, errors.WithMessage(err, fmt.Sprintf("make sure the chaincode %s has been successfully instantiated and try again", cid.Name))
        }
        version = cdLedger.CCVersion()

        err = e.s.CheckInstantiationPolicy(cid.Name, version, cdLedger)
        if err != nil {
            return nil, nil, nil, nil, err
        }
    } else {
        version = util.GetSysCCVersion()
    }

    // ---3. execute the proposal and get simulation results
    var simResult *ledger.TxSimulationResults
    var pubSimResBytes []byte
    var res *pb.Response
    var ccevent *pb.ChaincodeEvent
    res, ccevent, err = e.callChaincode(txParams, version, cis.ChaincodeSpec.Input, cid)
    if err != nil {
        endorserLogger.Errorf("[%s][%s] failed to invoke chaincode %s, error: %+v", txParams.ChannelID, shorttxid(txParams.TxID), cid, err)
        return nil, nil, nil, nil, err
    }

    if txParams.TXSimulator != nil {
        if simResult, err = txParams.TXSimulator.GetTxSimulationResults(); err != nil {
            txParams.TXSimulator.Done()
            return nil, nil, nil, nil, err
        }

        if simResult.PvtSimulationResults != nil {
            if cid.Name == "lscc" {
                // TODO: remove once we can store collection configuration outside of LSCC
                txParams.TXSimulator.Done()
                return nil, nil, nil, nil, errors.New("Private data is forbidden to be used in instantiate")
            }
            pvtDataWithConfig, err := e.AssemblePvtRWSet(simResult.PvtSimulationResults, txParams.TXSimulator)
            // To read collection config need to read collection updates before
            // releasing the lock, hence txParams.TXSimulator.Done()  moved down here
            txParams.TXSimulator.Done()

            if err != nil {
                return nil, nil, nil, nil, errors.WithMessage(err, "failed to obtain collections config")
            }
            endorsedAt, err := e.s.GetLedgerHeight(txParams.ChannelID)
            if err != nil {
                return nil, nil, nil, nil, errors.WithMessage(err, fmt.Sprint("failed to obtain ledger height for channel", txParams.ChannelID))
            }
            // Add ledger height at which transaction was endorsed,
            // `endorsedAt` is obtained from the block storage and at times this could be 'endorsement Height + 1'.
            // However, since we use this height only to select the configuration (3rd parameter in distributePrivateData) and
            // manage transient store purge for orphaned private writesets (4th parameter in distributePrivateData), this works for now.
            // Ideally, ledger should add support in the simulator as a first class function `GetHeight()`.
            pvtDataWithConfig.EndorsedAt = endorsedAt
            if err := e.distributePrivateData(txParams.ChannelID, txParams.TxID, pvtDataWithConfig, endorsedAt); err != nil {
                return nil, nil, nil, nil, err
            }
        }

        txParams.TXSimulator.Done()
        if pubSimResBytes, err = simResult.GetPubSimulationBytes(); err != nil {
            return nil, nil, nil, nil, err
        }
    }
    return cdLedger, res, pubSimResBytes, ccevent, nil
}

callChaincode()方法模拟提案执行

func (e *Endorser) callChaincode(txParams *ccprovider.TransactionParams, version string, input *pb.ChaincodeInput, cid *pb.ChaincodeID) (*pb.Response, *pb.ChaincodeEvent, error) {
    endorserLogger.Infof("[%s][%s] Entry chaincode: %s", txParams.ChannelID, shorttxid(txParams.TxID), cid)
    defer func(start time.Time) {
        logger := endorserLogger.WithOptions(zap.AddCallerSkip(1))
        elapsedMilliseconds := time.Since(start).Round(time.Millisecond) / time.Millisecond
        logger.Infof("[%s][%s] Exit chaincode: %s (%dms)", txParams.ChannelID, shorttxid(txParams.TxID), cid, elapsedMilliseconds)
    }(time.Now())

    var err error
    var res *pb.Response
    var ccevent *pb.ChaincodeEvent

    // is this a system chaincode
    res, ccevent, err = e.s.Execute(txParams, txParams.ChannelID, cid.Name, version, txParams.TxID, txParams.SignedProp, txParams.Proposal, input)
    if err != nil {
        return nil, nil, err
    }

    // per doc anything < 400 can be sent as TX.
    // fabric errors will always be >= 400 (ie, unambiguous errors )
    // "lscc" will respond with status 200 or 500 (ie, unambiguous OK or ERROR)
    if res.Status >= shim.ERRORTHRESHOLD {
        return res, nil, nil
    }

    // ----- BEGIN -  SECTION THAT MAY NEED TO BE DONE IN LSCC ------
    // if this a call to deploy a chaincode, We need a mechanism
    // to pass TxSimulator into LSCC. Till that is worked out this
    // special code does the actual deploy, upgrade here so as to collect
    // all state under one TxSimulator
    //
    // NOTE that if there's an error all simulation, including the chaincode
    // table changes in lscc will be thrown away
    if cid.Name == "lscc" && len(input.Args) >= 3 && (string(input.Args[0]) == "deploy" || string(input.Args[0]) == "upgrade") {
        userCDS, err := putils.GetChaincodeDeploymentSpec(input.Args[2], e.PlatformRegistry)
        if err != nil {
            return nil, nil, err
        }

        var cds *pb.ChaincodeDeploymentSpec
        cds, err = e.SanitizeUserCDS(userCDS)
        if err != nil {
            return nil, nil, err
        }

        // this should not be a system chaincode
        if e.s.IsSysCC(cds.ChaincodeSpec.ChaincodeId.Name) {
            return nil, nil, errors.Errorf("attempting to deploy a system chaincode %s/%s", cds.ChaincodeSpec.ChaincodeId.Name, txParams.ChannelID)
        }

        _, _, err = e.s.ExecuteLegacyInit(txParams, txParams.ChannelID, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, txParams.TxID, txParams.SignedProp, txParams.Proposal, cds)
        if err != nil {
            // increment the failure to indicate instantion/upgrade failures
            meterLabels := []string{
                "channel", txParams.ChannelID,
                "chaincode", cds.ChaincodeSpec.ChaincodeId.Name + ":" + cds.ChaincodeSpec.ChaincodeId.Version,
            }
            e.Metrics.InitFailed.With(meterLabels...).Add(1)
            return nil, nil, err
        }
    }
    // ----- END -------

    return res, ccevent, err
}

其中e.s.Execute实际调用了support.goSupportImplExecute()方法

func (s *SupportImpl) Execute(txParams *ccprovider.TransactionParams, cid, name, version, txid string, signedProp *pb.SignedProposal, prop *pb.Proposal, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) {
    cccid := &ccprovider.CCContext{
        Name:    name,
        Version: version,
    }

    // decorate the chaincode input
    decorators := library.InitRegistry(library.Config{}).Lookup(library.Decoration).([]decoration.Decorator)
    input.Decorations = make(map[string][]byte)
    input = decoration.Apply(prop, input, decorators...)
    txParams.ProposalDecorations = input.Decorations

    return s.ChaincodeSupport.Execute(txParams, cccid, input)
}

链码容器启动

ChaincodeSupport.Invoke 方法中

func (cs *ChaincodeSupport) Invoke(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.ChaincodeMessage, error) {
    # 这一句启动链码容器
    h, err := cs.Launch(txParams.ChannelID, cccid.Name, cccid.Version, txParams.TXSimulator)
    if err != nil {
        return nil, err
    }

    // TODO add Init exactly once semantics here once new lifecycle
    // is available.  Enforced if the target channel is using the new lifecycle
    //
    // First, the function name of the chaincode to invoke should be checked.  If it is
    // "init", then consider this invocation to be of type pb.ChaincodeMessage_INIT,
    // otherwise consider it to be of type pb.ChaincodeMessage_TRANSACTION,
    //
    // Secondly, A check should be made whether the chaincode has been
    // inited, then, if true, only allow cctyp pb.ChaincodeMessage_TRANSACTION,
    // otherwise, only allow cctype pb.ChaincodeMessage_INIT,
    cctype := pb.ChaincodeMessage_TRANSACTION

    return cs.execute(cctype, txParams, cccid, input, h)
}

最终跟到文件dockercontroller.go,在此文件中完成docker容器的创建

链码镜像生成以及容器生成

dockercontroller.go

// 容器创建-启动的主方法
func (vm *DockerVM) Start(ccid ccintf.CCID, args, env []string, filesToUpload map[string][]byte, builder container.Builder) error {
    imageName, err := vm.GetVMNameForDocker(ccid)
    if err != nil {
        return err
    }

    attachStdout := viper.GetBool("vm.docker.attachStdout")
    containerName := vm.GetVMName(ccid)
    logger := dockerLogger.With("imageName", imageName, "containerName", containerName)

    client, err := vm.getClientFnc()
    if err != nil {
        logger.Debugf("failed to get docker client", "error", err)
        return err
    }
    
    vm.stopInternal(client, containerName, 0, false, false)
    // 创建容器
    err = vm.createContainer(client, imageName, containerName, args, env, attachStdout)
    if err == docker.ErrNoSuchImage {
        // 链码实例化一般来说都会进入此块,因为不存在镜像
        // 此方法生成的reader就是DockerFile的文件流  很重要
        reader, err := builder.Build()
        if err != nil {
            return errors.Wrapf(err, "failed to generate Dockerfile to build %s", containerName)
        }

        err = vm.deployImage(client, ccid, reader)
        if err != nil {
            return err
        }

其中builder.Build()就是生产Dockerfile的地方,builder的实现是PlatformBuilder

func (b *PlatformBuilder) Build() (io.Reader, error) {
    return b.PlatformRegistry.GenerateDockerBuild(
        b.Type,
        b.Path,
        b.Name,
        b.Version,
        b.CodePackage,
    )
}

进入PlatformRegistry的方法GenerateDockerBuild

func (r *Registry) GenerateDockerBuild(ccType, path, name, version string, codePackage []byte) (io.Reader, error) {

    inputFiles := make(map[string][]byte)

    // Generate the Dockerfile specific to our context
    // 生成dockerfile
    dockerFile, err := r.GenerateDockerfile(ccType, name, version)
    if err != nil {
        return nil, fmt.Errorf("Failed to generate a Dockerfile: %s", err)
    }

    inputFiles["Dockerfile"] = []byte(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 := r.StreamDockerBuild(ccType, path, codePackage, inputFiles, tw)
        if err != nil {
            logger.Error(err)
        }

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

    return input, nil
}

进入方法GenerateDockerfile

func (r *Registry) GenerateDockerfile(ccType, name, version string) (string, error) {
    platform, ok := r.Platforms[ccType]
    if !ok {
        return "", fmt.Errorf("Unknown chaincodeType: %s", ccType)
    }

    var buf []string

    // ----------------------------------------------------------------------------------------------------
    // Let the platform define the base Dockerfile
    // ----------------------------------------------------------------------------------------------------
    // 根据不同的链码语言生成dockerfile
    base, err := platform.GenerateDockerfile()
    if err != nil {
        return "", fmt.Errorf("Failed to generate platform-specific Dockerfile: %s", err)
    }
    buf = append(buf, base)

    // ----------------------------------------------------------------------------------------------------
    // Add some handy labels
    // ----------------------------------------------------------------------------------------------------
    buf = append(buf, fmt.Sprintf(`LABEL %s.chaincode.id.name="%s" \`, metadata.BaseDockerLabel, name))
    buf = append(buf, fmt.Sprintf(`      %s.chaincode.id.version="%s" \`, metadata.BaseDockerLabel, version))
    buf = append(buf, fmt.Sprintf(`      %s.chaincode.type="%s" \`, metadata.BaseDockerLabel, ccType))
    buf = append(buf, fmt.Sprintf(`      %s.version="%s" \`, metadata.BaseDockerLabel, metadata.Version))
    buf = append(buf, fmt.Sprintf(`      %s.base.version="%s"`, metadata.BaseDockerLabel, metadata.BaseVersion))
    // ----------------------------------------------------------------------------------------------------
    // Then augment it with any general options
    // ----------------------------------------------------------------------------------------------------
    //append version so chaincode build version can be compared against peer build version
    buf = append(buf, fmt.Sprintf("ENV CORE_CHAINCODE_BUILDLEVEL=%s", metadata.Version))

    // ----------------------------------------------------------------------------------------------------
    // Finalize it
    // ----------------------------------------------------------------------------------------------------
    contents := strings.Join(buf, "\n")
    logger.Debugf("\n%s", contents)

    return contents, nil

我们主要看下JAVA链码平台core/chaincode/platforms/java/platform.goGenerateDockerfile方法

func (javaPlatform *Platform) GenerateDockerfile() (string, error) {
    var buf []string

    buf = append(buf, "FROM "+cutil.GetDockerfileFromConfig("chaincode.java.runtime"))
    buf = append(buf, "ADD binpackage.tar /root/chaincode-java/chaincode")

    dockerFileContents := strings.Join(buf, "\n")

    return dockerFileContents, nil
}

从上代码可以看到,Java链码容器依赖的镜像是从配置chaincode.java.runtime中读取的,而这个配置在integration/nwo/core_template.go中(以下只是部分配置)

chaincode:
  builder: $(DOCKER_NS)/fabric-ccenv:$(ARCH)-$(PROJECT_VERSION)
  pull: false
  golang:
    runtime: $(BASE_DOCKER_NS)/fabric-baseos:$(ARCH)-$(BASE_VERSION)
    dynamicLink: false
  car:
    runtime: $(BASE_DOCKER_NS)/fabric-baseos:$(ARCH)-$(BASE_VERSION)
  java:
    runtime: $(DOCKER_NS)/fabric-javaenv:$(ARCH)-$(PROJECT_VERSION)

可以看到,JAVA链码容器依赖镜像hyperledger/fabric-javaenv,这也是为什么我们在很多教程中可以看到搭建初始化网络时会让大家先pull这个镜像。

背书提案

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容