Hyperledger Fabric 链码生命周期
链码是实现了规定接口的程序,通常用 Go, Node.js, Java 编写。链码运行在安全的 Docker 容器内,与背书节点进程相隔离。链码通过客户端提交的交易初始化和管理账本状态。
链码通常处理网络成员同意的业务逻辑,因此可以将其视为“智能合约”。通过某个链码创建的账本只能通过这个账本进行更新,不能被其他的链码直接访问。然而,在同一个网络中,在获得正确的许可后,别的链码可以通过调用该链码访问该账本的状态。
在这个主题中,我们将通过区块链网络维护人员的视角,而不是应用程序开发人员的视角来探索链码。
部署链码
Fabric 链码的生命周期指的是,多个组织在通道上使用该链码之前,对如何处理链码达成一致意见的过程。一个网络运维人员需要使用 Fabric lifecycle 来完成以下的任务:
你可以通过新建一个通道,并将通道配置中的 Capabilities 设置为 V2_0 来使用 fabric chaincode lifecycle 命令。如果 V2_0 的 Capabilities 被打开,那么旧的链码生命周期将无法使用。然而,你仍然可以用以前的链码生命周期模型来调用已经安装了的链码。链码生命周期迁移教程。
安装并定义一个链码
链码的安装需要组织同意定义链码的参数,比如名字、版本号和链码的背书策略等。通道上的成员需要按照以下的步骤来同意一个链码(不需要每个组织都完成每一步):
- 打包链码(package):这一步可以由一个组织完成,也可以每个组织自己完成;
- 安装链码到 peers 上(install):每个要用这个链码来进行交易背书或者查询账本的组织都要完成这一步;
- 批准组织的链码定义(approve):每个要用这个链码来进行交易背书或者查询账本的组织都要完成这一步。在链码启动之前,链码的定义需要足够组织的批准来满足链码的生命周期背书策略( majority 或 default );
- 提交链码的定义到通道上(commit):一旦通道上所需数量的组织都批准了,提交交易需要由一个组织提交。这个提交组织首先要从已经批准的组织中,收集足够的 Peer 节点背书,然后提交交易,从而提交链码的定义。
本主题详细介绍了 Fabric 链码生命周期的操作,而不是特定的命令。要了解如何通过 Peer CLI 使用 Fabric 生命周期,请参阅将智能合约部署到通道教程或 peer lifecycle 命令手册。
第一步:智能合约打包
链码必须先打包成 .tar 文件,然后才能安装在 Peer 节点上。你可以使用 Fabric Peer 命令,Node Fabric SDK 或第三方工具(例如 GNU tar)来打包链码。打包链码时,需要提供一个链码包标签,以创建一个的简洁易读的描述。
如果你使用第三方工具打包链码,则生成的文件需要采用以下格式。 Fabric peer 二进制文件和 Fabric SDKs 将自动创建此格式的文件。
- 链码包需要打包成
tar文件,以.tar.gz结尾; - 链码包需要包含两个文件(不是目录):元数据文件
metadata.json和包含链码文件的*.tar.gz; metadata.json文件包含了链码使用的语言、代码路径和链码包标签。示例如下:
{
"Path": "fabric-samples/asset-transfer-basic/chaincode-go",
"Type": "golang",
"Label": "basicv1"
}
peer lifecycle chaincode package 把链码 *.tar.gz 和元数据文件 metadata.json 文件打包:
peer lifecycle chaincode package tmp/${CC_LABEL}.tar.gz \
--path ${CC_PATH} \
--lang $CC_LANG \
--label ${CC_LABEL}

链码由 Org1 和 Org2 单独打包。这两个组织都使用 MYCC_1 作为包标签,以便使用名称和版本来标识包。组织无需使用相同的包标签。
第二步:安装链码到 peers 中
每个需要执行交易和为交易背书的 Peer 节点上都要安装链码包。无论是使用 CLI 还是 SDK,你都需要使用 Peer Admin 完成此步骤。Peer 节点将在安装链码后构建链码,如果链码出现问题,则返回链码构建错误消息。建议组织只打包一次链码,然后在属于同一个组织的每个 Peer 上安装相同的链码包。如果一个通道想要确保每个组织运行相同的链码,一个组织可以打包一个链码并将其发送给带外的(out of band)的其他通道成员。
peer lifecycle chaincode install 命令安装链码包:
peer lifecycle chaincode install tmp/${CC_LABEL}.tar.gz
安装后可以使用如下命令查询 y 已经安装的链码:
peer lifecycle chaincode queryinstalled --output json | jq
如果输出如下,则链码安装成功,我们可以看到这个包标识符一般是由 链码名_版本号:哈希码 构成的
PACKAGE_ID=mycc_v1.0:03d34a995a0e6b5285746a7662ff42c3ca88145f3a265a5a4580c4c7c5e7e549
安装成功后,将返回一个由包标签与包的哈希组合而成的链码包标识符。此包标识符是用来关联安装在 Peer 上的链码包和组织批准的链码定义的。该标识符要保存起来为下一步准备。你还可以通过使用 Peer CLI 查询安装在 Peer 上的包来查找包标识符。

Org1 和 Org2 的 Peer 管理员在加入该通道的 Peer 上安装 MYCC_1 链码包。安装链码包的过程,完成了构建链码和创建包标识符 MYCC_1:<hashcode> 的的工作。
第三步:为组织批准的链码定义
代码如下:
# 如果 CORE_PEER_TLS_ENABLED 打开了,则用以下命令
peer lifecycle chaincode approveformyorg \
-o ${ORDERER_ADDRESS} \
--ordererTLSHostnameOverride orderer.mynetwork.com \
--tls $CORE_PEER_TLS_ENABLED \
--cafile $ORDERER_CA \
--channelID $CHANNEL_NAME \
# 以下是需要整个组织保持一致的链码定义
--name ${CC_NAME} \
--version ${CC_VERSION} \
--init-required \
--package-id ${PACKAGE_ID} \
--sequence $CC_SEQ \
--waitForEvent \
--signature-policy "$CC_POLICY" \
$PRIVATE_COLLECTION_DEF
# 如果没有打开则用以下命令
peer lifecycle chaincode approveformyorg \
-o ${ORDERER_ADDRESS} \
--channelID $CHANNEL_NAME \
# 以下是需要整个组织保持一致的链码定义
--name ${CC_NAME} \
--version ${CC_VERSION} \ # 包标识符
--init-required \
--package-id ${PACKAGE_ID} \
--sequence $CC_SEQ \
--waitForEvent \
--signature-policy "$CC_POLICY" \
$PRIVATE_COLLECTION_DEF
链码受链码定义的约束。当通道成员批准链码定义时,这个批准的过程,就像组织对链码参数的投票。这些经组织批准定义,可以让通道成员在使用链码之前就达成一致。链码定义包括以下参数,这些参数需要整个组织保持一致:
Name:应用程序在调用链码时将使用的名称。Version:与给定链码包关联的版本编号或值。如果你升级了链码二进制文件,你还需要更改链码版本。Sequence:已定义链码的次数。此值是一个整数,用于跟踪链码升级。例如,当你首次安装和批准链码定义时,序列号为 1。下次升级链码时,序列号将增加为 2。Endorsement Policy:哪些组织需要执行和验证交易输出。背书策略可以用字符串传递给 CLI ,也可以在通道配置中引用策略。默认情况下,背书策略被设置为Channel/Application/Endorsement,该背书策略默认要求该通道中的大多数组织为交易背书。Collection Configuration:与你的链码关联的私有数据收集定义文件的路径。有关私有数据收集的更多信息,请参阅私有数据架构手册。ESCC/VSCC Plugins:此链码将使用的自定义背书或验证插件的名称。Initialization:如果你使用 Fabric 链码 Shim API 提供的低级 APIs ,则你的链码需要包含用于初始化链码的Init函数。链码接口需要此函数,但不一定需要由你的应用程序调用。当你批准链码定义时,你可以指定在 invoke 之前是否必须调用Init。如果你指定需要Init, Fabric 将确保在链码中的任何其他函数之前调用Init函数,并且仅调用一次。执行Init函数的请求允许你实现在初始化链码时运行的逻辑,例如设置某些初始状态。每次链码版本更新时,你都需要调用Init来初始化链码,假设版本号增加的链码定义表示需要Init。
如果你正在使用 Fabric Peer CLI,你可以在批准并提交链码定义时使用 --init-required 的标记,以表示必须调用 Init 函数来初始化新的链码版本。要使用 Fabric peer CLI 调用 Init ,请使用 peer chaincode invoke 命令并传入 --isInit 标志。
如果你使用的是 Fabric Contract API ,则无需在链码中包含 Init 方法。但是,你仍然可以使用 --init-required 参数来请求通过应用程序的调用来初始化链码。如果使用 --init-required 标志,则每次将链码版本增加时,都需要将 --isInit 参数传递给链码调用,以初始化链码。你可以传递 --isInit 并使用链码中的任何函数来初始化链码。
链码定义还包括程序包标识符。这是每个要使用链码的组织的必需参数。程序包标识符不必对于所有组织都相同。组织可以批准链码定义,而无需安装链码包或在定义中不包含标识符。
每个想要使用链码的通道成员都需要为其组织批准链码定义。该批准需要提交给排序服务,然后再分发给所有 Peer 方。该批准需要由你的组织管理员提交。成功提交批准交易后,批准的定义将存储在一个集合中,该集合可供组织的所有 Peer 方用。因此,即使你有多个 Peer 方,你也只需要为组织批准一次链码。

Org1 和 Org2 的组织管理员批准其组织的 MYCC 链码定义。链码定义包括链码名称,版本和背书策略以及其他字段。由于两个组织都将使用链码来背书交易,因此两个组织的批准定义都需要包括程序包标识符。
组织可以批准链码定义而不安装链码包。如果组织不需要使用链码,则可以批准不带程序包标识符的链码定义,以确保满足生命周期背书策略。
在将链码定义提交到通道后,链码容器将在已安装链码的所有 Peer 上启动,从而允许通道成员开始使用链码。启动链码容器可能需要几分钟。你可以使用链码定义来要求调用 Init 函数来初始化链码。如果请求调用 Init 函数,则链码的第一次调用必须是对 Init 函数的调用。 Init 函数的调用受链码背书策略的约束。

一旦在通道上定义了 MYCC , Org1 和 Org2 就可以开始使用链码了。每个 Peer 上的链码的第一次调用都会在该 Peer 上启动链码容器。
第四步:提交链码定义到通道
一旦足够多的通道成员同意一个链码定义,某个组织能够提交定义到通道。你可以用命令 peer lifecycle chaincode checkcommitreadiness 基于哪个通道成员已经批准了该定义,来检查提交链码定义是否应该成功,然后使用 Peer CLI 工具将链码定义提交到通道。(译者注:根据通道成员同意的状况,来判断提交是否可能成功)。提交交易请求首先发送给通道成员的 peer 节点,peer 节点会查询链码定义被他们组织同意的状况,并且为定义背书如果所在组织已经同意了。交易然后提交给排序服务,排序服务会把链码定义提交给通道。提交定义交易需要以 Organization Administrator 身份来提交。
检查链码同意情况:
peer lifecycle chaincode checkcommitreadiness \
--channelID $CHANNEL_NAME \
--name ${CC_NAME} \
--version ${CC_VERSION} \
--sequence $CC_SEQ \
--output json \
--init-required \
--signature-policy "$CC_POLICY" \
$PRIVATE_COLLECTION_DEF
如果达到了背书条件,可以使用如下命令来提交链码定义到通道,
if [[ "$CORE_PEER_TLS_ENABLED" == "true" ]]; then
peer lifecycle chaincode commit \
-o ${ORDERER_ADDRESS} \
--ordererTLSHostnameOverride orderer.mynetwork.com \
--tls $CORE_PEER_TLS_ENABLED \
--cafile $ORDERER_CA \
--peerAddresses $PEER0_PROVIDER_ADDRESS \
--tlsRootCertFiles $PEER0_PROVIDER_TLS_ROOTCERT_FILE \
--peerAddresses $PEER0_SUBSCRIBER_ADDRESS \
--tlsRootCertFiles $PEER0_SUBSCRIBER_TLS_ROOTCERT_FILE \
-C $CHANNEL_NAME \
--name ${CC_NAME} \
--version ${CC_VERSION} \
--sequence $CC_SEQ \
--init-required \
--signature-policy "$CC_POLICY" \
$PRIVATE_COLLECTION_DEF
else
peer lifecycle chaincode commit -o ${ORDERER_ADDRESS} \
--peerAddresses $PEER0_PROVIDER_ADDRESS \
--peerAddresses $PEER0_SUBSCRIBER_ADDRESS \
-C $CHANNEL_NAME \
--name ${CC_NAME} \
--version ${CC_VERSION} \
--sequence $CC_SEQ \
--init-required \
--signature-policy "$CC_POLICY" \
$PRIVATE_COLLECTION_DEF
fi
使用如下命令检查提交情况,
peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name ${CC_NAME}
链码在被成功提交到通道之前,需要被同意的组织的数量是通过 Channel/Application/LifecycleEndorsement 策略来管理的。默认情况下,这个策略需要通道中大多数的组织来给交易背书。生命周期背书策略不同于链码背书策略。例如,尽管一个链码背书策略只需要一个或两个组织的签名,根据默认策略大多数的通道成员仍然需要批准链码定义。当提交一个通道定义,你需要面向足够多的 peer 组织,以确保你的生命周期背书策略被满足。你可以在 策略概念主题 中了解到更多关于 Fabric 链码生命周期策略。
你也可以设置 Channel/Application/LifecycleEndorsement 策略为一个签名策略并且明确指明通道上可以批准链码定义的组织集合。这允许你创建一个其中大多数组织作为链码管理者并且治理通道业务逻辑的通道。如果你的通道有大量的 Idemix(译者注:身份混合,实现零知识证明)组织,你也可以用一个签名策略(译者注:策略只需要一个签名),因为这些组织不能批准链码定义或者为链码背书并且可能阻碍通道达成大多数成员同意的结果。

Org1 或 Org2 的一个组织管理员提交链码定义到通道。通道上的定义不包含 packageID。
一个组织在不安装链码包的条件下能够批准链码定义。如果一个组织不需要使用链码,他们可以在没有包身份的情况下批准一个链码定义来确保生命周期背书策略被满足。
在链码定义已经提交到通道上后,链码容器会在所有的链码安装到的 peer 节点上启动,来允许通道成员开始使用链码。可能会花费几分钟的时间来启动链码容器。你可以用链码定义来要求调用 Init 方法初始化链码。如果 Init 方法调用是需要的,链码的第一个调用必须是调用 Init 方法。 Init 方法的调用服从于链码的背书策略。

一旦 MYCC 链码在通道上定义,Org1 和 Org2 能开始使用链码。每个 peer 节点上的链码的第一个调用会启动该 peer 上的链码容器。
调用链码的命令:
# bash
链码升级
升级链码的过程与安装和启动链码的 Fabric 生命周期相同。你可以升级链码二进制文件,或仅更新链码策略。请按照以下步骤升级链码:
-
重新打包链码:仅在升级链码二进制文件时才需要完成此步骤。

Org1 和 Org2 升级链码二进制文件并重新打包链码。两家公司使用不同的链码包标签。
-
在 Peer 上再次安装新的链码软件包:如果要升级链码二进制文件,则仅需要完成此步骤。安装新的链码软件包将生成一个程序包标识符 ,你需要将其传递给新的链码定义。你还需要更改链码版本,生命周期流程将使用它来追踪链码二进制文件,判断是否已升级。

Org1 和 Org2 在其 Peer 节点上安装新软件包。安装将创建一个新的链码包标识符。
-
批准新的链码定义:如果要升级链码二进制文件,则需要更新链码定义中的链码版本和程序包标识符。你也可以更新你的链码背书策略,而不必重新打包链码二进制文件。通道成员只需要批准新策略的定义。新定义需要将定义中的序列变量加 1。

Org1 和 Org2 的组织管理员为各自的组织批准新的链码定义。新定义引用了新的链码包标识符并更改了链码版本。由于这是链码的第一次更新,因此序列从 1 递增到 2 。
-
将定义提交给通道:当足够数量的通道成员批准了新的链码定义时,一个组织可以提交新定义以将链码定义升级到通道。作为生命周期过程的一部分,没有单独的升级命令。

来自 Org1 或 Org2 的组织管理员将新的链码定义提交到通道。
提交链码定义后,将使用升级的链码二进制文件中的代码启动新的链码容器。如果在链码定义中请求执行
Init函数,则需要在成功提交新定义后再次调用Init函数来始化升级的链码。如果在不更改链码版本的情况下更新了链码定义,则链码容器将保持不变,并且不需要调用Init函数。
将新定义提交给通道后,每个 Peer 将自动启动新的链码容器。
Fabric 链码生命周期使用链码定义中的 sequence 来跟踪升级。所有通道成员都需要将序列号加 1 ,并批准新的定义以升级链码。版本参数用于追踪链码二进制文件,仅在升级链码二进制文件时才需要更改。
部署场景
以下示例说明了如何使用 Fabric 链码生命周期来管理通道和链码。
加入通道
新的组织可以使用已定义的链码加入通道,在完成安装链码包和批准已经提交给该通道的链码定义两项工作后,可以开始使用链码。

org3 加入通道并批准先前由 org1 和 org2 提交给通道的相同链码定义。
批准链码定义后,新组织可以在将 软件包安装到 Peer 后开始使用链码。该定义不需要再次提交。如果将背书策略设置为要求大多数通道成员进行背书的默认策略,则背书策略将自动更新以囊括新组织。

链码容器将在 Org3 的 Peer 上首次调用链码后启动。
更新背书策略
你可以使用链码定义来更新背书策略,而不必重新打包或重新安装链码。通道成员可以批准带有新背书策略的链码定义,并将其提交给通道。

Org1 , Org2 和 Org3 批准了一项新的背书策略,要求三个组织全部都背书一项交易。它们将定义中的 sequence 从 1 增加到 2 ,但是不需要更新链码版本。
新的背书策略将在将新定义提交给通道后生效。通道成员不必通过调用链码或执行 Init 函数来重新启动链码容器即可更新背书策略。

一个组织将新的链码定义提交给通道以更新背书策略。
批准定义而不安装链码
你可以批准链码定义而不安装链码包。这使你可以在将链码定义提交到通道之前对其进行背书,即使你不想使用该链码对交易进背书或查询账本。你批准的链码定义要与通道的其他成员拥有相同的参数,但不需要将链码包标识符包含在链码定义中。

Org3 不会安装链码软件包。结果,他们不需要提供链码包标识符作为链码定义的一部分。但是, Org3 仍然可以背书已提交给该频道的 MYCC 的定义。
一个组织不同意链码定义
不批准已提交给通道的链码定义的组织不能使用该链码。未批准链码定义或批准其他链码定义的组织将无法在其 Peer 上执行链码。

Org3 批准的链码定义具有与 Org1 和 Org2 不同的背书 策略。结果, Org3 无法在通道上使用 MYCC 链码。但是, Org1 或 Org2 仍然可以获得足够的背书 ,以将定义提交到通道并使用链码。链码中的交易仍将添加到账本中,并存储在 Org3 的 Peer 中。但是, Org3 将无法背书 交易。
组织可以批准具有任何序列号或版本的新链码定义。这使你可以批准已提交给通道的定义并开始使用链码。你也可以批准新的链码定义,以更正在批准或打包链码过程中犯的任何错误。
通道在链码定义上不一致
如果通道上的组织们不同意链码定义,则无法将该定义提交给通道。任何通道成员都将无法使用链码。

Org1 , Org2 和 Org3 都批准不同的链码定义。结果,该通道的任何成员都无法获得足够的背书以将链码定义提交给该通道。任何通道成员都将无法使用链码。
组织安装不同的链码程序包
每个组织在批准链码定义时都可以使用不同的链码包标识符。这允许通道成员安装使用相同背书策略的不同链码二进制文件,并在同一链码命名空间中读取和写入数据。
组织们可以使用此功能来安装拥有特定业务逻辑的智能合约。每个组织的智能合约都可以包含组织在其 Peer 背书交易之前所需的其他验证。每个组织还可以编写代码,以帮助将智能合约与他们现有系统中的数据集成在一起。

Org1 和 Org2 每个都安装拥有特定的业务逻辑版本的 MYCC 链码。
使用一个包创建多个链码
你可以使用一个链码包通过批准和提交多个链码定义,在一个通道上创建多个链码实例。每个定义都需要指定一个不同的链码名称。这使你可以在一个通道上运行智能合约的多个实例,但是要让该合约服从不同的背书策略。

Org1 和 Org2 使用 MYCC_1 链码包来批准和提交两个不同的链码定义。结果,两个 Peer 都有两个在其 Peer 上运行的链码容器。 MYCC1 的背书策略为两者之一背书,而 MYCC2 的背书策略为两者都要背书。
迁移到新的 Fabric 生命周期
有关迁移到新生命周期的信息,请查看升级到 v2.0 的注意事项。
如果你需要更新通道配置以启用新的生命周期,请检查启用新的链码生命周期。
更多信息
你可以观看视频,以了解有关新 Fabric 链码生命周期的动机及其实现方式的更多信息。