第1章 引言
第2章 Hyperledger Fabric v2.0的新增功能
第3章 关键概念
第4章 入门
第5章 开发应用程序
第6章 教程(上)
第6章 教程(下)
第7章 部署生产网络
第8章 操作指南
第9章 升级到最新版本
在本主题中,我们将描述引导排序节点的过程。如果您想了解有关不同排序服务实现及其相对优缺点的更多信息,请查看我们关于排序的概念性文档。
大体上,本主题将涉及几个相互关联的步骤:
orderer.yaml
)注意:本主题假设您已经从docker hub中提取了Hyperledger Fabric orderer镜像。
与节点一样,所有排序节点必须属于必须在创建排序节点自身之前创建的组织。此组织有一个由成员服务提供者(MSP)封装的定义,该定义由专门为组织创建证书和MSP的证书颁发机构(CA)创建。
有关创建CA并使用它创建用户和MSP的信息,请参阅Fabric CA user’s guide(Fabric CA用户指南)。
排序程序的配置是通过名为orderer.yaml
的yaml
文件处理的。FABRIC_CFG_PATH
环境变量用于指向已配置的orderer.yaml
文件,该文件将提取文件系统上的一系列文件和证书。
要查看orderer.yaml
示例,请查看fabric-samples
github repo,在继续之前,应该仔细阅读和研究它。请特别注意以下几个值:
LocalMSPID
-这是排序节点组织的CA生成的MSP的名称。您的排序者组织管理员将在此列出。LocalMSPDir
-本地MSP所在的文件系统中的位置。# TLS enabled
,Enabled: false
。在这里可以指定是否要启用TLS。如果将此值设置为true
,则必须指定相关TLS证书的位置。请注意,对于Raft节点,这是必需的。BootstrapFile
-这是您将为这个排序服务生成的创世区块的名称。BootstrapMethod
-给出引导块的方法。目前,这只能是文件
,其中指定了BootstrapFile
中的文件。如果要将此节点部署为集群的一部分(例如,作为Raft节点集群的一部分),请记下集群
和共识
部分。
如果计划部署基于Kafka的排序服务,则需要完成Kafka
部分。
新形成的通道的第一个区块被称为“创世区块”。如果此创世区块是作为创建新网络的一部分而创建的(换句话说,如果正在创建的排序节点不会加入现有的排序节点集群),则此创世区块将是“排序节点系统通道”(也称为“排序系统通道”)的第一个块,该通道由包括允许创建通道的组织的列表。orderer系统通道的创世区块是特殊的:必须先创建它并将其包含在节点的配置中,然后才能启动节点。
要了解如何使用configtxgen
工具创建创世区块,请查看Channel Configuration(configtx)。
一旦您构建了镜像,创建了MSP,配置了orderer.yaml
,并创建了创世区块,您可以使用类似于以下命令启动排序程序:
docker-compose -f docker-compose-cli.yaml up -d --no-deps orderer.example.com
你的排序者的地址order.example.com
。
该文档旨在提供有关MSPs的设置和最佳实践的详细信息。
成员服务提供商(MSP)是一个Hyperledger Fabric组件,它提供了成员操作的抽象。
特别是,MSP抽象出了颁发证书、验证证书和用户身份验证背后的所有加密机制和协议。MSP可以定义他们自己的身份概念,以及管理这些身份的规则(身份验证)和身份验证(签名生成和验证)。
Hyperledger Fabric区块链网络可由一个或多个MSP管理。这提供了成员操作的模块化,以及不同成员标准和体系结构之间的互操作性。
在本文的其余部分中,我们将详细介绍由Hyperledger Fabric支持的MSPs实现的设置,并讨论有关其使用的最佳实践。
要设置MSP的实例,需要在每个节点和排序节点本地指定其配置(以启用普通节点和排序节点签名),并在通道上为所有通道成员启用普通节点、排序节点、客户端身份验证和各自的签名验证(验证)。
首先,需要为每个MSP指定一个名称,以便在网络中引用该MSP(例如msp1
、org2
和org3.divA
)。这是在通道中引用代表联盟、组织或组织部门的MSP的成员规则的名称。这也称为MSP标识符或MSP ID。每个MSP实例都要求MSP标识符是唯一的。例如,如果在系统通道生成中检测到两个具有相同标识符的MSP实例,排序程序设置将失败。
在MSP的默认实现中,需要指定一组参数来允许身份(证书)验证和签名验证。这些参数由RFC5280推导,包括:
此MSP实例的有效标识需要满足以下条件:
有关当前MSP实现中身份有效性的更多信息,我们建议读者参考MSP身份有效性规则。
除了与验证相关的参数外,要使MSP能够在其实例化的节点上进行签名或身份验证,还需要指定:
需要注意的是,MSP标识永远不会过期;只能通过将其添加到适当的crl中来撤销它们。另外,目前还不支持强制撤销TLS证书。
要生成X.509证书以提供MSP配置,应用程序可以使用Openssl。我们强调,在Hyperledger Fabric中,不支持包括RSA密钥在内的证书。
或者,可以使用cryptogen
工具,其操作在“入门”中进行了说明。
Hyperledger Fabric CA还可用于生成配置MSP所需的密钥和证书。
要设置本地MSP(针对普通节点或排序节点),管理员应创建一个包含六个子文件夹和一个文件的文件夹(例如$MY_PATH/mspconfig
):
adminicert
包含PEM文件,每个文件对应一个管理员证书cacerts
,其中包含对应于根CA证书的PEM文件intermediatecerts
,包含每个对应于中间CA证书的PEM文件config.yaml
配置支持的组织单位和身份分类(请参阅下面的相应部分)。crls
的文件夹CRLkeystore
文件夹,其中包含一个带有节点签名密钥的PEM文件;我们强调当前不支持RSA密钥signcert
文件夹,其中包含一个PEM文件和节点的X.509证书tlscacerts
文件夹,其中包含每个对应于TLS根CA证书的PEM文件tlsintermediatecerts
文件夹,其中包含每个对应于中间TLS CA证书的PEM文件在节点的配置文件中(普通节点为core.yaml
文件,排序节点为orderer.yaml
),需要指定mspconfig文件夹的路径和节点MSP的MSP标识符。mspconfig文件夹的路径应相对于FABRIC_CFG_path
,并作为节点的参数mspConfigPath
的值提供,而LocalMSPDir
则作为排序节点的参数值提供。节点的MSP的标识符作为参数localMspId
(对于普通节点)和localMspId
(对于排序节点)是作为值提供的。这些变量可以通过环境来重写,使用peer的CORE前缀(例如CORE_peer_LOCALMSPID)和排序者的order前缀(例如order_GENERAL_LOCALMSPID)。请注意,对于排序节点设置,需要生成系统通道的创世区块,并将其提供给排序节点。下一个MSP配置将在本节中详细介绍。
“本地”MSP的重新配置只能手动进行,并且需要重新启动普通节点或排序节点进程。在后续版本中,我们的目标是提供在线/动态重新配置(即,不需要通过使用节点管理的系统链码来停止节点)。
为了配置此MSP的有效成员应包含在其X.509证书中的组织单位列表,config.yaml
文件需要指定组织单位(简称OU)标识符。您可以在下面找到一个示例:
OrganizationalUnitIdentifiers:
- Certificate: "cacerts/cacert1.pem"
OrganizationalUnitIdentifier: "commercial"
- Certificate: "cacerts/cacert2.pem"
OrganizationalUnitIdentifier: "administrators"
上面的示例声明了两个组织单元标识符:commercial和administrators。如果MSP标识携带了这些组织单位标识符中的至少一个,则它是有效的。Certificate
字段是指CA或中间CA证书路径,在该路径下,应验证具有该特定OU的身份。路径相对于MSP根文件夹,不能为空。
默认的MSP实现允许组织根据x509证书的ou将身份进一步分类为客户端、管理员、普通节点和排序节点。
为了定义给定MSP的客户机、管理员、普通节点和排序节点,需要适当地设置config.yaml
文件。您可以在下面找到config.yaml
文件的NodeOU部分示例:
NodeOUs:
Enable: true
# For each identity classification that you would like to utilize, specify
# an OU identifier.
# You can optionally configure that the OU identifier must be issued by a specific CA
# or intermediate certificate from your organization. However, it is typical to NOT
# configure a specific Certificate. By not configuring a specific Certificate, you will be
# able to add other CA or intermediate certs later, without having to reissue all credentials.
# For this reason, the sample below comments out the Certificate field.
ClientOUIdentifier:
# Certificate: "cacerts/cacert.pem"
OrganizationalUnitIdentifier: "client"
AdminOUIdentifier:
# Certificate: "cacerts/cacert.pem"
OrganizationalUnitIdentifier: "admin"
PeerOUIdentifier:
# Certificate: "cacerts/cacert.pem"
OrganizationalUnitIdentifier: "peer"
OrdererOUIdentifier:
# Certificate: "cacerts/cacert.pem"
OrganizationalUnitIdentifier: "orderer"
当NodeOUs.Enable
设置为true
时,将启用标识分类。然后,通过设置NodeOUs.ClientOUIdentifier
(NodeOUs.AdminOUIdentifier
,NodeOUs.PeerOUIdentifier
,NodeOUs.OrdererOUIdentifier
)键的属性来定义客户机(admin,peer,order)组织单位标识符:
OrganizationalUnitIdentifier
:x509证书需要包含的OU值,才能将其视为客户端(分别为管理员、普通节点和排序节点)。如果此字段为空,则不应用分类。Certificate
:(可选)将其设置为CA或中间CA证书的路径,在该证书下应验证客户端(普通节点、管理员或排序节点)身份。该字段是相对于MSP根文件夹的。只能指定一个证书。如果不设置此字段,则将在组织的MSP配置中定义的任何CA下验证标识,如果将来需要添加其他CA或中间证书,则可能需要这样做。请注意,如果NodeOUs.ClientOUIdentifier
部分(NodeOUs.AdminOUIdentifier
、NodeOUs.PeerOUIdentifier
、NodeOUs.OrdererOUIdentifier
)丢失,则不应用该分类。如果NodeOUs.Enable
设置为true
且未定义分类键,则假定身份分类被禁用。
身份可以使用组织单位分类为客户端、管理员、普通节点或排序节点。这四种分类是相互排斥的。在将身份分类为客户端或普通节点之前,需要启用1.1通道功能。需要启用1.4.3通道功能,才能将身份分类为管理员或排序服务。
分类允许将身份分类为管理员(并执行管理员操作),而无需将证书存储在MSP的admincerts
文件夹中。相反,admincerts
文件夹可以保持为空,并且可以通过向administrationou注册标识来创建administrators。admincerts
文件夹中的证书仍将向其持有者授予管理员角色,前提是它们拥有客户端或管理OU。
在系统生成时,需要指定网络中出现的所有MSPs的验证参数,并将其包含在系统通道的创世区块中。回想一下,MSP验证参数包括MSP标识符、信任证书的根、中间CA和管理证书,以及OU规范和crl。系统生成块在排序者的设置阶段提供给他们,并允许他们验证通道创建请求。如果系统创世区块包含两个具有相同标识符的MSPs,那么排序节点将拒绝系统创世区块,因此网络的引导将失败。
对于应用程序通道,只有管理通道的MSPs的验证组件需要驻留在通道的创世区块中。我们强调,应用程序有责任在指示一个或多个节点加入通道之前,确保通道的创世区块(或最新的配置块)中包含正确的MSP配置信息。
在使用configtxgen工具引导通道时,可以通过在mspconfig文件夹中包含MSP的验证参数并在中的相关部分中设置该路径来配置通道MSP configtx.yaml
。
通道上的MSP的重新配置,包括与该MSP的ca相关联的证书吊销列表的通知,是通过MSP的一个管理员证书的所有者创建config_update
对象来实现的。然后,由管理员管理的客户端应用程序将向显示此MSP的通道宣布此更新。
在本节中,我们将详细介绍常见场景中MSP配置的最佳实践。
1) 组织/公司和MSP之间的映射
我们建议在组织和MSP之间进行一对一的映射。如果选择不同类型的映射,则需要考虑以下几点:
2) 一个组织有不同的部门(比如说组织单位),它想授予不同通道的访问权。
有两种处理方法:
3) 将客户端与同一组织的节点分离。
在许多情况下,需要从身份本身检索身份的“类型”(例如,可能需要保证背书是由普通节点而不是仅作为排序节点的客户端或节点派生的)。
对这种要求的支持有限。
允许这种分离的一种方法是为每种节点类型创建一个单独的中间CA—一个用于客户端,一个用于普通节点/排序节点;并配置两个不同的MSP—一个用于客户端,一个用于普通节点/排序节点。该组织应该访问的通道将需要包括两个MSP,而背书策略将只利用引用普通节点的MSP。这最终会导致组织被映射到两个MSP实例,并且会对普通节点和客户端的交互方式产生一定的影响。
gossip不会受到剧烈的影响,因为同一组织的所有同侪仍然属于一个MSP。普通节点可以将某些系统链码的执行限制为基于本地MSP的策略。例如,只有当请求由只能是客户端的本地MSP的管理员签名时,普通节点才会执行“joinChannel”请求(最终用户应该坐在请求的原点)。如果我们接受作为普通节点/排序节点MSP的唯一客户机是该MSP的管理员,我们就可以避免这种不一致。
使用这种方法需要考虑的另一点是,普通节点根据其本地MSP中请求发起方的成员身份来授权事件注册请求。显然,由于请求的发起者是客户机,因此请求发起者总是被认为属于与被请求普通节点不同的MSP,并且普通节点将拒绝该请求。
4) 管理员和CA证书。
将MSP管理证书设置为不同于MSP考虑的信任根或中间ca的任何证书,这一点很重要。对现有会员资格证书的签发与现有会员资格认证的共同职责是分开的。
5) 将中间CA列入黑名单。
如前几节所述,MSP的重新配置是通过重新配置机制实现的(本地MSP实例的手动重新配置,以及通道MSP实例的config_update
消息的正确构造)。显然,有两种方法可以确保MSP中考虑的中间CA不再用于该MSP的身份验证:
intermediatecerts
文件夹中删除此CA的证书。在当前的MSP实现中,我们只支持方法(1),因为它更简单,不需要将不再被考虑的中间CA列入黑名单。
6) CAs和TLS CAs
MSP标识的根CA和MSP TLS证书的根CA(以及相对中间CA)需要在不同的文件夹中声明。这是为了避免不同类别证书之间的混淆。不禁止对MSP标识和TLS证书重用相同的ca,但最佳实践建议在生产中避免这种情况。
Fabric节点执行的加密操作可以委托给硬件安全模块(HSM)。HSM保护您的私钥并处理加密操作,允许您的普通界节点和排序节点在不公开其私钥的情况下签署和背书交易。如果您要求遵守政府标准,如FIPS 140-2,有多个认证的HSM可供选择。
Fabric目前利用PKCS11标准与HSM通信。
要在Fabric节点上使用HSM,您需要更新节点配置文件(如core.yaml或orderer.yaml)的bccsp
(加密服务提供程序)部分。在bccsp
部分,您需要选择PKCS11作为提供程序,并输入要使用的PKCS11库的路径。您还需要提供为加密操作创建的令牌的Label
和PIN
。您可以使用一个令牌来生成和存储多个密钥。
预构建的Hyperledger Fabric Docker镜像未启用以使用PKCS11。如果使用docker部署Fabric,则需要构建自己的镜像并使用以下命令启用PKCS11:
make docker GO_TAGS=pkcs11
您还需要通过将PKCS11库安装或安装到容器中来确保节点可以使用PKCS11库。
例子
下面的示例演示如何配置Fabric节点以使用HSM。
首先,您需要安装PKCS11接口的实现。此示例使用softhsm开源实现。下载和配置softhsm后,需要设置SOFTHSM2_CONF环境变量以指向SOFTHSM2配置文件。
然后,您可以使用softhsm创建令牌,该令牌将在HSM插槽内处理Fabric节点的加密操作。在本例中,我们创建了一个标记为“fabric”的令牌,并将pin设置为“71811222”。创建令牌后,更新配置文件以使用PKCS11和您的令牌作为加密服务提供程序。您可以在下面找到bccsp
部分的示例:
#############################################################################
# BCCSP (BlockChain Crypto Service Provider) section is used to select which
# crypto library implementation to use
#############################################################################
bccsp:
default: PKCS11
pkcs11:
Library: /etc/hyperledger/fabric/libsofthsm2.so
Pin: 71811222
Label: fabric
hash: SHA2
security: 256
Immutable: false
默认情况下,当使用HSM生成私钥时,私钥是可变的,这意味着PKCS11私钥属性可以在生成密钥后更改。将Immutable
设置为true
意味着在密钥生成之后不能更改私钥属性。在通过设置Immutable:true
配置不变性之前,请确保HSM支持PKCS11对象副本。
还可以使用环境变量覆盖配置文件的相关字段。如果使用Fabric CA服务器连接到softhsm2,则可以设置以下环境变量或直接在CA服务器配置文件中设置相应的值:
FABRIC_CA_SERVER_BCCSP_DEFAULT=PKCS11
FABRIC_CA_SERVER_BCCSP_PKCS11_LIBRARY=/etc/hyperledger/fabric/libsofthsm2.so
FABRIC_CA_SERVER_BCCSP_PKCS11_PIN=71811222
FABRIC_CA_SERVER_BCCSP_PKCS11_LABEL=fabric
如果使用Fabric节点连接到softhsm2,可以设置以下环境变量或直接在节点配置文件中设置相应的值:
CORE_PEER_BCCSP_DEFAULT=PKCS11
CORE_PEER_BCCSP_PKCS11_LIBRARY=/etc/hyperledger/fabric/libsofthsm2.so
CORE_PEER_BCCSP_PKCS11_PIN=71811222
CORE_PEER_BCCSP_PKCS11_LABEL=fabric
如果使用Fabric排序程序连接到softhsm2,则可以设置以下环境变量或直接在排序程序配置文件中设置相应的值:
ORDERER_GENERAL_BCCSP_DEFAULT=PKCS11
ORDERER_GENERAL_BCCSP_PKCS11_LIBRARY=/etc/hyperledger/fabric/libsofthsm2.so
ORDERER_GENERAL_BCCSP_PKCS11_PIN=71811222
ORDERER_GENERAL_BCCSP_PKCS11_LABEL=fabric
如果使用docker-compose部署节点,则在构建自己的镜像后,可以更新docker compose文件,以使用卷将softhsm库和配置文件装入容器中。例如,您可以将以下环境和卷变量添加到docker compose文件中:
environment:
- SOFTHSM2_CONF=/etc/hyperledger/fabric/config.file
volumes:
- /home/softhsm/config.file:/etc/hyperledger/fabric/config.file
- /usr/local/Cellar/softhsm/2.1.0/lib/softhsm/libsofthsm2.so:/etc/hyperledger/fabric/libsofthsm2.so
如果使用HSM部署Fabric节点,则需要在HSM中生成并存储私钥,而不是将其存储在节点的本地MSP文件夹的keystore
文件夹中。MSP的keystore
文件夹将保持为空。相反,Fabric节点将使用signcerts
文件夹中签名证书的主题密钥标识符从HSM内部检索私钥。创建节点MSP文件夹的过程会有所不同,具体取决于您是否正在使用自己的Fabric证书颁发机构(CA)。
开始之前
在将Fabric节点配置为使用HSM之前,应完成以下步骤:
Label
和PIN
。使用带有Fabric CA的HSM
通过对CA服务器配置文件进行与对普通节点或排序节点所做的相同的编辑,可以将结构CA设置为使用HSM。因为您可以使用Fabric CA在HSM中生成密钥,所以创建本地MSP文件夹的过程非常简单。使用以下步骤:
Label
和PIN
。当Fabric CA服务器启动时,生成私钥并将其存储在HSM中。如果您不关心公开CA签名证书,则可以跳过此步骤,只为普通节点或排序节点配置HSM,如下面的步骤所述。bccsp
部分,或使用关联的环境变量指向普通节点或排序节点的HSM配置。在Fabric CA客户端配置文件中,将默认SW
配置替换为PKCS11
配置,并提供您自己的HSM的值:bccsp:
default: PKCS11
pkcs11:
Library: /etc/hyperledger/fabric/libsofthsm2.so
Pin: 71811222
Label: fabric
hash: SHA2
security: 256
Immutable: false
然后,对于每个节点,使用Fabric CA客户端根据您在步骤2中注册的节点标识注册,生成普通节点或排序节点的MSP文件夹。enroll命令使用节点的HSM生成并存储普通节点或排序节点的私钥,而不是将私钥存储在关联MSP的keystore
文件夹中。keystore
文件夹保持为空。
要配置普通节点或排序节点以使用HSM,同样地,请更新peer或order配置文件的bccsp
部分以使用PKCS11并提供Label
和PIN
。另外,编辑mspConfigPath
(对于普通节点)或LocalMSPDir
(对于排序节点)的值,以指向在上一步中使用Fabric CA客户端生成的MSP文件夹。现在普通节点或排序节点已配置为使用HSM,当您启动该节点时,它将能够使用HSM保护的私钥对事务进行签名和背书。
将HSM与您自己的CA一起使用
如果您使用自己的证书颁发机构来部署结构组件,则可以使用以下步骤使用HSM:
Label
和PIN
。然后使用CA为每个节点生成私钥和签名证书,私钥在HSM中生成。signcerts
文件夹中。您可以将keystore
文件夹保留为空。bccsp
部分更新为使用PKCS11和并提供Label
和PIN
。编辑mspConfigPath
(对于普通节点)或LocalMSPDir
(对于排序节点)的值,以指向在上一步中使用Fabric CA客户端生成的MSP文件夹。现在普通节点或排序节点已配置为使用HSM,当您启动该节点时,它将能够使用HSM保护的私钥对交易进行签名和背书。Hyperledger Fabric区块链网络的共享配置存储在集合配置交易中,每个通道一个。每个配置交易通常被简称为configtx。
通道配置具有以下重要属性:
配置作为HeaderType_CONFIG
类型的交易存储在没有其他交易的块中。这些块体被称为配置块体,其中第一块体称为创世区块。
配置的原型结构存储在fabric-protos/common/configtx.proto
中。HeaderType_CONFIG
类型的信封将ConfigEnvelope
消息编码为Payload
data
字段。ConfigEnvelope
的proto定义如下:
message ConfigEnvelope {
Config config = 1;
Envelope last_update = 2;
}
last_update
字段在下面的Updates to configuration部分中定义,但仅在验证配置时才需要,而不是读取配置。相反,当前提交的配置存储在config
字段中,其中包含config
消息。
message Config {
uint64 sequence = 1;
ConfigGroup channel_group = 2;
}
对于每个提交的配置,sequence
号递增一。channel_group
字段是包含配置的根组。ConfigGroup
结构是递归定义的,并构建一个组树,每个组都包含值和策略。定义如下:
message ConfigGroup {
uint64 version = 1;
map<string,ConfigGroup> groups = 2;
map<string,ConfigValue> values = 3;
map<string,ConfigPolicy> policies = 4;
string mod_policy = 5;
}
因为configroup
是递归结构,所以它有层次结构。为了清楚起见,下面的例子用golang符号表示。
// Assume the following groups are defined
var root, child1, child2, grandChild1, grandChild2, grandChild3 *ConfigGroup
// Set the following values
root.Groups["child1"] = child1
root.Groups["child2"] = child2
child1.Groups["grandChild1"] = grandChild1
child2.Groups["grandChild2"] = grandChild2
child2.Groups["grandChild3"] = grandChild3
// The resulting config structure of groups looks like:
// root:
// child1:
// grandChild1
// child2:
// grandChild2
// grandChild3
每个组在配置层次结构中定义一个级别,每个组都有一组关联的值(按字符串键索引)和策略(也按字符串键索引)。
值定义如下:
message ConfigValue {
uint64 version = 1;
bytes value = 2;
string mod_policy = 3;
}
政策定义如下:
message ConfigPolicy {
uint64 version = 1;
Policy policy = 2;
string mod_policy = 3;
}
请注意,值、策略和组都有一个版本
和一个mod_policy
。每次修改元素时,该元素的版本
都会递增。mod_policy
用于管理修改该元素所需的签名。对于组,修改是在值、策略或组映射中添加或删除元素(或更改mod_policy
)。对于值和策略,修改将分别更改值和策略字段(或更改mod_policy
)。在当前配置级别的上下文中计算每个元素的mod_policy
。考虑下面在Channel.Groups["Application"]
定义的mod policies示例(这里,我们使用golang映射引用语法,因此Channel.Groups["Application"].Policies["policy1"]
引用基本通道
组的应用程序
组的策略
映射的policy1
策略)
policy1
映射到Channel.Groups["Application"].Policies["policy1"]
Org1/policy2
映射到Channel.Groups["Application"].Groups["Org1"].Policies["policy2"]
/Channel/policy3
映射到Channel.Policies["policy3"]
请注意,如果mod_policy
引用了不存在的策略,则无法修改该项。
配置更新作为HeaderType_CONFIG_UPDATE
类型的Envelope
消息提交。交易的Payload
data
是封送ConfigUpdateEnvelope
。ConfigUpdateEnvelope
定义如下:
message ConfigUpdateEnvelope {
bytes config_update = 1;
repeated ConfigSignature signatures = 2;
}
signatures
字段包含授权配置更新的签名集。它的消息定义是:
message ConfigSignature {
bytes signature_header = 1;
bytes signature = 2;
}
signature_header
是为标准交易定义的,而签名是在ConfigUpdateEnvelope
消息的signature_header
字节和config_update
字节的级联之上的。
ConfigUpdateEnvelope
config_update
字节是封送的ConfigUpdate
消息,其定义如下:
message ConfigUpdate {
string channel_id = 1;
ConfigGroup read_set = 2;
ConfigGroup write_set = 3;
}
channel_id
是更新绑定到的通道ID,这对于确定支持此重新配置的签名范围是必要的。
read_set
指定现有配置的一个子集,稀疏地指定,其中只设置version
字段,而不必填充其他字段。决不应在read_set
中设置特定的ConfigValue
value
或ConfigPolicy策略字段。ConfigGroup
可以填充其映射字段的一个子集,以便引用配置树中更深层的元素。例如,要将应用程序组包括在read_set
中,其父级(通道
组)也必须包含在读取集中,但是通道组不需要填充所有键,例如Orderer
group
键,或任何值
或策略
键。
write_set
指定要修改的配置片段。由于配置的层次性,对层次结构中深层元素的写入操作也必须在其write_set
包含更高级别的元素。但是,对于同一版本的read_set
中指定的write_set
中的任何元素,应该像read_set
中一样稀疏地指定该元素。
例如,给定配置:
Channel: (version 0)
Orderer (version 0)
Application (version 3)
Org1 (version 2)
要提交修改Org1
的配置更新,read_set
将是:
Channel: (version 0)
Application: (version 3)
而write_set
将是
Channel: (version 0)
Application: (version 3)
Org1 (version 3)
当收到CONFIG_UPDATE
时,排序程序通过执行以下操作来计算结果配置
:
通道id
和read_set
。read_set
中的所有元素必须存在于给定的版本中。write_set
中所有不在read_set
中同一版本的元素来计算更新集。ConfigUpdateDevelope
的签名集是否满足更新集中每个元素的mod_policy
。ConfigEnvelope
,该信封包括作为last_UPDATE
字段的config_UPDATE
和在config
字段中编码的新配置,以及递增的序列值。ConfigEnvelope
写入CONFIG
类型的Envelope
,并最终将其作为新配置块中的唯一交易写入。当普通节点(或任何其他要传递的接收方)收到此配置块时,它应该通过将last_update
消息应用于当前配置并验证排序计算的config
字段是否包含正确的新配置来验证配置是否正确。
任何有效配置都是以下配置的子集。这里我们使用peer.
来定义一个ConfigValue
,它的值字段是fabric-protos/peer/configuration.proto
中定义的名为
的封送原型消息。common.
、msp.
和orderer.
的符号类似,但它们的消息分别在fabric-protos/common/configuration.proto
、fabric-protos/msp/mspconfig.proto
和fabric-protos/orderer/configuration.proto
中定义。
注意,键{org{uname}}
和{ {consortium}}
表示任意名称,并表示可以用不同名称重复的元素。
&ConfigGroup{
Groups: map<string, *ConfigGroup> {
"Application":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
"AnchorPeers":peer.AnchorPeers,
},
},
},
},
"Orderer":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
},
},
},
Values:map<string, *ConfigValue> {
"ConsensusType":orderer.ConsensusType,
"BatchSize":orderer.BatchSize,
"BatchTimeout":orderer.BatchTimeout,
"KafkaBrokers":orderer.KafkaBrokers,
},
},
"Consortiums":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
consortium_name}}:&ConfigGroup{
Groups:map<string, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
},
},
},
Values:map<string, *ConfigValue> {
"ChannelCreationPolicy":common.Policy,
}
},
},
},
},
Values: map<string, *ConfigValue> {
"HashingAlgorithm":common.HashingAlgorithm,
"BlockHashingDataStructure":common.BlockDataHashingStructure,
"Consortium":common.Consortium,
"OrdererAddresses":common.OrdererAddresses,
},
}
排序系统通道需要定义排序参数,以及创建通道的联盟。排序服务必须只有一个排序系统通道,并且它是要创建的第一个通道(或更准确地说是引导通道)。建议不要在排序系统通道生成配置中定义应用程序部分,但可以在测试时进行。请注意,任何对排序系统通道具有读取权限的成员都可能看到所有的通道创建,因此应该限制此通道的访问。
排序参数定义为配置的以下子集:
&ConfigGroup{
Groups: map<string, *ConfigGroup> {
"Orderer":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
},
},
},
Values:map<string, *ConfigValue> {
"ConsensusType":orderer.ConsensusType,
"BatchSize":orderer.BatchSize,
"BatchTimeout":orderer.BatchTimeout,
"KafkaBrokers":orderer.KafkaBrokers,
},
},
},
参与排序的每个组织在Orderer
组下都有一个group元素。此组定义一个包含该组织的加密标识信息的单个参数MSP
。orderer
组的值
决定了排序节点的工作方式。每个通道都有,所以orderer.BatchTimeout
例如,在一个通道上的指定可能不同于另一个通道。
在启动时,排序程序面临一个文件系统,其中包含许多通道的信息。排序方通过使用定义的联盟组标识通道来标识系统通道。联盟集团有以下结构。
&ConfigGroup{
Groups: map<string, *ConfigGroup> {
"Consortiums":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
consortium_name}}:&ConfigGroup{
Groups:map<string, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
},
},
},
Values:map<string, *ConfigValue> {
"ChannelCreationPolicy":common.Policy,
}
},
},
},
},
},
注意,每个联盟定义了一组成员,就像排序组织的组织成员一样。每个联盟还定义了一个ChannelCreationPolicy
。这是一个用于授权通道创建请求的策略。通常,此值将设置为ImplicitMetaPolicy
,要求通道的新成员签名以授权通道创建。有关通道创建的更多详细信息,请参阅本文档后面的部分。
应用程序配置用于为应用程序类型交易设计的通道。定义如下:
&ConfigGroup{
Groups: map<string, *ConfigGroup> {
"Application":&ConfigGroup{
Groups:map<String, *ConfigGroup> {
{
{
org_name}}:&ConfigGroup{
Values:map<string, *ConfigValue>{
"MSP":msp.MSPConfig,
"AnchorPeers":peer.AnchorPeers,
},
},
},
},
},
}
与Orderer
部分一样,每个组织都被编码为一个组。然而,不是只编码MSP
身份信息,每个组织都额外编码一个AnchorPeers
列表。这个列表允许不同组织的节点互相联系以建立节点gossip网络。
应用程序通道对排序者组织和一致性选项的副本进行编码,以允许对这些参数进行确定性更新,因此包含来自排序者系统通道配置的相同排序者部分。然而,从应用程序的角度来看,这在很大程度上被忽略了。
当排序节点收到不存在的通道的CONFIG_UPDATE
时,排序节点假定这必须是一个通道创建请求,并执行以下操作。
联盟
价值来实现这一点。应用
程序组中包含的组织是否是相应联盟中包含的组织的子集,并且ApplicationGroup
是否设置为版本1
。排序节点
组,并使用新指定的成员创建一个应用程序
组,并将其mod_policy
指定为在联盟配置中指定的ChannelCreationPolicy
来创建模板配置。请注意,策略是在新配置的环境中评估的,因此需要所有成员的策略将需要来自所有新通道成员的签名,而不是来自联盟的所有成员。CONFIG_UPDATE
应用于此模板配置的更新。因为CONFIG_UPDATE
将修改应用于应用程序组(其版本为1),因此配置代码将根据ChannelCreationPolicy
验证这些更新。如果通道创建包含任何其他修改,例如对单个组织的锚节点的修改,则将调用元素的相应mod策略。配置
的新配置交易被包装并发送到排序系统通道上进行排序。排序后,将创建通道。每个链码都有一个背书策略,指定通道上必须执行链码并背书执行结果的节点集,以便将交易视为有效。这些背书策略定义了必须“背书”(即批准)提案执行的组织(通过其节点)。
注意:回想一下,由键值对表示的状态与区块链数据是分开的。要了解更多信息,请查看我们的账本文档。
作为节点执行的交易验证步骤的一部分,每个验证节点都会进行检查,以确保交易包含适当数量的背书以及它们来自预期的来源(这两个都在背书策略中指定)。背书也要检查以确保它们是有效的(即,它们是来自有效证书的有效签名)。
默认情况下,背书策略是在链码定义中指定的,该定义由通道成员同意,然后提交给通道(即,一个背书策略覆盖与链码关联的所有状态)。
对于私有数据集合,您还可以在私有数据收集级别指定一个背书策略,该策略将覆盖私有数据集合中任何键的链码级背书策略,从而进一步限制哪些组织可以写入私有数据集合。
最后,在某些情况下,特定公共通道状态或私有数据收集状态(换句话说,特定的密钥-值对)可能需要具有不同的背书策略。此基于状态的背书允许链码级或集合级背书策略被指定键的不同策略覆盖。
为了说明可能使用各种类型的背书策略的情况,请考虑一个交换汽车的通道。“创造”(也称为“发行”)汽车作为可交易的资产(换言之,将代表它的键值对放入世界状态)必须满足链码级别的背书策略。要了解如何设置链码级别的背书策略,请查看下面的部分。
如果表示汽车的密钥需要特定的背书策略,则可以在创建汽车时或之后定义该密钥。有许多原因可以解释为什么有必要或更可取地制定一个州特定的背书策略。汽车可能具有历史重要性或价值,因此有必要获得持牌评估师的背书。另外,汽车的所有者(如果他们是通道的成员)也可能希望确保他们的节点签署了一项交易。在这两种情况下,特定资产都需要背书策略,该特定资产不同于与该链码关联的其他资产的默认背书策略。
我们将在后面的小节中向您展示如何定义基于状态的背书策略。但首先,让我们看看如何设置链码级别的背书策略。
通道成员在批准其组织的链码定义时,会同意链码级别的背书策略。在将定义提交给通道之前,需要有足够数量的通道成员批准链码定义以满足Channel/Application/LifecycleEndorsement
策略(默认情况下,策略设置为大多数通道成员)。一旦提交了定义,就可以使用链码了。任何将数据写入账本的链码调用都需要由足够的通道成员进行验证,以满足背书策略。
可以使用Fabric sdk为chainocode指定背书策略。例如,请访问node.js SDK文档中的How to install and start your chaincode。当您使用--signature-policy
标志批准并提交带有Fabric节点二进制文件的链码定义时,还可以从CLI创建背书策略。
注意:现在不要担心策略语法(Org1.member
等等)。我们将在下一节详细讨论语法。
例如:
peer lifecycle chaincode approveformyorg --channelID mychannel --signature-policy "AND('Org1.member', 'Org2.member')" --name mycc --version 1.0 --package-id mycc_1:3a8c52d70c36313cfebbaf09d8616e7a6318ababa01c7cbe40603c373bcfe173 --sequence 1 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --waitForEvent
上面的命令用策略AND('Org1.member','Org2.member')
批准mycc
的链码定义,这将要求Org1和Org2的一个成员对交易进行签名。在足够数量的通道成员批准mycc
的链码定义后,可以使用以下命令将定义和背书策略提交给通道:
peer lifecycle chaincode commit -o orderer.example.com:7050 --channelID mychannel --signature-policy "AND('Org1.member', 'Org2.member')" --name mycc --version 1.0 --sequence 1 --init-required --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --waitForEvent --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
请注意,如果启用了身份分类(请参阅成员服务提供商(MSP)),则可以使用节点角色将背书限制为仅限于节点
。
例如:
peer lifecycle chaincode approveformyorg --channelID mychannel --signature-policy "AND('Org1.peer', 'Org2.peer')" --name mycc --version 1.0 --package-id mycc_1:3a8c52d70c36313cfebbaf09d8616e7a6318ababa01c7cbe40603c373bcfe173 --sequence 1 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --waitForEvent
除了从CLI或SDK指定背书策略外,链码还可以将通道配置中的策略用作背书策略。您可以使用–channel-config-policy
标志选择通道配置和ACL使用的格式的通道策略。
例如:
peer lifecycle chaincode approveformyorg --channelID mychannel --channel-config-policy Channel/Application/Admins --name mycc --version 1.0 --package-id mycc_1:3a8c52d70c36313cfebbaf09d8616e7a6318ababa01c7cbe40603c373bcfe173 --sequence 1 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --waitForEvent
如果不指定策略,链码定义将默认使用Channel/Application/Endorsement
策略,这要求交易由大多数通道成员验证。此策略取决于通道的成员身份,因此在向通道添加或删除组织时,它将自动更新。使用通道策略的一个优点是,可以编写这些策略以随通道成员身份自动更新。
如果使用--signature-policy
标志或SDK指定背书策略,则需要在组织加入或退出通道时更新该策略。定义链码后添加到通道的新组织将能够查询链码(前提是查询具有通道策略定义的适当授权以及链码强制执行的任何应用程序级检查),但不能执行或背书链码。只有在背书策略语法中列出的组织才能签署交易。
背书策略语法
正如您在上面看到的,策略是用主体来表示的(“主体”是与角色匹配的标识)。主体被描述为'MSP.ROLE'
,其中MSP
表示所需的MSP ID,ROLE
表示四个可接受的角色之一:member
、admin
、client
和peer
。
以下是一些有效主体的示例:
'Org0.admin'
:Org0 MSP的任何管理员'Org1.member'
:Org1 MSP的任何成员'Org1.client'
:Org1 MSP的任何客户端'Org1.peer'
:Org1 MSP的任何节点语言的语法是:
EXPR(E[, E...])
其中EXPR
是AND
,OR
,或OutOf
,E
是主体(使用上面描述的语法)或对EXPR
的另一个嵌套调用。
例如:
AND('Org1.member','Org2.member','Org3.member')
请求三个主体中的每个主体一个签名。OR('Org1.member','Org2.member')
请求两个主体中的一个签名。OR('Org1.member','AND('Org2.member','Org3.member'))
请求Org1
MSP成员的一个签名,或Org2
MSP成员的一个签名和Org3
MSP的成员的一个签名。OutOf(1,'Org1.member','Org2.member')
,其解析结果与OR('Org1.member','Org2.member')
相同。OutOf(2,'Org1.member','Org2.member')
相当于AND('Org1.member', 'Org2.member')
,OutOf(2, 'Org1.member', 'Org2.member', 'Org3.member')
相当于OR(AND('Org1.member', 'Org2.member'), AND('Org1.member', 'Org3.member'), AND('Org2.member', 'Org3.member'))
。与链码级别的背书策略类似,在批准和提交链码定义时,还可以指定链码的私有数据集合和相应的集合级别背书策略。如果设置了集合级别背书策略,则写入私有数据收集密钥的交易将要求指定的组织节点已对该交易进行了背书。
可以使用集合级别的背书策略来限制哪些组织节点可以写入私有数据集合密钥命名空间,例如,以确保未经授权的组织无法写入集合,并且有信心在私人数据收集中的任何状态都得到了所需收集组织的背书。
集合级别的背书策略可能比链码级别的背书策略和集合的私有数据分发策略限制性更小或更严格。例如,大多数组织可能被要求背书一个链码交易,但是一个特定的组织可能被要求背书一个在特定集合中包含一个键的交易。
集合级背书策略的语法与链码级背书策略的语法完全匹配-在集合配置中,可以使用signaturePolicy
或channelConfigPolicy
指定endorsementPolicy
。有关详细信息,请参阅私有数据。
设置常规链码级别或集合级别背书策略与相应链码的生命周期相关联。它们只能在定义通道上的链码时设置或修改。
相反,可以在链码内以更细粒度的方式设置和修改密钥级别的背书策略。修改是常规交易的读写集的一部分。
shim API提供以下函数来设置和检索常规密钥的背书策略。
注意:下面的ep
代表“背书策略”,可以使用上面描述的相同语法或使用下面描述的便利函数来表示。任何一种方法都将生成可由基本填充程序API使用的背书策略的二进制版本。
SetStateValidationParameter(key string, ep []byte) error
GetStateValidationParameter(key string) ([]byte, error)
对于属于集合中私有数据一部分的键,以下函数适用:
SetPrivateDataValidationParameter(collection, key string, ep []byte) error
GetPrivateDataValidationParameter(collection, key string) ([]byte, error)
为了帮助设置背书策略并将其封送到验证参数字节数组中,Go shim提供了一个扩展,该功能允许链码开发人员根据组织的MSP标识符来处理背书策略,请参阅KeyEndorsementPolicy:
type KeyEndorsementPolicy interface {
// Policy returns the endorsement policy as bytes
Policy() ([]byte, error)
// AddOrgs adds the specified orgs to the list of orgs that are required
// to endorse
AddOrgs(roleType RoleType, organizations ...string) error
// DelOrgs delete the specified channel orgs from the existing key-level endorsement
// policy for this KVS key. If any org is not present, an error will be returned.
DelOrgs(organizations ...string) error
// ListOrgs returns an array of channel orgs that are required to endorse changes
ListOrgs() ([]string)
}
例如,要为需要两个特定组织对密钥更改进行背书的密钥设置背书策略,请将两个org MSPIDs
都传递给AddOrgs()
,然后调用policy()
构造可传递给SetStateValidationParameter()
的背书策略字节数组。
要将填充程序扩展作为依赖项添加到链码中,请参见管理在Go中编写的链码的外部依赖项。
在提交时,设置密钥的值与设置密钥的背书策略没有什么不同-都会更新密钥的状态,并根据相同的规则进行验证。
验证 | 没有验证参数集 | 验证参数集 |
---|---|---|
修改值 | 检查链码或集合ep | 检查密钥级别ep |
修改密钥级别ep | 检查链码或集合ep | 检查密钥级别ep |
如上所述,如果修改了密钥,并且不存在密钥级别的背书策略,那么缺省情况下将应用链码级别或集合级别的背书策略。第一次为密钥设置密钥级别背书策略时也是如此—新的密钥级别背书策略必须首先根据预先存在的链码级别或集合级别背书策略进行批注。
如果修改了密钥并且存在密钥级别背书策略,则密钥级别背书策略将覆盖链码级别或集合级别背书策略。在实践中,这意味着密钥级别的背书策略可以比链码级别或集合级别的背书策略限制更少或更严格。由于必须满足链码级别或集合级别的背书策略才能首次设置密钥级别的背书策略,因此没有违反任何信任假设。
如果删除了密钥的背书策略(设置为nil),那么链码级别或集合级别的背书策略将再次成为默认值。
如果一个交易使用不同的关联密钥级别背书策略修改多个密钥,则需要满足所有这些策略,以便交易有效。
在提交时验证交易时,节点在应用交易本身附带的状态更改之前执行各种检查:
有些用例要求自定义交易验证规则与默认结构验证规则不同,例如:
UTXO(Unspent Transaction Output未用交易输出)
:当验证考虑到经验是否不将其输入加倍时。匿名交易
:当背书不包含节点的身份,但签名和公钥是共享的,不能链接到节点的身份。Fabric允许以可插拔的方式将定制的背书和验证逻辑实现和部署到与链码处理相关联的节点中。这个逻辑可以被编译成peer中内置的可选逻辑,也可以作为Golang插件与peer一起编译和部署。
默认情况下,链码将使用内置的背书和验证逻辑。但是,用户可以选择自定义背书和验证插件作为链码定义的一部分。管理员可以通过定制节点的本地配置来扩展节点可用的背书/验证逻辑。
每个节点都有一个本地配置(core.yaml
)声明背书/验证逻辑名称和要运行的实现之间的映射。
默认逻辑称为ESCC
(“E”代表背书)和VSCC
(验证),它们可以在处理程序
部分的节点本地配置中找到:
handlers:
endorsers:
escc:
name: DefaultEndorsement
validators:
vscc:
name: DefaultValidation
当背书或验证实现编译到普通节点中时,name属性表示要运行的初始化函数,以获取创建背书/验证逻辑实例的工厂。
该函数是core/handlers/library/library.go
下HandlerLibrary
构造的一个实例方法,为了添加自定义背书或验证逻辑,该构造需要使用任何其他方法进行扩展。
由于这很麻烦,并且带来了部署挑战,因此还可以通过在名为library
的名称下添加另一个属性来将自定义背书和验证部署为Golang插件。
例如,如果我们有自定义的背书和验证逻辑,它被实现为一个插件,那么在core.yaml
中的配置中将有以下条目:
handlers:
endorsers:
escc:
name: DefaultEndorsement
custom:
name: customEndorsement
library: /etc/hyperledger/fabric/plugins/customEndorsement.so
validators:
vscc:
name: DefaultValidation
custom:
name: customValidation
library: /etc/hyperledger/fabric/plugins/customValidation.so
我们必须把.so
插件文件放在节点的本地文件系统中。
自定义插件的名称需要由chaincode定义引用才能由chaincode使用。如果使用节点CLI批准链代码定义,请使用--escc
和--vscc
标志选择自定义背书或验证库的名称。如果使用的是Fabric SDK node.js,请访问如何安装和启动链码。有关详细信息,请参见Fabric链码生命周期。
此后,自定义背书或验证逻辑实现将被称为“插件”,即使它们被编译到节点中。
要实现背书插件
,必须实现core/handlers/endorsement/api/endorsement.go
中的插件接口:
// Plugin endorses a proposal response
type Plugin interface {
// Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it.
// Returns:
// The Endorsement: A signature over the payload, and an identity that is used to verify the signature
// The payload that was given as input (could be modified within this function)
// Or error on failure
Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error)
// Init injects dependencies into the instance of the Plugin
Init(dependencies ...Dependency) error
}
通过让节点调用PluginFactory
接口中的New
方法,为每个通道创建一个给定插件类型的背书插件实例(通过方法名标识为HandlerLibrary
的实例方法或由plugin.so
文件路径标识),该实例也将由插件开发人员实现:
// PluginFactory creates a new instance of a Plugin
type PluginFactory interface {
New() Plugin
}
Init
方法将接收在core/handlers/endorsement/api/
下声明的所有依赖项,标识为嵌入Dependency
接口。
在创建Plugin
实例之后,节点使用作为参数传递的依赖项
调用Init
方法。
目前,Fabric为背书插件提供了以下依赖项:
SigningIdentityFetcher
:根据给定的签名建议返回SigningIdentity
的实例:// SigningIdentity signs messages and serializes its public identity to bytes
type SigningIdentity interface {
// Serialize returns a byte representation of this identity which is used to verify
// messages signed by this SigningIdentity
Serialize() ([]byte, error)
// Sign signs the given payload and returns a signature
Sign([]byte) ([]byte, error)
}
StateFetcher
:获取与世界状态交互的状态对象:// State defines interaction with the world state
type State interface {
// GetPrivateDataMultipleKeys gets the values for the multiple private data items in a single call
GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error)
// GetStateMultipleKeys gets the values for multiple keys in a single call
GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error)
// GetTransientByTXID gets the values private data associated with the given txID
GetTransientByTXID(txID string) ([]*rwset.TxPvtReadWriteSet, error)
// Done releases resources occupied by the State
Done()
}
要实现验证插件,必须实现core/handlers/validation/api/validation.go
中的插件
接口:
// Plugin validates transactions
type Plugin interface {
// Validate returns nil if the action at the given position inside the transaction
// at the given position in the given block is valid, or an error if not.
Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error
// Init injects dependencies into the instance of the Plugin
Init(dependencies ...Dependency) error
}
每个ContextDatum
都是额外的运行时派生的元数据,由节点传递给验证插件。目前,传递的唯一ContextDatum
是表示链码的背书策略的ContextDatum:
// SerializedPolicy defines a serialized policy
type SerializedPolicy interface {
validation.ContextDatum
// Bytes returns the bytes of the SerializedPolicy
Bytes() []byte
}
为每个通道创建一个给定插件类型的验证插件实例(通过方法名标识为HandlerLibrary
的实例方法或由plugin.so
文件路径标识),方法是让节点在PluginFactory
接口中调用New
方法,该接口也预期由插件开发人员实现:
// PluginFactory creates a new instance of a Plugin
type PluginFactory interface {
New() Plugin
}
Init
方法将接收core/handlers/validation/api/
下声明的所有依赖项作为输入,这些依赖项标识为嵌入依赖项
接口。
在创建插件
实例之后,节点使用作为参数传递的依赖项调用Init方法。
目前,Fabric为验证插件提供了以下依赖项:
IdentityDeserializer
:将标识的字节表示形式转换为身份
对象,这些对象可用于验证由其签名的签名,根据其对应的MSP进行自身验证,并查看它们是否满足给定的MSP主体。完整的规范可以在core/handlers/validation/api/identities/identities.go
中找到。PolicyEvaluator
:评估给定策略是否满足:// PolicyEvaluator evaluates policies
type PolicyEvaluator interface {
validation.Dependency
// Evaluate takes a set of SignedData and evaluates whether this set of signatures satisfies
// the policy with the given bytes
Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error
}
StateFetcher
:获取与世界状态
交互的状态对象:// State defines interaction with the world state
type State interface {
// GetStateMultipleKeys gets the values for multiple keys in a single call
GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error)
// GetStateRangeScanIterator returns an iterator that contains all the key-values between given key ranges.
// startKey is included in the results and endKey is excluded. An empty startKey refers to the first available key
// and an empty endKey refers to the last available key. For scanning all the keys, both the startKey and the endKey
// can be supplied as empty strings. However, a full scan should be used judiciously for performance reasons.
// The returned ResultsIterator contains results of type *KV which is defined in fabric-protos/ledger/queryresult.
GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error)
// GetStateMetadata returns the metadata for given namespace and key
GetStateMetadata(namespace, key string) (map[string][]byte, error)
// GetPrivateDataMetadata gets the metadata of a private data item identified by a tuple
GetPrivateDataMetadata(namespace, collection, key string) (map[string][]byte, error)
// Done releases resources occupied by the State
Done()
}
core/handlers/validation/api/validation.go
中定义的ExecutionFailureError类型的错误。返回的任何其他错误都将被视为背书策略错误,并通过验证逻辑将交易标记为无效。但是,如果返回ExecutionFailureError
,链处理将停止,而不是将交易标记为无效。这是为了防止不同节点之间的状态差异。StateFetcher
接口检索私有数据的元数据,则必须按如下方式处理错误:CollConfigNotDefinedError'' and ``InvalidCollNameError'', signalling that the specified collection does not exist, should be handled as deterministic errors and should not lead the plugin to return an ``ExecutionFailureError
。注意:本主题讨论通道管理级别的访问控制和策略。要了解链代码中的访问控制,请查看我们的chaincode for developers教程。
Fabric使用访问控制列表(acl)来管理对资源的访问,方法是将一个策略与资源关联起来,该策略指定一个对给定一组标识求值为true或false的规则。结构包含许多默认ACL。在本文档中,我们将讨论它们的格式以及如何覆盖默认值。
但是,在我们能够做到这一点之前,有必要对资源和政策有一点了解。
资源
用户通过针对在后台调用的用户链码、事件流源或系统链码与Fabric进行交互。因此,这些端点被视为“资源”,应该对其执行访问控制。
应用程序开发人员需要了解这些资源以及与它们相关联的默认策略。这些资源的完整列表可在中找到configtx.yaml
。 你可以看看样品文件configtx.yaml
。
configtx.yaml
中命名的资源是Fabric当前定义的所有内部资源的详尽列表。在那里采用的松散公约是
。因此cscc/GetConfigBlock
是CSCC
组件中GetConfigBlock
调用的资源。
策略
与请求关联的基本身份(或策略)被检查,因为它们与请求关联。背书政策用于确定交易是否已得到适当背书。在通道配置中定义的策略被引用为修改策略以及访问控制,并在通道配置本身中定义。
策略可以用以下两种方式之一构造:作为签名策略或隐式元策略。
Signature
策略
这些策略标识必须签名才能满足策略的特定用户。例如:
Policies:
MyPolicy:
Type: Signature
Rule: “Org1.Peer OR Org2.Peer”
这个策略结构可以解释为:名为MyPolicy
的策略只能通过一个角色为“来自Org1的节点”或“来自Org2的节点”的身份签名来满足。
签名策略支持AND
、OR
和NOutOf
的任意组合,允许构建非常强大的规则,例如:“Orga A的一个管理员和另外两个管理员,或者20个组织管理员中的11个”。
ImplicitMeta
策略
ImplicitMeta
策略聚合配置层次结构中更深层次的策略的结果,这些策略最终由Signature
策略定义。它们支持默认规则,如“大多数组织管理员”。与Signature
策略相比,这些策略使用了一种不同但仍然非常简单的语法:
。
例如:ANY
Readers
或MAJORITY
Admins
。
请注意,在默认策略配置中,管理员
具有操作角色。指定只有管理员(或管理员的某个子集)才有权访问资源的策略,往往是针对网络的敏感或操作方面(例如在通道上实例化链码)。编写者
倾向于能够提出账本更新,例如交易处理,但通常不具有管理权限。读者有一个被动的角色。他们可以访问信息,但无权提议账本更新,也不能执行管理任务。这些默认策略可以添加、编辑或补充,例如通过新的节点
和客户端
角色(如果您支持NodeOU
)。
下面是一个ImplicitMeta
策略结构的示例:
Policies:
AnotherPolicy:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
在这里,策略AnotherPolicy
可以被大多数
管理员
满足,其中管理员
最终由较低级别的签名
策略指定。
在哪里指定了访问控制?
内部存在访问控制默认值configtx.yaml
,configtxgen
用于构建通道配置的文件。
访问控制可以通过两种方式之一更新,或者通过编辑configtx.yaml
它本身,在创建新的通道配置时使用,或者通过更新现有通道的通道配置中的访问控制来使用。
configtx.yaml
中格式化ACLsACLs的格式是由资源函数名后跟字符串组成的键值对。要查看它的外观,请参考此示例configtx.yaml文件。
此示例的两个摘录:
# ACL policy for invoking chaincodes on peer
peer/Propose: /Channel/Application/Writers
# ACL policy for sending block events
event/Block: /Channel/Application/Readers
这些ACLs定义对peer/Propose
和event/Block
资源的访问仅限于满足分别在规范路径/Channel/Application/Writers
和/Channel/Application/Readers
中定义的策略的标识。
更新configtx.yaml
中的ACL默认值
如果在引导网络时需要覆盖ACL默认值,或者在引导通道之前更改ACL,最佳实践是更新configtx.yaml
。
假设您想要修改peer/Propose
ACL 默认值(指定在节点上调用链码的策略),从/Channel/Application/Writers
修改为名为MyPolicy的策略。
这是通过添加一个名为MyPolicy
的策略来实现的(它可以被称为任何东西,但在本例中,我们将其称为MyPolicy
)。策略在configtx.yaml
中的Application.Policies
部分定义,并指定要检查的规则,以授予或拒绝对用户的访问。在这个例子中,我们将创建一个Signature
策略来识别SampleOrg.admin
。
Policies: &ApplicationDefaultPolicies
Readers:
Type: ImplicitMeta
Rule: "ANY Readers"
Writers:
Type: ImplicitMeta
Rule: "ANY Writers"
Admins:
Type: ImplicitMeta
Rule: "MAJORITY Admins"
MyPolicy:
Type: Signature
Rule: "OR('SampleOrg.admin')"
然后,编辑configtx.yaml
内的Application:ACLs
部分,将peer/Propose
从:
peer/Propose: /Channel/Application/Writers
改为
peer/Propose: /Channel/Application/MyPolicy
一旦在configtx.yaml
中更改了这些字段,configtxgen
工具将使用在创建通道创建交易时定义的策略和ACLs。当联盟成员的某个管理员适当地签名和提交时,将创建一个具有定义的ACLs和策略的新通道。
一旦MyPolicy
引导到通道配置中,也可以引用它来覆盖其他ACL默认值。例如:
SampleSingleMSPChannel:
Consortium: SampleConsortium
Application:
<<: *ApplicationDefaults
ACLs:
<<: *ACLsDefault
event/Block: /Channel/Application/MyPolicy
这将限制向SampleOrg.admin
订阅块事件的能力。
如果已经创建了要使用此ACL的通道,则必须使用以下流程一次更新一个通道配置:
更新通道配置中的ACL默认值
如果已经创建了想要使用MyPolicy
来限制对peer/Propose
的访问的通道,或者如果他们想创建ACL,他们不想让其他通道知道,那么他们必须通过配置更新交易一次更新一个通道配置。
注意:通道配置事务是一个复杂的过程,我们在这里不会深入研究。如果您想了解更多关于他们的信息,请查看我们关于通道配置更新的文档和“向通道添加组织”教程。
在提取、转换和剥离其元数据的配置块之后,您可以通过在Application:policies
下添加MyPolicy
来编辑配置,其中Admins
、Writers
和Readers
策略已经存在。
"MyPolicy": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [
{
"principal": {
"msp_identifier": "SampleOrg",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [
{
"signed_by": 0
}
]
}
},
"version": 0
}
},
"version": "0"
},
请特别注意此处的msp_identifer
和角色
。
然后,在配置的ACLs部分,将peer/Propose
ACL从:
"peer/Propose": {
"policy_ref": "/Channel/Application/Writers"
改为
"peer/Propose": {
"policy_ref": "/Channel/Application/MyPolicy"
注意:如果通道配置中没有定义ACL,则必须添加整个ACL结构。
一旦配置被更新,它将需要通过通常的通道更新过程提交。
满足需要访问多个资源的ACL
如果成员发出调用多个系统链码的请求,则必须满足这些系统链码的所有ACL。
例如,peer/Propose
表示通道上的任何提案请求。如果特定方案需要访问两个需要满足标识的编写器的系统链码和一个需要满足标识的MyPolicy
的系统链码,则提交该方案的成员必须具有对Writers
和MyPolicy
求值为“true”的标识。
在默认配置中,Writers
是一个签名策略,其规则为SampleOrg.member
。 换句话说,“我组织的任何成员”。上面列出的MyPolicy
有一个规则SampleOrg.admin
,或“我组织的任何管理员”。为了满足这些ACLs,成员必须同时是管理员和SampleOrg
的成员。默认情况下,所有管理员都是成员(尽管不是所有管理员都是成员),但是可以将这些策略改写为您希望它们成为的任何内容。因此,必须跟踪这些策略,以确保节点建议的ACLs不会无法满足(除非这是目的)。
Demix是一个加密协议套件,它提供了强大的身份验证和隐私保护功能,如匿名性、在不暴露交易方身份的情况下进行交易的能力以及不链接性,即单个身份发送多个交易而不暴露交易是由同样的身份。
举个例子,假设“Alice”需要向Bob(商店职员)证明她有车管所发给她的驾照。
在这个场景中,Alice是用户,DMV是发布者,Bob是验证者。为了向鲍勃证明爱丽丝有驾照,她可以给他看。然而,鲍勃将能够看到爱丽丝的名字,地址,确切年龄等-远远超过鲍勃需要知道的信息。
相反,Alice可以使用Idemix为Bob生成一个“零知识证明”,它只显示她拥有有效的驾照,而没有其他任何东西。
所以从证据来看:
Idemix身份验证技术提供的信任模型和安全保证类似于标准X.509证书所保证的内容,但其底层加密算法有效地提供了高级隐私功能,包括上述功能。在下面的技术部分中,我们将详细比较一下Idemix和X.509技术。
要了解如何将Idemix与Hyperledger Fabric一起使用,我们需要查看哪些结构组件对应于Idemix中的用户、颁发者和验证者。
为了在Hyperledger Fabric中使用Idemix,需要以下三个基本步骤:
将此图像中的角色与上面的角色进行比较。
fabric-ca-server
启动(或通过fabric-ca server init
命令初始化)时,在fabric-ca-server
: IssuerPublicKey
和IssuerRevocationPublicKey
的主目录中自动创建以下两个文件。这些文件在步骤2中是必需的。idemixgen
来创建这些文件。IssuerPublicKey
和IssuerRevocationPublicKey
创建一个Idemix MSP。configtx.yaml
的以下摘录:- &Org1Idemix
# defaultorg defines the organization which is used in the sampleconfig
# of the fabric.git development environment
name: idemixMSP1
# id to load the msp definition as
id: idemixMSPID1
msptype: idemix
mspdir: crypto-config/peerOrganizations/org3.example.com
msptype
被设置为idemix
,mspdir
目录(本例中为crypto-config/peerOrganizations/org3.example.com/msp
)的内容包含IssuerPublicKey
和IssuerRevocationPublicKey
文件。
注意,在这个例子中,Org1Idemix
代表Org1
的Idemix MSP(未显示),它也有一个X509 MSP。
org.hyperledger.fabric_ca.sdk.HFCAClient
类的idemixEnroll
方法。例如,假设hfcaClient
是您的hfcaClient对象,x509registration是与X509证书关联的org.hyperledger.fabric.sdk.Enrollment
。org.hyperledger.fabric.sdk.Enrollment
对象。IdemixEnrollment idemixEnrollment = hfcaClient.idemixEnroll(x509enrollment, "idemixMSPID1");
另外请注意,IdemixEnrollment
实现了org.hyperledger.fabric.sdk.Enrollment
接口,因此可以像使用X509注册对象一样使用,当然,这会自动提供dimix的隐私增强功能。
从验证者的角度来看,还有一个参与者需要考虑:链码。当使用Idemix凭证时,链码可以了解到关于交易处理程序的哪些信息?
cid(客户机标识)库(仅适用于golang)已被扩展,以便在使用Idemix凭据时支持GetAttributeValue
函数。然而,正如下面的“当前限制”一节所述,在Idemix案例中只公开了两个属性:ou
和role
。
如果Fabric CA是凭据颁发者:
role
属性的值将为“member”或“admin”。“admin”值表示标识是MSP管理员。默认情况下,结构CA创建的标识将返回“成员”角色。若要创建“admin”标识,请使用role
属性和值2
注册标识。有关在JavaSDK中设置从属关系的示例,请参见此示例。
有关使用go-chaincode中的CID库检索属性的示例,请参阅此go chaincode。
Idemix组织不能用于背书链码或批准链码定义。在通道上设置生命周期代言和背书策略时,需要考虑到这一点。有关详细信息,请参阅下面的“限制”部分。
当前版本的dimex确实有一些局限性。
Channel/Application/LifecycleEndorsement
将需要来自通道上活动的大多数组织的签名。这意味着包含大量Idemix组织的通道可能无法达到实现默认策略所需的大多数。例如,如果一个通道有两个MSP组织和两个Idemix组织,则通道策略将要求四分之三的组织批准链码定义,以将该定义提交给通道。因为Idemix组织不能批准链码定义,所以策略只能验证四分之二的签名。当前支持以下四个属性:
组织单位属性(“ou”):
用法:同X.509
类型:字符串
透露:总是
角色属性(“角色”):
用法:同X.509
类型:整数
透露:总是
注册ID属性
用法:唯一标识一个用户-在属于同一用户的所有注册凭据中都相同(将在将来的版本中用于审核)
类型:大
揭示:从不在签名中,仅当为Fabric CA生成身份验证令牌时
吊销句柄属性
用法:唯一标识凭据(将在将来的版本中用于吊销)
类型:整数
透露:从来没有
尚不支持吊销
尽管正如上面提到的吊销句柄属性的存在所看到的那样,大部分吊销框架已经就位,但是还不支持对Idemix凭证的吊销。
节点不使用Idemix作为背书
目前,Idemix MSP仅被节点用于签名验证。只有通过客户端SDK才能使用Idemix进行签名。Idemix MSP将支持更多角色(包括“节点”角色)。
将Idemix凭据与X.509证书进行比较
证书/凭证的概念和颁发过程在Idemix和X.509证书中非常相似:一组属性用一个无法伪造的签名进行数字签名,并且有一个秘密密钥以加密方式绑定到该密钥。
标准X.509证书和身份混合器凭证之间的主要区别是用于验证属性的签名方案。基于身份混合器系统的签名允许有效地证明拥有签名和相应的属性,而不暴露签名和(选定的)属性值本身。我们使用零知识证明来确保这种“知识”或“信息”不会被泄露,同时确保某些属性上的签名是有效的,并且用户拥有相应的凭证密钥。
此类证明,如X.509证书,可以使用原始签署凭证的机构的公钥进行验证,并且不能成功伪造。只有知道凭证密钥的用户才能生成凭证及其属性的证明。
关于不链接性,当一个X.509证书被呈现时,所有的属性都必须被揭示以验证证书签名。这意味着签名交易的所有证书用法都是可链接的。
为了避免这种链接性,每次都需要使用新的X.509证书,这会导致复杂的密钥管理和通信及存储开销。此外,在某些情况下,即使颁发证书的CA也不能将所有交易链接到用户,这一点很重要。
Idemix有助于避免CA和验证器的链接性,因为即使CA也不能将证明链接到原始凭证。颁发者和验证者都无法判断两个证明是否来自同一个凭证(或来自两个不同的凭证)。
本文详细介绍了身份混合器技术的概念和特点,并对基于属性的隐私保护认证的概念和语言进行了描述。
拓扑信息
考虑到上述限制,建议每个通道或在极端情况下,每个网络只有一个基于Idemix的MSP。事实上,例如,每个通道拥有多个基于Idemix的msp将允许一方在阅读该渠道的账本时,区分属于不同基于Idemix的MSPs的各方签署的交易。这是因为,每个交易都会泄漏签名者的MSP-ID。换言之,Idemix目前只提供同一组织(MSP)中客户的匿名性。
将来,可以扩展Idemix来支持基于Idemix的认证机构的匿名层次结构,这些机构的认证证书可以通过使用唯一的公钥进行验证,从而实现跨组织的匿名性(msp)。这将允许多个基于Idemix的MSPs在同一个通道中共存。
原则上,一个通道可以配置为有一个基于Idemix的MSP和多个基于X.509的MSP。当然,这些MSP之间的相互作用可能会泄露信息。对泄露的信息需要逐一进行评估case.wq
底层加密协议
Idemix技术是基于一个支持多个消息的盲签名方案和有效的零知识签名证明而构建的。所有用于Idemix的密码构建块都在顶级会议和期刊上发表,并得到科学界的验证。
这种针对Fabric的特定的dimix实现使用了基于配对的签名方案,该方案由Camenisch和Lysyanskaya简要提出,Au等人对此进行了详细描述。在零知识证明Camenisch等中证明签名知识的能力。被利用了。
本文档描述了idemixgen
实用程序的用法,该实用程序可用于为基于身份混合器的MSP创建配置文件。以前为一个密钥创建一个密钥对,可以使用两个命令创建一个密钥对。
idemixgen
工具将创建具有以下结构的目录:
- /ca/
IssuerSecretKey
IssuerPublicKey
RevocationKey
- /msp/
IssuerPublicKey
RevocationPublicKey
- /user/
SignerConfig
ca
目录包含颁发者密钥(包括吊销密钥),并且应该只存在于ca中。msp
目录包含设置msp验证idemix签名所需的信息。用户
目录指定默认签名者。
可以使用命令idemixgen ca-keygen
创建适合于身份混合器的CA(发卡器)密钥。这将在工作目录中创建目录ca
和msp
。
在用idemixgen ca-keygen
生成ca
和msp
目录后,可以使用idemixgen signerconfig
将用户
目录中指定的默认签名者添加到config中。
$ idemixgen signerconfig -h
usage: idemixgen signerconfig [>]
Generate a default signer for this Idemix MSP
Flags:
-h, --help Show context-sensitive help (also try --help-long and --help-man).
-u, --org-unit=ORG-UNIT The Organizational Unit of the default signer
-a, --admin Make the default signer admin
-e, --enrollment-id=ENROLLMENT-ID
The enrollment id of the default signer
-r, --revocation-handle=REVOCATION-HANDLE
The handle used to revoke this signer
例如,我们可以使用以下命令创建一个默认签名者,该签名者是组织单元“OrgUnit1”的成员,注册标识为“johndoe”,吊销句柄为“1234”,并且是管理员:
idemixgen signerconfig -u OrgUnit1 --admin -e "johndoe" -r 1234
普通节点和排序节点托管一个提供RESTful“操作”API的HTTP服务器。此API与Fabric network服务无关,仅供网络的操作员使用,而不是由网络的管理员或“用户”使用。
API公开了以下功能:
操作服务需要两个基本配置:
普通节点
对于每个节点,可以在core.yaml
的operations
部分配置操作服务器:
operations:
# host and port for the operations server
listenAddress: 127.0.0.1:9443
# TLS configuration for the operations endpoint
tls:
# TLS enabled
enabled: true
# path to PEM encoded server certificate for the operations server
cert:
file: tls/server.crt
# path to PEM encoded server key for the operations server
key:
file: tls/server.key
# most operations service endpoints require client authentication when TLS
# is enabled. clientAuthRequired requires client certificate authentication
# at the TLS layer to access all resources.
clientAuthRequired: false
# paths to PEM encoded ca certificates to trust for client authentication
clientRootCAs:
files: []
listenAddress
键定义操作服务器将侦听的主机和端口。如果服务器应该监听所有地址,则可以省略主机部分。
tls
部分用于指示是否为操作服务启用tls、服务的证书和私钥的位置,以及应信任用于客户端身份验证的证书颁发机构根证书的位置。当enabled
为true时,大多数操作服务端点都需要客户端身份验证,因此clientRootCAs.files
必须设置。当clientAuthRequired
为true
时,TLS层将要求客户机为每个请求提供一个用于身份验证的证书。有关详细信息,请参阅下面的操作安全部分。
排序节点
对于每个排序节点,可以在core.yaml
的operations部分配置操作服务器:
Operations:
# host and port for the operations server
ListenAddress: 127.0.0.1:8443
# TLS configuration for the operations endpoint
TLS:
# TLS enabled
Enabled: true
# PrivateKey: PEM-encoded tls key for the operations endpoint
PrivateKey: tls/server.key
# Certificate governs the file location of the server TLS certificate.
Certificate: tls/server.crt
# Paths to PEM encoded ca certificates to trust for client authentication
ClientRootCAs: []
# Most operations service endpoints require client authentication when TLS
# is enabled. ClientAuthRequired requires client certificate authentication
# at the TLS layer to access all resources.
ClientAuthRequired: false
ListenAddress
键定义操作服务器将侦听的主机和端口。如果服务器应该监听所有地址,则可以省略主机部分。
TLS
部分用于指示是否为操作服务启用TLS、服务的证书和私钥的位置,以及应信任用于客户端身份验证的证书颁发机构根证书的位置。当Enabled
为true时,大多数操作服务端点都需要客户端身份验证,因此必须设置RootCAs
。当ClientAuthRequired
为true
时,TLS层将要求客户机为每个请求提供一个用于身份验证的证书。有关详细信息,请参阅下面的操作安全部分。
运营安全
由于操作服务专注于操作,故意与Fabric网络无关,因此它不使用成员资格服务提供程序进行访问控制。相反,操作服务完全依赖于具有客户端证书身份验证的相互TLS。
当TLS被禁用时,授权被绕过,任何可以连接到操作端点的客户端都可以使用API。
启用TLS时,必须提供有效的客户端证书才能访问所有资源,除非下面另有明确说明。
当clientAuthRequired也被启用时,无论访问的资源是什么,TLS层都需要一个有效的客户端证书。
日志级别管理
操作服务提供/logspec
资源,操作员可以使用该资源管理节点或排序方的活动日志规范。该资源是常规的REST资源,支持GET
和PUT
请求。
当操作服务接收到GET/logspec
请求时,它将使用包含当前日志规范的JSON负载进行响应:
{
"spec":"info"}
当操作服务接收到PUT/logspec
请求时,它将把主体作为JSON负载读取。有效负载必须由一个名为spec
的属性组成。
{
"spec":"chaincode=debug:info"}
如果规范激活成功,服务将以204 "No Content"
响应响应。如果发生错误,服务将以400 "Bad Request"
和错误有效负载响应:
{
"error":"error message"}
操作服务提供了一个/healthz
资源,操作员可以使用它来帮助确定普通节点和排序节点的活跃度和运行状况。资源是支持GET请求的常规REST资源。该实现旨在与Kubernetes使用的liveness探测模型兼容,但也可以在其他环境中使用。
当收到GET/healthz
请求时,操作服务将调用进程的所有已注册的运行状况检查程序。当所有健康检查程序成功返回时,操作服务将以200“OK”
和一个JSON主体响应:
{
"status": "OK",
"time": "2009-11-10T23:00:00Z"
}
如果一个或多个运行状况检查程序返回错误,则操作服务将以503 "Service Unavailable"
和JSON主体响应,其中包含有关哪个运行状况检查程序失败的信息:
{
"status": "Service Unavailable",
"time": "2009-11-10T23:00:00Z",
"failed_checks": [
{
"component": "docker",
"reason": "failed to connect to Docker daemon: invalid endpoint"
}
]
}
在当前版本中,唯一注册的运行状况检查是针对Docker的。未来的版本将得到增强,以添加额外的运行状况检查。
启用TLS时,除非clientAuthRequired
设置为true
,否则不需要有效的客户端证书来使用此服务。
Fabric节点和排序节点的某些组件公开了有助于深入了解系统行为的指标。操作员和管理员可以使用这些信息来更好地了解系统在一段时间内的运行情况。
配置指标
Fabric提供了两种公开度量的方法:基于Prometheus的pull
模型和基于StatsD的push
模型。
Prometheus
典型的Prometheus部署通过从检测目标公开的HTTP端点请求度量来获取度量。由于普罗米修斯负责请求度量,因此它被认为是一个拉动系统。
配置后,结构普通节点或排序节点将在操作服务上显示/metrics
资源。
普通节点
通过在core.yaml
的metrics
部分将metrics提供者设置为Prometheus,可以将节点配置为公开供Prometheus
使用的/metrics
端点。
metrics:
provider: prometheus
排序节点
通过在orderer.yaml
的metrics
部分将metrics提供者设置为Prometheus,可以将节点配置为公开供Prometheus
使用的/metrics
端点。
Metrics:
Provider: prometheus
StatsD
StatsD是一个简单的统计信息聚合守护进程。度量被发送到statsd
守护进程,在那里收集、聚合并推送到后端进行可视化和警报。由于此模型需要插入指令的进程将度量数据发送到StatsD,因此这被认为是一个推送系统。
普通节点
通过在core.yaml
的metrics
部分将metrics提供程序设置为StatsD,可以将节点配置为向StatsD发送度量。statsd
子节还必须配置statsd守护进程的地址、要使用的网络类型(tcp
或udp
)以及发送度量的频率。可以指定一个可选的前缀
来帮助区分度量的来源—例如,区分来自不同节点的度量—该前缀将附加到所有生成的度量中。
metrics:
provider: statsd
statsd:
network: udp
address: 127.0.0.1:8125
writeInterval: 10s
prefix: peer-0
排序节点
通过在orederer.yaml
的metrics
部分将metrics提供程序设置为StatsD,可以将节点配置为向StatsD发送度量。statsd
子节还必须配置statsd守护进程的地址、要使用的网络类型(tcp
或udp
)以及发送度量的频率。可以指定一个可选的前缀
来帮助区分度量的来源—例如,区分来自不同节点的度量—该前缀将附加到所有生成的度量中。
Metrics:
Provider: statsd
Statsd:
Network: udp
Address: 127.0.0.1:8125
WriteInterval: 30s
Prefix: org-orderer
要查看生成的不同度量,请查看metrics Reference。
Prometheus
名字 | 类型 | 描述 | 标签 |
---|---|---|---|
blockcutter_block_fill_duration | 直方图 | 从第一个交易分配到块被切割的时间(以秒为单位) | 通道 |
broadcast_enqueue_duration | 直方图 | 将交易排队的时间(以秒为单位) | 通道 类型 状态 |
broadcast_processed_count | 计数器 | 处理的交易数 | 通道 类型 状态 |
broadcast_validate_duration | 直方图 | 验证交易的时间(以秒为单位) | 通道 类型 状态 |
cluster_comm_egress_queue_capacity | 计量器 | 出口队列的容量 | 主机 消息类型 通道 |
cluster_comm_egress_queue_length | 计量器 | 出口队列的长度 | 主机 消息类型 通道 |
cluster_comm_egress_queue_workers | 计量器 | 出口队列工作人员的数量 | 通道 |
cluster_comm_egress_stream_count | 计量器 | 到其他节点的流数量 | 通道 |
cluster_comm_egress_tls_connection_count | 计量器 | 到其他节点的TLS连接数 | |
cluster_comm_ingress_stream_count | 计量器 | 来自其他节点的流的计数 | |
cluster_comm_msg_dropped_count | 计数器 | 丢失的消息数 | 主机 通道 |
cluster_comm_msg_send_time | 直方图 | 以秒为单位发送信息所花费的时间 | 主机 通道 |
consensus_etcdraft_active_nodes | 计量器 | 此通道中的活动节点数 | 通道 |
consensus_etcdraft_cluster_size | 计量器 | 此通道中的节点数 | 通道 |
consensus_etcdraft_committed_block_number | 计量器 | 最近提交的块的块号 | 通道 |
consensus_etcdraft_config_proposals_received | 计数器 | 接收到的配置类型交易提案的总数 | 通道 |
consensus_etcdraft_data_persist_duration | 直方图 | etcd/raft数据保存在存储中的时间(秒) | 通道 |
consensus_etcdraft_is_leader | 计量器 | 当前节点的领导状态:如果是领导,则为1,否则为0 | 通道 |
consensus_etcdraft_leader_changes | 计数器 | 流程启动后领导变更的数量 | 通道 |
consensus_etcdraft_normal_proposals_received | 计数器 | 收到的用于正常类型j交易的提案的总数 | 通道 |
consensus_etcdraft_proposal_failures | 计数器 | 提案失败的次数 | 通道 |
consensus_etcdraft_snapshot_block_number | 计量器 | 最新快照的块号 | 通道 |
consensus_kafka_batch_size | 计量器 | 发送到主题的平均批处理大小(以字节为单位) | 主题 |
consensus_kafka_compression_ratio | 计量器 | 主题的平均压缩比(百分比) | 主题 |
consensus_kafka_incoming_byte_rate | 计量器 | 字节/秒读取代理 | broker_id |
consensus_kafka_last_offset_persisted | 计量器 | 在最近提交块的块元数据中指定的偏移量 | 通道 |
consensus_kafka_outgoing_byte_rate | 计量器 | 字节/秒写入代理 | broker_id |
consensus_kafka_record_send_rate | 计量器 | 每秒发送到主题的记录数 | 主题 |
consensus_kafka_records_per_request | 计量器 | 每个请求发送到主题的平均记录数 | 主题 |
consensus_kafka_request_latency | 计量器 | 在ms中对代理的平均请求延迟 | broker_id |
consensus_kafka_request_rate | 计量器 | 发送给代理的请求/秒 | broker_id |
consensus_kafka_request_size | 计量器 | 发送给代理的平均请求大小(以字节为单位) | broker_id |
consensus_kafka_response_rate | 计量器 | 发送给代理的请求/秒 | broker_id |
consensus_kafka_response_size | 计量器 | 来自代理的平均响应大小(以字节为单位) | broker_id |
couchdb_processing_time | 直方图 | CouchDB完成请求所用的时间(秒) | 数据库 函数名 结果 |
deliver_blocks_sent | 计数器 | 由交付服务发送的块数 | 通道 滤过的 数据类型 |
deliver_requests_completed | 计数器 | 已完成的交付请求的数量 | 通道 滤过的 数据类型 成功 |
deliver_requests_received | 计数器 | 已接收的传递请求数 | 通道 滤过的 数据类型 |
deliver_streams_closed | 计数器 | 已为交付服务关闭的GRPC流的数量 | |
deliver_streams_opened | 计数器 | 已为交付服务打开的GRPC流的数量 | |
fabric_version | 计量器 | Fabric的活动版本 | 版本 |
grpc_comm_conn_closed | 计数器 | gRPC连接关闭。打开的连接数减去关闭的连接数 | |
grpc_comm_conn_opened | 计数器 | gRPC连接打开。打开的连接数减去关闭的连接数 | |
grpc_server_stream_messages_received | 计数器 | 接收到的流消息的数量 | 服务 方法 |
grpc_server_stream_messages_sent | 计数器 | 发送的流消息的数量 | 服务 方法 |
grpc_server_stream_request_duration | 直方图 | 完成流请求的时间 | 服务 方法 代码 |
grpc_server_stream_requests_completed | 计数器 | 已完成的流请求数 | 服务 方法 代码 |
grpc_server_stream_requests_received | 计数器 | 接收到的流请求的数量 | 服务 方法 |
grpc_server_unary_request_duration | 直方图 | 完成一个一元请求的时间 | 服务 方法 代码 |
grpc_server_unary_requests_completed | 计数器 | 单目请求完成的数目 | 服务 方法 代码 |
grpc_server_unary_requests_received | 计数器 | 收到的单目请求数 | 服务 方法 |
ledger_blockchain_height | 计量器 | 链的块高度 | 通道 |
ledger_blockstorage_commit_time | 直方图 | 将块提交到存储所花费的时间(以秒为单位) | 通道 |
logging_entries_checked | 计数器 | 根据活动日志记录级别检查的日志条目数 | 等级 |
logging_entries_written | 计数器 | 写入的日志条目的数量 | 等级 |
StatsD
下面的order度量由StatsD发出以供使用。%{variable_name}
命名法表示因上下文而异的段。
例如,%{channel}
将替换为与度量关联的通道的名称。
Bucket | 类型 | 描述 |
---|---|---|
blockcutter.block_fill_duration.%{channel} | 直方图 | 从第一个交易查询到块被删除的时间,以秒为单位 |
broadcast.enqueue_duration.%{channel}.%{type}.%{status} | 直方图 | 交易进入队列的时间(以秒为单位) |
broadcast.processed_count.%{channel}.%{type}.%{status} | 计数器 | 处理的交易数 |
broadcast.validate_duration.%{channel}.%{type}.%{status} | 直方图 | 验证交易的时间(以秒为单位) |
cluster.comm.egress_queue_capacity.%{host}.%{msg_type}.%{channel} | 计量器 | 输出队列容量 |
cluster.comm.egress_queue_length.%{host}.%{msg_type}.%{channel} | 计量器 | 输出队列的长度 |
cluster.comm.egress_queue_workers.%{channel} | 计量器 | 输出队列工作人员的计数 |
cluster.comm.egress_stream_count.%{channel} | 计量器 | 到其他节点的流的计数 |
cluster.comm.egress_tls_connection_count | 计量器 | 到其他节点的TLS连接计数 |
cluster.comm.ingress_stream_count | 计量器 | 来自其他节点的流的计数 |
cluster.comm.msg_dropped_count.%{host}.%{channel} | 计数器 | 丢失的消息数 |
cluster.comm.msg_send_time.%{host}.%{channel} | 直方图 | 以秒为单位发送信息所花费的时间 |
consensus.etcdraft.active_nodes.%{channel} | 计量器 | 此通道中的活动节点数 |
consensus.etcdraft.cluster_size.%{channel} | 计量器 | 此通道中的节点数 |
consensus.etcdraft.committed_block_number.%{channel} | 计量器 | 最近提交的块的块号 |
consensus.etcdraft.config_proposals_received.%{channel} | 计数器 | 接收到的配置类型交易提案的总数 |
consensus.etcdraft.data_persist_duration.%{channel} | 直方图 | etcd/raft数据保存在存储中的时间(秒) |
consensus.etcdraft.is_leader.%{channel} | 计量器 | 当前节点的领导状态:如果是领导,则为1,否则为0 |
consensus.etcdraft.leader_changes.%{channel} | 计数器 | 流程启动后领导变更的数量 |
consensus.etcdraft.normal_proposals_received.%{channel} | 计数器 | 收到的用于正常类型交易的提案的总数 |
consensus.etcdraft.proposal_failures.%{channel} | 计数器 | 提案失败的次数 |
consensus.etcdraft.snapshot_block_number.%{channel} | 计量器 | 最新快照的块号 |
consensus.kafka.batch_size.%{topic} | 计量器 | 发送到主题的平均批处理大小(以字节为单位) |
consensus.kafka.compression_ratio.%{topic} | 计量器 | 主题的平均压缩比(百分比) |
consensus.kafka.incoming_byte_rate.%{broker_id} | 计量器 | 字节/秒读取代理 |
consensus.kafka.last_offset_persisted.%{channel} | 计量器 | 在最近提交块的块元数据中指定的偏移量 |
consensus.kafka.outgoing_byte_rate.%{broker_id} | 计量器 | 字节/秒写入代理 |
consensus.kafka.record_send_rate.%{topic} | 计量器 | 每秒发送到主题的记录数 |
consensus.kafka.records_per_request.%{topic} | 计量器 | 每个请求发送到主题的平均记录数 |
consensus.kafka.request_latency.%{broker_id} | 计量器 | ms到代理的平均请求延迟 |
consensus.kafka.request_rate.%{broker_id} | 计量器 | 发送给代理的请求/秒 |
consensus.kafka.request_size.%{broker_id} | 计量器 | 发送给代理的平均请求大小(以字节为单位) |
consensus.kafka.response_rate.%{broker_id} | 计量器 | 发送给代理的请求/秒 |
consensus.kafka.response_size.%{broker_id} | 计量器 | 来自代理的平均响应大小(以字节为单位) |
couchdb.processing_time.%{database}.%{function_name}.%{result} | 直方图 | 函数完成对CouchDB的请求所花费的时间(秒) |
deliver.blocks_sent.%{channel}.%{filtered}.%{data_type} | 计数器 | 由交付服务发送的块数 |
deliver.requests_completed.%{channel}.%{filtered}.%{data_type}.%{success} | 计数器 | 已完成的交付请求的数量 |
deliver.requests_received.%{channel}.%{filtered}.%{data_type} | 计数器 | 已收到的交付请求的数量 |
deliver.streams_closed | 计数器 | 已为交付服务关闭的GRPC流的数量 |
deliver.streams_opened | 计数器 | 已为交付服务打开的GRPC流的数量 |
fabric_version.%{version} | 计量器 | Fabric的活动版本 |
grpc.comm.conn_closed | 计数器 | gRPC连接关闭。打开的连接数减去关闭的连接数。 |
grpc.comm.conn_opened | 计数器 | gRPC连接打开。打开的连接数减去关闭的连接数。 |
grpc.server.stream_messages_received.%{service}.%{method} | 计数器 | 接收到的流消息的数量 |
grpc.server.stream_messages_sent.%{service}.%{method} | 计数器 | 发送的流消息的数量 |
grpc.server.stream_request_duration.%{service}.%{method}.%{code} | 直方图 | 完成流请求的时间 |
grpc.server.stream_requests_completed.%{service}.%{method}.%{code} | 计数器 | 已完成的流请求数 |
grpc.server.stream_requests_received.%{service}.%{method} | 计数器 | 接收到的流请求的数量 |
grpc.server.unary_request_duration.%{service}.%{method}.%{code} | 直方图 | 完成一个一元请求的时间 |
grpc.server.unary_requests_completed.%{service}.%{method}.%{code} | 计数器 | 单目请求完成的数目 |
grpc.server.unary_requests_received.%{service}.%{method} | 计数器 | 收到的单目请求数 |
ledger.blockchain_height.%{channel} | 计量器 | 链的块高度 |
ledger.blockstorage_commit_time.%{channel} | 直方图 | 将块提交到存储所花费的时间(以秒为单位) |
logging.entries_checked.%{level} | 计数器 | 根据活动日志记录级别检查的日志条目数 |
logging.entries_written.%{level} | 计数器 | 写入的日志条目的数量 |
Prometheus
以下节点度量标准被导出供Prometheus使用。
名字 | 类型 | 描述 | 标签 |
---|---|---|---|
chaincode_execute_timeouts | 计数器 | 超时的链码执行(Init或调用)的数量 | 链码 |
chaincode_launch_duration | 直方图 | 是时候启动链码了 | 链码 成功 |
chaincode_launch_failures | 计数器 | 链码启动失败的次数 | 链码 |
chaincode_launch_timeouts | 计数器 | 超时发射链码的数量 | 链码 |
chaincode_shim_request_duration | 直方图 | 完成链码垫片请求的时间 | 类型 通道 链码 成功 |
chaincode_shim_requests_completed | 计数器 | 已完成的链码填充程序请求数 | 类型 通道 链码 成功 |
chaincode_shim_requests_received | 计数器 | 收到的链码填充程序请求的数量 | 类型 通道 链码 |
couchdb_processing_time | 直方图 | 函数完成对CouchDB的请求所花费的时间(秒) | 数据库 函数名 结果 |
deliver_blocks_sent | 计数器 | 由交付服务发送的块数 | 通道 滤过的 数据类型 |
deliver_requests_completed | 计数器 | 已完成的交付请求的数量 | 通道 滤过的 数据类型 成功 |
deliver_requests_received | 计数器 | 已收到的交付请求的数量 | 通道 滤过的 数据类型 |
deliver_streams_closed | 计数器 | 已为交付服务关闭的GRPC流的数量 | |
deliver_streams_opened | 计数器 | 已为交付服务打开的GRPC流的数量 | |
dockercontroller_chaincode_container_build_duration | 直方图 | 以秒为单位构建链码镜像的时间 | 链码 成功 |
endorser_chaincode_instantiation_failures | 计数器 | 失败的链码实例化或升级的数目 | 通道 链码 |
endorser_duplicate_transaction_failures | 计数器 | 由于重复的交易ID导致的失败提案的数量 | 通道 链码 |
endorser_endorsement_failures | 计数器 | 背书失败的数目 | 通道 链码 链码错误 |
endorser_proposal_acl_failures | 计数器 | ACL检查失败的提案的数量 | 通道 链码 |
endorser_proposal_duration | 直方图 | 完成提案的时间 | 通道 链码 成功 |
endorser_proposal_validation_failures | 计数器 | 初步验证失败的提案数量 | |
endorser_proposals_received | 计数器 | 收到的提案数 | |
endorser_successful_proposals | 计数器 | 成功提案的数目 | |
fabric_version | 计量器 | Fabric的活动版本 | 版本 |
gossip_comm_messages_received | 计数器 | 收到的消息数 | |
gossip_comm_messages_sent | 计数器 | 发送的消息数 | |
gossip_comm_overflow_count | 计数器 | 传出队列缓冲区溢出的数量 | |
gossip_leader_election_leader | 计量器 | 节点是领导者(1)或追随者(0) | 通道 |
gossip_membership_total_peers_known | 计量器 | 总已经节点 | 通道 |
gossip_payload_buffer_size | 计量器 | 有效载荷缓冲区的大小 | 通道 |
gossip_privdata_commit_block_duration | 直方图 | 提交私有数据和相应块所需的时间(以秒为单位) | 通道 |
gossip_privdata_fetch_duration | 直方图 | 从节点获取丢失的私有数据所花费的时间(以秒为单位) | 通道 |
gossip_privdata_list_missing_duration | 直方图 | 列出缺少的私有数据所需的时间(以秒为单位) | 通道 |
gossip_privdata_pull_duration | 直方图 | 提取丢失的私有数据元素所需的时间(以秒为单位) | 通道 |
gossip_privdata_purge_duration | 直方图 | 清除私有数据所需的时间(以秒为单位) | 通道 |
gossip_privdata_reconciliation_duration | 直方图 | 完成对账所需的时间(以秒为单位) | 通道 |
gossip_privdata_retrieve_duration | 直方图 | 从账本中检索丢失的私有数据元素所需的时间(秒) | 通道 |
gossip_privdata_send_duration | 直方图 | 发送丢失的私有数据元素所需的时间(以秒为单位) | 通道 |
gossip_privdata_validation_duration | 直方图 | 验证一个块所需的时间(以秒为单位) | 通道 |
gossip_state_commit_duration | 直方图 | 提交一个块所需的时间(以秒为单位) | 通道 |
gossip_state_height | 计量器 | 当前账本高度 | 通道 |
grpc_comm_conn_closed | 计数器 | gRPC连接关闭。打开的连接数减去关闭的连接数 | |
grpc_comm_conn_opened | 计数器 | gRPC连接打开。打开的连接数减去关闭的连接数 | |
grpc_server_stream_messages_received | 计数器 | 接收到的流消息的数量 | 服务 方法 |
grpc_server_stream_messages_sent | 计数器 | 发送的流消息的数量 | 服务 方法 |
grpc_server_stream_request_duration | 直方图 | 完成流请求的时间 | 服务 方法 代码 |
grpc_server_stream_requests_completed | 计数器 | 已完成的流请求数 | 服务 方法 代码 |
grpc_server_stream_requests_received | 计数器 | 接收到的流请求的数量 | 服务 方法 |
grpc_server_unary_request_duration | 直方图 | 完成一个一元请求的时间 | 服务 方法 代码 |
grpc_server_unary_requests_completed | 计数器 | 单目请求完成的数目 | 服务 方法 代码 |
grpc_server_unary_requests_received | 计数器 | 收到的单目请求数 | 服务 方法 |
ledger_block_processing_time | 直方图 | 用于分类块处理的时间(秒) | 通道 |
ledger_blockchain_height | 计量器 | 链的块高度 | 通道 |
ledger_blockstorage_and_pvtdata_commit_time | 直方图 | 将块和私有数据提交到存储器所花费的时间(以秒为单位) | 通道 |
ledger_blockstorage_commit_time | 直方图 | 将块提交到存储所花费的时间(以秒为单位) | 通道 |
ledger_statedb_commit_time | 直方图 | 将块更改提交到状态数据库所花费的时间(秒) | 通道 |
ledger_transaction_count | 计数器 | 处理的交易数 | 通道 交易类型 链码 验证代码 |
logging_entries_checked | 计数器 | 根据活动日志记录级别检查的日志条目数 | 等级 |
logging_entries_written | 计数器 | 写入的日志条目的数量 | 等级 |
StatsD
以下节点指标由StatsD使用。%{variable_name}
命名法表示因环境而异的段。
例如,%{channel}
将替换为与度量关联的通道的名称。
Bucket | 类型 | 描述 |
---|---|---|
chaincode.execute_timeouts.%{chaincode} | 计数器 | 链码执行(Init或调用)超时的数量 |
chaincode.launch_duration.%{chaincode}.%{success} | 直方图 | 启动链码的时间 |
chaincode.launch_failures.%{chaincode} | 计数器 | 链码发射失败的次数 |
chaincode.launch_timeouts.%{chaincode} | 计数器 | 启动链码超时的数量 |
chaincode.shim_request_duration.%{type}.%{channel}.%{chaincode}.%{success} | 直方图 | 完成链码填充程序请求的时间 |
chaincode.shim_requests_completed.%{type}.%{channel}.%{chaincode}.%{success} | 计数器 | 已完成的链码填充程序请求数 |
chaincode.shim_requests_received.%{type}.%{channel}.%{chaincode} | 计数器 | 收到的链码填充程序请求的数量 |
couchdb.processing_time.%{database}.%{function_name}.%{result} | 直方图 | 函数完成对CouchDB的请求所花费的时间(秒) |
deliver.blocks_sent.%{channel}.%{filtered}.%{data_type} | 计数器 | 由交付服务发送的块数 |
deliver.requests_completed.%{channel}.%{filtered}.%{data_type}.%{success} | 计数器 | 已完成的交付请求的数量 |
deliver.requests_received.%{channel}.%{filtered}.%{data_type} | 计数器 | 已收到的交付请求的数量 |
deliver.streams_closed | 计数器 | 已为交付服务关闭的GRPC流的数量 |
deliver.streams_opened | 计数器 | 已为交付服务打开的GRPC流的数量 |
dockercontroller.chaincode_container_build_duration.%{chaincode}.%{success} | 直方图 | 以秒为单位构建链码镜像的时间 |
endorser.chaincode_instantiation_failures.%{channel}.%{chaincode} | 计数器 | 失败的链码实例化或升级的数目 |
endorser.duplicate_transaction_failures.%{channel}.%{chaincode} | 计数器 | 由于重复的交易ID导致的失败提案的数量 |
endorser.endorsement_failures.%{channel}.%{chaincode}.%{chaincodeerror} | 计数器 | 背书失败的数目 |
endorser.proposal_acl_failures.%{channel}.%{chaincode} | 计数器 | ACL检查失败的提案的数量 |
endorser.proposal_duration.%{channel}.%{chaincode}.%{success} | 直方图 | 完成提案的时间 |
endorser.proposal_validation_failures | 计数器 | 初步验证失败的提案数量 |
endorser.proposals_received | 计数器 | 收到的提案数 |
endorser.successful_proposals | 计数器 | 成功提案的数目 |
fabric_version.%{version} | 计量器 | Fabric的活动版本 |
gossip.comm.messages_received | 计数器 | 收到的消息数 |
gossip.comm.messages_sent | 计数器 | 发送的消息数 |
gossip.comm.overflow_count | 计数器 | 传出队列缓冲区溢出的数量 |
gossip.leader_election.leader.%{channel} | 计量器 | 节点是领导者(1)或追随者(0) |
gossip.membership.total_peers_known.%{channel} | 计量器 | 总已知节点数 |
gossip.payload_buffer.size.%{channel} | 直方图 | 有效载荷缓冲区的大小 |
gossip.privdata.commit_block_duration.%{channel} | 直方图 | 提交私有数据和相应块所需的时间(以秒为单位) |
gossip.privdata.fetch_duration.%{channel} | 直方图 | 从节点获取丢失的私有数据所需的时间(秒) |
gossip.privdata.list_missing_duration.%{channel} | 直方图 | 列出缺少的私有数据所需的时间(以秒为单位) |
gossip.privdata.pull_duration.%{channel} | 直方图 | 提取丢失的私有数据元素所需的时间(以秒为单位) |
gossip.privdata.purge_duration.%{channel} | 直方图 | 清除私有数据所需的时间(以秒为单位) |
gossip.privdata.reconciliation_duration.%{channel} | 直方图 | 完成对账所需的时间(以秒为单位) |
gossip.privdata.retrieve_duration.%{channel} | 直方图 | 从账本中检索丢失的私有数据元素所需的时间(秒) |
gossip.privdata.send_duration.%{channel} | 直方图 | 发送丢失的私有数据元素所需的时间(以秒为单位) |
gossip.privdata.validation_duration.%{channel} | 直方图 | 验证一个块所需的时间(以秒为单位) |
gossip.state.commit_duration.%{channel} | 直方图 | 提交一个块所需的时间(以秒为单位) |
gossip.state.height.%{channel} | 计量器 | 当前账本高度 |
grpc.comm.conn_closed | 计数器 | gRPC连接关闭。打开的连接数减去关闭的连接数 |
grpc.comm.conn_opened | 计数器 | gRPC连接打开。打开的连接数减去关闭的连接数 |
grpc.server.stream_messages_received.%{service}.%{method} | 计数器 | 接收到的流消息的数量 |
grpc.server.stream_messages_sent.%{service}.%{method} | 计数器 | 发送的流消息的数量 |
grpc.server.stream_request_duration.%{service}.%{method}.%{code} | 直方图 | 完成流请求的时间 |
grpc.server.stream_requests_completed.%{service}.%{method}.%{code} | 计数器 | 已完成的流请求数 |
grpc.server.stream_requests_received.%{service}.%{method} | 计数器 | 接收到的流请求的数量 |
grpc.server.unary_request_duration.%{service}.%{method}.%{code} | 直方图 | 完成一个单目请求的时间 |
grpc.server.unary_requests_completed.%{service}.%{method}.%{code} | 计数器 | 单目请求完成的数目 |
grpc.server.unary_requests_received.%{service}.%{method} | 计数器 | 收到的单目请求数 |
ledger.block_processing_time.%{channel} | 直方图 | 用于账本处理的时间(秒) |
ledger.blockchain_height.%{channel} | 计量器 | 链的块高度 |
ledger.blockstorage_and_pvtdata_commit_time.%{channel} | 直方图 | 将块和私有数据提交到存储器所花费的时间(以秒为单位) |
ledger.blockstorage_commit_time.%{channel} | 直方图 | 将块提交到存储所花费的时间(以秒为单位) |
ledger.statedb_commit_time.%{channel} | 直方图 | 将块更改提交到状态数据库所花费的时间(秒) |
ledger.transaction_count.%{channel}.%{transaction_type}.%{chaincode}.%{validation_code} | 计数器 | 处理的交易数 |
logging.entries_checked.%{level} | 计数器 | 根据活动日志记录级别检查的日志条目数 |
logging.entries_written.%{level} | 计数器 | 写入的日志条目的数量 |
在Hyperledger Fabric2.0之前,用于构建和启动链码的过程是节点实现的一部分,不容易定制。安装在节点上的所有链码都将使用节点中硬编码的特定语言逻辑“构建”。这个构建过程将生成一个Docker容器镜像,该镜像将启动以执行作为客户端连接到节点的链码。
这种方法将链码的实现限制为少数几种语言,要求Docker成为部署环境的一部分,并防止将链码作为长时间运行的服务器进程运行。
从Fabric 2.0开始,外部构建器和启动器通过允许操作员使用可以构建、启动和发现链码的程序来扩展节点来解决这些限制。要利用此功能,您需要创建自己的构建包,然后修改节点core.yaml以包含一个新的externalBuilder
配置元素,该元素让节点知道有外部构建器可用。以下各节描述了此过程的详细信息。
请注意,如果没有配置的外部生成器声明链码包,则节点将尝试处理该包,就像它是使用节点CLI或节点SDK等标准结构打包工具创建的一样。
Hyperledger Fabric外部构建器和启动器松散地基于Heroku BuildPack。buildpack实现只是将应用程序构件转换为可以运行的程序或脚本的集合。buildpack模型已经针对链码包进行了调整,并进行了扩展,以支持链码的执行和发现。
外部生成器和启动器API
外部生成器和启动器由四个程序或脚本组成:
bin/detect
:确定是否应使用此buildpack生成链码包并启动它。bin/build
:将链码包转换为可执行的链码。bin/release
(可选):向节点提供链码的元数据。bin/run
(可选):运行链码。bin/detect
bin/detect
脚本负责确定是否应该使用buildpack来构建链码包并启动它。节点使用两个参数调用detect
:
bin/detect CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR
调用detect
时,CHAINCODE_SOURCE_DIR
包含链码源,CHAINCODE_METADATA_DIR
包含安装到节点的链码包中的metadata.json
文件。CHAINCODE_SOURCE_DIR
和CHAINCODE_METADATA_DIR
应视为只读输入。如果应将buildpack应用于chaincode源包,detect
必须返回退出代码0;任何其他退出代码都将指示不应应用buildpack。
以下是go-chaincode的简单detect
脚本示例:
#!/bin/bash
CHAINCODE_METADATA_DIR="$2"
# use jq to extract the chaincode type from metadata.json and exit with
# success if the chaincode type is golang
if [ "$(jq -r .type "$CHAINCODE_METADATA_DIR/metadata.json" | tr '[:upper:]' '[:lower:]')" = "golang" ]; then
exit 0
fi
exit 1
bin/build
bin/build
脚本负责构建、编译或将链码包的内容转换为可供发布
和运行
使用的构件。同级调用三个参数:
bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR
当调用build
时,CHAINCODE_SOURCE_DIR
包含链码源,CHAINCODE_METADATA_DIR
包含安装到节点的CHAINCODE包中的metadata.json
文件。BUILD_OUTPUT_DIR
是build
必须放置发布
和运行
所需工件的目录。构建脚本应将输入目录CHAINCODE_SOURCE_DIR
和CHAINCODE_METADATA_DIR
视为只读,但BUILD_OUTPUT_DIR
是可写的。
当build
以退出代码0
完成时,BUILD_OUTPUT_DIR
的内容将被复制到节点维护的持久存储中;任何其他退出代码都将被视为失败。
下面是go-chaincode的一个简单build
脚本示例:
#!/bin/bash
CHAINCODE_SOURCE_DIR="$1"
CHAINCODE_METADATA_DIR="$2"
BUILD_OUTPUT_DIR="$3"
# extract package path from metadata.json
GO_PACKAGE_PATH="$(jq -r .path "$CHAINCODE_METADATA_DIR/metadata.json")"
if [ -f "$CHAINCODE_SOURCE_DIR/src/go.mod" ]; then
cd "$CHAINCODE_SOURCE_DIR/src"
go build -v -mod=readonly -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
else
GO111MODULE=off go build -v -o "$BUILD_OUTPUT_DIR/chaincode" "$GO_PACKAGE_PATH"
fi
# save statedb index metadata to provide at release
if [ -d "$CHAINCODE_SOURCE_DIR/META-INF" ]; then
cp -a "$CHAINCODE_SOURCE_DIR/META-INF" "$BUILD_OUTPUT_DIR/"
fi
bin/release
bin/release
脚本负责向节点提供链码元数据。bin/release
是可选的。如果未提供,则跳过此步骤。节点使用两个参数调用release
:
bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR
当调用release
时,BUILD_OUTPUT_DIR
包含由构建
程序填充的构件,应将其视为只读输入。RELEASE_OUTPUT_DIR
是release
必须放置工件以供节点使用的目录。
当release
完成时,节点将从RELEASE_OUTPUT_DIR
使用两种类型的元数据:
chaincode/server/connection.json
)如果链码需要CouchDB索引定义,则release
负责将索引放入RELEASE_OUTPUT_DIR
下的statedb/CouchDB/indexes
目录中。索引必须具有.json
扩展名。有关详细信息,请参阅CouchDB索引文档。
在使用链码服务器实现的情况下,release
负责chaincode/server/connection.json
和链码服务器的地址以及与链码通信所需的任何TLS资产。当服务器连接信息提供给节点时,将不调用run
。有关详细信息,请参阅链码服务器文档。
下面是go-chaincode的一个简单release
脚本示例:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RELEASE_OUTPUT_DIR="$2"
# copy indexes from META-INF/* to the output directory
if [ -d "$BUILD_OUTPUT_DIR/META-INF" ] ; then
cp -a "$BUILD_OUTPUT_DIR/META-INF/"* "$RELEASE_OUTPUT_DIR/"
fi
bin/run
bin/run
脚本负责运行链码。节点使用两个参数调用run
:
bin/run BUILD_OUTPUT_DIR RUN_METADATA_DIR
调用run
时,BUILD_OUTPUT_DIR
包含由构建程序填充的构件,RUN_METADATA_DIR
由一个名为chaincode.json
它包含链码连接和向节点注册所需的信息。注意bin/run
脚本应该将这些BUILD_OUTPUT_DIR
和RUN_METADATA_DIR
目录视为只读输入。包括在chaincode.json
是:
chaincode_id
:与chaincode包关联的唯一id。peer_address
:中的地址主机:端口格式由节点托管的ChaincodeSupport gRPC服务器终结点的。client_cert
:由节点生成的PEM编码的TLS客户端证书,在链码建立与节点的连接时必须使用该证书。client_key
:由节点生成的PEM编码的客户端密钥,当链码与节点建立连接时必须使用该密钥。root_cert
:由节点托管的ChaincodeSupport
gRPC服务器端点的PEM编码的TLS根证书。mspid
:节点的本地mspid。当run
终止时,节点认为链码已终止。如果链码的另一个请求到达,节点将再次调用run
来尝试启动链码的另一个实例。内容chaincode.json
不能跨调用缓存。
下面是go-chaincode的一个简单运行
脚本示例:
#!/bin/bash
BUILD_OUTPUT_DIR="$1"
RUN_METADATA_DIR="$2"
# setup the environment expected by the go chaincode shim
export CORE_CHAINCODE_ID_NAME="$(jq -r .chaincode_id "$RUN_METADATA_DIR/chaincode.json")"
export CORE_PEER_TLS_ENABLED="true"
export CORE_TLS_CLIENT_CERT_FILE="$RUN_METADATA_DIR/client.crt"
export CORE_TLS_CLIENT_KEY_FILE="$RUN_METADATA_DIR/client.key"
export CORE_PEER_TLS_ROOTCERT_FILE="$RUN_METADATA_DIR/root.crt"
export CORE_PEER_LOCALMSPID="$(jq -r .mspid "$RUN_METADATA_DIR/chaincode.json")"
# populate the key and certificate material used by the go chaincode shim
jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_CERT_FILE"
jq -r .client_key "$RUN_METADATA_DIR/chaincode.json" > "$CORE_TLS_CLIENT_KEY_FILE"
jq -r .root_cert "$RUN_METADATA_DIR/chaincode.json" > "$CORE_PEER_TLS_ROOTCERT_FILE"
if [ -z "$(jq -r .client_cert "$RUN_METADATA_DIR/chaincode.json")" ]; then
export CORE_PEER_TLS_ENABLED="false"
fi
# exec the chaincode to replace the script with the chaincode process
exec "$BUILD_OUTPUT_DIR/chaincode" -peer.address="$(jq -r .peer_address "$ARTIFACTS/chaincode.json")"
将节点配置为使用外部构建器涉及到在定义外部构建器的core.yaml
中的chaincode配置块下添加externalBuilder元素。每个外部builderdefinition必须包含一个名称(用于日志记录)和指向包含构建器脚本的bin
目录的父目录的路径。
还可以提供调用外部构建器脚本时从节点传播的环境变量名的可选列表。
以下示例定义了两个外部生成器:
chaincode:
externalBuilders:
- name: my-golang-builder
path: /builders/golang
environmentWhitelist:
- GOPROXY
- GONOPROXY
- GOSUMDB
- GONOSUMDB
- name: noop-builder
path: /builders/binary
在本例中,“my golang builder”的实现包含在/builders/golang
目录中,其构建脚本位于/builders/golang/bin
中。当节点调用与“MyGolang builder”关联的任何构建脚本时,它将只传播白名单中环境变量的值。
注意:以下环境变量始终传播到外部生成器:
当存在externalBuilder
配置时,节点将按照提供的顺序遍历构建器列表,调用bin/detect
,直到成功完成一个构建器。如果没有构建器成功完成检测
,节点将回退到使用节点内部实现的旧Docker构建过程。这意味着外部构建器是完全可选的。
在上面的例子中,节点将尝试使用“my golang builder”,然后是“noop builder”,最后是节点内部构建过程。
作为Fabric 2.0引入的新生命周期的一部分,chaincode包格式从序列化的协议缓冲区消息更改为gzip压缩的POSIX磁带存档。使用peer lifecycle chaincode package
创建的链码包使用这种新格式。
生命周期链码包内容
生命周期链码包包含两个文件。第一个文件,code.tar.gz
是一个gzip压缩的POSIX磁带存档。此文件包含链码的源构件。由节点CLI创建的包将把链码实现源放在src
目录下,而链码元数据(比如CouchDB索引)放在META-INF
目录下。
第二个文件,metadata.json
是具有三个键的JSON文档:
type
:链码类型(如GOLANG、JAVA、NODE)path
:对于go-chaincode,是指向主链代码包的GOPATH或GOMOD相对路径;对于其他类型,未定义label
:用于生成包id的链码标签,在新的链码生命周期过程中,通过它来标识包。请注意,类型
和路径
字段仅由docker平台构建使用。
链码包和外部构建器
当链码包安装到节点时,在调用外部构建器之前不会处理code.tar.gz
和metadata.json
的内容,除了新的生命周期过程用来计算包id的label字段之外,这为用户提供了很大的灵活性,使他们能够打包将由外部构建器和启动器处理的源和元数据。
例如,可以构建一个自定义链码包,该包包含在code.tar.gz
中预先编译的链码实现,metadata.json
允许二进制构建包检测自定义包,验证二进制文件的哈希,并将程序作为链码运行。
另一个例子是一个链码包,它只包含状态数据库索引定义和外部启动器连接到正在运行的链码服务器所需的数据。在这种情况下,构建过程将简单地从过程中提取元数据,然后发布将其呈现给节点。
唯一的要求是code.tar.gz
只能包含常规的文件和目录条目,并且条目不能包含可能导致文件被写入到chaincode包的逻辑根之外的路径。
Fabric v2.0支持在Fabric之外部署和执行链码,这样用户就可以独立于节点来管理链码运行时。这有助于在Kubernetes等Fabric云部署上部署链码。不再在每个节点上构建和启动链码,链码现在可以作为一个服务运行,其生命周期在Fabric之外进行管理。此功能利用了Fabric v2.0外部构建器和启动器功能,使操作员能够通过程序扩展节点来构建、启动和发现链码。在阅读本主题之前,您应该熟悉外部构建器和启动器内容。
在外部构建器可用之前,链码包内容要求是一组特定语言的源代码文件,可以作为链码二进制文件构建和启动。新的外部构建和启动程序功能现在允许用户有选择地定制构建过程。关于将链码作为外部服务运行,构建过程允许您指定正在运行链码的服务器的端点信息。因此,该包只包含外部运行的链码服务器端点信息和用于安全连接的TLS构件。TLS是可选的,但强烈建议用于除简单测试环境以外的所有环境。
在Fabric v2.0链码生命周期中,链码以.tar.gz
格式打包和安装。以下myccpackage.tgz
档案展示了所需的结构:
$ tar xvfz myccpackage.tgz
metadata.json
code.tar.gz
链码包应该用于向外部构建器和启动程序进程提供两条信息
bin/detect
部分描述了一种使用metadata.json
文件connection.json
文件放在发布目录中。bin/run
部分描述了connection.json
文件收集上述信息有很大的灵活性。外部构建器和启动程序示例脚本中的示例脚本演示了一种提供信息的简单方法。作为灵活性的一个例子,考虑打包couchdb索引文件(请参见将索引添加到chaincode文件夹)。下面的示例脚本描述了将文件打包到code.tar.gz中的方法。
tar cfz code.tar.gz connection.json metadata
tar cfz $1-pkg.tgz metadata.json code.tar.gz
在本节中,我们将介绍所需的配置
connection.json
发布目录中的文件修改节点core.yaml以包含externalBuilder
假设脚本位于bin
目录中的节点上,如下所示
<fully qualified path on the peer's env>
└── bin
├── build
├── detect
└── release
修改节点core.yaml
文件的chaincode
节以包含externalBuilders
配置元素:
externalBuilders:
- name: myexternal
path: <fully qualified path on the peer's env>
外部生成器和启动程序示例脚本
为了帮助理解每个脚本需要包含哪些内容才能将链码作为外部服务使用,本节包含bin/detect
、bin/build
、bin/release
和bin/run
脚本的示例。
注意:这些示例使用jq
命令来parse json
。您可以运行jq--version
来检查是否安装了它。否则,请安装jq或适当地修改脚本。
bin/detect
bin/detect
脚本负责确定是否应该使用buildpack来构建链码包并启动它。对于作为外部
服务的链码,示例脚本在metadata.json
文件:
{
"path":"","type":"external","label":"mycc"}
节点使用两个参数调用detect:
bin/detect CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR
示例bin/detect
脚本可以包含:
#!/bin/bash
set -euo pipefail
METADIR=$2
#check if the "type" field is set to "external"
if [ "$(jq -r .type "$METADIR/metadata.json")" == "external" ]; then
exit 0
fi
exit 1
bin/build
对于作为外部服务的chaincode,示例构建脚本假定chaincode包的code.tar.gz
文件包含connection.json
,它只是将其复制到BUILD_OUTPUT_DIR
。节点使用三个参数调用构建脚本:
bin/build CHAINCODE_SOURCE_DIR CHAINCODE_METADATA_DIR BUILD_OUTPUT_DIR
示例bin/build
脚本可以包含:
#!/bin/bash
set -euo pipefail
SOURCE=$1
OUTPUT=$3
#external chaincodes expect connection.json file in the chaincode package
if [ ! -f "$SOURCE/connection.json" ]; then
>&2 echo "$SOURCE/connection.json not found"
exit 1
fi
#simply copy the endpoint information to specified output location
cp $SOURCE/connection.json $OUTPUT/connection.json
if [ -d "$SOURCE/metadata" ]; then
cp -a $SOURCE/metadata $OUTPUT/metadata
fi
exit 0
bin/release
对于作为外部服务的链码,bin/release
脚本负责提供connection.json
通过将其放在RELEASE_OUTPUT_DIR
中,将其发送到节点。这个connection.json
文件具有以下JSON结构
例如:
{
"address": "your.chaincode.host.com:9999",
"dial_timeout": "10s",
"tls_required": "true",
"client_auth_required": "true",
"client_key": "-----BEGIN EC PRIVATE KEY----- ... -----END EC PRIVATE KEY-----",
"client_cert": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----",
"root_cert": "-----BEGIN CERTIFICATE---- ... -----END CERTIFICATE-----"
}
如bin/build
部分所述,此示例假定chaincode包直接包含构建脚本复制到BUILD_OUTPUT_DIR
的connection.json
文件。节点使用两个参数调用发布脚本:
bin/release BUILD_OUTPUT_DIR RELEASE_OUTPUT_DIR
示例bin/release
脚本可以包含:
#!/bin/bash
set -euo pipefail
BLD="$1"
RELEASE="$2"
if [ -d "$BLD/metadata" ]; then
cp -a "$BLD/metadata/"* "$RELEASE/"
fi
#external chaincodes expect artifacts to be placed under "$RELEASE"/chaincode/server
if [ -f $BLD/connection.json ]; then
mkdir -p "$RELEASE"/chaincode/server
cp $BLD/connection.json "$RELEASE"/chaincode/server
#if tls_required is true, copy TLS files (using above example, the fully qualified path for these fils would be "$RELEASE"/chaincode/server/tls)
exit 0
fi
exit 1
目前,链码作为一种外部服务模型只被GO-chaincode-shim支持。在Fabric v2.0中,GO-shim-API添加了一个ChaincodeServer
类型,开发人员应该使用它来创建chaincode服务器。调用
和查询
API不受影响。开发者应该写信给shim.ChaincodeServerAPI
,然后构建链码并在所选的外部环境中运行它。下面是一个简单的链码程序示例来说明这种模式:
package main
import (
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
)
// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}
func (s *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
// init code
}
func (s *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// invoke code
}
//NOTE - parameters such as ccid and endpoint information are hard coded here for illustration. This can be passed in in a variety of standard ways
func main() {
//The ccid is assigned to the chaincode on install (using the “peer lifecycle chaincode install ” command) for instance
ccid := "mycc:fcbf8724572d42e859a7dd9a7cd8e2efb84058292017df6e3d89178b64e6c831"
server := &shim.ChaincodeServer{
CCID: ccid,
Address: "myhost:9999"
CC: new(SimpleChaincode),
TLSProps: shim.TLSProperties{
Disabled: true,
},
}
err := server.Start()
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
将链码作为外部服务运行的关键是使用shim.ChaincodeServer
。 这将使用新的shim APIshim.ChaincodeServer
链码服务属性如下所述:
peer lifecycle chaincode install
CLI命令返回的已安装链码关联的CCID。这可以在安装后使用“peer lifecycle chaincode queryinstalled”命令获得。然后构建适合您的代码环境。
当GO链码准备好进行部署时,您可以按照打包链码部分中的说明打包链码,并按照Fabric链码生命周期概念主题中的说明部署链码。
创建在编写要作为外部服务节运行的链码时指定的链码。在您选择的环境中运行构建的可执行文件,例如Kubernetes,或者直接作为节点上的进程运行。
使用此链码作为外部服务模型,不再需要在每个节点上安装链码。将chaincode端点部署到节点并运行chaincode后,您可以继续将chaincode定义提交到通道并调用chaincode的正常过程。
超级账本结构代码应使用供应商提供的包github.com/pkg/errors代替Go提供的标准错误类型。此软件包允许生成和显示带有错误消息的堆栈跟踪。
github.com/pkg/errors应该用来代替所有fmt.Errorf()
或errors.New()
。使用此包将生成一个调用堆栈,该堆栈将附加到错误消息中。
使用这个包很简单,只需要对代码进行简单的调整。
首先,您需要导入github.com/pkg/errors。
接下来,更新代码生成的所有错误,以使用其中一个错误创建函数(errors.New(), errors.Errorf(), errors.WithMessage(), errors.Wrap(), errors.Wrapf())
注意:有关可用错误创建函数的完整文档,请参阅https://godoc.org/github.com/pkg/errors。另外,请参阅下面的“通用指南”部分,以了解有关使用Fabric代码包的更具体的指南。
最后,将任何logger或fmt.Printf()调用的格式化指令从%s
更改为%+v
,以打印调用堆栈和错误消息。
下面的示例程序清楚地演示了如何使用该包:
package main
import (
"fmt"
"github.com/pkg/errors"
)
func wrapWithStack() error {
err := createError()
// do this when error comes from external source (go lib or vendor)
return errors.Wrap(err, "wrapping an error with stack")
}
func wrapWithoutStack() error {
err := createError()
// do this when error comes from internal Fabric since it already has stack trace
return errors.WithMessage(err, "wrapping an error without stack")
}
func createError() error {
return errors.New("original error")
}
func main() {
err := createError()
fmt.Printf("print error without stack: %s\n\n", err)
fmt.Printf("print error with stack: %+v\n\n", err)
err = wrapWithoutStack()
fmt.Printf("%+v\n\n", err)
err = wrapWithStack()
fmt.Printf("%+v\n\n", err)
}
在peer
和orderer
中的登录由common/flogging
包提供。此程序包支持
所有日志当前都定向到stderr
。为用户和开发人员提供了按严重性对日志进行全局和日志记录级别的控制。目前对于每种严重程度级别提供的信息类型没有正式的规则。在提交bug报告时,开发人员可能希望查看到调试级别的完整日志。
在打印得很好的日志中,日志级别由颜色和四个字符代码表示,例如,“ERRO”表示错误,“DEBU”表示调试等。在日志上下文中,logger是开发人员给相关消息组指定的任意名称(字符串)。在下面这个漂亮的示例中,loggers legrmgmt
、kvlegder
和peer
正在生成日志。
2018-11-01 15:32:38.268 UTC [ledgermgmt] initialize -> INFO 002 Initializing ledger mgmt
2018-11-01 15:32:38.268 UTC [kvledger] NewProvider -> INFO 003 Initializing ledger provider
2018-11-01 15:32:38.342 UTC [kvledger] NewProvider -> INFO 004 ledger provider Initialized
2018-11-01 15:32:38.357 UTC [ledgermgmt] initialize -> INFO 005 ledger mgmt initialized
2018-11-01 15:32:38.357 UTC [peer] func1 -> INFO 006 Auto-detected peer address: 172.24.0.3:7051
2018-11-01 15:32:38.357 UTC [peer] func1 -> INFO 007 Returning peer0.org1.example.com:7051
在运行时可以创建任意数量的记录器,因此没有日志记录器的“主列表”,日志控制结构无法检查日志记录器是否实际存在或将存在。
peer
和orderer
命令的日志记录级别由日志规范控制,该规范通过FABRIC_LOGGING_SPEC
环境变量设置。
完整的日志级别规范的格式为
[<logger>[,<logger>...]=]<level>[:[<logger>[,<logger>...]=]<level>...]
日志严重性级别是使用从中选择的不区分大小写的字符串指定的
FATAL | PANIC | ERROR | WARNING | INFO | DEBUG
日志级别本身被视为总体默认级别。否则,可以使用
<logger>[,<logger>...]=<level>
语法。规范示例:
info - Set default to INFO
warning:msp,gossip=warning:chaincode=info - Default WARNING; Override for msp, gossip, and chaincode
chaincode=info:msp,gossip=warning:warning - Same as above
peer
和orderer
命令的日志记录格式是通过FABRIC_LOGGING_FORMAT
环境变量控制的。可以将其设置为格式字符串,例如默认值
"%{color}%{time:2006-01-02 15:04:05.000 MST} [%{module}] %{shortfunc} -> %{level:.4s} %{id:03x}%{color:reset} %{message}"
以人类可读的控制台格式打印日志。也可以将其设置为json
,以JSON格式输出日志。
链码日志记录是链码开发人员的责任
作为独立执行的程序,用户提供的链码在技术上也可以在stdout/stderr上产生输出。虽然对“devmode”很有用,但这些通道通常在生产网络上被禁用,以减少损坏或恶意代码造成的滥用。但是,通过CORE_VM_DOCKER_ATTACHSTDOUT=true配置选项,即使是节点管理的容器(例如“netmode”)也可以启用此输出。
一旦启用,每个链码将接收由其container-id键控的日志通道。写入stdout或stderr的任何输出都将以每行为单位与节点的日志集成。不建议在生产中启用此功能。
未转发到节点容器的Stdout和stderr可以使用容器平台的标准命令从链码容器中查看。
docker logs <chaincode_container_id>
kubectl logs -n <namespace> <pod_name>
oc logs -n <namespace> <pod_name>
结构支持使用TLS在节点之间进行安全通信。TLS通信可以使用单向(仅限于服务器)和双向(服务器和客户端)身份验证。
节点既是TLS服务器又是TLS客户端。当另一个节点、应用程序或CLI连接到它时,它是前者;当它连接到另一个节点或排序程序时,则是前者。
要在节点上启用TLS,请设置以下配置属性:
peer.tls.enabled
=true
peer.tls.cert.file
=包含TLS服务器证书的文件的完全限定路径peer.tls.key.file
=包含TLS服务器私钥的文件的完全限定路径peer.tls.rootcert.file
=包含颁发TLS服务器证书的证书颁发机构(CA)的证书链的文件的完全限定路径默认情况下,当节点上启用TLS时,TLS客户端身份验证将关闭。这意味着节点在TLS握手期间不会验证客户端(另一个节点、应用程序或CLI)的证书。要在节点上启用TLS客户端身份验证,请设置节点配置属性peer.tls.clientAuthRequired
为true
并设置peer.tls.clientRootCAs.files
属性设置为CA链文件,其中包含为组织的客户端颁发TLS证书的CA证书链。
默认情况下,节点在充当TLS服务器和客户端时将使用相同的证书和私钥对。要在客户端使用不同的证书和私钥对,请设置peer.tls.clientCert.file
和peer.tls.clientKey.file
配置属性分别指向客户端证书和密钥文件的完全限定路径。
还可以通过设置以下环境变量来启用具有客户端身份验证的TLS:
CORE_PEER_TLS_ENABLED
=true
CORE_PEER_TLS_CERT_FILE
=服务器证书的完全限定路径CORE_PEER_TLS_KEY_FILE
=服务器私钥的完全限定路径CORE_PEER_TLS_ROOTCERT_FILE
=CA链文件的完全限定路径CORE_PEER_TLS_CLIENTAUTHREQUIRED
=true
CORE_PEER_TLS_CLIENTROOTCAS_FILES
=CA链文件的完全限定路径CORE_PEER_TLS_CLIENTCERT_FILE
=客户端证书的完全限定路径CORE_PEER_TLS_CLIENTKEY_FILE
=客户端密钥的完全限定路径在节点上启用客户端身份验证时,客户端需要在TLS握手期间发送其证书。如果客户端不发送证书,握手将失败,节点将关闭连接。
当节点加入通道时,从通道的配置块读取通道成员的根CA证书链,并将其添加到TLS客户机和服务器根CA数据结构中。因此,点对点通信、点对点通信应该无缝工作。
要在排序节点上启用TLS,请设置以下排序节点配置属性:
General.TLS.Enabled
=true
General.TLS.PrivateKey
=包含服务器私钥的文件的完全限定路径General.TLS.Certificate
=包含服务器证书的文件的完全限定路径General.TLS.RootCAs
=包含颁发TLS服务器证书的CA的证书链的文件的完全限定路径默认情况下,TLS客户端身份验证在orderer上是关闭的,peer也是如此。要启用TLS客户端身份验证,请设置以下配置属性:
General.TLS.ClientAuthRequired
=true
General.TLS.ClientRootCAs
=包含颁发TLS服务器证书的CA的证书链的文件的完全限定路径还可以通过设置以下环境变量来启用具有客户端身份验证的TLS:
ORDERER_GENERAL_TLS_ENABLED
=true
ORDERER_GENERAL_TLS_PRIVATEKEY
=包含服务器私钥的文件的完全限定路径ORDERER_GENERAL_TLS_CERTIFICATE
=包含服务器证书的文件的完全限定路径ORDERER_GENERAL_TLS_ROOTCAS
=包含颁发TLS服务器证书的CA的证书链的文件的完全限定路径ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED
=true
ORDERER_GENERAL_TLS_CLIENTROOTCAS
=包含颁发TLS服务器证书的CA的证书链的文件的完全限定路径对启用TLS的普通节点运行节点CLI命令时,必须设置以下环境变量:
CORE_PEER_TLS_ENABLED
=true
CORE_PEER_TLS_ROOTCERT_FILE
=包含颁发TLS服务器证书的CA的证书链的文件的完全限定路径如果远程服务器上也启用了TLS客户端身份验证,则除了上述变量外,还必须设置以下变量:
CORE_PEER_TLS_CLIENTAUTHREQUIRED
=true
CORE_PEER_TLS_CLIENTCERT_FILE
=客户端证书的完全限定路径CORE_PEER_TLS_CLIENTKEY_FILE
=客户端私钥的完全限定路径当运行连接到排序服务的命令时,如peer channel
如果在排序程序上启用了TLS客户端身份验证,则还必须指定以下参数:
在调试TLS问题之前,建议在TLS客户端和服务器端启用GRPC debug
以获取更多信息。要启用GRPC debug
,请将环境变量FABRIC_LOGGING_SPEC
设置为包括GRPC=debug
。例如,要将默认日志记录级别设置为INFO,将GRPC日志记录级别设置为DEBUG
,请将日志规范设置为GRPC=debug:info
。
如果在客户端看到错误消息remote error:tls:bad certificate
,这通常意味着tls服务器已启用客户端身份验证,而服务器没有收到正确的客户端证书,或者收到了不信任的客户端证书。请确保客户端正在发送其证书,并且该证书已由普通节点或排序节点信任的某个CA证书签名。
如果在链码日志中看到错误消息remote error:tls:bad certificate
,请确保您的链码是使用Fabric v1.1或更高版本提供的chaincode填充程序构建的。
听众:Raft排序节点管理员
有关排序的概念以及受支持的排序服务实现(包括Raft)如何在高层工作的概述,请查看我们关于排序服务的概念性文档。
要了解设置排序节点的过程(包括创建本地MSP和创建genesis块),请查看我们关于设置ordering节点的文档。
虽然每个Raft节点都必须添加到系统通道中,但不需要将节点添加到每个应用程序通道中。此外,您可以在不影响其他节点的情况下动态地从通道中移除和添加节点,这一过程在下面的重新配置部分中描述。
Raft节点使用TLS pinning相互识别,因此为了模拟Raft节点,攻击者需要获取其TLS证书的私钥。因此,如果没有有效的TLS配置,就不可能运行Raft节点。
Raft集群分为两个平面:
回想一下,每个通道都有自己运行的Raft协议实例。因此,Raft节点必须通过将其服务器和客户端TLS证书(PEM
格式)添加到channel config中来引用它所属的每个通道的配置。这样可以确保当其他节点接收到来自它的消息时,它们可以安全地确认发送消息的节点的身份。
以下部分来自configtx.yaml
显示通道中的三个Raft节点(也称为“共识者”):
Consenters:
- Host: raft0.example.com
Port: 7050
ClientTLSCert: path/to/ClientTLSCert0
ServerTLSCert: path/to/ServerTLSCert0
- Host: raft1.example.com
Port: 7050
ClientTLSCert: path/to/ClientTLSCert1
ServerTLSCert: path/to/ServerTLSCert1
- Host: raft2.example.com
Port: 7050
ClientTLSCert: path/to/ClientTLSCert2
ServerTLSCert: path/to/ServerTLSCert2
注意:排序节点将作为共识者列在系统通道以及他们加入的任何应用程序通道中。
创建channel config块时,configtxgen
工具读取TLS证书的路径,并用证书的相应字节替换路径。
本地配置
orderer.yaml
有两个与Raft排序节点相关的配置部分:
集群,它决定TLS通信配置。以及共识,它决定了预写日志和快照的存储位置。
集群参数:
默认情况下,Raft服务与面向客户端的服务器(用于发送事务或拉块)在同一个gRPC服务器上运行,但可以将其配置为具有单独端口的单独gRPC服务器。
这对于需要由组织CA颁发的TLS证书(但仅由集群节点用于彼此通信)以及公共TLS CA为面向客户端的API颁发的TLS证书的情况非常有用。
ClientCertificate
,ClientPrivateKey
:客户端TLS证书的文件路径和对应的私钥。ListenPort
:集群监听的端口。如果为空,则端口与排序节点常规端口相同(常规.listenPort)ListenAddress
:集群服务正在侦听的地址。ServerCertificate
,ServerPrivateKey
:当集群服务在单独的gRPC服务器(不同的端口)上运行时使用的TLS服务器证书密钥对。SendBufferSize
:控制出口缓冲区中的消息数。注意:ListenPort
、ListenAddress
、ServerCertificate
、ServerPrivateKey
必须一起设置或取消设置。如果未设置,则从general TLS部分继承,例如general.tls.{privateKey, certificate}
。
general.cluster
还有一些隐藏的配置参数,可用于进一步微调群集通信或复制机制:
DialTimeout
,RPCTimeout
:指定创建连接和建立流的超时。ReplicationBufferSize
:可为每个内存缓冲区分配的最大字节数,用于从其他群集节点进行块复制。每个通道都有自己的内存缓冲区。默认为20971520
,即20MB
。PullTimeout
:排序节点在中止之前等待接收到块的最长持续时间。默认为5秒。ReplicationRetryTimeout
:排序节点在两次连续尝试之间等待的最长持续时间。默认为5秒。ReplicationBackgroundRefreshInterval
:连续两次尝试复制此节点已添加到的现有通道或此节点过去未能复制的通道之间的时间。默认为5分钟。TLSHandshakeTimeShift
:如果排序节点的TLS证书过期且未及时更换(参见下面的TLS证书轮换),则无法建立它们之间的通信,并且无法向排序服务发送新的交易。为了从这样的场景中恢复,有可能使排序节点之间的TLS握手考虑向后移动的时间(配置为TLSHandshakeTimeShift
)。为了尽可能不具侵入性,此配置选项仅影响使用单独gRPC服务器进行集群内通信的排序节点。如果您的集群通过用于为客户机和节点提供服务的同一个gRPC服务器进行通信,则需要首先通过另外设置general.cluster.ListenPort
、general.cluster.ListenAddress
、ServerCertificate
和ServerPrivateKey
来重新配置排序程序,然后重新启动排序程序,以便新配置生效。共识参数:
WALDir
:etcd/raft
的预写日志的存储位置。每个通道都有自己的子目录,以通道ID命名。SnapDir
:指定存储etcd/raft
快照的位置。每个通道都有自己的子目录,以通道ID命名。还有一个隐藏的配置参数,可以通过将其添加到orderer.yaml
的共识部分来设置:
EvictionSuspicion
:通道逐出怀疑的累计时间段,触发节点从其他节点拉取块,并查看是否已从通道中逐出以确认其怀疑。如果怀疑被确认(被检查的块不包含节点的TLS证书),则节点将停止对该通道的操作。当一个节点不知道任何被选举的领导者,也不能被选为通道中的领导者时,它就会怀疑自己的通道被逐出。默认为10分钟。通道配置
除了(已经讨论过的)共识者之外,Raft通道配置还有一个选项
部分,与协议特定旋钮有关。当前无法在节点运行时动态更改这些值。必须重新配置并重新启动节点。
唯一的例外是SnapshotIntervalSize
,它可以在运行时进行调整。
注意:建议避免更改以下值,因为配置错误可能会导致根本无法选举领导人的状态(即,如果TickInterval
和ElectionTick
非常低)。无法选出领导人的情况是不可能解决的,因为领导人需要做出改变。由于这些危险,我们建议不要为大多数用例调整这些参数。
TickInterval
:两次Node.Tick
调用之间的时间间隔。ElectionTick
:两次选举之间必须通过的Node.Tick
调用数。也就是说,如果一个追随者在ElectionTick
过去之前没有收到现任领导人的任何信息,他将成为候选人并开始选举。ElectionTick
必须大于HeartbeatTick
。HeartbeatTick
:必须在心跳之间传递的Node.Tick
调用数。也就是说,一个领导者在每次HeartbeatTick
跳动时都会发送心跳信息来保持其领导地位。MaxInflightBlocks
:限制乐观复制阶段正在执行的追加块的最大数量。SnapshotIntervalSize
:定义每个快照的字节数。只要一次只添加或删除一个节点,Raft order就支持动态(即,当通道正在服务时)添加和删除节点。请注意,您的集群必须是可操作的,并且在您尝试重新配置它之前能够达成共识。例如,如果有三个节点,而两个节点出现故障,则无法重新配置集群以删除这些节点。类似地,如果在具有三个节点的通道中有一个失败的节点,则不应尝试旋转证书,因为这会导致第二个错误。通常,您不应尝试对Raft共识者进行任何配置更改,例如添加或删除共识者,或旋转共识者证书,除非所有共识者在线且健康。
如果您确实决定更改这些参数,建议您仅在维护周期内尝试这样的更改。当在只有几个节点的集群中尝试配置时,当一个节点关闭时,最有可能发生问题。例如,如果您的共识者集中有三个节点,其中一个节点关闭,则意味着您有三个节点中的两个处于活动状态。如果在这种状态下将集群扩展到四个节点,则四个节点中只有两个节点处于活动状态,这不是仲裁。第四个节点不能挂载,因为节点只能挂载到功能正常的集群上(除非集群的总大小是一到两个)。
因此,通过将一个由三个节点组成的集群扩展到四个节点(只有两个节点是活动的),您将被有效地卡住,直到原始脱机节点被恢复。
向Raft集群添加新节点的方法如下:
General.BootstrapFile
配置参数中config块的路径启动新的Raft节点。当节点本身正在运行时,可以将已在运行的节点(并已参与某些通道)添加到通道中。为此,只需将节点的证书添加到通道的通道配置中。节点将自动检测其添加到新通道中(此处的默认值为5分钟,但如果您希望节点更快地检测到新通道,请重新启动节点),并从通道中的排序节点处提取通道块,然后启动该链的Raft实例。
成功完成后,可以更新通道配置以包括新Raft排序节点的端点。
从Raft集群中移除节点的方法如下:
从特定通道中删除节点,但保持其为其他通道提供服务的方法是:
EvictionSuspicion
时间过后(默认为10分钟)自动检测其移除,并关闭其Raft实例。排序节点的TLS证书轮换
所有TLS证书都有一个由颁发者确定的到期日期。这些有效期可以从发行之日起10年到最短几个月,所以请向发行人咨询。在过期日期之前,您需要在节点本身和节点加入的每个通道(包括系统通道)上轮换这些证书。
对于节点参与的每个通道:
由于一个节点只能有一个TLS证书密钥对,因此在更新过程中,该节点将无法为尚未添加新证书的通道提供服务,从而降低了容错能力。因此,一旦开始证书轮换过程,就应该尽快完成。
如果出于某种原因,TLS证书的轮换已开始,但无法在所有通道中完成,建议将TLS证书旋转回原来的状态,并稍后尝试旋转。
证书过期相关身份验证
每当具有过期日期的标识(例如基于x509证书的标识)的客户机向排序节点发送交易时,排序节点将检查其身份是否已过期,如果已过期,则拒绝交易提交。
但是,可以通过启用orderer.yaml
中的General.Authentication.NoExpirationChecks
配置选项来配置排序节点忽略身份的过期。
只有在极端情况下才应这样做,即管理员的证书已过期,因此无法发送配置更新来用更新的证书替换管理员证书,因为由现有管理员签名的配置交易现在被拒绝,因为它们已过期。更新通道后,建议更改回默认配置,该配置将对身份强制执行过期检查。
有关操作服务的说明以及如何设置它,请查看我们关于操作服务的文档。
有关操作服务收集的度量的列表,请查看我们关于指标的参考资料。
虽然您优先考虑的指标与您的特定用例和配置有很大关系,但您可能需要监视两个指标:
consultus_etcdraft_is_leader
:标识集群中的哪个节点当前是leader。如果没有节点具有此集,则您已失去仲裁。consistence_etcdraft_data_persistent_duration
:指示对Raft集群的持久预写日志的写入操作需要多长时间。为了协议安全,消息必须持久地持久化,在可以与共识者集共享之前,在适当的情况下调用fsync
。如果该值开始上升,则该节点可能无法参与协商一致(这可能导致该节点和网络的服务中断)。对节点施加的压力越大,可能需要更改的某些参数就越多。与任何系统、计算机或机械一样,压力会导致性能下降。正如我们在概念文档中所指出的,Raft中的leader选举是在跟随节点在一定时间内没有接收到从leader携带数据的“heartbeat”消息或“append”消息时触发的。因为Raft节点跨通道共享同一通信层(这并不意味着它们共享数据-它们不共享!),如果Raft节点是许多通道中的共识者集的一部分,则可能需要延长触发选举所需的时间,以避免无意中的领导人选举。
注意:本文档假定您对通道配置更新交易具有高度的专业知识。由于迁移过程涉及多个通道配置更新交易,因此在未熟悉“将组织添加到通道”教程之前,不要尝试从Kafka迁移到Raft,该教程详细描述了通道更新过程。
对于希望从使用基于Kafka的排序服务过渡到基于Raft的排序服务的用户,v1.4.2或更高版本的节点允许通过网络中每个通道上的一系列配置更新交易来实现这一点。
本教程将在较高的层次上描述这个过程,在必要时调用特定的细节,而不是详细地显示每个命令。
在尝试迁移之前,请考虑以下事项:
移民分五个阶段进行。
在尝试迁移之前,您应该采取几个步骤。
元数据
配置的材料。注:所有通道应接收相同的Raft元数据
配置。有关这些字段的更多信息,请参阅Raft配置指南。注意:您可能会发现使用Raft共识协议引导一个新的排序网络是最简单的,然后从其配置中复制和修改一致性元数据部分。在任何情况下,您都需要(对于每个排序节点):
主机名
端口
服务器证书
客户端证书
V1_4_2
(或更高)。V1_4_2
(或更高)。进入维护模式
在将排序服务设置为维护模式之前,建议停止网络的节点和客户端。但是,让节点或客户机保持正常运行是安全的,因为偏差服务将拒绝它们的所有请求,它们的日志将充满良性但具有误导性的失败。
按照向通道添加组织教程中的过程,从系统通道开始,提取、转换和确定每个通道的配置范围。在这个步骤中,您唯一应该更改的字段是位于/Channel/Orderer/ConsensusType
的通道配置中。在通道配置的JSON表示中,这将是.channel_group.groups.Orderer.values.ConsensusType
。
ConsensusType(共识类型)
由三个值表示:类型
、元数据
和状态
,其中:
类型
为kafka
或etcdraft
(Raft)。此值只能在维护模式下更改。类型
为kafka,元数据
将为空,但如果ConsensusType
为etcdraft
,则必须携带有效的Raft元数据。更多信息请参见下文。状态
为STATE_NORMAL
,在迁移过程中为STATE_MAINTENANCE
。在通道配置更新的第一步中,只需将状态
从STATE_NORMAL
更改为STATE_MAINTENANCE
。请不要更改类型
或元数据
字段。注意,当前的类型应该是kafka
。
在维护模式下,正常交易、与迁移无关的配置更新以及来自用于检索新块的节点的传递
请求都将被拒绝。这样做是为了防止在迁移过程中需要备份和(如有必要)恢复节点,因为它们只在迁移成功完成后才接收更新。换言之,我们希望将排序服务备份点(这是下一步)保持在节点账本之前,以便能够在需要时执行回滚。但是,排序节点管理员可以发出传递
请求(他们需要能够这样做才能继续迁移过程)。
验证每个排序服务节点是否已在每个通道上进入维护模式。这可以通过获取最后一个config块并确保每个通道上的类型
、元数据
和状态
分别为kafka
、empty(回想一下没有kafka的元数据)和State_MAINTENANCE
来完成。
如果通道已成功更新,则排序服务现在可以备份了。
备份文件并关闭服务器
关闭所有排序节点、Kafka服务器和Zookeeper服务器。首先关闭排序服务节点很重要。然后,在允许Kafka服务将其日志刷新到磁盘(这通常需要大约30秒,但可能需要更长的时间,具体取决于您的系统),Kafka服务器应该关闭。在排序节点同时关闭Kafka代理可能会导致排序节点的文件系统状态比Kafka代理更新,这可能会阻止您的网络启动。
创建这些服务器的文件系统的备份。然后重新启动Kafka服务,然后重新启动排序服务节点。
在维护模式下切换到Raft
迁移过程的下一步是对每个通道进行另一次通道配置更新。在这个配置更新中,将类型切换到etcdraft
(对于Raft),同时保持状态为State_MAINTENANCE
,并填写元数据
配置。强烈建议所有通道上的元数据
配置都相同。如果您想用不同的节点建立不同的共识者集,您可以在系统重新启动到etcdraft
模式后重新配置元数据配置。提供相同的元数据对象,因此,提供相同的共识者集,意味着当节点重新启动时,如果系统通道形成仲裁并可以退出维护模式,则其他通道可能也可以这样做。向每个通道提供不同的共识者集可以导致一个通道成功地形成集群,而另一个通道将失败。
然后,通过拉取和检查每个通道的配置,验证每个排序服务节点已提交ConsensusType
更改配置更新。
注意:对于每个通道,更改ConsensusType
类型的交易必须是重新启动节点之前的最后一个配置交易(在下一步中)。如果在此步骤之后发生其他配置交易,则节点很可能在重新启动时崩溃,或导致未定义的行为。
重新启动并验证leader
注意:必须在重启后退出维护模式。
在每个通道上完成ConsensusType
更新后,停止所有排序服务节点,停止所有Kafka代理和Zookeepers,然后仅重新启动排序服务节点。它们应该作为Raft节点重新启动,每个通道形成一个集群,并在每个通道上选择一个领导者。
注意:由于基于Raft的排序服务需要排序节点之间的相互TLS,所以在您再次启动它们之前,还需要额外的配置,有关更多详细信息,请参阅“本地配置”一节。
重新启动过程完成后,确保通过检查节点日志(您可以看到下面要查找的内容)来验证是否在每个通道上选择了一个领导者。这将确认该过程已成功完成。
当一个领导者被选出时,日志将显示每个通道:
"Raft leader changed: 0 -> node-number channel=channel-name
node=node-number "
例如:
2019-05-26 10:07:44.075 UTC [orderer.consensus.etcdraft] serveRequest ->
INFO 047 Raft leader changed: 0 -> 1 channel=testchannel1 node=2
在这个例子中,node 2
报告了一个leader被通道testchannel1
的集群选择(leader是node 1
)。
退出维护模式
在每个通道上执行另一个通道配置更新(将配置更新发送到之前发送配置更新的同一个排序节点),将状态
从State_MAINTENANCE
切换到State_NORMAL
。像往常一样,从系统开始。如果它在排序系统通道上成功,那么迁移可能会在所有通道上成功。要进行验证,请从排序节点获取系统通道的最后一个配置块,验证状态现在是否为State_NORMAL
。为了完整性,请在每个排序节点上验证这一点。
完成此过程后,排序服务现在可以接受所有通道上的所有交易。如果您按照建议停止了节点和应用程序,现在可以重新启动它们。
如果在退出维护模式之前的迁移过程中出现问题,只需执行以下回滚过程。
共识类型
之前,将这些服务器的文件系统回滚到在维护模式下执行的备份。元数据
重试迁移。以下几种状态可能表示迁移失败:
STATE_NORMAL
模式失败。本文假设读者知道如何设置Kafka集群和ZooKeeper集成,并通过防止未经授权的访问来保证它们的安全性。本指南的唯一目的是确定您需要采取的步骤,以便让一组超级账本Fabric排序服务节点(OSN)使用您的Kafka群集,并为您的区块链网络提供排序服务。
有关排序节点者在网络和交易流中扮演的角色的信息,请查看我们的排序服务文档。
有关如何设置排序节点的信息,请参阅我们的设置排序节点文档。
有关配置Raft排序服务的信息,请参阅配置和操作Raft排序服务。
在Kafka中,每个通道映射到一个单独的分区主题。当OSN通过广播RPC接收交易时,它检查以确保广播客户端具有在通道上写入的权限,然后将这些交易中继(即生成)到Kafka中的相应分区。这个分区也被OSN使用,OSN将接收到的经验分组到本地块中,将它们保存在本地z中,并通过deliverpc将它们提供给接收客户端。有关低层次的详细信息,请参阅描述我们如何进行此设计的文档。图8是上述过程的示意图。
设K
和Z
分别为Kafka集群和ZooKeeper群集中的节点数:
然后按以下步骤进行:
configtxgen
,请编辑configtx.yaml
。或者,为系统通道的创世区块选择一个预设配置文件,以便:Orderer.OrdererType
设定kafka
。Orderer.Kafka.Brokers
以IP:port
符号包含集群中至少两个Kafka代理的地址。清单不必详尽无遗。(这些是您的引导代理。)Orderer.AbsoluteMaxBytes
字节(不包括头),这个值可以在configtx.yaml
中设置。让您在这里选择的值是A并记下它-–它将影响您在步骤6中配置Kafka代理的方式。configtxgen
。您在上面的步骤3和4中选择的设置是系统范围的设置,即它们适用于所有OSN的网络。记下创世区块的位置。unclean.leader.election.enable = false
:数据一致性是区块链环境中的关键。我们不能在同步副本集之外选择一个通道领导者,否则我们会冒覆盖上一个领导者产生的偏移量的风险,并因此重写排序节点产生的区块链。min.insync.replicas = M
:其中选择一个值M
,使1(参见default.replication.factor
以下)。当数据写入至少M
个副本(这些副本随后被视为同步并属于同步副本集或ISR)时,该数据被视为已提交。在任何其他情况下,写操作都会返回一个错误。然后:
- 如果多达
N-M
个副本(其中N个副本中的N
个副本)不可用,则操作将正常进行。
- 如果有更多副本不可用,Kafka将无法维护M的ISR集,因此它将停止接受写操作。阅读工作没有问题。当
M
个副本同步时,通道将再次变为可写。
default.replication.factor = N
:其中选择一个N
值,使N。复制因子N
意味着每个通道将其数据复制到N
个代理。这些是通道的ISR集合的候选对象。正如我们在最小同步副本以上部分,并非所有这些经纪人都必须随时可用。N应该被严格地设置为K
,因为如果少于N
个代理正在运行,则无法继续创建通道。因此,如果你设置N=K
,一个经纪人倒下意味着在区块链网络上不能创建新的通道-排序服务的崩溃容错是不存在的。
根据我们上面的描述,M
和N
的最小允许值分别为2和3。此配置允许继续创建新通道,并允许所有通道继续可写。
message.max.bytes
和replica.fetch.max.bytes
应设置为大于A
的值,即您在上面第4步中在Orderer.AbsoluteMaxBytes
中选择的值。添加一些缓冲区来考虑标头-–1 MiB就足够了。以下条件适用:Orderer.AbsoluteMaxBytes < replica.fetch.max.bytes <= message.max.bytes
(为了完整起见,我们注意到message.max.bytes
应该严格小于socket.request.max.bytes
,默认情况下设置为100mib。如果您希望块大于100mib,则需要在fabric/orderer/kafka/config.go
中编辑brokerConfig.Producer.MaxMessageBytes
中的硬编码值,并从源代码重建二进制文件。这是不可取的。)
log.retention.ms = -1
在排序服务添加对Kafka日志修剪的支持之前,您应该禁用基于时间的保留并防止段过期。(在撰写本文时,Kafka默认禁用了基于大小的保留(请参见log.retention.bytes
),因此不需要显式设置它。)orderer.yaml
中编辑General.BootstrapFile
,使其指向在上面步骤5中创建的创世区块。在执行此操作时,请确保该YAML文件中的所有其他键都已正确设置。orderer.yaml
文件中的Kafka.Retry
部分允许您调整metadata/producer/consumer请求的频率以及套接字超时。(这些都是您希望在kafka制作人或消费者身上看到的设置。)Kafka.Retry.ShortInterval
的总Kafka.Retry.ShortTotal
,然后每个Kafka.Retry.LongInterval
的总Kafka.Retry.LongTotal
,直到他们成功。请注意,在成功完成上述所有步骤之前,排序节点将无法写入或读取通道。orderer.yaml
中的Kafka.TLS
下设置密钥。首选邮件大小。在上面的步骤4中(参见步骤部分),您还可以通过设置Orderer.Batchsize.PreferredMaxBytes
钥匙。当处理相对较小的消息时,Kafka提供了更高的吞吐量;目标值不超过1 MiB。
使用环境变量覆盖设置。使用Fabric提供的示例Kafka和Zookeeper Docker镜像时(分别参见images/Kafka
和images/Zookeeper
),可以使用环境变量覆盖Kafka代理或Zookeeper服务器的设置。将配置密钥的点替换为下划线。例如,KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false
将允许您覆盖unclean.leader.election.enable
的默认值。这同样适用于OSN的本地配置,即可以在其中设置orderer.yaml
。例如,ORDERER_KAFKA_RETRY_SHORTINTERVAL=1s
允许您覆盖Orderer.Kafka.Retry.ShortInterval
的默认值。
Fabric使用sarama客户端库,并提供了一个支持Kafka 0.10到1.0的版本,但是仍然可以使用旧版本。
使用orderer.yaml
中的Kafka.Version
密钥,您可以配置使用哪个版本的Kafka协议来与Kafka集群的代理进行通信。Kafka代理向后兼容旧的协议版本。由于Kafka代理与旧协议版本的向后兼容性,将Kafka代理升级到新版本不需要更新Kafka.Version
密钥值,但是Kafka群集在使用旧协议版本时可能会受到性能损失。
在orderer.yaml
中将环境变量FABRIC_LOGGING_SPEC
设置为DEBUG
,并将Kafka.Verbose
设置为true
。
参考自官方文档
如有侵权,请联系作者删除,谢谢!
If there is infringement, please contact the author to delete, thank you!