Fabric v2.0 源码解析——容器运行智能合约(chaincode)

本文还未编写完成,后面会逐步完成。

文章目录

  • 1. Fabric各个组件的交互
  • 2. chaincode开发
  • 3. chaincode启动
    • 3.1 镜像
    • 3.2 生成chaincode二进制文件
      • 不同语言平台的抽象
    • 3.3 启动chaincode容器
    • 3.4 启动chaincode进程
  • 4. chaincode调用

1. Fabric各个组件的交互

Fabric v1.x 的组件包括client、orderer节点、peer节点、chaincode容器,各个容器节点间的交互关系如下:
Fabric v2.0 源码解析——容器运行智能合约(chaincode)_第1张图片
上图中可知,最主要的5个交互步骤为:

  • Client → Peer: EndorseProposal,Client向Endorser发送交易提案,请求背书;
  • Peer ↔ Chaincode: ChaincodeInteractions,Endorser调用chaincode,与chaincode容器交互,这也是本文需要详细解析的
  • Client → Orderer: BroadcastTxResponse,client调用Orderer的Broadcast接口,开始交易全排序以及广播区块;
  • Orderer → Peer: DeliverBlock,Orderer向Peer广播区块;
  • Peer → Client: SendBlockEvents,Peer向Client发送事件,通知交易已被打包到区块中。

下图中可以看到各个容器节点间通过gRPC进行通信,其中Peer与chaincode容器是通过ChaincodeSuppport的gRPC接口服务进行交互:
Fabric v2.0 源码解析——容器运行智能合约(chaincode)_第2张图片

2. chaincode开发

关于chaincode的开发请参考Fabric v1.x 应用开发指南(2 3 4章节)。

3. chaincode启动

开发模式:ChaincodeSupportService会等待来自独立chaincode进程的连接;
非开发模式:大致包括这些启动步骤

  1. 尝试启动chaincode容器(第一次将会失败);
  2. 根据chaincode语言,生成对相应的DockerFile;
  3. 使用生成的DockerFile创建一个用于构建chaincode的Docker容器并获取chaincode二进制文件;
  4. 创建并启动一个Docker容器以运行Chaincode;
  5. chaincode启动,建立与在peer上的ChaincodeSupportService的gRPC连接。通常ChaincodeSupportService将会调用Init函数。

3.1 镜像

func (vm *DockerVM) GetEnv(ccid string, tlsConfig *ccintf.TLSConfig) []string {
	envs := []string{fmt.Sprintf("CORE_CHAINCODE_ID_NAME=%s", ccid)}
	envs = append(envs, vm.LoggingEnv...)
	if tlsConfig != nil {
		envs = append(envs, "CORE_PEER_TLS_ENABLED=true")
		envs = append(envs, fmt.Sprintf("CORE_TLS_CLIENT_KEY_PATH=%s", TLSClientKeyPath))
		envs = append(envs, fmt.Sprintf("CORE_TLS_CLIENT_CERT_PATH=%s", TLSClientCertPath))
		envs = append(envs, fmt.Sprintf("CORE_TLS_CLIENT_KEY_FILE=%s", TLSClientKeyFile))
		envs = append(envs, fmt.Sprintf("CORE_TLS_CLIENT_CERT_FILE=%s", TLSClientCertFile))
		envs = append(envs, fmt.Sprintf("CORE_PEER_TLS_ROOTCERT_FILE=%s", TLSClientRootCertFile))
	} else {
		envs = append(envs, "CORE_PEER_TLS_ENABLED=false")
	}
	envs = append(envs, fmt.Sprintf("CORE_PEER_LOCALMSPID=%s", vm.MSPID))
	return envs
}
func (vm *DockerVM) GetArgs(ccType string, peerAddress string) ([]string, error) {
	switch ccType {
	case pb.ChaincodeSpec_GOLANG.String(), pb.ChaincodeSpec_CAR.String():
		return []string{"chaincode", fmt.Sprintf("-peer.address=%s", peerAddress)}, nil
	case pb.ChaincodeSpec_JAVA.String():
		return []string{"/root/chaincode-java/start", "--peerAddress", peerAddress}, nil
	case pb.ChaincodeSpec_NODE.String():
		return []string{"/bin/sh", "-c", fmt.Sprintf(nodeStartScript, peerAddress)}, nil
	default:
		return nil, errors.Errorf("unknown chaincodeType: %s", ccType)
	}
}

args: chaincode启动命令
envs: chaincode环境变量

3.2 生成chaincode二进制文件

不同语言平台的抽象

平台抽象的接口为Platform :
core/chaincode/platforms/platforms.go

type Platform interface {
	Name() string
	GenerateDockerfile() (string, error)
	DockerBuildOptions(path string) (util.DockerBuildOptions, error)
}

Fabric支持golang、java、node语言的chaincode平台,下面主要解析golang的platform抽象。
core/chaincode/platforms/golang/platform.go
GenerateDockerfile函数用于生成Dockerfile:

func (p *Platform) GenerateDockerfile() (string, error) {
	var buf []string
	buf = append(buf, "FROM "+util.GetDockerImageFromConfig("chaincode.golang.runtime"))
	buf = append(buf, "ADD binpackage.tar /usr/local/bin")
	return strings.Join(buf, "\n"), nil
}

chaincode.golang.runtime参数在core.yaml中配置:

chaincode:
    golang:
        runtime: $(DOCKER_NS)/fabric-baseos:$(TWO_DIGIT_VERSION)
  • chaincode二进制运行基于baseos(ccenv也是基于baseos,加了一些go的dependence,是一个平台相关的,但是依赖都打到binary上了,我们只需要一个纯净的环境)。
  • 把binpackage.tar 放到了/usr/local/bin目录。

DockerBuildOptions函数:
core/chaincode/platforms/golang/platform.go

func (p *Platform) DockerBuildOptions(path string) (util.DockerBuildOptions, error) {
	env := []string{}
	for _, key := range []string{"GOPROXY", "GOSUMDB"} {
		if val, ok := os.LookupEnv(key); ok {
			env = append(env, fmt.Sprintf("%s=%s", key, val))
			continue
		}
		if key == "GOPROXY" {
			env = append(env, "GOPROXY=https://proxy.golang.org")
		}
	}
	ldFlagOpts := getLDFlagsOpts()
	return util.DockerBuildOptions{
		Cmd: fmt.Sprintf(buildScript, ldFlagOpts, path),
		Env: env,
	}, nil
}

DockerBuildOptions为构造二进制文件时增加一些选项,包括GOPROXY、GOSUMDB。

3.3 启动chaincode容器

3.4 启动chaincode进程

core/chaincode/handler.go:
hyperledger/fabric-chaincode-go/shim/handler.go:

4. chaincode调用

readyStateHandler:不同的消息类型,选择不同的handler
transactionContext:对应chaincodeSupport,queryIteratorMap、historyQueryExecutor

你可能感兴趣的:(Hyperledger,Fabric,#,Fabric,v2.x)