在 Fabric 中,有一些链码是比较特殊的,叫系统链码。它们作为peer进程的一部分运行,而不是像用户链码一样运行在独立的 docker 容器中。因此它们有更高的权限来访问 peer 中的资源来实现用户链码难以实现或者不能实现的功能。
在 Fabric 1.4 版本之前,总共有五种系统链码,它们分别是:
在我学习的过程中都是按照 Fabric 1.4 版本的源码及资料学习的,在 1.4 版本中,官方文档明确表示,当前现有只支持 LSCC、CSCC、QSCC 三种链码,而 ESCC 与 VSCC 则是被可插拔背书和验证方法所取代,具体的描述文档可以参考官方文档——可插拔交易背书与交易验证,主要是是因为在某些情况下,用户需要自定义背书与验证的规则。
和用户链码不同的是,系统链码不使用 SDK 或 CLI 的提案安装和实例化。它在 peer 节点启动的时候注册和部署。
本篇文章将从源码角度,分析系统链码是如何在 peer 节点启动的时候就注册和部署的,并且它们主要对外主要提供了哪些方法。
注:本篇文章会着重分析 1.4 版本中支持的三种链码,对于 ESCC 与 VSCC 只会简单带过。
peer 节点是以 docker 容器的形式存在的,在容器启动是会执行 peer node start
命令来启动 peer 节点,因此我们顺藤摸瓜就可以找到 peer 节点启动时的源码文件 —— peer/node/start.go
我把有关系统链码的部分贴出来,别的部分暂时先不关心。start 命令的主要入口函数就是 serve 函数。
func serve(args []string) error {
//.................
// Initialize chaincode service
chaincodeSupport, ccp, sccp, packageProvider := startChaincodeServer(peerHost, aclProvider, pr, opsSystem)
//.................
// deploy system chaincodes
sccp.DeploySysCCs("", ccp)
//.................
}
在 serve 函数主要就是调用了一个 startChaincodeServer 函数,创建了我们关心的两个对象 ccp (ChainCodeProvider,链码提供者)和 sccp (system ChainCodeProvider,系统链码提供者),之后调用 sccp.DeploySysCCs 方法来部署系统链码。因此系统链码的安装和部署就完成了,我们主要关心的是创建了一个 sccp 对象,之后调用了 sccp 的 sccp.DeploySysCCs 方法部署系统链码。那么我们就先来关心一下 startChaincodeServer 函数中创建系统链码的部分。
// startChaincodeServer will finish chaincode related initialization, including:
// 1) setup local chaincode install path
// 2) create chaincode specific tls CA
// 3) start the chaincode specific gRPC listening service
func startChaincodeServer(
peerHost string,
aclProvider aclmgmt.ACLProvider,
pr *platforms.Registry,
ops *operations.System,
) (*chaincode.ChaincodeSupport, ccprovider.ChaincodeProvider, *scc.Provider, *persistence.PackageProvider) {
//.................
// 调用registerChaincodeSupport注册
chaincodeSupport, ccp, sccp := registerChaincodeSupport(
ccSrv,
ccEndpoint,
ca,
packageProvider,
aclProvider,
pr,
lifecycleSCC,
ops,
)
}
//NOTE - when we implement JOIN we will no longer pass the chainID as param
//The chaincode support will come up without registering system chaincodes
//which will be registered only during join phase.
func registerChaincodeSupport(
grpcServer *comm.GRPCServer,
ccEndpoint string,
ca tlsgen.CA,
packageProvider *persistence.PackageProvider,
aclProvider aclmgmt.ACLProvider,
pr *platforms.Registry,
lifecycleSCC *lifecycle.SCC,
ops *operations.System,
) (*chaincode.ChaincodeSupport, ccprovider.ChaincodeProvider, *scc.Provider) {
sccp := scc.NewProvider(peer.Default, peer.DefaultSupport, ipRegistry)
// 创建LSCC,CSCC,QSCC
lsccInst := lscc.New(sccp, aclProvider, pr)
// 创建chaincodeSupport
chaincodeSupport := chaincode.NewChaincodeSupport(
chaincode.GlobalConfig(),
ccEndpoint,
userRunsCC,
ca.CertBytes(),
authenticator,
packageProvider,
lsccInst,
aclProvider,
container.NewVMController(
map[string]container.VMProvider{
dockercontroller.ContainerType: dockerProvider,
inproccontroller.ContainerType: ipRegistry,
},
),
sccp,
pr,
peer.DefaultSupport,
ops.Provider,
)
ipRegistry.ChaincodeSupport = chaincodeSupport
// 创建chaincodeProvider
ccp := chaincode.NewProvider(chaincodeSupport)
// 创建 CSCC 与 QSCC
csccInst := cscc.New(ccp, sccp, aclProvider)
qsccInst := qscc.New(aclProvider)
//Now that chaincode is initialized, register all system chaincodes.
// CreatePluginSysCCs creates all of the system chaincodes which are compiled into fabric
sccs := scc.CreatePluginSysCCs(sccp)
for _, cc := range append([]scc.SelfDescribingSysCC{lsccInst, csccInst, qsccInst, lifecycleSCC}, sccs...) {
// sccp 注册给定的系统链码
sccp.RegisterSysCC(cc)
}
}
在 startChaincodeServer 函数 中调用了 registerChaincodeSupport 函数来创建 sccp 对象,在该函数中,分别创建了 LSCC,CSCC 与 QSCC,并在最后完成了系统链码的注册操作。具体注册的内容交给 sccp 的 RegisterSysCC 函数去做,本文就不再继续展开了,有兴趣的可以继续追踪源码下去阅读。
需要注意一点的是,在正式注册之前,还执行了一步 scc.CreatePluginSysCCs(sccp) 的操作,根据注释的意思是创建编译进 fabric 的系统链码。系统链码可以通过两种方式链接到 peer 节点:
用户可以自定义一些系统插件,在这一步就会被注册并部署到系统插件中。具体的操作流程可以参考官方文档——系统链码插件。
介绍完了 peer 节点是如何注册系统链码并进行部署的,下面来看看各个链码具体都做了一些什么内容。
LSCC 主要用于管理链码的生命周期,主要包括以下几种行为:
这一部分的源代码主要在这里—— core/scc/lscc/lscc.go
链码的具体调用是通过 Invoke 函数,并通过 Args 参数指定需要执行的函数。 LSCC 提供的主要函数都定义成了常量,我们来看下
const (
// 安装链码,即 peer chaincode install 命令
INSTALL = "install"
// 部署链码,实例化链码,即 peer chaincode instantiate 命令
DEPLOY = "deploy"
// 升级链码,即 peer chaincode upgrade
UPGRADE = "upgrade"
// 获取合约ID
CCEXISTS = "getid"
// 以下就不一一解释了 =_=,都可以根据字面意思理解
// CHAINCODEEXISTS get chaincode alias
CHAINCODEEXISTS = "ChaincodeExists"
// GETDEPSPEC get ChaincodeDeploymentSpec
GETDEPSPEC = "getdepspec"
// GETDEPLOYMENTSPEC get ChaincodeDeploymentSpec alias
GETDEPLOYMENTSPEC = "GetDeploymentSpec"
// GETCCDATA get ChaincodeData
GETCCDATA = "getccdata"
// GETCHAINCODEDATA get ChaincodeData alias
GETCHAINCODEDATA = "GetChaincodeData"
// GETCHAINCODES gets the instantiated chaincodes on a channel
GETCHAINCODES = "getchaincodes"
// GETCHAINCODESALIAS gets the instantiated chaincodes on a channel
GETCHAINCODESALIAS = "GetChaincodes"
// GETINSTALLEDCHAINCODES gets the installed chaincodes on a peer
GETINSTALLEDCHAINCODES = "getinstalledchaincodes"
// GETINSTALLEDCHAINCODESALIAS gets the installed chaincodes on a peer
GETINSTALLEDCHAINCODESALIAS = "GetInstalledChaincodes"
// GETCOLLECTIONSCONFIG gets the collections config for a chaincode
GETCOLLECTIONSCONFIG = "GetCollectionsConfig"
// GETCOLLECTIONSCONFIGALIAS gets the collections config for a chaincode
GETCOLLECTIONSCONFIGALIAS = "getcollectionsconfig"
)
CSCC 管理 peer 上通道相关的信息以及执行通道配置交易
这一部分的源代码主要在这里—— core/scc/cscc/configure.go
来看下它主要提供的方法:
const (
// 让一个peer加入通道,即 peer channel join
JoinChain string = "JoinChain"
// 获取给定通道的当前配置区块,即 peer channel fetch
GetConfigBlock string = "GetConfigBlock"
// 获取peer当前所加入的通道,即 peer channel list
GetChannels string = "GetChannels"
// 获取当前指定通道的配置信息
GetConfigTree string = "GetConfigTree"
// 模拟执行config结构更新
SimulateConfigTreeUpdate string = "SimulateConfigTreeUpdate"
)
如果要从一个通道添加或移除组织,必须获取通道配置(GetConfigTree)来进行修改,并调用 SimulateConfigTreeUpdate 模拟执行配置更新,必须获取 CSCC 的背书
QSCC 将指定的方法暴露给用户,使得用户可以查询存储在账本(世界状态)中的信息。
这一部分的源代码主要在这里—— core/scc/qscc/query.go
来看下它主要提供的方法:
const (
// 获取通道信息
GetChainInfo string = "GetChainInfo"
// 通过区块高度查询区块
GetBlockByNumber string = "GetBlockByNumber"
// 通过区块hash查询区块
GetBlockByHash string = "GetBlockByHash"
// 通过交易ID查询交易
GetTransactionByID string = "GetTransactionByID"
// 通过交易ID查询区块
GetBlockByTxID string = "GetBlockByTxID"
)
ESCC 被背书节点调用。背书节点在执行交易之后,将背书结果放在交易响应中。
Fabric 实现了默认的 DefaultEndorsementFactory 默认背书系统链码,具体实现在 core/handlers/endorsement/builtin/default_endorsement.go,根据官方文档说明,用户可以自定义背书链码—— 可插拔交易背书与交易验证
VSCC 被记账节点调用,来根据合约的背书策略验证每个交易的签名集合
Fabric 实现了默认的 DefaultValidation 默认验证系统链码,具体实现在 core/handlers/validation/builtin/default_validation.go,根据官方文档说明,用户可以自定义背书链码—— 可插拔交易背书与交易验证