Hyperledger Fabric 2.0 官方文档中文版 第8章 操作指南

Hyperledger Fabric 2.0 官方文档中文版 第8章 操作指南


总目录

第1章 引言
第2章 Hyperledger Fabric v2.0的新增功能
第3章 关键概念
第4章 入门
第5章 开发应用程序
第6章 教程(上)
第6章 教程(下)
第7章 部署生产网络
第8章 操作指南
第9章 升级到最新版本


8.操作指南

设置排序节点

在本主题中,我们将描述引导排序节点的过程。如果您想了解有关不同排序服务实现及其相对优缺点的更多信息,请查看我们关于排序的概念性文档

大体上,本主题将涉及几个相互关联的步骤:

  • 创建排序节点所属的组织(如果尚未创建)
  • 配置节点(使用orderer.yaml)
  • 为排序者系统通道创建创世区块
  • 引导排序程序

注意:本主题假设您已经从docker hub中提取了Hyperledger Fabric orderer镜像。

创建组织定义

与节点一样,所有排序节点必须属于必须在创建排序节点自身之前创建的组织。此组织有一个由成员服务提供者(MSP)封装的定义,该定义由专门为组织创建证书和MSP的证书颁发机构(CA)创建。

有关创建CA并使用它创建用户和MSP的信息,请参阅Fabric CA user’s guide(Fabric CA用户指南)。

配置节点

排序程序的配置是通过名为orderer.yamlyaml文件处理的。FABRIC_CFG_PATH环境变量用于指向已配置的orderer.yaml文件,该文件将提取文件系统上的一系列文件和证书。

要查看orderer.yaml示例,请查看fabric-samples github repo,在继续之前,应该仔细阅读和研究它。请特别注意以下几个值:

  • LocalMSPID-这是排序节点组织的CA生成的MSP的名称。您的排序者组织管理员将在此列出。
  • LocalMSPDir-本地MSP所在的文件系统中的位置。
  • # TLS enabledEnabled: false。在这里可以指定是否要启用TLS。如果将此值设置为true,则必须指定相关TLS证书的位置。请注意,对于Raft节点,这是必需的。
  • BootstrapFile-这是您将为这个排序服务生成的创世区块的名称。
  • BootstrapMethod-给出引导块的方法。目前,这只能是文件,其中指定了BootstrapFile中的文件。

如果要将此节点部署为集群的一部分(例如,作为Raft节点集群的一部分),请记下集群共识部分。

扫描二维码关注公众号,回复: 11902145 查看本文章

如果计划部署基于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

成员服务提供商(MSP)

该文档旨在提供有关MSPs的设置和最佳实践的详细信息。

成员服务提供商(MSP)是一个Hyperledger Fabric组件,它提供了成员操作的抽象。

特别是,MSP抽象出了颁发证书、验证证书和用户身份验证背后的所有加密机制和协议。MSP可以定义他们自己的身份概念,以及管理这些身份的规则(身份验证)和身份验证(签名生成和验证)。

Hyperledger Fabric区块链网络可由一个或多个MSP管理。这提供了成员操作的模块化,以及不同成员标准和体系结构之间的互操作性。

在本文的其余部分中,我们将详细介绍由Hyperledger Fabric支持的MSPs实现的设置,并讨论有关其使用的最佳实践。

MSP配置

要设置MSP的实例,需要在每个节点和排序节点本地指定其配置(以启用普通节点和排序节点签名),并在通道上为所有通道成员启用普通节点、排序节点、客户端身份验证和各自的签名验证(验证)。

首先,需要为每个MSP指定一个名称,以便在网络中引用该MSP(例如msp1org2org3.divA)。这是在通道中引用代表联盟、组织或组织部门的MSP的成员规则的名称。这也称为MSP标识符或MSP ID。每个MSP实例都要求MSP标识符是唯一的。例如,如果在系统通道生成中检测到两个具有相同标识符的MSP实例,排序程序设置将失败。

在MSP的默认实现中,需要指定一组参数来允许身份(证书)验证和签名验证。这些参数由RFC5280推导,包括:

  • 构成信任根的自签名(X.509)CA证书的列表
  • 一个X.509证书的列表,表示此提供程序考虑用于证书验证的中间CA;这些证书应该由信任根目录下的一个证书进行认证;中间CA是可选参数
  • 代表此MSP管理员的X.509证书列表,该证书具有指向信任根的一个CA证书的可验证证书路径;这些证书的所有者有权请求对此MSP配置进行更改(例如根CA、中间CA)
  • 此MSP的有效成员应包括在其X.509证书中的组织单位的列表;这是一个可选的配置参数,当多个组织利用相同的信任根和中间CA时使用,并为其成员保留了OU字段
  • 证书吊销列表(CRL)的列表,每个证书吊销列表(CRL)与列出的(中间或根)MSP证书颁发机构中的一个完全对应;这是一个可选参数
  • 构成TLS证书的TLS信任根的自签名(X.509)证书列表。
  • 表示此提供程序考虑的中间TLS CA的X.509证书的列表;这些证书应该由TLS信任根中的一个证书进行认证;中间CA是可选参数。

此MSP实例的有效标识需要满足以下条件:

  • 它们的形式是X.509证书,其中一个可验证的证书路径正好指向信任证书的根之一;
  • 它们不包括在任何CRL中;
  • 它们在X.509证书结构的OU字段中列出MSP配置的一个或多个组织单元。

有关当前MSP实现中身份有效性的更多信息,我们建议读者参考MSP身份有效性规则

除了与验证相关的参数外,要使MSP能够在其实例化的节点上进行签名或身份验证,还需要指定:

  • 节点用于签名的签名密钥(当前仅支持ECDSA密钥),以及
  • 节点的X.509证书,它是此MSP的验证参数下的有效标识。

需要注意的是,MSP标识永远不会过期;只能通过将其添加到适当的crl中来撤销它们。另外,目前还不支持强制撤销TLS证书。


如何生成MSP证书及其签名密钥?

要生成X.509证书以提供MSP配置,应用程序可以使用Openssl。我们强调,在Hyperledger Fabric中,不支持包括RSA密钥在内的证书。

或者,可以使用cryptogen工具,其操作在“入门”中进行了说明。

Hyperledger Fabric CA还可用于生成配置MSP所需的密钥和证书。

普通节点和排序节点的MSP设置

要设置本地MSP(针对普通节点或排序节点),管理员应创建一个包含六个子文件夹和一个文件的文件夹(例如$MY_PATH/mspconfig):

  • 文件夹adminicert包含PEM文件,每个文件对应一个管理员证书
  • 一个文件夹cacerts,其中包含对应于根CA证书的PEM文件
  • (可选)一个文件夹intermediatecerts,包含每个对应于中间CA证书的PEM文件
  • (可选)文件config.yaml配置支持的组织单位和身份分类(请参阅下面的相应部分)。
  • (可选)包含所考虑的crls的文件夹CRL
  • 一个keystore文件夹,其中包含一个带有节点签名密钥的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"

上面的示例声明了两个组织单元标识符:commercialadministrators。如果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.ClientOUIdentifierNodeOUs.AdminOUIdentifierNodeOUs.PeerOUIdentifierNodeOUs.OrdererOUIdentifier)键的属性来定义客户机(admin,peer,order)组织单位标识符:

  • OrganizationalUnitIdentifier:x509证书需要包含的OU值,才能将其视为客户端(分别为管理员、普通节点和排序节点)。如果此字段为空,则不应用分类。
  • Certificate:(可选)将其设置为CA或中间CA证书的路径,在该证书下应验证客户端(普通节点、管理员或排序节点)身份。该字段是相对于MSP根文件夹的。只能指定一个证书。如果不设置此字段,则将在组织的MSP配置中定义的任何CA下验证标识,如果将来需要添加其他CA或中间证书,则可能需要这样做。

请注意,如果NodeOUs.ClientOUIdentifier部分(NodeOUs.AdminOUIdentifierNodeOUs.PeerOUIdentifierNodeOUs.OrdererOUIdentifier)丢失,则不应用该分类。如果NodeOUs.Enable设置为true且未定义分类键,则假定身份分类被禁用。

身份可以使用组织单位分类为客户端、管理员、普通节点或排序节点。这四种分类是相互排斥的。在将身份分类为客户端或普通节点之前,需要启用1.1通道功能。需要启用1.4.3通道功能,才能将身份分类为管理员或排序服务。

分类允许将身份分类为管理员(并执行管理员操作),而无需将证书存储在MSP的admincerts文件夹中。相反,admincerts文件夹可以保持为空,并且可以通过向administrationou注册标识来创建administrators。admincerts文件夹中的证书仍将向其持有者授予管理员角色,前提是它们拥有客户端或管理OU。

通道MSP设置

在系统生成时,需要指定网络中出现的所有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之间进行一对一的映射。如果选择不同类型的映射,则需要考虑以下几点:

  • 一个组织雇佣不同的MSP。这对应于一个组织的情况,包括各种部门,每个部门由其MSP代表,或者出于管理独立性的原因,或者出于隐私的原因。在这种情况下,普通节点只能由一个MSP拥有,并且不会将具有来自其他MSP的身份的普通节点识别为同一组织的普通节点。这意味着普通节点可以通过gossip组织与属于同一细分的一组普通节点共享数据,而不是与组成实际组织的全套提供者共享数据。
  • 多个组织使用单个MSP。这对应于一个由类似成员体系结构管理的组织联盟的情况。这里需要知道的是,普通节点会将组织范围内的消息传播给在同一MSP下具有标识的普通节点,而不管它们是否属于同一个实际组织。这是对MSP定义的粒度和/或节点配置的限制。

2) 一个组织有不同的部门(比如说组织单位),它想授予不同通道的访问权。

有两种处理方法:

  • 定义一个MSP以容纳所有组织成员的成员资格。MSP的配置将包括根ca、中间ca和管理证书的列表;成员身份将包括成员所属的组织单元(OU)。然后可以定义策略来捕获特定OU的成员,这些策略可以构成通道的读/写策略或链码的背书策略。这种方法的一个局限性是,gossip节点将其本地MSP下具有成员身份的普通节点视为同一个组织的成员,并因此与他们闲聊组织范围内的数据(例如他们的状态)。
  • 定义一个MSP来代表每个部门。这将涉及为每个部门指定根CA、中间CA和管理证书的一组证书,以便在MSP之间没有重叠的证书路径。这意味着,例如,每个细分使用不同的中间CA。这里的缺点是管理多个msp而不是一个msp,但是这回避了前面方法中存在的问题。还可以通过利用MSP配置的OU扩展为每个部门定义一个MSP。

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的身份验证:

  • 重新配置MSP,使其不再将该中间CA的证书包含在受信任的中间CA证书列表中。对于本地配置的MSP,这意味着将从intermediatecerts文件夹中删除此CA的证书。
  • 重新配置MSP以包含由trust根生成的CRL,该CRL将退出所述的中间CA的证书。

在当前的MSP实现中,我们只支持方法(1),因为它更简单,不需要将不再被考虑的中间CA列入黑名单。

6) CAs和TLS CAs

MSP标识的根CA和MSP TLS证书的根CA(以及相对中间CA)需要在不同的文件夹中声明。这是为了避免不同类别证书之间的混淆。不禁止对MSP标识和TLS证书重用相同的ca,但最佳实践建议在生产中避免这种情况。

使用硬件安全模块(HSM)

Fabric节点执行的加密操作可以委托给硬件安全模块(HSM)。HSM保护您的私钥并处理加密操作,允许您的普通界节点和排序节点在不公开其私钥的情况下签署和背书交易。如果您要求遵守政府标准,如FIPS 140-2,有多个认证的HSM可供选择。

Fabric目前利用PKCS11标准与HSM通信。

配置HSM

要在Fabric节点上使用HSM,您需要更新节点配置文件(如core.yaml或orderer.yaml)的bccsp(加密服务提供程序)部分。在bccsp部分,您需要选择PKCS11作为提供程序,并输入要使用的PKCS11库的路径。您还需要提供为加密操作创建的令牌的LabelPIN。您可以使用一个令牌来生成和存储多个密钥。

预构建的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设置网络

如果使用HSM部署Fabric节点,则需要在HSM中生成并存储私钥,而不是将其存储在节点的本地MSP文件夹的keystore文件夹中。MSP的keystore文件夹将保持为空。相反,Fabric节点将使用signcerts文件夹中签名证书的主题密钥标识符从HSM内部检索私钥。创建节点MSP文件夹的过程会有所不同,具体取决于您是否正在使用自己的Fabric证书颁发机构(CA)。

开始之前

在将Fabric节点配置为使用HSM之前,应完成以下步骤:

  • 在您的HSM服务器上创建了一个分区,并记录了分区的LabelPIN
  • 按照HSM提供商文档中的说明配置与HSM服务器通信的HSM客户端。

使用带有Fabric CA的HSM

通过对CA服务器配置文件进行与对普通节点或排序节点所做的相同的编辑,可以将结构CA设置为使用HSM。因为您可以使用Fabric CA在HSM中生成密钥,所以创建本地MSP文件夹的过程非常简单。使用以下步骤:

  • 修改Fabric CA服务器配置文件的bccsp部分,并指向为HSM创建的LabelPIN。当Fabric CA服务器启动时,生成私钥并将其存储在HSM中。如果您不关心公开CA签名证书,则可以跳过此步骤,只为普通节点或排序节点配置HSM,如下面的步骤所述。
  • 使用Fabric CA客户端向CA注册普通节点或排序节点标识。
  • 在部署具有HSM支持的普通节点或排序节点之前,需要通过将节点的私钥存储在HSM中来注册节点标识。编辑Fabric CA client配置文件的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并提供LabelPIN。另外,编辑mspConfigPath(对于普通节点)或LocalMSPDir(对于排序节点)的值,以指向在上一步中使用Fabric CA客户端生成的MSP文件夹。现在普通节点或排序节点已配置为使用HSM,当您启动该节点时,它将能够使用HSM保护的私钥对事务进行签名和背书。

将HSM与您自己的CA一起使用

如果您使用自己的证书颁发机构来部署结构组件,则可以使用以下步骤使用HSM:

  • 配置CA以使用PKCS11与HSM通信并创建LabelPIN。然后使用CA为每个节点生成私钥和签名证书,私钥在HSM中生成。
  • 使用您的CA来构建普通或排序节点MSP文件夹。将步骤1中生成的签名证书放在signcerts文件夹中。您可以将keystore文件夹保留为空。
  • 要将普通节点或排序节点配置为使用HSM,同样地,请将普通节点或排序程序配置文件的bccsp部分更新为使用PKCS11和并提供LabelPIN。编辑mspConfigPath(对于普通节点)或LocalMSPDir(对于排序节点)的值,以指向在上一步中使用Fabric CA客户端生成的MSP文件夹。现在普通节点或排序节点已配置为使用HSM,当您启动该节点时,它将能够使用HSM保护的私钥对交易进行签名和背书。

通道配置(configtx)

Hyperledger Fabric区块链网络的共享配置存储在集合配置交易中,每个通道一个。每个配置交易通常被简称为configtx。

通道配置具有以下重要属性:

  • Versioned:配置的所有元素都有一个关联的版本,该版本在每次修改时都是高级的。此外,每个提交的配置都会收到一个序列号。
  • Permissioned:配置的每个元素都有一个关联的策略,该策略控制是否允许对该元素进行修改。任何拥有先前configtx副本(无其他信息)的人都可以根据这些策略验证新配置的有效性。
  • Versioned:根配置组包含子组,层次结构的每个组都有关联的值和策略。这些策略可以利用层次结构从较低级别的策略派生一个级别的策略。

配置的剖析

配置作为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是封送ConfigUpdateEnvelopeConfigUpdateEnvelope定义如下:

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时,排序程序通过执行以下操作来计算结果配置

  • 验证通道idread_setread_set中的所有元素必须存在于给定的版本中。
  • 通过收集write_set中所有不在read_set中同一版本的元素来计算更新集。
  • 验证更新集中的每个元素是否将元素更新的版本号正好递增1。
  • 验证附加到ConfigUpdateDevelope的签名集是否满足更新集中每个元素的mod_policy
  • 通过将更新集应用于当前配置来计算配置的新的完整版本。
  • 将新配置写入一个ConfigEnvelope,该信封包括作为last_UPDATE字段的config_UPDATE和在config字段中编码的新配置,以及递增的序列值。
  • 将新的ConfigEnvelope写入CONFIG类型的Envelope,并最终将其作为新配置块中的唯一交易写入。

当普通节点(或任何其他要传递的接收方)收到此配置块时,它应该通过将last_update消息应用于当前配置并验证排序计算的config字段是否包含正确的新配置来验证配置是否正确。

允许的配置组和值

任何有效配置都是以下配置的子集。这里我们使用peer.<MSG>来定义一个ConfigValue,它的值字段是fabric-protos/peer/configuration.proto中定义的名为<MSG>的封送原型消息。common.<MSG>msp.<MSG>orderer.<MSG>的符号类似,但它们的消息分别在fabric-protos/common/configuration.protofabric-protos/msp/mspconfig.protofabric-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元素。此组定义一个包含该组织的加密标识信息的单个参数MSPorderer组的决定了排序节点的工作方式。每个通道都有,所以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表示四个可接受的角色之一:memberadminclientpeer

以下是一些有效主体的示例:

  • 'Org0.admin':Org0 MSP的任何管理员
  • 'Org1.member':Org1 MSP的任何成员
  • 'Org1.client':Org1 MSP的任何客户端
  • 'Org1.peer':Org1 MSP的任何节点

语言的语法是:

EXPR(E[, E...])

其中EXPRANDOR,或OutOfE是主体(使用上面描述的语法)或对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'))

设置集合级别背书策略

与链码级别的背书策略类似,在批准和提交链码定义时,还可以指定链码的私有数据集合和相应的集合级别背书策略。如果设置了集合级别背书策略,则写入私有数据收集密钥的交易将要求指定的组织节点已对该交易进行了背书。

可以使用集合级别的背书策略来限制哪些组织节点可以写入私有数据集合密钥命名空间,例如,以确保未经授权的组织无法写入集合,并且有信心在私人数据收集中的任何状态都得到了所需收集组织的背书。

集合级别的背书策略可能比链码级别的背书策略和集合的私有数据分发策略限制性更小或更严格。例如,大多数组织可能被要求背书一个链码交易,但是一个特定的组织可能被要求背书一个在特定集合中包含一个键的交易。

集合级背书策略的语法与链码级背书策略的语法完全匹配-在集合配置中,可以使用signaturePolicychannelConfigPolicy指定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.goHandlerLibrary构造的一个实例方法,为了添加自定义背书或验证逻辑,该构造需要使用任何其他方法进行扩展。

由于这很麻烦,并且带来了部署挑战,因此还可以通过在名为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 <namespace, collection, key>
    GetPrivateDataMetadata(namespace, collection, key string) (map[string][]byte, error)

    // Done releases resources occupied by the State
    Done()
}

重要注意事项

  • 验证插件在同级之间的一致性:在未来的版本中,Fabric通道基础设施将保证通道中任何给定区块链高度的所有节点对给定链码使用相同的验证逻辑,以消除可能导致意外运行不同实现的节点之间状态分歧的错误配置的可能性。然而,目前,确保这种情况不会发生是系统操作员和管理员的唯一责任。
  • 验证插件错误处理:当验证插件无法确定给定事务是否有效时,由于无法访问数据库等暂时执行问题,它应该返回在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
  • 将Fabric代码导入插件:强烈建议将属于Fabric而不是protobufs的代码作为插件的一部分导入,当Fabric代码在不同版本之间更改时可能会导致问题,或者在运行混合节点版本时可能会导致不可操作性问题。理想情况下,插件代码应该只使用给定的依赖项,并且应该导入除protobufs之外的最小值。

访问控制列表(ACL)

什么是访问控制列表?

注意:本主题讨论通道管理级别的访问控制和策略。要了解链代码中的访问控制,请查看我们的chaincode for developers教程

Fabric使用访问控制列表(acl)来管理对资源的访问,方法是将一个策略与资源关联起来,该策略指定一个对给定一组标识求值为true或false的规则。结构包含许多默认ACL。在本文档中,我们将讨论它们的格式以及如何覆盖默认值。

但是,在我们能够做到这一点之前,有必要对资源和政策有一点了解。

资源

用户通过针对在后台调用的用户链码事件流源或系统链码与Fabric进行交互。因此,这些端点被视为“资源”,应该对其执行访问控制。

应用程序开发人员需要了解这些资源以及与它们相关联的默认策略。这些资源的完整列表可在中找到configtx.yaml。 你可以看看样品文件configtx.yaml

configtx.yaml中命名的资源是Fabric当前定义的所有内部资源的详尽列表。在那里采用的松散公约是<component>/<resource>。因此cscc/GetConfigBlockCSCC组件中GetConfigBlock调用的资源。

策略

与请求关联的基本身份(或策略)被检查,因为它们与请求关联。背书政策用于确定交易是否已得到适当背书。在通道配置中定义的策略被引用为修改策略以及访问控制,并在通道配置本身中定义。

策略可以用以下两种方式之一构造:作为签名策略或隐式元策略。

Signature策略

这些策略标识必须签名才能满足策略的特定用户。例如:

Policies:
  MyPolicy:
    Type: Signature
    Rule: “Org1.Peer OR Org2.Peer”

这个策略结构可以解释为:名为MyPolicy的策略只能通过一个角色为“来自Org1的节点”或“来自Org2的节点”的身份签名来满足。

签名策略支持ANDORNOutOf的任意组合,允许构建非常强大的规则,例如:“Orga A的一个管理员和另外两个管理员,或者20个组织管理员中的11个”。

ImplicitMeta策略

ImplicitMeta策略聚合配置层次结构中更深层次的策略的结果,这些策略最终由Signature策略定义。它们支持默认规则,如“大多数组织管理员”。与Signature策略相比,这些策略使用了一种不同但仍然非常简单的语法:<ALL|ANY|MAJORITY> <sub_policy>

例如:ANY ReadersMAJORITY Admins

请注意,在默认策略配置中,管理员具有操作角色。指定只有管理员(或管理员的某个子集)才有权访问资源的策略,往往是针对网络的敏感或操作方面(例如在通道上实例化链码)。编写者倾向于能够提出账本更新,例如交易处理,但通常不具有管理权限。读者有一个被动的角色。他们可以访问信息,但无权提议账本更新,也不能执行管理任务。这些默认策略可以添加、编辑或补充,例如通过新的节点客户端角色(如果您支持NodeOU)。

下面是一个ImplicitMeta策略结构的示例:

Policies:
  AnotherPolicy:
    Type: ImplicitMeta
    Rule: "MAJORITY Admins"

在这里,策略AnotherPolicy可以被大多数 管理员满足,其中管理员最终由较低级别的签名策略指定。

在哪里指定了访问控制?

内部存在访问控制默认值configtx.yamlconfigtxgen用于构建通道配置的文件。

访问控制可以通过两种方式之一更新,或者通过编辑configtx.yaml它本身,在创建新的通道配置时使用,或者通过更新现有通道的通道配置中的访问控制来使用。

如何在configtx.yaml中格式化ACLs

ACLs的格式是由资源函数名后跟字符串组成的键值对。要查看它的外观,请参考此示例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/Proposeevent/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来编辑配置,其中AdminsWritersReaders策略已经存在。

"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的系统链码,则提交该方案的成员必须具有对WritersMyPolicy求值为“true”的标识。

在默认配置中,Writers是一个签名策略,其规则为SampleOrg.member。 换句话说,“我组织的任何成员”。上面列出的MyPolicy有一个规则SampleOrg.admin,或“我组织的任何管理员”。为了满足这些ACLs,成员必须同时是管理员和SampleOrg的成员。默认情况下,所有管理员都是成员(尽管不是所有管理员都是成员),但是可以将这些策略改写为您希望它们成为的任何内容。因此,必须跟踪这些策略,以确保节点建议的ACLs不会无法满足(除非这是目的)。

用身份混合器实现MSP

什么是Idemix?

Demix是一个加密协议套件,它提供了强大的身份验证和隐私保护功能,如匿名性、在不暴露交易方身份的情况下进行交易的能力以及不链接性,即单个身份发送多个交易而不暴露交易是由同样的身份。

在一个Idemix流中有三个参与者:用户发布者验证者
idemix-overview

  • 颁发者证明一组用户属性是以数字证书(以下称为“凭证”)的形式颁发的。
  • 用户随后生成拥有该凭证的“零知识证明”,并且还选择性地仅公开用户选择公开的属性。证据,因为它是零知识,没有透露额外的信息给验证者,发行者,或任何其他人。

举个例子,假设“Alice”需要向Bob(商店职员)证明她有车管所发给她的驾照。

在这个场景中,Alice是用户,DMV是发布者,Bob是验证者。为了向鲍勃证明爱丽丝有驾照,她可以给他看。然而,鲍勃将能够看到爱丽丝的名字,地址,确切年龄等-远远超过鲍勃需要知道的信息。

相反,Alice可以使用Idemix为Bob生成一个“零知识证明”,它只显示她拥有有效的驾照,而没有其他任何东西。

所以从证据来看:

  • Bob没有了解到关于Alice的任何其他信息,除了她有一个有效的许可证(匿名)。
  • 如果Alice多次访问该商店,并且每次都为Bob生成一个证据,Bob将无法从证据中判断出是同一个人(unlinkability)。

Idemix身份验证技术提供的信任模型和安全保证类似于标准X.509证书所保证的内容,但其底层加密算法有效地提供了高级隐私功能,包括上述功能。在下面的技术部分中,我们将详细比较一下Idemix和X.509技术。

如何使用Idemix?

要了解如何将Idemix与Hyperledger Fabric一起使用,我们需要查看哪些结构组件对应于Idemix中的用户、颁发者和验证者。

  • Fabric java sdk是用户的API。未来,其他Fabric SDK也将支持Idemix。
  • Fabric提供了两个可能的Idemix发行者:
    用于生产环境或开发的结构CA,以及
    用于开发环境的idemixgen工具。
  • 验证器是一个Idemix MSP in Fabric。

为了在Hyperledger Fabric中使用Idemix,需要以下三个基本步骤:
idemix-three-steps
将此图像中的角色与上面的角色进行比较。

  • 以发行人为例。
    Fabric CA(1.3版或更高版本)已增强,可以自动作为一个Demix发行者运行。当fabric-ca-server启动(或通过fabric-ca server init命令初始化)时,在fabric-ca-server: IssuerPublicKeyIssuerRevocationPublicKey的主目录中自动创建以下两个文件。这些文件在步骤2中是必需的。
    对于开发环境,如果您不使用Fabric CA,则可以使用idemixgen来创建这些文件。
  • 考虑验证者
    您需要使用步骤1中的IssuerPublicKeyIssuerRevocationPublicKey创建一个Idemix MSP。
    例如,考虑一下Hyperledger Java SDK示例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被设置为idemixmspdir目录(本例中为crypto-config/peerOrganizations/org3.example.com/msp)的内容包含IssuerPublicKeyIssuerRevocationPublicKey文件。

注意,在这个例子中,Org1Idemix代表Org1的Idemix MSP(未显示),它也有一个X509 MSP。

  • 考虑用户。回想一下,Java SDK是用户的API。
    为了在Java SDK中使用Idemix,只需要一个额外的API调用:org.hyperledger.fabric_ca.sdk.HFCAClient类的idemixEnroll方法。例如,假设hfcaClient是您的hfcaClient对象,x509registration是与X509证书关联的org.hyperledger.fabric.sdk.Enrollment
    下面的调用将返回与您的Idemix凭据关联的org.hyperledger.fabric.sdk.Enrollment对象。
IdemixEnrollment idemixEnrollment = hfcaClient.idemixEnroll(x509enrollment, "idemixMSPID1");

另外请注意,IdemixEnrollment实现了org.hyperledger.fabric.sdk.Enrollment接口,因此可以像使用X509注册对象一样使用,当然,这会自动提供dimix的隐私增强功能。

Idemix和链码

从验证者的角度来看,还有一个参与者需要考虑:链码。当使用Idemix凭证时,链码可以了解到关于交易处理程序的哪些信息?

cid(客户机标识)库(仅适用于golang)已被扩展,以便在使用Idemix凭据时支持GetAttributeValue函数。然而,正如下面的“当前限制”一节所述,在Idemix案例中只公开了两个属性:ourole

如果Fabric CA是凭据颁发者:

  • ou属性的值是身份的从属关系(例如“org1.department1”);
  • role属性的值将为“member”或“admin”。“admin”值表示标识是MSP管理员。默认情况下,结构CA创建的标识将返回“成员”角色。若要创建“admin”标识,请使用role属性和值2注册标识。

有关在JavaSDK中设置从属关系的示例,请参见此示例

有关使用go-chaincode中的CID库检索属性的示例,请参阅此go chaincode

Idemix组织不能用于背书链码或批准链码定义。在通道上设置生命周期代言和背书策略时,需要考虑到这一点。有关详细信息,请参阅下面的“限制”部分。

当前限制

当前版本的dimex确实有一些局限性。

  • Idemix组织和背书政策
    Idemix组织不能用于背书链码交易或批准链码定义。默认情况下,Channel/Application/LifecycleEndorsement将需要来自通道上活动的大多数组织的签名。这意味着包含大量Idemix组织的通道可能无法达到实现默认策略所需的大多数。例如,如果一个通道有两个MSP组织和两个Idemix组织,则通道策略将要求四分之三的组织批准链码定义,以将该定义提交给通道。因为Idemix组织不能批准链码定义,所以策略只能验证四分之二的签名。
    如果您的通道包含足够数量的Idemix组织来影响背书策略,则可以使用签名策略显式指定所需的MSP组织。
  • 固定属性集
    现在还无法颁发或使用具有自定义属性的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等中证明签名知识的能力。被利用了。

身份混合器MSP配置生成器(idemixgen)

本文档描述了idemixgen实用程序的用法,该实用程序可用于为基于身份混合器的MSP创建配置文件。以前为一个密钥创建一个密钥对,可以使用两个命令创建一个密钥对。

目录结构

idemixgen工具将创建具有以下结构的目录:

- /ca/
    IssuerSecretKey
    IssuerPublicKey
    RevocationKey
- /msp/
    IssuerPublicKey
    RevocationPublicKey
- /user/
    SignerConfig

ca目录包含颁发者密钥(包括吊销密钥),并且应该只存在于ca中。msp目录包含设置msp验证idemix签名所需的信息。用户目录指定默认签名者。

CA密钥生成

可以使用命令idemixgen ca-keygen创建适合于身份混合器的CA(发卡器)密钥。这将在工作目录中创建目录camsp

添加默认签名者

在用idemixgen ca-keygen生成camsp目录后,可以使用idemixgen signerconfig用户目录中指定的默认签名者添加到config中。

$ idemixgen signerconfig -h
usage: idemixgen signerconfig [<flags>]

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公开了以下功能:

  • 日志级别管理
  • 健康检查
  • 运行指标的普罗米修斯目标(配置时)

配置操作服务

操作服务需要两个基本配置:

  • 要监听的地址端口
  • 用于身份验证和加密的TLS证书密钥。注意,这些证书应该由单独的专用CA生成。不要使用已经为任何通道中的任何组织生成证书的CA。

普通节点

对于每个节点,可以在core.yamloperations部分配置操作服务器:

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必须设置。当clientAuthRequiredtrue时,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。当ClientAuthRequiredtrue时,TLS层将要求客户机为每个请求提供一个用于身份验证的证书。有关详细信息,请参阅下面的操作安全部分。

运营安全

由于操作服务专注于操作,故意与Fabric网络无关,因此它不使用成员资格服务提供程序进行访问控制。相反,操作服务完全依赖于具有客户端证书身份验证的相互TLS。

当TLS被禁用时,授权被绕过,任何可以连接到操作端点的客户端都可以使用API。

启用TLS时,必须提供有效的客户端证书才能访问所有资源,除非下面另有明确说明。

当clientAuthRequired也被启用时,无论访问的资源是什么,TLS层都需要一个有效的客户端证书。

日志级别管理

操作服务提供/logspec资源,操作员可以使用该资源管理节点或排序方的活动日志规范。该资源是常规的REST资源,支持GETPUT请求。

当操作服务接收到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.yamlmetrics部分将metrics提供者设置为Prometheus,可以将节点配置为公开供Prometheus使用的/metrics端点。

metrics:
  provider: prometheus

排序节点

通过在orderer.yamlmetrics部分将metrics提供者设置为Prometheus,可以将节点配置为公开供Prometheus使用的/metrics端点。

Metrics:
  Provider: prometheus

StatsD

StatsD是一个简单的统计信息聚合守护进程。度量被发送到statsd守护进程,在那里收集、聚合并推送到后端进行可视化和警报。由于此模型需要插入指令的进程将度量数据发送到StatsD,因此这被认为是一个推送系统。

普通节点

通过在core.yamlmetrics部分将metrics提供程序设置为StatsD,可以将节点配置为向StatsD发送度量。statsd子节还必须配置statsd守护进程的地址、要使用的网络类型(tcpudp)以及发送度量的频率。可以指定一个可选的前缀来帮助区分度量的来源—例如,区分来自不同节点的度量—该前缀将附加到所有生成的度量中。

metrics:
  provider: statsd
  statsd:
    network: udp
    address: 127.0.0.1:8125
    writeInterval: 10s
    prefix: peer-0

排序节点

通过在orederer.yamlmetrics部分将metrics提供程序设置为StatsD,可以将节点配置为向StatsD发送度量。statsd子节还必须配置statsd守护进程的地址、要使用的网络类型(tcpudp)以及发送度量的频率。可以指定一个可选的前缀来帮助区分度量的来源—例如,区分来自不同节点的度量—该前缀将附加到所有生成的度量中。

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_DIRCHAINCODE_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_DIRbuild必须放置发布运行所需工件的目录。构建脚本应将输入目录CHAINCODE_SOURCE_DIRCHAINCODE_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_DIRrelease必须放置工件以供节点使用的目录。

release完成时,节点将从RELEASE_OUTPUT_DIR使用两种类型的元数据:

  • CouchDB的状态数据库索引定义
  • 外部链码服务器连接信息(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_DIRRUN_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”关联的任何构建脚本时,它将只传播白名单中环境变量的值。

注意:以下环境变量始终传播到外部生成器:

  • LD_LIBRARY_PATH
  • LIBPATH
  • PATH
  • TMPDIR

当存在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.gzmetadata.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/detectbin/buildbin/releasebin/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结构

  • 地址:可从节点访问的链码服务器终结点。必须以“:”格式指定。
  • 拨号超时:等待连接完成的间隔。指定为带有时间单位的字符串(例如,“10s”、“500ms”、“1m”)。如果未指定,默认值为“3s”。
  • 是否需要tls:如果为false,则不需要“客户端身份验证”、“客户端密钥”、“客户端证书”和“根证书”。默认值为“true”。
  • 是否需要客户端验证:如果为true,则需要“客户端密钥”和“客户端证书”。默认值为false。如果tls_required为false,则忽略此项。
  • 客户端密钥:客户端私钥的PEM编码字符串。
  • 客户端证书:客户端证书的PEM编码字符串。
  • 根证书:服务器(节点)根证书的PEM编码字符串。

例如:

{
    
    
  "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_DIRconnection.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 <package>” 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链码服务属性如下所述:

  • CCID(字符串)-CCID应该与节点上的链码包名匹配。这是与peer lifecycle chaincode install<package>CLI命令返回的已安装链码关联的CCID。这可以在安装后使用“peer lifecycle chaincode queryinstalled”命令获得。
  • Address(string)-Address是链码服务器的侦听地址
  • CC(Chaincode)-CC是处理Init和Invoke的链代码
  • TLSProps(TLSProperties)-TLSProps是传递给链码服务器的TLS属性
  • KaOpts(keepalive.server参数)-KaOpts keepalive选项,如果为零,则提供合理的默认值

然后构建适合您的代码环境。


部署链码

当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,以打印调用堆栈和错误消息。

Hyperledger Fabric中错误处理的一般准则

  • 如果您正在处理一个用户请求,您应该记录错误并返回它。
  • 如果错误来自外部源,例如Go库或供应商包,请使用错误。换行()生成错误的调用堆栈。
  • 如果错误来自另一个Fabric函数,如果需要,可以使用errors.WithMessage()而不影响调用堆栈。
  • 不应允许恐慌传播到其他包。

示例程序

下面的示例程序清楚地演示了如何使用该包:

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)
}

日志控制

概述

peerorderer中的登录由common/flogging包提供。此程序包支持

  • 基于消息严重性的日志记录控制
  • 基于生成消息的软件记录器的日志控制
  • 根据邮件的严重程度不同的打印选项

所有日志当前都定向到stderr。为用户和开发人员提供了按严重性对日志进行全局和日志记录级别的控制。目前对于每种严重程度级别提供的信息类型没有正式的规则。在提交bug报告时,开发人员可能希望查看到调试级别的完整日志。

在打印得很好的日志中,日志级别由颜色和四个字符代码表示,例如,“ERRO”表示错误,“DEBU”表示调试等。在日志上下文中,logger是开发人员给相关消息组指定的任意名称(字符串)。在下面这个漂亮的示例中,loggers legrmgmtkvlegderpeer正在生成日志。

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

在运行时可以创建任意数量的记录器,因此没有日志记录器的“主列表”,日志控制结构无法检查日志记录器是否实际存在或将存在。

日志规范

peerorderer命令的日志记录级别由日志规范控制,该规范通过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

日志格式

peerorderer命令的日志记录格式是通过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

节点既是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.clientAuthRequiredtrue并设置peer.tls.clientRootCAs.files属性设置为CA链文件,其中包含为组织的客户端颁发TLS证书的CA证书链。

默认情况下,节点在充当TLS服务器和客户端时将使用相同的证书和私钥对。要在客户端使用不同的证书和私钥对,请设置peer.tls.clientCert.filepeer.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

要在排序节点上启用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的证书链的文件的完全限定路径

为节点CLI配置TLS

对启用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<create | update | fetch>或peer chaincode,如果在排序节点上启用了TLS,则还必须指定以下命令行参数:

  • –tls
  • –cafile<包含排序节点CA证书链的文件的完全限定路径>

如果在排序程序上启用了TLS客户端身份验证,则还必须指定以下参数:

  • –clientauth
  • –keyfile<包含客户端私钥的文件的完全限定路径>
  • –certfile<包含客户端证书的文件的完全限定路径>

调试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排序节点管理员

概念概述

有关排序的概念以及受支持的排序服务实现(包括Raft)如何在高层工作的概述,请查看我们关于排序服务的概念性文档。

要了解设置排序节点的过程(包括创建本地MSP和创建genesis块),请查看我们关于设置ordering节点的文档。


配置

虽然每个Raft节点都必须添加到系统通道中,但不需要将节点添加到每个应用程序通道中。此外,您可以在不影响其他节点的情况下动态地从通道中移除和添加节点,这一过程在下面的重新配置部分中描述。

Raft节点使用TLS pinning相互识别,因此为了模拟Raft节点,攻击者需要获取其TLS证书的私钥。因此,如果没有有效的TLS配置,就不可能运行Raft节点。

Raft集群分为两个平面:

  • 本地配置:控制特定于节点的方面,例如TLS通信、复制行为和文件存储。
  • 通道配置:定义相应通道的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证书的情况非常有用。

  • ClientCertificateClientPrivateKey:客户端TLS证书的文件路径和对应的私钥。
  • ListenPort:集群监听的端口。如果为空,则端口与排序节点常规端口相同(常规.listenPort)
  • ListenAddress:集群服务正在侦听的地址。
  • ServerCertificateServerPrivateKey:当集群服务在单独的gRPC服务器(不同的端口)上运行时使用的TLS服务器证书密钥对。
  • SendBufferSize:控制出口缓冲区中的消息数。

注意:ListenPortListenAddressServerCertificateServerPrivateKey必须一起设置或取消设置。如果未设置,则从general TLS部分继承,例如general.tls.{privateKey, certificate}

general.cluster还有一些隐藏的配置参数,可用于进一步微调群集通信或复制机制:

  • DialTimeoutRPCTimeout:指定创建连接和建立流的超时。
  • ReplicationBufferSize:可为每个内存缓冲区分配的最大字节数,用于从其他群集节点进行块复制。每个通道都有自己的内存缓冲区。默认为20971520,即20MB
  • PullTimeout:排序节点在中止之前等待接收到块的最长持续时间。默认为5秒。
  • ReplicationRetryTimeout:排序节点在两次连续尝试之间等待的最长持续时间。默认为5秒。
  • ReplicationBackgroundRefreshInterval:连续两次尝试复制此节点已添加到的现有通道或此节点过去未能复制的通道之间的时间。默认为5分钟。
  • TLSHandshakeTimeShift:如果排序节点的TLS证书过期且未及时更换(参见下面的TLS证书轮换),则无法建立它们之间的通信,并且无法向排序服务发送新的交易。为了从这样的场景中恢复,有可能使排序节点之间的TLS握手考虑向后移动的时间(配置为TLSHandshakeTimeShift)。为了尽可能不具侵入性,此配置选项仅影响使用单独gRPC服务器进行集群内通信的排序节点。如果您的集群通过用于为客户机和节点提供服务的同一个gRPC服务器进行通信,则需要首先通过另外设置general.cluster.ListenPortgeneral.cluster.ListenAddressServerCertificateServerPrivateKey来重新配置排序程序,然后重新启动排序程序,以便新配置生效。

共识参数:

  • WALDiretcd/raft的预写日志的存储位置。每个通道都有自己的子目录,以通道ID命名。
  • SnapDir:指定存储etcd/raft快照的位置。每个通道都有自己的子目录,以通道ID命名。

还有一个隐藏的配置参数,可以通过将其添加到orderer.yaml的共识部分来设置:

  • EvictionSuspicion:通道逐出怀疑的累计时间段,触发节点从其他节点拉取块,并查看是否已从通道中逐出以确认其怀疑。如果怀疑被确认(被检查的块不包含节点的TLS证书),则节点将停止对该通道的操作。当一个节点不知道任何被选举的领导者,也不能被选为通道中的领导者时,它就会怀疑自己的通道被逐出。默认为10分钟。

通道配置

除了(已经讨论过的)共识者之外,Raft通道配置还有一个选项部分,与协议特定旋钮有关。当前无法在节点运行时动态更改这些值。必须重新配置并重新启动节点。

唯一的例外是SnapshotIntervalSize,它可以在运行时进行调整。

注意:建议避免更改以下值,因为配置错误可能会导致根本无法选举领导人的状态(即,如果TickIntervalElectionTick非常低)。无法选出领导人的情况是不可能解决的,因为领导人需要做出改变。由于这些危险,我们建议不要为大多数用例调整这些参数。

  • TickInterval:两次Node.Tick调用之间的时间间隔。
  • ElectionTick:两次选举之间必须通过的Node.Tick调用数。也就是说,如果一个追随者在ElectionTick过去之前没有收到现任领导人的任何信息,他将成为候选人并开始选举。
  • ElectionTick必须大于HeartbeatTick
  • HeartbeatTick:必须在心跳之间传递的Node.Tick调用数。也就是说,一个领导者在每次HeartbeatTick跳动时都会发送心跳信息来保持其领导地位。
  • MaxInflightBlocks:限制乐观复制阶段正在执行的追加块的最大数量。
  • SnapshotIntervalSize:定义每个快照的字节数。

重新配置

只要一次只添加或删除一个节点,Raft order就支持动态(即,当通道正在服务时)添加和删除节点。请注意,您的集群必须是可操作的,并且在您尝试重新配置它之前能够达成共识。例如,如果有三个节点,而两个节点出现故障,则无法重新配置集群以删除这些节点。类似地,如果在具有三个节点的通道中有一个失败的节点,则不应尝试旋转证书,因为这会导致第二个错误。通常,您不应尝试对Raft共识者进行任何配置更改,例如添加或删除共识者,或旋转共识者证书,除非所有共识者在线且健康。

如果您确实决定更改这些参数,建议您仅在维护周期内尝试这样的更改。当在只有几个节点的集群中尝试配置时,当一个节点关闭时,最有可能发生问题。例如,如果您的共识者集中有三个节点,其中一个节点关闭,则意味着您有三个节点中的两个处于活动状态。如果在这种状态下将集群扩展到四个节点,则四个节点中只有两个节点处于活动状态,这不是仲裁。第四个节点不能挂载,因为节点只能挂载到功能正常的集群上(除非集群的总大小是一到两个)。

因此,通过将一个由三个节点组成的集群扩展到四个节点(只有两个节点是活动的),您将被有效地卡住,直到原始脱机节点被恢复。

向Raft集群添加新节点的方法如下:

  1. 通过通道配置更新交易将新节点的TLS证书添加到通道中。注意:在添加到一个或多个应用程序通道之前,必须先将新节点添加到系统通道。
  2. 从属于系统通道的orderer节点获取系统通道的最新配置块
  3. 通过检查获取的配置块是否包含(即将添加)添加节点的证书,确保将要添加的节点是系统通道的一部分
  4. 使用General.BootstrapFile配置参数中config块的路径启动新的Raft节点
  5. 等待Raft节点从现有节点复制其证书已添加到的所有通道的块。完成此步骤后,节点开始为通道提供服务。
  6. 将新添加的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,该教程详细描述了通道更新过程。

对于希望从使用基于Kafka的排序服务过渡到基于Raft的排序服务的用户,v1.4.2或更高版本的节点允许通过网络中每个通道上的一系列配置更新交易来实现这一点。

本教程将在较高的层次上描述这个过程,在必要时调用特定的细节,而不是详细地显示每个命令。


假设和考虑

在尝试迁移之前,请考虑以下事项:

  • 这个过程只是从kafka迁移到Raft。目前不支持在任何其他排序共识类型之间迁移。
  • 迁移是一种方式。一旦排序服务被迁移到Raft并开始提交交易,就不可能再回到Kafka。
  • 因为排序节点必须关闭并重新启动,所以在迁移期间必须允许停机。
  • 只有在本文档后面指定的迁移点执行备份时,才能从失败的迁移中恢复。如果不进行备份,并且迁移失败,则无法恢复以前的状态。
  • 必须在同一个维护时段内迁移所有通道。在恢复操作之前,不可能只迁移一些通道。
  • 在迁移过程结束时,每个通道将具有相同的共识者集Raft节点。这是将存在于排序系统通道中的同一共识者集。这使得诊断成功的迁移成为可能。
  • 迁移在适当的地方完成,利用已部署的排序节点的现有指标。应在迁移之后执行排序节点的添加或删除。

高等级迁移流

移民分五个阶段进行。

  • 系统处于维护模式,应用程序交易被拒绝,只有排序服务管理员才能更改通道配置。
  • 系统将停止,并在迁移过程中发生错误时进行备份。
  • 系统启动,每个通道都有其共识类型和元数据修改。
  • 系统重新启动,现在按照Raft共识运行;检查每个通道以确认其已成功达到法定人数。
  • 系统退出维护模式,恢复正常功能。

准备迁移

在尝试迁移之前,您应该采取几个步骤。

  • 设计Raft部署,确定哪些排序服务节点将作为Raft共识者保留。您应该在集群中部署至少三个排序节点,但是请注意,部署至少包含五个节点的共识者集将在一个节点出现故障时保持高可用性,而一旦单个节点因任何原因停机(例如,在维护周期内),三节点配置将失去高可用性。
  • 准备构建Raft元数据配置的材料。注:所有通道应接收相同的Raft元数据配置。有关这些字段的更多信息,请参阅Raft配置指南。注意:您可能会发现使用Raft共识协议引导一个新的排序网络是最简单的,然后从其配置中复制和修改一致性元数据部分。在任何情况下,您都需要(对于每个排序节点):
    • 主机名
    • 端口
    • 服务器证书
    • 客户端证书
  • 编制系统中所有通道(系统和应用程序)的列表。请确保您具有正确的凭据来签署配置更新。例如,相关的排序服务管理员标识。
  • 确保所有排序服务节点都运行相同版本的Fabric,并且此版本为v1.4.2或更高版本。
  • 确保所有节点至少运行1.4.2版的Fabric。确保所有通道都配置了启用迁移的通道功能。
    • 排序节点功能 V1_4_2(或更高)。
    • 通道容量 V1_4_2(或更高)。

进入维护模式

在将排序服务设置为维护模式之前,建议停止网络的节点和客户端。但是,让节点或客户机保持正常运行是安全的,因为偏差服务将拒绝它们的所有请求,它们的日志将充满良性但具有误导性的失败。

按照向通道添加组织教程中的过程,从系统通道开始,提取、转换和确定每个通道的配置范围。在这个步骤中,您唯一应该更改的字段是位于/Channel/Orderer/ConsensusType的通道配置中。在通道配置的JSON表示中,这将是.channel_group.groups.Orderer.values.ConsensusType

ConsensusType(共识类型)由三个值表示:类型元数据状态,其中:

  • 类型kafkaetcdraft(Raft)。此值只能在维护模式下更改。
  • 如果类型为kafka,元数据将为空,但如果ConsensusTypeetcdraft,则必须携带有效的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。为了完整性,请在每个排序节点上验证这一点。

完成此过程后,排序服务现在可以接受所有通道上的所有交易。如果您按照建议停止了节点和应用程序,现在可以重新启动它们。

中止和回滚

如果在退出维护模式之前的迁移过程中出现问题,只需执行以下回滚过程。

  • 关闭排序节点和Kafka服务(服务器和Zookeeper集成)。
  • 在更改共识类型之前,将这些服务器的文件系统回滚到在维护模式下执行的备份。
  • 重启所述服务器后,排序节点将以维护模式引导至Kafka。
  • 发送配置更新退出维护模式以继续使用Kafka作为您的共识机制,或在备份点后恢复指令,并修复阻止Raft仲裁形成的错误,并使用更正的Raft配置元数据重试迁移。

以下几种状态可能表示迁移失败:

  • 某些节点崩溃或关闭。
  • 日志中没有每个通道成功选举领导人的记录。
  • 尝试在系统通道上切换到STATE_NORMAL模式失败。

提供一个基于kafka的排序服务

本文假设读者知道如何设置Kafka集群和ZooKeeper集成,并通过防止未经授权的访问来保证它们的安全性。本指南的唯一目的是确定您需要采取的步骤,以便让一组超级账本Fabric排序服务节点(OSN)使用您的Kafka群集,并为您的区块链网络提供排序服务。

有关排序节点者在网络和交易流中扮演的角色的信息,请查看我们的排序服务文档。

有关如何设置排序节点的信息,请参阅我们的设置排序节点文档。

有关配置Raft排序服务的信息,请参阅配置和操作Raft排序服务

重点

在Kafka中,每个通道映射到一个单独的分区主题。当OSN通过广播RPC接收交易时,它检查以确保广播客户端具有在通道上写入的权限,然后将这些交易中继(即生成)到Kafka中的相应分区。这个分区也被OSN使用,OSN将接收到的经验分组到本地块中,将它们保存在本地z中,并通过deliverpc将它们提供给接收客户端。有关低层次的详细信息,请参阅描述我们如何进行此设计的文档图8是上述过程的示意图。

步骤

KZ分别为Kafka集群和ZooKeeper群集中的节点数:

  1. K至少应设置为4。(正如我们将在下面的步骤4中解释的那样,这是展示崩溃容错能力所需的最小节点数,即使用4个代理,您可以让1个代理停止运行,所有通道将继续可写和可读,并且可以创建新通道。)
  2. Z要么是3,5,要么是7。它必须是一个奇数,以避免分裂大脑的情况,并大于1,以避免单点故障。任何超过7台ZooKeeper服务器的服务器都被认为是过度滥用。

然后按以下步骤进行:

  1. 排序节点:将kafka的相关信息编码到网络的“创世”模块中。如果您使用的是configtxgen,请编辑configtx.yaml。或者,为系统通道的创世区块选择一个预设配置文件,以便:
  • Orderer.OrdererType设定kafka
  • Orderer.Kafka.BrokersIP:port符号包含集群中至少两个Kafka代理的地址。清单不必详尽无遗。(这些是您的引导代理。)
  1. 排序节点:设置最大块大小。每个块最多有Orderer.AbsoluteMaxBytes字节(不包括头),这个值可以在configtx.yaml中设置。让您在这里选择的值是A并记下它-–它将影响您在步骤6中配置Kafka代理的方式。
  2. 排序节点:创建创世区块。使用configtxgen。您在上面的步骤3和4中选择的设置是系统范围的设置,即它们适用于所有OSN的网络。记下创世区块的位置。
  3. Kafka集群:适当配置您的Kafka代理。确保每个Kafka代理都配置了以下密钥:
  • unclean.leader.election.enable = false:数据一致性是区块链环境中的关键。我们不能在同步副本集之外选择一个通道领导者,否则我们会冒覆盖上一个领导者产生的偏移量的风险,并因此重写排序节点产生的区块链。
  • min.insync.replicas = M:其中选择一个值M,使1<M<N(参见default.replication.factor以下)。当数据写入至少M个副本(这些副本随后被视为同步并属于同步副本集或ISR)时,该数据被视为已提交。在任何其他情况下,写操作都会返回一个错误。然后:
    • 如果多达N-M个副本(其中N个副本中的N个副本)不可用,则操作将正常进行。
    • 如果有更多副本不可用,Kafka将无法维护M的ISR集,因此它将停止接受写操作。阅读工作没有问题。当M个副本同步时,通道将再次变为可写。
  • default.replication.factor = N:其中选择一个N值,使N<K。复制因子N意味着每个通道将其数据复制到N个代理。这些是通道的ISR集合的候选对象。正如我们在最小同步副本以上部分,并非所有这些经纪人都必须随时可用。N应该被严格地设置为K,因为如果少于N个代理正在运行,则无法继续创建通道。因此,如果你设置N=K,一个经纪人倒下意味着在区块链网络上不能创建新的通道-排序服务的崩溃容错是不存在的。
    根据我们上面的描述,MN的最小允许值分别为2和3。此配置允许继续创建新通道,并允许所有通道继续可写。
  • message.max.bytesreplica.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),因此不需要显式设置它。)
  1. Orderers:把每个OSN指向创世区块。在orderer.yaml中编辑General.BootstrapFile,使其指向在上面步骤5中创建的创世区块。在执行此操作时,请确保该YAML文件中的所有其他键都已正确设置。
  2. Orderers:调整轮询间隔和超时。(可选步骤。)
  • orderer.yaml文件中的Kafka.Retry部分允许您调整metadata/producer/consumer请求的频率以及套接字超时。(这些都是您希望在kafka制作人或消费者身上看到的设置。)
  • 此外,当创建新通道或重新加载现有通道时(在刚重新启动排序程序的情况下),排序程序将通过以下方式与Kafka集群交互:
    它为对应于通道的Kafka分区创建一个Kafka producer(writer)。它使用该生产者向该分区发布一条no-op CONNECT消息。它为该分区创建一个Kafka消费者(读卡器)。
    如果这些步骤中的任何一个失败,您可以调整重复这些步骤的频率。具体地说,他们将被重新尝试每个Kafka.Retry.ShortInterval的总Kafka.Retry.ShortTotal,然后每个Kafka.Retry.LongInterval的总Kafka.Retry.LongTotal,直到他们成功。请注意,在成功完成上述所有步骤之前,排序节点将无法写入或读取通道。
  1. 设置OSN和Kafka集群,以便它们通过SSL进行通信。(可选步骤,但强烈推荐。)请参考方程式中Kafka群集侧的汇合指南,并相应地在每个OSN上的orderer.yaml中的Kafka.TLS下设置密钥。
  2. 按照以下顺序启动节点:ZooKeeper集成、Kafka集群、排序服务节点

其他注意事项

首选邮件大小。在上面的步骤4中(参见步骤部分),您还可以通过设置Orderer.Batchsize.PreferredMaxBytes钥匙。当处理相对较小的消息时,Kafka提供了更高的吞吐量;目标值不超过1 MiB。

使用环境变量覆盖设置。使用Fabric提供的示例Kafka和Zookeeper Docker镜像时(分别参见images/Kafkaimages/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的默认值。


Kafka协议版本兼容性

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!

猜你喜欢

转载自blog.csdn.net/bean_business/article/details/108669133
今日推荐