0%

前言

之前看到一幅描述etcd raft的流程图,感觉非常直观,但和自己看源码的又有些不同,所以自己模仿着画了一下,再介绍一下。

下图从左到右依次分为4个部分:

  1. raft:raft主体功能部分
  2. Node:raft提供的接口,raft跟上层的通信接口,会运行一个run函数,持续循环处理通道上的数据
  3. raftNode:上层应用逻辑
  4. 其他:Client、Network、State

etcd raft workflow

图中的箭头为数据的流向,这幅图包含了多个流程,接下来会分成4个流程介绍:

  1. 客户端请求
  2. 发送消息给其他节点
  3. 接收其他节点消息及处理
  4. 应用达成一致的日志

客户端请求

客户端请求的流程,在下图已经使用红色箭头标出,流程如下:

  1. 客户端将请求发送给应用层raftNode
  2. raftNode使用Propose方法,请求写入到propc通道
  3. raft.Step接收到通道数据,会通过append等函数加入到raftLog
  4. raftLog用来暂时存储和查询日志,请求会先加入到unstable

etcd raft request flow

发送消息

发送消息的数据流,已经用红色箭头标出,流程如下:

  1. raft发现有数据发送给其他节点,数据可以是leader要发送给follower的日志、snapshot,或者其他类型的消息,比如follower给leader的响应消息
  2. 利用NewReady创建结构体Ready,并写入到readyc通道
  3. raftNode从通道读到Ready,取出其中的消息,交给Network发送给其他节点

etcd raft send message flow

接收消息

接收消息的数据流,已经在下图用红色箭头标出,流程如下:

  1. 从Network收到消息,可以是leader给follower的消息,也可以是follower发给leader的响应消息,Network的handler函数将数据回传给raftNode
  2. raftNode调用Step函数,将数据发给raft,数据被写入recvc通道
  3. raft的Step从recvc收到消息,并修改raftLog中的日志

etcd raft receive msg flow

应用日志

raft会将达成一致的log通知给raftNode,让它应用到上层的数据库,数据流已经在下图用红色箭头标出,流程如下:

  1. raft发现有日志需要交给raftNode,调用NewReady创建Ready,从raftLog读取日志,并存到Ready结构体
  2. Ready结构体写入到readyc通道
  3. raftNode读到Ready结构体,发现Ready结构体中包含日志
  4. raftNode会把日志写入到storage和WAL,把需要应用的日志,提交给状态机或数据库,去修改数据
  5. raftNode处理完Ready后,调用Advance函数,通过advancec发送一个信号给raft,告知raft传出来的Ready已经处理完毕

可以发现有2个storage,1个是raftLog.Storage,一个是raftNode.storage,Storage是一个接口,可以用来读取storage中的数据,但不写入,storage的数据写入是由raftNode完成的,但raftNode.storage就是raft.MemoryStorage,所以不稳定的、稳定的都由raft存储,持久化存储由WAL负责,etcd中有现成实现的WAL操作可用,用来存储历史Entry、快照。

Storage接口更多信息请看Storage接口介绍

etcd raft apply logs flow

序言

Etcd提供了一个样例contrib/raftexample,用来展示如何使用etcd raft。这篇文章通过raftexample介绍如何使用etcd raft。

raft服务

raftexample是一个分布式KV数据库,客户端可以向集群的节点发送写数据和读数据,以及修改集群配置的请求,它使用etcd raft保持各集群之间数据的一致性。

cluster

etcd raft

etcd raft实现了raft论文的核心,所有的IO(磁盘存储、网络通信)它都没有实现,它做了解耦。

它是一个状态机,有数据作为输入,经过当前状态和输入,得到确定性的输出,即每个节点上都是一样的。

etcd-raft

raft应用架构

raft集群会由多个节点组成,客户端的请求发送给raft leader,再由raft leader通过网络通信在集群之中对请求达成共识。

集群中的每个节点从架构上都可以分为两层:

  • 应用层,负责处理用户请求,数据存储以及集群节点间的网络通信,
  • 共识层,负责相同和输入数据和状态,生成确定性的、一致的输出,

共识层由etcd raft负责,应用层要负责业务逻辑,数据存储和网络通信不需要应用层实现,而是由不同的模块负责,应用层负责起衔接存储存储和网络通信即可。

app-arch

应用层有3个重要组成部分:http API、kv store和raftNode。

http API

每个节点都会启动一个http API用来接受客户端请求,它只是接收请求,不对请求做处理。它会把客户端的写入请求PUT和查询请求GET都交给kv store。

对于修改raft集群配置请求,它会生成ConfChange交给raftNode。

kv store

一个kv数据库服务,它保存有一个kv db,用来存储用户数据

  • 对于查询请求,它直接从db中读取数据。
  • 对于写入请求,需要修改用户数据,这就需要集群节点使用raft对请求达成共识,它把请求传递给raftNode。

raftNode

raftNode用来跟etcd raft交互,他需要:

  • 把客户端的写请求,修改raft配置的请求交给etcd raft
  • 衔接网络通信跟etcd raft之间的桥梁,把etcd raft的消息发送出去,或接受到的raft消息交给raft
  • 保存raft的WAL和snapshot。

对于写请求,它会把请求数据编码后发送给etcd raft,etcd raft会把写请求封装成raft的Propose消息MsgProp,编码后的数据成为log Entry。因为raft并不关心具体的请求内容,它只需要保证每个集群节点在相同的log index拥有相同的log Entry,即请求即可。

raftNode还会启动1个http server,用来集群节点之间的通信,传递raft消息,让集群节点达成共识。它与http api是不同的,http api用来接收用户请求。

raftNode与raft交互

raft模块内部定义了一个Node接口,它代表了raft集群中的一个raft节点,它是应用层跟共识层交互的接口。

其中有几个与数据传递相关函数的是:

  • Propose:应用层通过此函数把客户端写请求传递raft。
  • ProposeConfChange:应用层通过此函数把客户端修改raft集群配置的请求传递raft。
  • Step:应用层把收到的raft集群之间通信的消息传递给raft。
  • Ready:raft对外的出口只有1个,就是Ready函数,Ready函数返回一个通道,应用层可以从这个通道中读到raft要输出的所有数据,这个数据被称为Ready结构体,包括log entry,集群间的通信消息等。
  • Advance:应用层处理完Ready结构体后,调用Advance通知raft,它已处理完刚读到的Ready结构体,raft可以根据最新状态生成下一个Ready结构体。

还有一个ApplyConfChange函数,当Ready结构体中包含修改raft集群配置的log entry时,应用层会调用此函数,把配置应用到raft。

raft架构

瞄完raft应用架构,可以从宏观角度看一下raft是如何跟应用层对接的。

raft包内部有2个很重要的结构体:node和raft。

node结构体

node结构体(后续称为raft.node)实现了Node接口,负责跟应用层对接,raft.node有个goroutine持续运行,应用层raftNode也有goroutine持续运行,raftNode调用raft.node的函数,每个函数都有对应的一个channel,用来把raftNode要传递给raft的数据,发送给raft.node。比如Propose函数的通道是proc,Step函数的通道是recvc。

raft结构体

raft结构体(后续称为raft.raft)是raft算法的主要实现。

raft.node把输入推给raft.raft,raft.raft根据输入和当前的状态数据生成输出,输出临时保存在raft内,raft.node会检查raft.raft是否有输出,如果有输出数据,就把输出生成Ready结构体,并传递给应用层。

raft.raft应用层有一个storage,存放的是当前的状态数据,包含了保存在内存中的log entry,但这个storage并不是raft.raft的,是应用层的,raft.raft只从中读取数据,log entry的写入由应用层负责。

raft-arch

几个存储相关的概念

WAL是Write Ahead Logs的缩写,存储的是log entry记录,即所有写请求的记录。

storage也是存的log entry,只不过是保存在内存中的。

kv db是保存了所有数据的最新值,而log entry是修改数据值的操作记录。

log entry在集群节点之间达成共识之后,log entry会写入WAL文件,也会写入storage,然后会被应用到kv store中,改变kv db中的数据。

Snapshot是kv db是某个log entry被应用后生成的快照,可以根据快照快速回复kv db,而无需从所有的历史log entry依次应用,恢复kv db。

一个写请求的处理过程

有了上面架构层面的了解,我们从宏观的角度看一下一个写请求被处理的过程。

  1. 客户端把写请求发给leader节点
  2. leader节点的http api接收请求,并把请求传递给kv store,kv sotre把写请求发送给raftNode,raftNode把写请求传递给raft.node
  3. leader节点的raft.node把写请求转化为log entry,并交给raft.raft,raft.raft生成发送给每一个follower的Append消息
  4. leader节点的raft.node取出raft.raft中的Append消息以及其他数据,封装成Ready传递给raft.Node
  5. leader节点的raft.Node把Ready中的entry保存到storage,然后把Ready中的消息,发送给相应的节点
  6. follower节点的raft.Node收到消息,把消息传递给raft.node,raft.node推给raft.raft
  7. follower的raft.raft处理Append消息,进行匹配和校验后,生成Append Response消息和保存log entry
  8. follower的raft.node从raft.raft获取数据,然后生成Ready传递给raft.Node
  9. follower节点的raft.Node把Ready中的entry保存到storage,然后把Ready中的消息,发送给相应的节点
  10. leader节点的raft.Node收到消息,把消息传递给raft.node,raft.node推给raft.raft
  11. leader节点的raft.raft处理Append Response消息,然后检查已经达成半数以上同意的log entry,更新已经被commit的log entry的index
  12. leader节点的raft.raft在创建Append等消息的时候,填写了已被commited的log index,所以下次在生成消息,并发送给follower后,follower就根据committed log index提交本地的log entry
  13. 无论是leader,还是follower在生成Ready的时候,会包含已经被committed的log entry,这些entry是等待应用到kv store的,raftNode拿到Ready后,会把这些entry取出来,传递给kv store,kv store会修改key-value的最新值。

总结

本文从宏观角度介绍了:

  • 使用etcd raft应用的架构
  • 使用etcd raft应用应当提供哪些功能供raft使用
  • 应用是如何和etcd raft交互的
  • etcd raft涉及到的存储概念
  • 一个写请求从客户端到在节点之间达成一致,应用到状态机的过程

前言

本文在 https://github.com/maemual/raft-zh_cn/blob/master/raft-zh_cn.md 基础上修改、标注重点。如果遇到中文别扭的地方、不懂的地方,建议配合[英文原文][paper]一起阅读。

Raft与PBFT、Paxos等其他一致性算法相比,确实简单不少,易于理解和实现,还有很强的可用性。

Raft的3大核心是强领导者、领导者选举和成员关系变更(增加和减少Raft节点),论文就是围绕着3个核心进行介绍,以及论证安全性(正确性、一致性)和可用性。但成员关系变更的细节,依然还有一些疑问,做了一些注释在文中,需要去看源码和讨论弄清楚。

除了3大核心,论文还提到了一些优化策略:

  1. 日志快照能够解决2个问题:
    1. 日志不断积累占用空间不断增长,带来了可用性问题
    2. 性能低下的机器跟不上领导者的节奏,领导者可以把快照发送给跟随者,慢的跟随者就能“大跨步”到比较新的状态
  2. 在领导人接到大量请求时,领导人批量发送日志,可以提高系统的吞吐率

除了论文提到的优化,工业实践中也有几大类优化策略:

  1. Batch and Pipeline
  2. Append Log Parallelly
  3. Asynchronous Apply
  4. Asynchronous Lease Read
  5. Lease Read

相关优化文章:

  1. TiKV 源码解析系列 - Raft 的优化
  2. TiKV 源码解析系列 - Lease Read
  3. 详解蚂蚁金服 SOFAJRaft:生产级高性能 Java 实现
  4. 蚂蚁金服开源 SOFAJRaft:生产级 Java Raft 算法库

寻找一种易于理解的一致性算法(扩展版)

摘要

Raft 是一种为了管理复制日志的一致性算法。它提供了和 Paxos 算法相同的功能和性能,但是它的算法结构和 Paxos 不同,使得 Raft 算法更加容易理解并且更容易构建实际的系统。为了提升可理解性,Raft 将一致性算法分解成了几个关键模块,例如领导人选举、日志复制和安全性。同时它通过实施一个更强的一致性来减少需要考虑的状态的数量。从一个用户研究的结果可以证明,对于学生而言,Raft 算法比 Paxos 算法更加容易学习。Raft 算法还包括一个新的机制来允许集群成员的动态改变,它利用重叠的大多数来保证安全性。

1 介绍

一致性算法允许一组机器像一个整体一样工作,即使其中一些机器出现故障也能够继续工作下去。正因为如此,一致性算法在构建可信赖的大规模软件系统中扮演着重要的角色。在过去的 10 年里,Paxos 算法统治着一致性算法这一领域:绝大多数的实现都是基于 Paxos 或者受其影响。同时 Paxos 也成为了教学领域里讲解一致性问题时的示例。

但是不幸的是,尽管有很多工作都在尝试降低它的复杂性,但是 Paxos 算法依然十分难以理解。并且,Paxos 自身的算法结构需要进行大幅的修改才能够应用到实际的系统中。这些都导致了工业界和学术界都对 Paxos 算法感到十分头疼。

和 Paxos 算法进行过努力之后,我们开始寻找一种新的一致性算法,可以为构建实际的系统和教学提供更好的基础。我们的做法是不寻常的,我们的首要目标是可理解性:我们是否可以在实际系统中定义一个一致性算法,并且能够比 Paxos 算法以一种更加容易的方式来学习。此外,我们希望该算法方便系统构建者的直觉的发展。不仅一个算法能够工作很重要,而且能够显而易见的知道为什么能工作也很重要。

Raft 一致性算法就是这些工作的结果。在设计 Raft 算法的时候,我们使用一些特别的技巧来提升它的可理解性,包括算法分解(Raft 主要被分成了领导人选举,日志复制和安全三个模块)和减少状态机的状态(相对于 Paxos,Raft 减少了非确定性和服务器互相处于非一致性的方式)。一份针对两所大学 43 个学生的研究表明 Raft 明显比 Paxos 算法更加容易理解。在这些学生同时学习了这两种算法之后,和 Paxos 比起来,其中 33 个学生能够回答有关于 Raft 的问题。

Raft 算法在许多方面和现有的一致性算法都很相似(主要是 Oki 和 Liskov 的 Viewstamped Replication),但是它也有一些独特的特性:

  • 强领导者:和其他一致性算法相比,Raft 使用一种更强的领导能力形式。比如,日志条目只从领导者发送给其他的服务器。这种方式简化了对复制日志的管理并且使得 Raft 算法更加易于理解。
  • 领导选举:Raft 算法使用一个随机计时器来选举领导者。这种方式只是在任何一致性算法都必须实现的心跳机制上增加了一点机制。在解决冲突的时候会更加简单快捷。
  • 成员关系调整:Raft 使用一种联结一致(joint consensus)的方法来处理集群成员变换的问题,在这种方法下,处于调整过程中的两种不同的配置集群中大多数机器会有重叠,这就使得集群在成员变换的时候依然可以继续工作。

我们相信,Raft 算法不论出于教学目的还是作为实践项目的基础都是要比 Paxos 或者其他一致性算法要优异的。它比其他算法更加简单,更加容易理解;它的算法描述足以实现一个现实的系统;它有好多开源的实现并且在很多公司里使用;它的安全性已经被证明;它的效率和其他算法比起来也不相上下。

接下来,这篇论文会介绍以下内容:复制状态机问题(第 2 节),讨论 Paxos 的优点和缺点(第 3 节),讨论我们为了可理解性而采取的方法(第 4 节),阐述 Raft 一致性算法(第 5-8 节),评价 Raft 算法(第 9 节),以及一些相关的工作(第 10 节)。

2 复制状态机

一致性算法是从复制状态机的背景下提出的(参考英文原文引用37)。在这种方法中,一组服务器上的状态机产生相同状态的副本,并且在一些机器宕掉的情况下也可以继续运行。复制状态机在分布式系统中被用于解决很多容错的问题。例如,大规模的系统中通常都有一个集群领导者,像 GFS、HDFS 和 RAMCloud,典型应用就是一个独立的的复制状态机去管理领导选举和存储配置信息并且在领导人宕机的情况下也要存活下来。比如 Chubby 和 ZooKeeper。

图 1

图 1 :复制状态机的结构。一致性算法管理着来自客户端指令的复制日志。状态机从日志中处理相同顺序的相同指令,所以产生的结果也是相同的。

复制状态机通常都是基于复制日志实现的,如图 1。每一个服务器存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。每一个日志都按照相同的顺序包含相同的指令,所以每一个服务器都执行相同的指令序列。因为每个状态机都是确定的,每一次执行操作都产生相同的状态和同样的序列。

保证复制日志相同就是一致性算法的工作了。在一台服务器上,一致性模块接收客户端发送来的指令然后增加到自己的日志中去。它和其他服务器上的一致性模块进行通信来保证每一个服务器上的日志最终都以相同的顺序包含相同的请求,尽管有些服务器会宕机。一旦指令被正确的复制,每一个服务器的状态机按照日志顺序处理他们,然后输出结果被返回给客户端。因此,服务器集群看起来形成一个高可靠的状态机。

实际系统中使用的一致性算法通常含有以下特性:

  • 安全性保证(绝对不会返回一个错误的结果):在非拜占庭错误情况下,包括网络延迟、分区、丢包、冗余和乱序等错误都可以保证正确。
  • 可用性:集群中只要有大多数的机器可运行并且能够相互通信、和客户端通信,就可以保证可用。因此,一个典型的包含 5 个节点的集群可以容忍两个节点的失败。服务器被停止就认为是失败。他们当有稳定的存储的时候可以从状态中恢复回来并重新加入集群。
  • 不依赖时序来保证一致性:物理时钟错误或者极端的消息延迟只有在最坏情况下才会导致可用性问题
  • 通常情况下,一条指令可以尽可能快的在集群中大多数节点响应一轮远程过程调用时完成。小部分比较慢的节点不会影响系统整体的性能

3 Paxos 算法的问题

在过去的 10 年里,Leslie Lamport 的 Paxos 算法几乎已经成为一致性的代名词:Paxos 是在课程教学中最经常使用的算法,同时也是大多数一致性算法实现的起点。Paxos 首先定义了一个能够达成单一决策一致的协议,比如单条的复制日志项。我们把这一子集叫做单决策 Paxos。然后通过组合多个 Paxos 协议的实例来促进一系列决策的达成。Paxos 保证安全性和活性,同时也支持集群成员关系的变更。Paxos 的正确性已经被证明,在通常情况下也很高效。

不幸的是,Paxos 有两个明显的缺点。第一个缺点是 Paxos 算法特别的难以理解。完整的解释是出了名的不透明;通过极大的努力之后,也只有少数人成功理解了这个算法。因此,有了几次用更简单的术语来解释 Paxos 的尝试。尽管这些解释都只关注了单决策的子集问题,但依然很具有挑战性。在 2012 年 NSDI 的会议中的一次调查显示,很少有人对 Paxos 算法感到满意,甚至在经验老道的研究者中也是如此。我们自己也尝试去理解 Paxos;我们一直没能理解 Paxos 直到我们读了很多对 Paxos 的简化解释并且设计了我们自己的算法之后,这一过程花了近一年时间。

我们假设 Paxos 的不透明性来自它选择单决策问题作为它的基础。单决策 Paxos 是晦涩微妙的,它被划分成了两种没有简单直观解释和无法独立理解的情景。因此,这导致了很难建立起直观的感受为什么单决策 Paxos 算法能够工作。构成多决策 Paxos 增加了很多错综复杂的规则。我们相信,在多决策上达成一致性的问题(一份日志而不是单一的日志记录)能够被分解成其他的方式并且更加直接和明显。

Paxos算法的第二个问题就是它没有提供一个足够好的用来构建一个现实系统的基础。一个原因是还没有一种被广泛认同的多决策问题的算法。Lamport 的描述基本上都是关于单决策 Paxos 的;他简要描述了实施多决策 Paxos 的方法,但是缺乏很多细节。当然也有很多具体化 Paxos 的尝试,但是他们都互相不一样,和 Paxos 的概述也不同。例如 Chubby 这样的系统实现了一个类似于 Paxos 的算法,但是大多数的细节并没有被公开。

而且,Paxos 算法的结构也不是十分易于构建实践的系统;单决策分解也会产生其他的结果。例如,独立的选择一组日志条目然后合并成一个序列化的日志并没有带来太多的好处,仅仅增加了不少复杂性。围绕着日志来设计一个系统是更加简单高效的;新日志条目以严格限制的顺序增添到日志中去。另一个问题是,Paxos 使用了一种对等的点对点的方式作为它的核心(尽管它最终提议了一种弱领导人的方法来优化性能)。在只有一个决策会被制定的简化世界中是很有意义的,但是很少有现实的系统使用这种方式。如果有一系列的决策需要被制定,首先选择一个领导人,然后让他去协调所有的决议,会更加简单快速。

因此,实际的系统中很少有和 Paxos 相似的实践。每一种实现都是从 Paxos 开始研究,然后发现很多实现上的难题,再然后开发了一种和 Paxos 明显不一样的结构。这样是非常费时和容易出错的,并且理解 Paxos 的难度使得这个问题更加糟糕。Paxos 算法在理论上被证明是正确可行的,但是现实的系统和 Paxos 差别是如此的大,以至于这些证明没有什么太大的价值。下面来自 Chubby 实现非常典型:

在Paxos算法描述和实现现实系统中间有着巨大的鸿沟。最终的系统建立在一种没有经过证明的算法之上。

大彬注:Paxos 只提供了多进程如何确定出一个共同的值。没有提供像Raft一样的日志、快照、节点成员变更等,如果用Paxos实现一致性,就需要开发者自己设计,也就出现了上面提到的,各家的实现有非常大的不同,整套系统也没有经过完整的证明。

由于以上问题,我们认为 Paxos 算法既没有提供一个良好的基础给实践的系统,也没有给教学很好的帮助。基于一致性问题在大规模软件系统中的重要性,我们决定看看我们是否可以设计一个拥有更好特性的替代 Paxos 的一致性算法。Raft算法就是这次实验的结果。

4 为了可理解性的设计

设计 Raft 算法我们有几个初衷:它必须提供一个完整的实际的系统实现基础,这样才能大大减少开发者的工作;它必须在任何情况下都是安全的并且在大多数的情况下都是可用的;并且它的大部分操作必须是高效的。但是我们最重要也是最大的挑战是可理解性。它必须保证对于普遍的人群都可以十分容易的去理解。另外,它必须能够让人形成直观的认识,这样系统的构建者才能够在现实中进行必然的扩展。

在设计 Raft 算法的时候,有很多的点需要我们在各种备选方案中进行选择。在这种情况下,我们评估备选方案基于可理解性原则:解释各个备选方案有多大的难度(例如,Raft 的状态空间有多复杂,是否有微妙的暗示)?对于一个读者而言,完全理解这个方案和暗示是否容易?

我们意识到对这种可理解性分析上具有高度的主观性;尽管如此,我们使用了两种通常适用的技术来解决这个问题。第一个技术就是众所周知的问题分解:只要有可能,我们就将问题分解成几个相对独立的,可被解决的、可解释的和可理解的子问题。例如,Raft 算法被我们分成领导人选举,日志复制,安全性和角色改变几个部分

我们使用的第二个方法是通过减少状态的数量来简化需要考虑的状态空间,使得系统更加连贯并且在可能的时候消除不确定性。特别的,所有的日志是不允许有空洞的,并且 Raft 限制了日志之间变成不一致状态的可能。尽管在大多数情况下我们都试图去消除不确定性,但是也有一些情况下不确定性可以提升可理解性。尤其是,随机化方法增加了不确定性,但是他们有利于减少状态空间数量,通过处理所有可能选择时使用相似的方法。我们使用随机化去简化 Raft 中领导人选举算法

5 Raft 一致性算法

Raft 是一种用来管理章节 2 中描述的复制日志的算法。图 2 为了参考之用,总结这个算法的简略版本,图 3 列举了这个算法的一些关键特性。图中的这些元素会在剩下的章节逐一介绍。

Raft 通过选举一个权威的领导人,然后给予他全部的管理复制日志的责任来实现一致性。领导人从客户端接收日志条目,把日志条目复制到其他服务器上,并且当保证安全性的时候告诉其他的服务器应用日志条目到他们的状态机中。拥有一个领导人大大简化了对复制日志的管理。例如,领导人可以决定新的日志条目需要放在日志中的什么位置而不需要和其他服务器商议,并且数据都从领导人流向其他服务器。一个领导人可以宕机,可以和其他服务器失去连接,这时一个新的领导人会被选举出来。

通过领导人的方式,Raft 将一致性问题分解成了三个相对独立的子问题,这些问题会在接下来的子章节中进行讨论:

  • 领导选举:一个新的领导人需要被选举出来,当现存的领导人宕机的时候(章节 5.2)
  • 日志复制:领导人必须从客户端接收日志然后复制到集群中的其他节点,并且强制要求其他节点的日志保持和自己相同。
  • 安全性:在 Raft 中安全性的关键是在图 3 中展示的状态机安全:如果有任何的服务器节点已经应用了一个确定的日志条目到它的状态机中,那么其他服务器节点不能在同一个日志索引位置应用一个不同的指令。章节 5.4 阐述了 Raft 算法是如何保证这个特性的;这个解决方案涉及到一个额外的选举机制(5.2 节)上的限制。

在展示一致性算法之后,这一章节会讨论可用性的一些问题和计时在系统的作用。

状态

状态 所有服务器上持久存在的状态
currentTerm 服务器最后一次知道的任期号(初始化为 0,持续递增)
votedFor 在当前任期获得选票的候选人的 Id
log[] 日志条目集;每一个条目包含一个用户状态机执行的指令,和收到时的任期号
状态 所有服务器上经常变的状态
commitIndex 已知的最大的已经被提交的日志条目的索引
lastApplied 最后被应用到状态机的日志条目索引值(初始化为 0,持续递增)
状态 在领导人上经常改变的状态 (选举后重新初始化)
nextIndex[] 为每个服务器保存的,需要发送给他的下一个日志条目的索引值(初始化为领导人最后索引值加1)
matchIndex[] 为每个服务器保存的,已经复制给他的日志的最高索引值(初始值为0)

追加日志的RPC

由领导人负责调用来复制日志指令;也会用作heartbeat

RPC请求参数 解释
term 领导人的任期号
leaderId 领导人的 Id,以便于跟随者重定向请求
prevLogIndex 前一条日志条目索引
prevLogTerm prevLogIndex 条目的所在的任期
entries[] 准备存储的日志条目(表示心跳时为空;一次性发送多个是为了提高效率)
leaderCommit 领导人已经提交的日志的索引值
RPC结果返回值 解释
term 当前的任期号,用于领导人去更新自己
success 跟随者包含了匹配上 prevLogIndex 和 prevLogTerm 的日志时为真

接收者实现:

  1. 如果 term < currentTerm 就返回 false (5.1 节)
  2. 如果日志在 prevLogIndex 位置处的日志条目的任期号和 prevLogTerm 不匹配,则返回 false (5.3 节)
  3. 如果已经存在的日志条目和新的产生冲突(相同索引值但是任期号不同),删除这一条和之后所有的条目 (5.3 节)
  4. 追加日志中不存在的任何新条目
  5. 如果 leaderCommit > commitIndex,令 commitIndex 等于 leaderCommit 和 新日志条目索引值中较小的一个

请求投票的RPC

由候选人负责调用用来征集选票(5.2 节)

请求的参数 解释
term 候选人的任期号
candidateId 请求选票的候选人的 Id
lastLogIndex 候选人的最后日志条目的索引值
lastLogTerm 候选人最后日志条目的任期号
请求的结果 解释
term 当前任期号,以便于候选人去更新自己的任期号
voteGranted 候选人赢得了此张选票时为真

接收者实现:

  1. 如果term < currentTerm返回 false (5.2 节)
  2. 如果 votedFor 为空或者为 candidateId,并且候选人的日志至少和自己一样新,那么就投票给他(5.2 节,5.4 节)

所有服务器需遵守的规则

所有服务器:

  • 如果commitIndex > lastApplied,那么就 lastApplied 加一,并把log[lastApplied]应用到状态机中(5.3 节)
  • 如果接收到的 RPC 请求或响应中,任期号T > currentTerm,那么就令 currentTerm 等于 T,并切换状态为跟随者(5.1 节)

跟随者(5.2 节):

  • 响应来自候选人和领导者的请求
  • 如果在超过选举超时时间的情况之前都没有收到领导人的心跳,或者是候选人请求投票的,就自己变成候选人

候选人(5.2 节):

  • 在转变成候选人后就立即开始选举过程
    • 自增当前的任期号(currentTerm)
    • 给自己投票
    • 重置选举超时计时器
    • 发送请求投票的 RPC 给其他所有服务器
  • 如果接收到大多数服务器的选票,那么就变成领导人
  • 如果接收到来自新的领导人的追加日志 RPC,转变成跟随者
  • 如果选举过程超时,再次发起一轮选举

领导人:

  • 一旦成为领导人:发送空的追加日志 RPC(心跳)给其他所有的服务器;在一定的空余时间之后不停的重复发送,以阻止跟随者超时(5.2 节)
  • 如果接收到来自客户端的请求:追加条目到本地日志中,在条目被应用到状态机后响应客户端(5.3 节)
  • 如果对于一个跟随者,最后日志条目的索引值大于等于 nextIndex,那么:发送从 nextIndex 开始的所有日志条目:
    • 如果成功:更新相应跟随者的 nextIndex 和 matchIndex
    • 如果因为日志不一致而失败,减少 nextIndex 重试
  • 如果存在一个满足N > commitIndex的 N,并且大多数的matchIndex[i] ≥ N成立,并且log[N].term == currentTerm成立,那么令 commitIndex 等于这个 N (5.3 和 5.4 节)

图 2

图 2:一个关于 Raft 一致性算法的浓缩总结(不包括成员变换和日志压缩)。

特性 解释
选举安全特性 对于一个给定的任期号,最多只会有一个领导人被选举出来(5.2 节)
领导人只追加原则 领导人绝对不会删除或者覆盖自己的日志,只会增加(5.3 节)
日志匹配原则 如果两个日志在相同的索引位置的日志条目的任期号相同,那么我们就认为这个日志从头到这个索引位置之间全部完全相同(5.3 节)
领导人完全特性 如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中(5.4 节)
状态机安全特性 如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志(5.4.3 节)

图 3

图 3:Raft 在任何时候都保证以上的各个特性。

5.1 Raft 基础

一个 Raft 集群包含若干个服务器节点;通常是 5 个,这允许整个系统容忍 2 个节点的失效。在任何时刻,每一个服务器节点都处于这三个状态之一:领导人、跟随者或者候选人。在通常情况下,系统中只有一个领导人并且其他的节点全部都是跟随者。跟随者都是被动的:他们不会发送任何请求,只是简单的响应来自领导者或者候选人的请求。领导人处理所有的客户端请求(如果一个客户端和跟随者联系,那么跟随者会把请求重定向给领导人)。第三种状态,候选人,是用来在 5.2 节描述的选举新领导人时使用。图 4 展示了这些状态和他们之间的转换关系;这些转换关系会在接下来进行讨论。

图 4

图 4:服务器状态。跟随者只响应来自其他服务器的请求。如果跟随者接收不到消息,那么他就会变成候选人并发起一次选举。获得集群中大多数选票的候选人将成为领导者。在一个任期内,领导人一直都会是领导人直到自己宕机了。

图 5

图 5:时间被划分成一个个的任期,每个任期开始都是一次选举。在选举成功后,领导人会管理整个集群直到任期结束。有时候选举会失败,那么这个任期就会没有领导人而结束。任期之间的切换可以在不同的时间不同的服务器上观察到。

Raft 把时间分割成任意长度的任期,如图 5。任期用连续的整数标记。每一段任期从一次选举开始,就像章节 5.2 描述的一样,一个或者多个候选人尝试成为领导者。如果一个候选人赢得选举,然后他就在接下来的任期内充当领导人的职责。在某些情况下,一次选举过程会造成选票的瓜分。在这种情况下,这一任期会以没有领导人结束;一个新的任期(和一次新的选举)会很快重新开始。Raft 保证了在一个给定的任期内,最多只有一个领导者。

不同的服务器节点可能多次观察到任期之间的转换,但在某些情况下,一个节点也可能观察不到任何一次选举或者整个任期全程。任期在 Raft 算法中充当逻辑时钟的作用,这会允许服务器节点查明一些过期的信息比如陈旧的领导者。每一个节点存储一个当前任期号,这一编号在整个时期内单调的增长。当服务器之间通信的时候会交换当前任期号;如果一个服务器的当前任期号比其他人小,那么他会更新自己的编号到较大的编号值。如果一个候选人或者领导者发现自己的任期号过期了,那么他会立即恢复成跟随者状态。如果一个节点接收到一个包含过期的任期号的请求,那么他会直接拒绝这个请求。

Raft 算法中服务器节点之间通信使用远程过程调用(RPCs),并且基本的一致性算法只需要两种类型的 RPCs。请求投票(RequestVote) RPCs 由候选人在选举期间发起(章节 5.2),然后追加条目(AppendEntries)RPCs 由领导人发起,用来复制日志和提供一种心跳机制(章节 5.3)。第 7 节为了在服务器之间传输快照增加了第三种 RPC。当服务器没有及时的收到 RPC 的响应时,会进行重试, 并且他们能够并行的发起 RPCs 来获得最佳的性能

5.2 领导人选举

Raft 使用一种心跳机制来触发领导人选举。当服务器程序启动时,他们都是跟随者身份。一个服务器节点继续保持着跟随者状态只要他从领导人或者候选者处接收到有效的 RPCs。领导者周期性的向所有跟随者发送心跳包(即不包含日志项内容的追加日志项 RPCs)来维持自己的权威如果一个跟随者在一段时间里没有接收到任何消息,也就是选举超时,那么他就会认为系统中没有可用的领导者,并且发起选举以选出新的领导者

要开始一次选举过程,跟随者先要增加自己的当前任期号并且转换到候选人状态。然后他会并行的向集群中的其他服务器节点发送请求投票的 RPCs 来给自己投票。候选人会继续保持着当前状态直到以下三件事情之一发生:(a) 他自己赢得了这次的选举,(b) 其他的服务器成为领导者,(c) 一段时间之后没有任何一个获胜的人。这些结果会分别的在下面的段落里进行讨论。

当一个候选人从整个集群的大多数服务器节点获得了针对同一个任期号的选票,那么他就赢得了这次选举并成为领导人。每一个服务器最多会对一个任期号投出一张选票,按照先来先服务的原则(注意:5.4 节在投票上增加了一点额外的限制)。要求大多数选票的规则确保了最多只会有一个候选人赢得此次选举(图 3 中的选举安全性)。一旦候选人赢得选举,他就立即成为领导人。然后他会向其他的服务器发送心跳消息来建立自己的权威并且阻止新的领导人的产生

在等待投票的时候,候选人可能会从其他的服务器接收到声明它是领导人的追加日志项 RPC。如果这个领导人的任期号(包含在此次的 RPC中)不小于候选人当前的任期号,那么候选人会承认领导人合法并回到跟随者状态。 如果此次 RPC 中的任期号比自己小,那么候选人就会拒绝这次的 RPC 并且继续保持候选人状态。

第三种可能的结果是候选人既没有赢得选举也没有输:如果有多个跟随者同时成为候选人,那么选票可能会被瓜分以至于没有候选人可以赢得大多数人的支持。当这种情况发生的时候,每一个候选人都会超时,然后通过增加当前任期号来开始一轮新的选举。然而,没有其他机制的话,选票可能会被无限的重复瓜分。

Raft 算法使用随机选举超时时间的方法来确保很少会发生选票瓜分的情况,就算发生也能很快的解决。为了阻止选票起初就被瓜分,选举超时时间是从一个固定的区间(例如 150-300 毫秒)随机选择。这样可以把服务器都分散开以至于在大多数情况下只有一个服务器会选举超时;然后他赢得选举并在其他服务器超时之前发送心跳包。同样的机制被用在选票瓜分的情况下。每一个候选人在开始一次选举的时候会重置一个随机的选举超时时间,然后在超时时间内等待投票的结果;这样减少了在新的选举中另外的选票瓜分的可能性。9.3 节展示了这种方案能够快速的选出一个领导人。

领导人选举这个例子,体现了可理解性原则是如何指导我们进行方案设计的。起初我们计划使用一种排名系统:每一个候选人都被赋予一个唯一的排名,供候选人之间竞争时进行选择。如果一个候选人发现另一个候选人拥有更高的排名,那么他就会回到跟随者状态,这样高排名的候选人能够更加容易的赢得下一次选举。但是我们发现这种方法在可用性方面会有一点问题(如果高排名的服务器宕机了,那么低排名的服务器可能会超时并再次进入候选人状态。而且如果这个行为发生得足够快,则可能会导致整个选举过程都被重置掉)。我们针对算法进行了多次调整,但是每次调整之后都会有新的问题。最终我们认为随机重试的方法是更加明显和易于理解的。

5.3 日志复制

一旦一个领导人被选举出来,他就开始为客户端提供服务。客户端的每一个请求都包含一条被复制状态机执行的指令。领导人把这条指令作为一条新的日志条目追加到日志中去,然后并行的发起追加条目 RPCs 给其他的服务器,让他们复制这条日志条目。当这条日志条目被安全的复制(下面会介绍),领导人会应用这条日志条目到它的状态机中然后把执行的结果返回给客户端。如果跟随者崩溃或者运行缓慢,再或者网络丢包,领导人会不断的重复尝试追加日志条目 RPCs (尽管已经回复了客户端)直到所有的跟随者都最终存储了所有的日志条目。

所以这时并没有真正的写入日志,如果所有的follower都没收到这个日志,并且leader这时候挂了,其实这条日志可能丢失了。

图 6

图 6:日志由有序序号标记的条目组成。每个条目都包含创建时的任期号(图中框中的数字),和一个状态机需要执行的指令。一个条目当可以安全的被应用到状态机中去的时候,就认为是可以提交了。

日志以图 6 展示的方式组织。每一个日志条目存储一条状态机指令和从领导人收到这条指令时的任期号。日志中的任期号用来检查是否出现不一致的情况,同时也用来保证图 3 中的某些性质。每一条日志条目同时也都有一个整数索引值来表明它在日志中的位置

领导人来决定什么时候把日志条目应用到状态机中是安全的:这种日志条目被称为已提交。Raft 算法保证所有已提交的日志条目都是持久化的并且最终会被所有可用的状态机执行。在领导人将创建的日志条目复制到大多数的服务器上的时候,日志条目就会被提交(例如在图 6 中的条目 7)。同时,领导人的日志中之前的所有日志条目也都会被提交,包括由其他领导人创建的条目。5.4 节会讨论某些当在领导人改变之后应用这条规则的隐晦内容,同时他也展示了这种提交的定义是安全的。领导人跟踪了最大的将会被提交的日志项的索引,并且索引值会被包含在未来的所有追加日志 RPCs (包括心跳包),这样其他的服务器才能最终知道领导人的提交位置。一旦跟随者知道一条日志条目已经被提交,那么他也会将这个日志条目应用到本地的状态机中(按照日志的顺序)。

我们设计了 Raft 的日志机制来维护一个不同服务器的日志之间的高层次的一致性。这么做不仅简化了系统的行为也使得更加可预计,同时他也是安全性保证的一个重要组件。Raft 维护着以下的特性,这些同时也组成了图 3 中的日志匹配特性

  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们存储了相同的指令。
  • 如果在不同的日志中的两个条目拥有相同的索引和任期号,那么他们之前的所有日志条目也全部相同。

第一个特性来自这样的一个事实,领导人最多在一个任期里在指定的一个日志索引位置创建一条日志条目,同时日志条目在日志中的位置也从来不会改变。第二个特性由追加日志 RPC 的一个简单的一致性检查所保证。在发送追加日志 RPC 的时候,领导人会把新的日志条目紧接着之前的条目的索引位置和任期号包含在里面。如果跟随者在它的日志中找不到包含相同索引位置和任期号的条目,那么他就会拒绝接收新的日志条目。一致性检查就像一个归纳步骤:一开始空的日志状态肯定是满足日志匹配特性的,当日志扩展的时候,一致性检查也确保了日志匹配特性。因此,每当追加日志 RPC 返回成功时,领导人就知道跟随者的日志一定是和自己相同的了。

在正常的操作中,领导人和跟随者的日志保持一致性,所以追加日志 RPC 的一致性检查从来不会失败。然而,领导人崩溃的情况会使得日志处于不一致的状态(老的领导人可能还没有完全复制所有的日志条目)。这种不一致问题会在领导人和跟随者的一系列崩溃下加剧。图 7 展示了跟随者的日志可能和新的领导人不同的方式。跟随者可能会丢失一些在新的领导人中有的日志条目,他也可能拥有一些领导人没有的日志条目,或者两者都发生。丢失或者多出日志条目可能会持续多个任期

图 7

图 7:当一个领导人成功当选时,跟随者可能是任何情况(a-f)。每一个盒子表示是一个日志条目;里面的数字表示任期号。跟随者可能会缺少一些日志条目(a-b),可能会有一些未被提交的日志条目(c-d),或者两种情况都存在(e-f)。例如,场景 f 可能会这样发生,某服务器在任期 2 的时候是领导人,已追加了一些日志条目到自己的日志中,但在提交之前就崩溃了;很快这个机器就被重启了,在任期 3 重新被选为领导人,并且又增加了一些日志条目到自己的日志中;在任期 2 和任期 3 的日志被提交之前,这个服务器又宕机了,并且在接下来的几个任期里一直处于宕机状态。

在 Raft 算法中,领导人处理不一致是通过强制跟随者直接复制自己的日志来解决了。这意味着在跟随者中的冲突的日志条目会被领导人的日志覆盖。5.4 节会阐述如何通过增加一些限制来使得这样的操作是安全的。

要使得跟随者的日志进入和自己一致的状态,领导人必须找到最后两者达成一致的地方,然后删除跟随者从那个点之后的所有日志条目,发送自己的日志给跟随者。所有的这些操作都在进行追加日志 RPCs 的一致性检查时完成。领导人针对每一个跟随者维护了一个 nextIndex,这表示下一个需要发送给跟随者的日志条目的索引地址。当一个领导人刚获得权力的时候,他初始化所有的 nextIndex 值为自己的最后一条日志的index加1(图 7 中的 11)。如果一个跟随者的日志和领导人不一致,那么在下一次的追加日志 RPC 时的一致性检查就会失败。在被跟随者拒绝之后,领导人就会减小 nextIndex 值并进行重试。最终 nextIndex 会在某个位置使得领导人和跟随者的日志达成一致。当这种情况发生,追加日志 RPC 就会成功,这时就会把跟随者冲突的日志条目全部删除并且加上领导人的日志。一旦追加日志 RPC 成功,那么跟随者的日志就会和领导人保持一致,并且在接下来的任期里一直继续保持。

如果需要的话,算法可以通过减少被拒绝的追加日志 RPCs 的次数来优化。例如,当追加日志 RPC 的请求被拒绝的时候,跟随者可以包含冲突的条目的任期号和自己存储的那个任期的最早的索引地址。借助这些信息,领导人可以减小 nextIndex 越过所有那个任期冲突的所有日志条目;这样就变成每个任期需要一次追加条目 RPC 而不是每个条目一次。在实践中,我们十分怀疑这种优化是否是必要的,因为失败是很少发生的并且也不大可能会有这么多不一致的日志。

通过这种机制,领导人在获得权力的时候就不需要任何特殊的操作来恢复一致性。他只需要进行正常的操作,然后日志就能自动的在回复追加日志 RPC 的一致性检查失败的时候自动趋于一致。领导人从来不会覆盖或者删除自己的日志(图 3 的领导人只追加特性)。

日志复制机制展示出了第 2 节中形容的一致性特性:Raft 能够接受,复制并应用新的日志条目只要大部分的机器是工作的;在通常的情况下,新的日志条目可以在一次 RPC 中被复制给集群中的大多数机器;并且单个的缓慢的跟随者不会影响整体的性能。

5.4 安全性

前面的章节里描述了 Raft 算法是如何选举和复制日志的。然而,到目前为止描述的机制并不能充分的保证每一个状态机会按照相同的顺序执行相同的指令。例如,一个跟随者可能会进入不可用状态同时领导人已经提交了若干的日志条目,然后这个跟随者可能会被选举为领导人并且覆盖这些日志条目;因此,不同的状态机可能会执行不同的指令序列

这一节通过在领导选举的时候增加一些限制来完善 Raft 算法。这一限制保证了任何的领导人对于给定的任期号,都拥有了之前任期的所有被提交的日志条目(图 3 中的领导人完整特性)。增加这一选举时的限制,我们对于提交时的规则也更加清晰。最终,我们将展示对于领导人完整特性的简要证明,并且说明领导人是如何领导复制状态机的做出正确行为的。

5.4.1 选举限制

在任何基于领导人的一致性算法中,领导人都必须存储所有已经提交的日志条目。在某些一致性算法中,例如 Viewstamped Replication,某个节点即使是一开始并没有包含所有已经提交的日志条目,它也能被选为领导者。这些算法都包含一些额外的机制来识别丢失的日志条目并把他们传送给新的领导人,要么是在选举阶段要么在之后很快进行。不幸的是,这种方法会导致相当大的额外的机制和复杂性。Raft 使用了一种更加简单的方法,它可以保证所有之前的任期号中已经提交的日志条目在选举的时候都会出现在新的领导人中,不需要传送这些日志条目给领导人。这意味着日志条目的传送是单向的,只从领导人传给跟随者,并且领导人从不会覆盖自身本地日志中已经存在的条目。

Raft 使用投票的方式来阻止一个候选人赢得选举,除非这个候选人包含了所有已经提交的日志条目。候选人为了赢得选举必须联系集群中的大部分节点,这意味着每一个已经提交的日志条目在这些服务器节点中肯定存在于至少一个节点上。如果候选人的日志至少和大多数的服务器节点一样新(这个新的定义会在下面讨论),那么他一定持有了所有已经提交的日志条目。请求投票 RPC 实现了这样的限制: RPC 中包含了候选人的日志信息,然后投票人会拒绝掉那些日志没有自己的日志新的投票请求

这样选举出来的leader,包含的日志数也是尽量多的,可能比某几个少,但比大多数节点多。

Raft 通过比较两份日志中最后一条日志条目的索引值和任期号定义谁的日志比较新。如果两份日志最后的条目的任期号不同,那么任期号大的日志更加新。如果两份日志最后的条目任期号相同,那么日志比较长的那个就更加新。

5.4.2 提交之前任期内的日志条目

如同 5.3 节介绍的那样,领导人知道一条当前任期内的日志记录是可以被提交的,只要它被存储到了大多数的服务器上。如果一个领导人在提交日志条目之前崩溃了,未来后续的领导人会继续尝试复制这条日志记录。然而,一个领导人不能断定一个之前任期里的日志条目被保存到大多数服务器上的时候就一定已经提交了。图 8 展示了一种情况,一条已经被存储到大多数节点上的老日志条目,也依然有可能会被未来的领导人覆盖掉。

图 8

图 8:如图的时间序列展示了为什么领导人无法决定对老任期号的日志条目进行提交。在 (a) 中,S1 是领导者,部分的复制了索引位置 2 的日志条目。在 (b) 中,S1 崩溃了,然后 S5 在任期 3 里通过 S3、S4 和自己的选票赢得选举,然后从客户端接收了一条不一样的日志条目放在了索引 2 处。然后到 (c),S5 又崩溃了;S1 重新启动,选举成功,开始复制日志。在这时,来自任期 2 的那条日志已经被复制到了集群中的大多数机器上,但是还没有被提交。如果 S1 在 (d) 中又崩溃了,S5 可以重新被选举成功(通过来自 S2,S3 和 S4 的选票),然后覆盖了他们在索引 2 处的日志。反之,如果在崩溃之前,S1 把自己主导的新任期里产生的日志条目复制到了大多数机器上,就如 (e) 中那样,那么在后面任期里面这些新的日志条目就会被提交(因为S5 就不可能选举成功)。 这样在同一时刻就同时保证了,之前的所有老的日志条目就会被提交。

为了消除图 8 里描述的情况,Raft 永远不会通过计算副本数目的方式去提交一个之前任期内的日志条目。只有领导人当前任期里的日志条目通过计算副本数目可以被提交;一旦当前任期的日志条目以这种方式被提交,那么由于日志匹配特性,之前的日志条目也都会被间接的提交。在某些情况下,领导人可以安全的知道一个老的日志条目是否已经被提交(例如,该条目是否存储到所有服务器上),但是 Raft 为了简化问题使用一种更加保守的方法。

补充:获得多数投票的节点被选举为leader,leader并不一定包含上一个任期未提交的log。
如果包含,当前新日志提交的时候,新日志之前的日志也被确认,那些没有前一个任期日志的节点,会跟leader进行同步。

当领导人复制之前任期里的日志时,Raft 会为所有日志保留原始的任期号, 这在提交规则上产生了额外的复杂性。在其他的一致性算法中,如果一个新的领导人要重新复制之前的任期里的日志时,它必须使用当前新的任期号。Raft 使用的方法更加容易辨别出日志,因为它可以随着时间和日志的变化对日志维护着同一个任期编号。另外,和其他的算法相比,Raft 中的新领导人只需要发送更少日志条目(其他算法中必须在他们被提交之前发送更多的冗余日志条目来为他们重新编号)。

5.4.3 安全性论证

补:本节证明被提交的log是不会被删除的,即使发生leader切换,以及怎么确保所有机器数据一致。

在给定了完整的 Raft 算法之后,我们现在可以更加精确的讨论领导人完整性特性(这一讨论基于 9.2 节的安全性证明)。我们假设领导人完全性特性是不存在的,然后我们推出矛盾来。假设任期 T 的领导人(领导人 T)在任期内提交了一条日志条目,但是这条日志条目没有被存储到未来某个任期的领导人的日志中。设大于 T 的最小任期 U 的领导人 U 没有这条日志条目。

图 9

图 9:如果 S1 (任期 T 的领导者)提交了一条新的日志在它的任期里,然后 S5 在之后的任期 U 里被选举为领导人,然后至少会有一个机器,如 S3,既拥有来自 S1 的日志,也给 S5 投票了。

  1. 在领导人 U 选举的时候一定没有那条被提交的日志条目(领导人从不会删除或者覆盖任何条目)。
  2. 领导人 T 复制这条日志条目给集群中的大多数节点,同时,领导人U 从集群中的大多数节点赢得了选票。因此,至少有一个节点(投票者、选民)同时接受了来自领导人T 的日志条目,并且给领导人U 投票了,如图 9。这个投票者是产生这个矛盾的关键。
  3. 这个投票者必须在给领导人 U 投票之前先接受了从领导人 T 发来的已经被提交的日志条目;否则他就会拒绝来自领导人 T 的追加日志请求(因为此时他的任期号会比 T 大)。
  4. 投票者在给领导人 U 投票时依然保存有这条日志条目,因为任何中间的领导人都包含该日志条目(根据上述的假设),领导人从不会删除条目,并且跟随者只有在和领导人冲突的时候才会删除条目。
  5. 投票者把自己选票投给领导人 U 时,领导人 U 的日志必须和投票者自己一样新。这就导致了两者矛盾之一。
  6. 首先,如果投票者和领导人 U 的最后一条日志的任期号相同,那么领导人 U 的日志至少和投票者一样长,所以领导人 U 的日志一定包含所有投票者的日志。这是另一处矛盾,因为投票者包含了那条已经被提交的日志条目,但是在上述的假设里,领导人 U 是不包含的。
  7. 除此之外,领导人 U 的最后一条日志的任期号就必须比投票人大了。此外,他也比 T 大,因为投票人的最后一条日志的任期号至少和 T 一样大(他包含了来自任期 T 的已提交的日志)。创建了领导人 U 最后一条日志的之前领导人一定已经包含了那条被提交的日志(根据上述假设,领导人 U 是第一个不包含该日志条目的领导人)。所以,根据日志匹配特性,领导人 U 一定也包含那条被提交的日志,这里产生矛盾。
  8. 这里完成了矛盾。因此,所有比 T 大的领导人一定包含了所有来自 T 的已经被提交的日志。
  9. 日志匹配原则保证了未来的领导人也同时会包含被间接提交的条目,例如图 8 (d) 中的索引 2。

通过领导人完全特性,我们就能证明图 3 中的状态机安全特性,即如果服务器已经在某个给定的索引值应用了日志条目到自己的状态机里,那么其他的服务器不会应用一个不一样的日志到同一个索引值上。在一个服务器应用一条日志条目到他自己的状态机中时,他的日志必须和领导人的日志,在该条目和之前的条目上相同,并且已经被提交。现在我们来考虑在任何一个服务器应用一个指定索引位置的日志的最小任期;日志完全特性保证拥有更高任期号的领导人会存储相同的日志条目,所以之后的任期里应用某个索引位置的日志条目也会是相同的值。因此,状态机安全特性是成立的。

最后,Raft 要求服务器按照日志中索引位置顺序应用日志条目,和状态机安全特性结合起来看,这就意味着所有的服务器会应用相同的日志序列集到自己的状态机中,并且是按照相同的顺序

5.5 跟随者和候选人崩溃

到目前为止,我们都只关注了领导人崩溃的情况。跟随者和候选人崩溃后的处理方式比领导人要简单的多,并且他们的处理方式是相同的。如果跟随者或者候选人崩溃了,那么后续发送给他们的 RPCs 都会失败。Raft 中处理这种失败就是简单的通过无限的重试;如果崩溃的机器重启了,那么这些 RPC 就会完整的成功。如果一个服务器在完成了一个 RPC,但是还没有响应的时候崩溃了,那么在他重新启动之后就会再次收到同样的请求。Raft 的 RPCs 都是幂等的,所以这样重试不会造成任何问题。例如一个跟随者如果收到追加日志请求但是他已经包含了这一日志,那么他就会直接忽略这个新的请求。

5.6 时间和可用性

Raft 的要求之一就是安全性不能依赖时间:整个系统不能因为某些事件运行的比预期快一点或者慢一点就产生了错误的结果。但是,可用性(系统可以及时的响应客户端)不可避免的要依赖于时间。例如,如果消息交换比服务器故障间隔时间长,候选人将没有足够长的时间来赢得选举;没有一个稳定的领导人,Raft 将无法工作。

领导人选举是 Raft 中对时间要求最为关键的方面。Raft 可以选举并维持一个稳定的领导人,只要系统满足下面的时间要求

广播时间(broadcastTime) << 选举超时时间(electionTimeout) << 平均故障间隔时间(MTBF)

在这个不等式中,广播时间指的是从一个服务器并行的发送 RPCs 给集群中的其他服务器并接收响应的平均时间;选举超时时间就是在 5.2 节中介绍的选举的超时时间限制;然后平均故障间隔时间就是对于一台服务器而言,两次故障之间的平均时间。广播时间必须比选举超时时间小一个量级,这样领导人才能够发送稳定的心跳消息来阻止跟随者开始进入选举状态;通过随机化选举超时时间的方法,这个不等式也使得选票瓜分的情况变得不可能。选举超时时间应该要比平均故障间隔时间小上几个数量级,这样整个系统才能稳定的运行。当领导人崩溃后,整个系统会大约相当于选举超时的时间里不可用;我们希望这种情况在整个系统的运行中很少出现。

广播时间和平均故障间隔时间是由系统决定的,但是选举超时时间是我们自己选择的。Raft 的 RPCs 需要接收方将信息持久化的保存到稳定存储中去,所以广播时间大约是 0.5 毫秒到 20 毫秒,取决于存储的技术。因此,选举超时时间可能需要在 10 毫秒到 500 毫秒之间。大多数的服务器的平均故障间隔时间都在几个月甚至更长,很容易满足时间的需求。

6 集群成员变化

到目前为止,我们都假设集群的配置(加入到一致性算法的服务器集合)是固定不变的。但是在实践中,偶尔是会改变集群的配置的,例如替换那些宕机的机器或者改变复制级别。尽管可以通过暂停整个集群,更新所有配置,然后重启整个集群的方式来实现,但是在更改的时候集群会不可用。另外,如果存在手工操作步骤,那么就会有操作失误的风险。为了避免这样的问题,我们决定自动化配置改变并且将其纳入到 Raft 一致性算法中来。

为了让配置修改机制能够安全,那么在转换的过程中不能够存在任何时间点使得两个领导人同时被选举成功在同一个任期里。不幸的是,任何服务器直接从旧的配置直接转换到新的配置的方案都是不安全的。一次性自动的转换所有服务器是不可能的,所以在转换期间整个集群存在划分成两个独立的大多数群体的可能性(见图 10)。

补:成员变更有脑裂的风险。

图 10

图 10:直接从一种配置转到新的配置是十分不安全的,因为各个机器可能在任何的时候进行转换。在这个例子中,集群配额从 3 台机器变成了 5 台。不幸的是,存在这样的一个时间点,两个不同的领导人在同一个任期里都可以被选举成功。一个是通过旧的配置,一个通过新的配置。

为了保证安全性,配置更改必须使用两阶段方法。目前有很多种两阶段的实现。例如,有些系统在第一阶段停掉旧的配置所以集群就不能处理客户端请求;然后在第二阶段在启用新的配置。在 Raft 中,集群先切换到一个过渡的配置,我们称之为联结一致(joint consensus);一旦联结一致(joint consensus)已经被提交了,那么系统就切换到新的配置上。联结一致(joint consensus)是老配置和新配置的结合:

  • 日志条目被复制给集群中新、老配置的所有服务器。
  • 新、旧配置的服务器都可以成为领导人。
  • 达成一致(针对选举和提交)需要分别在两种配置上获得大多数的支持。

联结一致(joint consensus)允许独立的服务器在不影响安全性的前提下,在不同的时间进行配置转换过程。此外,联结一致(joint consensus)可以让集群在配置转换的过程人依然响应客户端的请求。

集群配置在复制日志中以特殊的日志条目来存储和通信;图 11 展示了配置转换的过程。当一个领导人接收到一个改变配置从 C-old 到 C-new 的请求,他会为了联结一致(joint consensus)存储配置(图中的 C-old,new),以前面描述的日志条目和副本的形式。一旦一个服务器将新的配置日志条目增加到它的日志中,他就会用这个配置来做出未来所有的决定(服务器总是使用最新的配置,无论他是否已经被提交)。这意味着领导人要使用 C-old,new 的规则来决定日志条目 C-old,new 什么时候需要被提交。如果领导人崩溃了,被选出来的新领导人可能是使用 C-old 配置也可能是 C-old,new 配置,这取决于赢得选举的候选人是否已经接收到了 C-old,new 配置。在任何情况下, C-new 配置在这一时期都不会单方面的做出决定。

一旦 C-old,new 被提交,那么无论是 C-old 还是 C-new,在没有经过他人批准的情况下都不可能做出决定,并且领导人完全特性保证了只有拥有 C-old,new 日志条目的服务器才有可能被选举为领导人。这个时候,领导人创建一条关于 C-new 配置的日志条目并复制给集群就是安全的了。再者,每个服务器在见到新的配置的时候就会立即生效。当新的配置在 C-new 的规则下被提交,旧的配置就变得无关紧要,同时不使用新的配置的服务器就可以被关闭了。如图 11,C-old 和 C-new 没有任何机会同时做出单方面的决定;这保证了安全性。

图 11

图 11:一个配置切换的时间线。虚线表示已经被创建但是还没有被提交的条目,实线表示最后被提交的日志条目。领导人首先创建了 C-old,new 的配置条目在自己的日志中,并提交到 C-old,new 中(C-old 的大多数和 C-new 的大多数)。然后他创建 C-new 条目并提交到 C-new 中的大多数。这样就不存在 C-new 和 C-old 可以同时做出决定的时间点。

在关于重新配置还有三个问题需要解决。第一个问题是,新的服务器可能初始化没有存储任何的日志条目。当这些服务器以这种状态加入到集群中,那么他们需要一段时间来更新追赶,这时还不能提交新的日志条目。为了避免这种可用性的间隔时间,Raft 在配置更新的时候使用了一种额外的阶段,在这个阶段,新的服务器以没有投票权身份加入到集群中来(领导人复制日志给他们,但是不考虑他们是大多数)。一旦新的服务器追赶上了集群中的其他机器,重新配置可以像上面描述的一样处理。

第二个问题是,集群的领导人可能不是新配置的一员。在这种情况下,领导人就会在提交了 C-new 日志之后退位(回到跟随者状态)。这意味着有这样的一段时间,领导人管理着集群,但是不包括他自己;他复制日志但是不把他自己算作是大多数之一。当 C-new 被提交时,会发生领导人过渡,因为这时是最早新的配置可以独立工作的时间点(将总是能够在 C-new 配置下选出新的领导人)。在此之前,可能只能从 C-old 中选出领导人。

第三个问题是,移除不在 C-new 中的服务器可能会扰乱集群。这些服务器将不会再接收到心跳,所以当选举超时,他们就会进行新的选举过程。他们会发送拥有新的任期号的请求投票 RPCs,这样会导致当前的领导人回退成跟随者状态。新的领导人最终会被选出来,但是被移除的服务器将会再次超时,然后这个过程会再次重复,导致整体可用性大幅降低。为了避免这个问题,当服务器确认当前领导人存在时,服务器会忽略请求投票 RPCs。特别的,当服务器在当前最小选举超时时间内收到一个请求投票 RPC,他不会更新当前的任期号或者投出选票。这不会影响正常的选举,每个服务器在开始一次选举之前,至少等待一个最小选举超时时间。然而,这有利于避免被移除的服务器扰乱:如果领导人能够发送心跳给集群,那么他就不会被更大的任期号废黜。

简述版本:

  1. 客户端向leader发送更新配置的请求C-new,
  2. 领导人生成联结一致的配置C-old,new,即一条特殊的log,
  3. 并分发给所有follower,包含old和new2部分,
  4. 节点始终使用最新的配置,即使是未提交的,
  5. 所以leader把C-old,new分发给所有follower,包含old和new两部分,
  6. follower收到C-old,new后,follower就开始使用新配置,向leader发送响应,
  7. leader收到old和new多数的响应后,提交C-old,new,
  8. leader创建一个包含C-new的log,
  9. 按照“节点始终使用最新的配置,即使是未提交的”原则,那leader只需要发送给new集群的节点了,那old咋能知道leader发送新日志了?old咋知道切换到新配置?还得给old follower发送新log,
  10. follower按收到的新log 配置进行,new集群的follower向leader发送响应,(问题:按新配置了,leader不在新配置中,为啥还得向leader发送响应,但此时也不能进行选举,会造成存在2个leader的情况),
  11. leader将新log提交,
  12. 如果leader不在新集群中,leader主动退位,新集群进行选举。

7 日志压缩

Raft 的日志在正常操作中不断的增长,但是在实际的系统中,日志不能无限制的增长。随着日志不断增长,他会占用越来越多的空间,花费越来越多的时间来重置。如果没有一定的机制去清除日志里积累的陈旧的信息,那么会带来可用性问题。

快照是最简单的压缩方法。在快照系统中,整个系统的状态都以快照的形式写入到稳定的持久化存储中,然后到那个时间点之前的日志全部丢弃。快照技术被使用在 Chubby 和 ZooKeeper 中,接下来的章节会介绍 Raft 中的快照技术。

增量压缩的方法,例如日志清理或者日志结构合并树,都是可行的。这些方法每次只对一小部分数据进行操作,这样就分散了压缩的负载压力。首先,他们先选择一个已经积累的大量已经被删除或者被覆盖对象的区域,然后重写那个区域还活跃的对象,之后释放那个区域。和简单操作整个数据集合的快照相比,需要增加复杂的机制来实现。状态机可以实现 LSM tree 使用和快照相同的接口,但是日志清除方法就需要修改 Raft 了。

图 12

图 12:一个服务器用新的快照替换了从 1 到 5 的条目,快照值存储了当前的状态。快照中包含了最后的索引位置和任期号。

图 12 展示了 Raft 中快照的基础思想。每个服务器独立的创建快照,只包括已经被提交的日志。主要的工作包括将状态机的状态写入到快照中。Raft 也包含一些少量的元数据到快照中:最后被包含索引指的是被快照取代的最后的条目在日志中的索引值(状态机最后应用的日志),最后被包含的任期指的是该条目的任期号。保留这些数据是为了支持快照后紧接着的第一个条目的追加日志请求时的一致性检查,因为这个条目需要前一日志条目的索引值和任期号。为了支持集群成员更新(第 6 节),快照中也将最后的一次配置作为最后一个条目存下来。一旦服务器完成一次快照,他就可以删除最后索引位置之前的所有日志和快照了。

尽管通常服务器都是独立的创建快照,但是领导人必须偶尔的发送快照给一些落后的跟随者。这通常发生在当领导人已经丢弃了下一条需要发送给跟随者的日志条目的时候。幸运的是这种情况不是常规操作:一个与领导人保持同步的跟随者通常都会有这个条目。然而一个运行非常缓慢的跟随者或者新加入集群的服务器(第 6 节)将不会有这个条目。这时让这个跟随者更新到最新的状态的方式就是通过网络把快照发送给他们。

安装快照 RPC

由领导人调用以将快照的分块发送给跟随者。领导者总是按顺序发送分块。

参数 解释
term 领导人的任期号
leaderId 领导人的 Id,以便于跟随者重定向请求
lastIncludedIndex 快照中包含的最后日志条目的索引值
lastIncludedTerm 快照中包含的最后日志条目的任期号
offset 分块在快照中的字节偏移量
data[] 原始数据
done 如果这是最后一个分块则为 true
结果 解释
term 当前任期号(currentTerm),便于领导人更新自己

接收者实现

  1. 如果term < currentTerm就立即回复
  2. 如果是第一个分块(offset 为 0)就创建一个新的快照
  3. 在指定偏移量写入数据
  4. 如果 done 是 false,则继续等待更多的数据
  5. 保存快照文件,丢弃具有较小索引的任何现有或部分快照
  6. 如果现存的日志条目与快照中最后包含的日志条目具有相同的索引值和任期号,则保留其后的日志条目并进行回复
  7. 丢弃整个日志
  8. 使用快照重置状态机(并加载快照的集群配置)

图 13

图 13:一个关于安装快照的简要概述。为了便于传输,快照都是被分成分块的;每个分块都给了跟随者生命的迹象,所以跟随者可以重置选举超时计时器

快照可能比较大,分片传输可以降低快照传输对log传输的影响,同时可以让跟随者重置超时定时器,避免发生选举。

在这种情况下领导人使用一种叫做安装快照的新的 RPC 来发送快照给太落后的跟随者;见图 13。当跟随者通过这种 RPC 接收到快照时,他必须自己决定对于已经存在的日志该如何处理。通常快照会包含没有在接收者日志中存在的信息。在这种情况下,跟随者丢弃其整个日志;它全部被快照取代,并且可能包含与快照冲突的未提交条目。如果接收到的快照是自己日志的前面部分(由于网络重传或者错误),那么被快照包含的条目将会被全部删除,但是快照后面的条目仍然有效,必须保留。

这种快照的方式背离了 Raft 的强领导人原则,因为跟随者可以在不知道领导人情况下创建快照。但是我们认为这种背离是值得的。领导人的存在,是为了解决在达成一致性的时候的冲突,但是在创建快照的时候,一致性已经达成,这时不存在冲突了,所以没有领导人也是可以的。数据依然是从领导人传给跟随者,只是跟随者可以重新组织他们的数据了

我们考虑过一种替代的基于领导人的快照方案,即只有领导人创建快照,然后发送给所有的跟随者。但是这样做有两个缺点。第一,发送快照会浪费网络带宽并且延缓了快照处理的时间。每个跟随者都已经拥有了所有产生快照需要的信息,而且很显然,自己从本地的状态中创建快照比通过网络接收别人发来的要经济。第二,领导人的实现会更加复杂。例如,领导人需要发送快照的同时并行的将新的日志条目发送给跟随者,这样才不会阻塞新的客户端请求。

还有两个问题影响了快照的性能。首先,服务器必须决定什么时候应该创建快照。如果快照创建的过于频繁,那么就会浪费大量的磁盘带宽和其他资源;如果创建快照频率太低,他就要承受耗尽存储容量的风险,同时也增加了从日志重建的时间。一个简单的策略就是当日志大小达到一个固定大小的时候就创建一次快照。如果这个阈值设置的显著大于期望的快照的大小,那么快照对磁盘压力的影响就会很小了。

第二个影响性能的问题就是写入快照需要花费显著的一段时间,并且我们还不希望影响到正常操作。解决方案是通过写时复制的技术,这样新的更新就可以被接收而不影响到快照。例如,具有函数式数据结构的状态机天然支持这样的功能。另外,操作系统的写时复制技术的支持(如 Linux 上的 fork)可以被用来创建完整的状态机的内存快照(我们的实现就是这样的)

8 客户端交互

这一节将介绍客户端是如何和 Raft 进行交互的,包括客户端如何发现领导人和 Raft 是如何支持线性化语义的。这些问题对于所有基于一致性的系统都存在,并且 Raft 的解决方案和其他的也差不多。

Raft 中的客户端发送所有请求给领导人。当客户端启动的时候,他会随机挑选一个服务器进行通信。如果客户端第一次挑选的服务器不是领导人,那么那个服务器会拒绝客户端的请求并且提供他最近接收到的领导人的信息(追加条目请求包含了领导人的网络地址)。如果领导人已经崩溃了,那么客户端的请求就会超时;客户端之后会再次重试随机挑选服务器的过程。

我们 Raft 的目标是要实现线性化语义(每一次操作立即执行,只执行一次,在他调用和收到回复之间)。但是,如上述,Raft 是可以执行同一条命令多次的:例如,如果领导人在提交了这条日志之后,但是在响应客户端之前崩溃了,那么客户端会和新的领导人重试这条指令,导致这条命令就被再次执行了。解决方案就是客户端对于每一条指令都赋予一个唯一的序列号。然后,状态机跟踪每条指令最新的序列号和相应的响应。如果接收到一条指令,它的序列号已经被执行了,那么就立即返回结果,而不重新执行指令

log的是有状态的,无需重复计算状态。

只读的操作可以直接处理而不需要记录日志。但是,在不增加任何限制的情况下,这么做可能会冒着返回脏数据的风险,因为领导人响应客户端请求时可能已经被新的领导人作废了,但是他还不知道。线性化的读操作必须不能返回脏数据,Raft 需要使用两个额外的措施在不使用日志的情况下保证这一点。首先,领导人必须有关于被提交日志的最新信息。领导人完全特性保证了领导人一定拥有所有已经被提交的日志条目,但是在他任期开始的时候,他可能不知道哪些是已经被提交的。为了知道这些信息,他需要在他的任期里提交一条日志条目。Raft 中通过领导人在任期开始的时候提交一个空白的没有任何操作的日志条目到日志中去来实现第二,领导人在处理只读的请求之前必须检查自己是否已经被废黜了(他自己的信息已经变脏了如果一个更新的领导人被选举出来)。Raft 中通过让领导人在响应只读请求之前,先和集群中的大多数节点交换一次心跳信息来处理这个问题。可选的,领导人可以依赖心跳机制来实现一种租约的机制,但是这种方法依赖时间来保证安全性(假设时间误差是有界的)。

9 算法实现和评估

我们已经为 RAMCloud 实现了 Raft 算法作为存储配置信息的复制状态机的一部分,并且帮助 RAMCloud 协调故障转移。这个 Raft 实现包含大约 2000 行 C++ 代码,其中不包括测试、注释和空行。这些代码是开源的。同时也有大约 25 个其他独立的第三方的基于这篇论文草稿的开源实现,针对不同的开发场景。同时,很多公司已经部署了基于 Raft 的系统。

这一节会从三个方面来评估 Raft 算法:可理解性、正确性和性能。

9.1 可理解性

为了和 Paxos 比较 Raft 算法的可理解能力,我们针对高层次的本科生和研究生,在斯坦福大学的高级操作系统课程和加州大学伯克利分校的分布式计算课程上,进行了一次学习的实验。我们分别拍了针对 Raft 和 Paxos 的视频课程,并准备了相应的小测验。Raft 的视频讲课覆盖了这篇论文的所有内容除了日志压缩;Paxos 讲课包含了足够的资料来创建一个等价的复制状态机,包括单决策 Paxos,多决策 Paxos,重新配置和一些实际系统需要的性能优化(例如领导人选举)。小测验测试一些对算法的基本理解和解释一些边角的示例。每个学生都是看完第一个视频,回答相应的测试,再看第二个视频,回答相应的测试。大约有一半的学生先进行 Paxos 部分,然后另一半先进行 Raft 部分,这是为了说明两者从第一部分的算法学习中获得的表现和经验的差异。我们计算参加人员的每一个小测验的得分来看参与者是否在 Raft 算法上更加容易理解。

我们尽可能的使得 Paxos 和 Raft 的比较更加公平。这个实验偏爱 Paxos 表现在两个方面:43 个参加者中有 15 个人在之前有一些 Paxos 的经验,并且 Paxos 的视频要长 14%。如表格 1 总结的那样,我们采取了一些措施来减轻这种潜在的偏见。我们所有的材料都可供审查。

关心 缓和偏见采取的手段 可供查看的材料
相同的讲课质量 两者使用同一个讲师。Paxos 使用的是现在很多大学里经常使用的。Paxos 会长 14%。 视频
相同的测验难度 问题以难度分组,在两个测验里成对出现。 小测验
公平评分 使用评价量规。随机顺序打分,两个测验交替进行。 评价量规(rubric)

表 1:考虑到可能会存在的偏见,对于每种情况的解决方法,和相应的材料。

参加者平均在 Raft 的测验中比 Paxos 高 4.9 分(总分 60,那么 Raft 的平均得分是 25.7,而 Paxos 是 20.8);图 14 展示了每个参与者的得分。配置t-检验(又称student‘s t-test)表明,在 95% 的可信度下,真实的 Raft 分数分布至少比 Paxos 高 2.5 分。

图 14

图 14:一个散点图表示了 43 个学生在 Paxos 和 Raft 的小测验中的成绩。在对角线之上的点表示在 Raft 获得了更高分数的学生。

我们也建立了一个线性回归模型来预测一个新的学生的测验成绩,基于以下三个因素:他们使用的是哪个小测验,之前对 Paxos 的经验,和学习算法的顺序。模型预测,对小测验的选择会产生 12.5 分的差别。这显著的高于之前的 4.9 分,因为很多学生在之前都已经有了对于 Paxos 的经验,这相当明显的帮助 Paxos,对 Raft 就没什么太大影响了。但是奇怪的是,模型预测对于先进行 Paxos 小测验的人而言,Raft的得分低了6.3分; 虽然我们不知道为什么,这似乎在统计上是有意义的。

我们同时也在测验之后调查了参与者,他们认为哪个算法更加容易实现和解释;这个的结果在图 15 上。压倒性的结果表明 Raft 算法更加容易实现和解释(41 人中的 33个)。但是,这种自己报告的结果不如参与者的成绩更加可信,并且参与者可能因为我们的 Raft 更加易于理解的假说而产生偏见。

图 15

图 15:通过一个 5 分制的问题,参与者(左边)被问哪个算法他们觉得在一个高效正确的系统里更容易实现,右边被问哪个更容易向学生解释。

关于 Raft 用户学习有一个更加详细的讨论。

9.2 正确性

在第 5 节,我们已经制定了正式的规范,和对一致性机制的安全性证明。这个正式规范使用 TLA+ 规范语言使图 2 中总结的信息非常清晰。它长约400行,并作为证明的主题。同时对于任何想实现 Raft 的人也是十分有用的。我们通过 TLA 证明系统非常机械的证明了日志完全特性。然而,这个证明依赖的约束前提还没有被机械证明(例如,我们还没有证明规范的类型安全)。而且,我们已经写了一个非正式的证明关于状态机安全性是完备的,并且是相当清晰的(大约 3500 个词)。

9.3 性能

Raft 和其他一致性算法例如 Paxos 有着差不多的性能。在性能方面,最重要的关注点是,当领导人被选举成功时,什么时候复制新的日志条目。Raft 通过很少数量的消息包(一轮从领导人到集群大多数机器的消息)就达成了这个目的。同时,进一步提升 Raft 的性能也是可行的。例如,很容易通过支持批量操作和管道操作来提高吞吐量和降低延迟。对于其他一致性算法已经提出过很多性能优化方案;其中有很多也可以应用到 Raft 中来,但是我们暂时把这个问题放到未来的工作中去。

我们使用我们自己的 Raft 实现来衡量 Raft 领导人选举的性能并且回答两个问题。首先,领导人选举的过程收敛是否快速?第二,在领导人宕机之后,最小的系统宕机时间是多久?

图 16

图 16:发现并替换一个已经崩溃的领导人的时间。上面的图考察了在选举超时时间上的随机化程度,下面的图考察了最小选举超时时间。每条线代表了 1000 次实验(除了 150-150 毫秒只试了 100 次),和相应的确定的选举超时时间。例如,150-155 毫秒意思是,选举超时时间从这个区间范围内随机选择并确定下来。这个实验在一个拥有 5 个节点的集群上进行,其广播时延大约是 15 毫秒。对于 9 个节点的集群,结果也差不多。

为了衡量领导人选举,我们反复的使一个拥有五个节点的服务器集群的领导人宕机,并计算需要多久才能发现领导人已经宕机并选出一个新的领导人(见图 16)。为了构建一个最坏的场景,在每一次的尝试里,服务器都有不同长度的日志,意味着有些候选人是没有成为领导人的资格的。另外,为了促成选票瓜分的情况,我们的测试脚本在终止领导人之前同步的发送了一次心跳广播(这大约和领导人在崩溃前复制一个新的日志给其他机器很像)。领导人均匀的随机的在心跳间隔里宕机,也就是最小选举超时时间的一半。因此,最小宕机时间大约就是最小选举超时时间的一半。

图 16 中上面的图表明,只需要在选举超时时间上使用很少的随机化就可以大大避免选票被瓜分的情况。在没有随机化的情况下,在我们的测试里,选举过程往往都需要花费超过 10 秒钟由于太多的选票瓜分的情况。仅仅增加 5 毫秒的随机化时间,就大大的改善了选举过程,现在平均的宕机时间只有 287 毫秒。增加更多的随机化时间可以大大改善最坏情况:通过增加 50 毫秒的随机化时间,最坏的完成情况(1000 次尝试)只要 513 毫秒。

图 16 中下面的图显示,通过减少选举超时时间可以减少系统的宕机时间。在选举超时时间为 12-24 毫秒的情况下,只需要平均 35 毫秒就可以选举出新的领导人(最长的一次花费了 152 毫秒)。然而,进一步降低选举超时时间的话就会违反 Raft 的时间不等式需求:在选举新领导人之前,领导人就很难发送完心跳包。这会导致没有意义的领导人改变并降低了系统整体的可用性。我们建议使用更为保守的选举超时时间,比如 150-300 毫秒;这样的时间不大可能导致没有意义的领导人改变,而且依然提供不错的可用性。

10 相关工作

已经有很多关于一致性算法的工作被发表出来,其中很多都可以归到下面的类别中:

  • Lamport 关于 Paxos 的原始描述,和尝试描述的更清晰。
  • 关于 Paxos 的更详尽的描述,补充遗漏的细节并修改算法,使得可以提供更加容易的实现基础。
  • 实现一致性算法的系统,例如 Chubby,ZooKeeper 和 Spanner。对于 Chubby 和 Spanner 的算法并没有公开发表其技术细节,尽管他们都声称是基于 Paxos 的。ZooKeeper 的算法细节已经发表,但是和 Paxos 着实有着很大的差别。
  • Paxos 可以应用的性能优化。
  • Oki 和 Liskov 的 Viewstamped Replication(VR),一种和 Paxos 差不多的替代算法。原始的算法描述和分布式传输协议耦合在了一起,但是核心的一致性算法在最近的更新里被分离了出来。VR 使用了一种基于领导人的方法,和 Raft 有很多相似之处。

Raft 和 Paxos 最大的不同之处就在于 Raft 的强领导特性:Raft 使用领导人选举作为一致性协议里必不可少的部分,并且将尽可能多的功能集中到了领导人身上。这样就可以使得算法更加容易理解。例如,在 Paxos 中,领导人选举和基本的一致性协议是正交的:领导人选举仅仅是性能优化的手段,而且不是一致性所必须要求的。但是,这样就增加了多余的机制:Paxos 同时包含了针对基本一致性要求的两阶段提交协议和针对领导人选举的独立的机制。相比较而言,Raft 就直接将领导人选举纳入到一致性算法中,并作为两阶段一致性的第一步。这样就减少了很多机制。

像 Raft 一样,VR 和 ZooKeeper 也是基于领导人的,因此他们也拥有一些 Raft 的优点。但是,Raft 比 VR 和 ZooKeeper 拥有更少的机制因为 Raft 尽可能的减少了非领导人的功能。例如,Raft 中日志条目都遵循着从领导人发送给其他人这一个方向:追加条目 RPC 是向外发送的。在 VR 中,日志条目的流动是双向的(领导人可以在选举过程中接收日志);这就导致了额外的机制和复杂性。根据 ZooKeeper 公开的资料看,它的日志条目也是双向传输的,但是它的实现更像 Raft。

和上述我们提及的其他基于一致性的日志复制算法中,Raft 的消息类型更少。例如,我们数了一下 VR 和 ZooKeeper 使用的用来基本一致性需要和成员改变的消息数(排除了日志压缩和客户端交互,因为这些都比较独立且和算法关系不大)。VR 和 ZooKeeper 都分别定义了 10 中不同的消息类型,相对的,Raft 只有 4 中消息类型(两种 RPC 请求和对应的响应)。Raft 的消息都稍微比其他算法的要信息量大,但是都很简单。另外,VR 和 ZooKeeper 都在领导人改变时传输了整个日志;所以为了能够实践中使用,额外的消息类型就很必要了。

Raft 的强领导人模型简化了整个算法,但是同时也排斥了一些性能优化的方法。例如,平等主义 Paxos (EPaxos)在某些没有领导人的情况下可以达到很高的性能。平等主义 Paxos 充分发挥了在状态机指令中的交换性。任何服务器都可以在一轮通信下就提交指令,除非其他指令同时被提出了。然而,如果指令都是并发的被提出,并且互相之间不通信沟通,那么 EPaxos 就需要额外的一轮通信。因为任何服务器都可以提交指令,所以 EPaxos 在服务器之间的负载均衡做的很好,并且很容易在 WAN 网络环境下获得很低的延迟。但是,他在 Paxos 上增加了非常明显的复杂性。

一些集群成员变换的方法已经被提出或者在其他的工作中被实现,包括 Lamport 的原始的讨论,VR 和 SMART。我们选择使用联结一致(joint consensus)的方法因为他对一致性协议的其他部分影响很小,这样我们只需要很少的一些机制就可以实现成员变换。Lamport 的基于 α 的方法之所以没有被 Raft 选择是因为它假设在没有领导人的情况下也可以达到一致性。和 VR 和 SMART 相比较,Raft 的重新配置算法可以在不限制正常请求处理的情况下进行;相比较的,VR 需要停止所有的处理过程,SMART 引入了一个和 α 类似的方法,限制了请求处理的数量。Raft 的方法同时也需要更少的额外机制来实现,和 VR、SMART 比较而言。

11 结论

算法的设计通常会把正确性,效率或者简洁作为主要的目标。尽管这些都是很有意义的目标,但是我们相信,可理解性也是一样的重要。在开发者把算法应用到实际的系统中之前,这些目标没有一个会被实现,这些都会必然的偏离发表时的形式。除非开发人员对这个算法有着很深的理解并且有着直观的感觉,否则将会对他们而言很难在实现的时候保持原有期望的特性。

在这篇论文中,我们尝试解决分布式一致性问题,但是一个广为接受但是十分令人费解的算法 Paxos 已经困扰了无数学生和开发者很多年了。我们创造了一种新的算法 Raft,显而易见的比 Paxos 要容易理解。我们同时也相信,Raft 也可以为实际的实现提供坚实的基础。把可理解性作为设计的目标改变了我们设计 Raft 的方式;随着设计的进展,我们发现自己重复使用了一些技术,比如分解问题和简化状态空间。这些技术不仅提升了 Raft 的可理解性,同时也使我们坚信其正确性。

12 感谢

这项研究必须感谢以下人员的支持:Ali Ghodsi,David Mazie`res,和伯克利 CS 294-91 课程、斯坦福 CS 240 课程的学生。Scott Klemmer 帮我们设计了用户调查,Nelson Ray 建议我们进行统计学的分析。在用户调查时使用的关于 Paxos 的幻灯片很大一部分是从 Lorenzo Alvisi 的幻灯片上借鉴过来的。特别的,非常感谢 DavidMazieres 和 Ezra Hoch,他们找到了 Raft 中一些难以发现的漏洞。许多人提供了关于这篇论文十分有用的反馈和用户调查材料,包括 Ed Bugnion,Michael Chan,Hugues Evrard,Daniel Giffin,Arjun Gopalan,Jon Howell,Vimalkumar Jeyakumar,Ankita Kejriwal,Aleksandar Kracun,Amit Levy,Joel Martin,Satoshi Matsushita,Oleg Pesok,David Ramos,Robbert van Renesse,Mendel Rosenblum,Nicolas Schiper,Deian Stefan,Andrew Stone,Ryan Stutsman,David Terei,Stephen Yang,Matei Zaharia 以及 24 位匿名的会议审查人员(可能有重复),并且特别感谢我们的领导人 Eddie Kohler。Werner Vogels 发了一条早期草稿链接的推特,给 Raft 带来了极大的关注。我们的工作由 Gigascale 系统研究中心和 Multiscale 系统研究中心给予支持,这两个研究中心由关注中心研究程序资金支持,一个是半导体研究公司的程序,由 STARnet 支持,一个半导体研究公司的程序由 MARCO 和 DARPA 支持,在国家科学基金会的 0963859 号批准,并且获得了来自 Facebook,Google,Mellanox,NEC,NetApp,SAP 和 Samsung 的支持。Diego Ongaro 由 Junglee 公司,斯坦福的毕业团体支持。

Paxos和Raft类比

用2个小故事场景,来对比Paxos和Raft,Paxos和Raft都是非拜占庭算法,所以就不要考虑故事中的作弊、欺骗等问题。

故事背景

小岛上有个村子,村里的大事必须进行提案,如果想让所有村民都执行同1个提案,必须得到大部门村民的同意,但是,它们有2种达成一致的办法,分别称为Paxos和Raft。

Paxos

Paxos是一种原始的民主,每一个人可以提案,提案人把提案发送给所有村民,村民投赞成或者反对,提案人收集投票,收到一半投票的时候,把投票结果分发给所有人,所有人执行提案。

但是,因为每个人都可以提案,假如有个村民提案了,在村民执行提案之前,收到其他村民的新提案,老提案就会被打断,被废弃掉了。如果这种情况持续,就一直没有办法达成一致了。

Raft

Raft是一种进化的民主,村民可以选举,村民都选举个村长,让村长来提案,大家来投票就行,当村长收到大多数村民,对提案的投票时,就告诉村民,某个提案通过了,大家都执行提案。

如果村长不在了,村民还可以选举一个新的村长。

无论是Paxos还是Raft,当参与投票的村民达不到半数时,都无法对提案达成一致。

参考

联盟链中动态加入组织是很正常的一件事,但联盟链不会像公链那样,可以自由加入和退出,所以,加入是要费一般功夫的。

需要做以下几件事情:

  1. 生成新组织的证书和在要加入通道中的配置
  2. 拉取要加入通道的配置,根据新组织通道中的配置和通道配置,最终生成更新通道配置的交易,pb格式
  3. 根据通道配置更新策略,让组织节点对交易签名,然后发送更新配置交易到排序节点,并打包上链
  4. 新组织利用通道创世块加入通道
  5. 可选:新组织设置本组织在通道中的锚节点

生成org3的证书和在通道中的配置

生成新组织的证书,结果在当前目录下生成crypto-config

1
cryptogen generate --config=./org3-crypto.yaml

当前目录下有configtx.yaml,里面是新组织的配置,利用configtxgen生成更新配置的json文件,放到channel的配置文件。

1
2
export FABRIC_CFG_PATH=$PWD
configtxgen -printOrg Org3MSP > ../channel-artifacts/org3.json

拉取最新通道配置

连接cli,修改环境变量,设置Orederer的CA以及通道名称,后续操作会使用

1
2
3
docker exec -it cli bash
export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
export CHANNEL_NAME=mychannel

拉取当前的配置块,它会把cahnnel的配置,保存到二进制的protobuf文件config_block.pb,保存在当前目录,这个命令默认会拉去最新的配置:

1
peer channel fetch config config_block.pb -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

peer channel fetch config可以确保获取最新的配置区块,config_block.pb实际是个区块,上述命令日志的最后一行,会显示出当前获取的是哪个区块:

1
2
3
4
5
2019-07-30 09:40:53.047 UTC [msp] GetDefaultSigningIdentity -> DEBU 044 Obtaining default signing identity
2019-07-30 09:40:53.047 UTC [msp] GetDefaultSigningIdentity -> DEBU 045 Obtaining default signing identity
2019-07-30 09:40:53.047 UTC [msp.identity] Sign -> DEBU 046 Sign: plaintext: 0AED060A1508051A0608A5A180EA0522...3849120C0A041A02080212041A020802
2019-07-30 09:40:53.047 UTC [msp.identity] Sign -> DEBU 047 Sign: digest: 2202E1BA573DD47D5F54FA5E022F1ABD78B1DCDBB9CA10C5843A675C031CDAD8
2019-07-30 09:40:53.049 UTC [cli.common] readBlock -> INFO 048 Received block: 2

在byfn中,已经做了几次更改:

  1. 区块0:使用应用通道创世块创建应用通道
  2. 区块1:更新org1的锚节点
  3. 区块2:更新org2的锚节点

所以,通过上诉命令,得到了区块2中保持的配置。

需要把protobuf文件转换为人可读的JSON配置,configtxlator把protobuf转换为JSON,jq是一个处理JSON的工具,这里是把配置相关的数据读出来,存到config.json中,因为配置块里不仅仅包含配置:

1
configtxlator proto_decode --input config_block.pb --type common.Block | jq .data.data[0].payload.data.config > config.json

配置文件内容700+行,此处省略,具体可看这利用工具解析fabric区块:应用通道创世块

利用通道配置和org3配置生成更新通道的配置

config.json中包含了各组织的信息,已经包含了org1和org2的,现在要把org3.json加入到config.json中:

1
jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}}' config.json ./channel-artifacts/org3.json > modified_config.json

上面的命令类似格式化输出,-s指定了格式,.[0],.[1]代表了第1个参数和第2个参数,效果就是增加了更org1、org2平级的org3的配置,然后保存到新的配置文件modified_config.json,可以使用diff对比。

1
2
3
4
5
6
7
8
9
diff config.json modified_config.json
> },
> "Org3MSP": {
> "groups": {},
> "mod_policy": "Admins",
...
> }
> },
> "version": "0"

接下来要做3件事:

  1. config.json -> config.pb
  2. modified_config.json -> modified_config.pb
  3. modified_config.pb - config.pb -> org3_update.pb

目的是利用pb类型的配置文件的差集,生成升级配置的配置文件org3_update.pb

1
2
3
configtxlator proto_encode --input config.json --type common.Config --output config.pb
configtxlator proto_encode --input modified_config.json --type common.Config --output modified_config.pb
configtxlator compute_update --channel_id $CHANNEL_NAME --original config.pb --updated modified_config.pb --output org3_update.pb

然而,org3_update.pb也不是能直接用来升级的,但是距离升级又进了一步,再在它外面封装一层,就可以用来升级了。但是,封装这一层,需要使用json来处理,所以需要把org3_update.pb转为json格式,封装完成,再转为pb格式。

1
2
3
configtxlator proto_decode --input org3_update.pb --type common.ConfigUpdate | jq . > org3_update.json
echo '{"payload":{"header":{"channel_header":{"channel_id":"mychannel", "type":2}},"data":{"config_update":'$(cat org3_update.json)'}}}' | jq . > org3_update_in_envelope.json
configtxlator proto_encode --input org3_update_in_envelope.json --type common.Envelope --output org3_update_in_envelope.pb

org3_update_in_envelope.pb就是用来升级的。

搞了这么一大圈,怎么不用modified_config.json和config.json直接来生成org3_update.json?

对更新配置进行签名

通道的默认修改策略是MAJORITY,即需要多数的org的Admin账号进行签名。

因为cli中的环境变量就是设置的org1 admin的,所以可以直接签名:

1
peer channel signconfigtx -f org3_update_in_envelope.pb

导出org2的环境变量设置,然后重新执行签名命令:

1
2
3
4
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=peer0.org2.example.com:9051

签名的结果在哪?就在pb文件里。

发送更新通道配置的交易

1
peer channel update -f org3_update_in_envelope.pb -c $CHANNEL_NAME -o orderer.example.com:7050 --tls --cafile $ORDERER_CA

交易提交成功:

1
2019-07-31 06:17:58.376 UTC [channelCmd] update -> INFO 04d Successfully submitted channel update

配置选举以接收区块

新加入的组织节点只能使用genesis区块启动,创世块不包含他们已经加入到通道的配置,所以它们无法利用gossip验证它们从本组织其他peer发来的区块,直到他们接收到了它们加入通道的配置交易。所以新加入的节点必须,配置它们从哪接收区块的排序服务。

如果利用的静态leader模式,使用如下配置:

1
2
CORE_PEER_GOSSIP_USELEADERELECTION=false
CORE_PEER_GOSSIP_ORGLEADER=true

否则如果是动态leader,使用如下配置:

1
2
CORE_PEER_GOSSIP_USELEADERELECTION=true
CORE_PEER_GOSSIP_ORGLEADER=false

这样新组织的peer都宣称自己是leader,加速了获取区块,等他们接收到它们自己的配置交易,就会只有1个leader peer代表本组织。

启动新组织节点

启动容器

指定组织的compose文件,启动组织的容器。这会创建3个容器,peer1.org3, peer2.org3和org3cli,其中org3cli是为org3特制的cli,里面已经设置好了org3的环境变量。

1
docker-compose -f docker-compose-org3.yaml up -d

后续都是连接到org3cli进行操作。

设置orderer的信息。

1
export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem && export CHANNEL_NAME=mychannel

拉取mychannel的创世块

peer channel fetch 0指拉去0号区块,也就是mychannel的创世区块,保存到mychannel.block。

1
peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

加入通道

1
peer channel join -b mychannel.block

设置新组织锚节点

设置锚节点也需要更新通道配置,但流程与初始的锚节点不太一致,因为新组织的配置不在configtx.yanml中,设置新组织锚节点跟添加新组织的流程一样,具体见:
Updating the Channel Config to include an Org3 Anchor Peer (Optional)

参考资料

  1. 官方文档
  2. first network项目中的eyfn,运行脚本可以看到具体流程。

获取人类可读的区块内容分2步:

  1. 从账本里把区块取出来,格式为protobuf
  2. 把protobuf格式的区块,转换为JSON格式

所以这篇文章3步走:

  1. 获取区块
  2. 解析区块
  3. 常见区块类型样例

获取区块

拉取应用通道区块

peer channel fetch能够拉去某个通道最新、最老、特定高度的区块,只不过得到的区块是protobuf格式的,人眼不可读。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@eedf1a41eb00:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel fetch -h
Fetch a specified block, writing it to a file.

Usage:
peer channel fetch <newest|oldest|config|(number)> [outputfile] [flags]

Flags:
--bestEffort Whether fetch requests should ignore errors and return blocks on a best effort basis
-c, --channelID string In case of a newChain command, the channel ID to create. It must be all lower case, less than 250 characters long and match the regular expression: [a-z][a-z0-9.-]*
-h, --help help for fetch

Global Flags:
--cafile string Path to file containing PEM-encoded trusted certificate(s) for the ordering endpoint
--certfile string Path to file containing PEM-encoded X509 public key to use for mutual TLS communication with the orderer endpoint
--clientauth Use mutual TLS when communicating with the orderer endpoint
--connTimeout duration Timeout for client to connect (default 3s)
--keyfile string Path to file containing PEM-encoded private key to use for mutual TLS communication with the orderer endpoint
-o, --orderer string Ordering service endpoint
--ordererTLSHostnameOverride string The hostname override to use when validating the TLS connection to the orderer.
--tls Use TLS when communicating with the orderer endpoint

以下命令下载了指定channel的第0个区块,即此channel的创世块,区块保存为mychannel.block。

1
peer channel fetch 0 mychannel.block  -c mychannel

如果不指定保存的区块名,会自动生成,格式:通道名_区块号,比如:

1
peer channel fetch 3 -c mychannel

操作:

1
2
3
4
5
6
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel fetch 3 -c mychannel
2019-08-01 02:20:45.378 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-08-01 02:20:45.382 UTC [cli.common] readBlock -> INFO 002 Received block: 3
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@ca8843f81b89:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto log.txt mychannel.block mychannel_3.block scripts

获取通道配置去块

如果使用peer channel fetch config可以获取某个通道的最新配置所在的区块。

1
peer channel fetch config -c mychannel

命令执行结果的最后一行会显示配置所在的区块号,区块文件保存为mychannel_config.block

拉取系统通道区块

拉取系统通道通常是为了获取系统配置,系统配置存在系统区块中,下面就是获取系统区块的方法。

系统通道区块是保存在orderer节点上的,需要在获取的时候指定orderer配置和证书,然后使用config而不是区块号,直接获取最新的配置,创世块使用区块0获取。

  1. 连接到orderer节点,查询系统通道名称

    1
    ls /var/hyperledger/production/orderer/chains/
  2. cli上设置orderer的设置,设置上面查询的通道名字,获取的区块与通道同名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    export CHANNEL_NAME=byfn-sys-channel
    export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
    export CORE_PEER_ADDRESS=orderer.example.com:7050
    export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
    export CORE_PEER_LOCALMSPID="OrdererMSP"
    export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp
    export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

    peer channel fetch config $CHANNEL_NAME.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

以上命令执行结果的最后一行,会显示当前配置所在的区块高度。

解析工具

configtxlator是一个fabric中protbuf和JSON格式之间的转换工具,fabric中任何的使用Protobuf定义的类型,都可使用该工具进行转换。

解析示例

比如创建通道交易:channel.tx也是protobuf格式的,可以利用此工具解析:

1
configtxlator proto_decode  --type common.Envelope --input channel.tx
  • --type xxx:阅读源码,找出该proto格式数据对应的数据类型
  • --input xxx:proto格式数据文件

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": {
"name": "SampleConsortium"
},
"version": "0"
}
},
"version": "0"
}
},
"signatures": []
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T06:30:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": null
}
},
"signature": null
}

解析区块

使用configtxlator可以把区块从protobuf解析成JSON格式,

1
configtxlator proto_decode  --type common.Block --input mychannel.block > mychannel.block.json

结果见样例

拓展查询

Fabric提供了使用peer channel fetch获取区块的功能,但没有提供查询交易、db等信息的接口,那如何办?

获取交易,有方法:GetTransactionByID

获取DB,有方法:GetStateLevelDBData

如果结果是非结构体格式的[]byte或者string,可以直接组装成JSON格式,比如GetStateLevelDBDataGetTransactionByID是返回protobuf定义的结构体,可以直接使用configtxlator调用的接口DeepMarshalJSON把结构体转换为JSON字符串。

DeepMarshalJSON是tools protolator提供的一个接口,protolator这个工具是完成protobuf数据和JSON数据之间转换的实际工具。利用这些工具和简单的Web框架,可以搭建出查询通道、区块、交易、数据(KV)的简易网站。

常见区块样例

fabric里包含了2大类区块:

  1. 配置区块
  2. 普通区块

区块0是配置区块,又被称为创世块,后续对配置的每一次改动都会生成1个配置块存入所修改配置的通道以及系统通道。

系统通道创世块

主要是包含了一下配置:

  1. 组织
  2. 通道
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Consortiums": {
"groups": {
"SampleConsortium": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "/Channel/Orderer/Admins",
"policies": {},
"values": {
"ChannelCreationPolicy": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Admins"
}
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "/Channel/Orderer/Admins",
"policies": {
"Admins": {
"mod_policy": "/Channel/Orderer/Admins",
"policy": {
"type": 1,
"value": {
"identities": [],
"rule": {
"n_out_of": {
"n": 0,
"rules": []
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {},
"version": "0"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "0"
},
"last_update": null
},
"header": {
"channel_header": {
"channel_id": "byfn-sys-channel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:40Z",
"tls_cert_hash": null,
"tx_id": "1c988034ab1d1658ebe508f89acd5f1e4e7bc2eeb6355e9d6084c36fba8ea562",
"type": 1,
"version": 1
},
"signature_header": {
"creator": null,
"nonce": "vPlG4HADPlXSQNWhjggubdDnUWXuH+wP"
}
}
},
"signature": null
}]
},
"header": {
"data_hash": "uzdDxPqI54ONV6pv/4m/l2OXNBFKuprirariF2kW278=",
"number": "0",
"previous_hash": null
},
"metadata": {
"metadata": [
"",
"",
"",
""
]
}
}

应用通道创世块

系通道的配置的信息主要是:

  1. 组织关系,权限、证书
  2. 配置更新策略

某个链码的配置包含在链码里,不在通道的配置里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"Consortium": {
"mod_policy": "Admins",
"value": {
"name": "SampleConsortium"
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "1"
},
"last_update": {
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {
"Consortium": {
"mod_policy": "",
"value": {
"name": "SampleConsortium"
},
"version": "0"
}
},
"version": "0"
}
},
"signatures": [{
"signature": "MEQCIEuHMvFeH442rJZ8BURKXjseQ3MMpGO0VprPj8WdW5nOAiB1aDbHtIKo5ThxqvWCCJEdsXiKtAAbzN8DkE5VV4hUrg==",
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "Org1MSP"
},
"nonce": "d3VBJn3yTRNFp7JKJCL/XFObhMe3pS3W"
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:45Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "Org1MSP"
},
"nonce": "tGLvhwDbf/Py2be3k/WLsZzKRPcry9po"
}
}
},
"signature": "MEUCIQDmxLlWl9H6CdVqUbQ6OPmeZ1JnJYRYDtD18KQ1b82WkwIgNxFDuaYCdBs3ZzZxvsF2CD7pAtrbER4ZDOq3SkKie7E="
}
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:18:45Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 1,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "OrdererMSP"
},
"nonce": "xfIU/l1eFdFbtagz4fuZaJzuPW/hroRx"
}
}
},
"signature": "MEUCIQDm/oh3UE8/ZQu0Cad7rJw/FaUsCu4SsXZfxY3VSaMsCQIgbEsRitpgRb9vy80u9C7z103nj1/kQtka1LRiGSLy/6I="
}]
},
"header": {
"data_hash": "tJk5enGTIZYB/+XlYPbOwpjRH9341YajXd5YaWv86/k=",
"number": "0",
"previous_hash": null
},
"metadata": {
"metadata": [
"",
"",
"AA==",
""
]
}
}

应用通道配置块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
{
"data": {
"data": [{
"payload": {
"data": {
"config": {
"channel_group": {
"groups": {
"Application": {
"groups": {
"Org1MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org1MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org1MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org1.example.com",
"port": 7051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLVENDQWRDZ0F3SUJBZ0lRUEtTRHJHNWRTT0liVHRLM3pqcEhlVEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVE4d0RRWURWUVFMRXdaamJHbGxiblF4SHpBZEJnTlZCQU1NRmtGa2JXbHVRRzl5Clp6RXVaWGhoYlhCc1pTNWpiMjB3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVFiNEZydmg1V2oKclBSOTFIdFdSaFVORVpxQXFOL2pEYkhUbUtDSXBkY3k3K2JGTUduaUprdTExaTl2ajN6TnNQMGQrSWlSRDdiMgpJSlhVaGxZbVJjcXVvMDB3U3pBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQXJCZ05WCkhTTUVKREFpZ0NCVUdsTldyVThlZFBPakorMkc3M2UyU1FEdjVYdFUzSWYrSTJEMmo4VWdFREFLQmdncWhrak8KUFFRREFnTkhBREJFQWlBRFB6Qk5uKytROURXd0VUS0ErRTQyNG5vekVzZjRFd2JzQWxuUCtXNU10UUlnSDc3Two3dEVJUFZacTRvVUtSb1I5QWsvUEUrWUFzYk4vdHZhVlZlZ2dLTGc9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org1MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVRENDQWZlZ0F3SUJBZ0lRSnJqamM3b1RSLy8zSG9wcDFYY0Y4VEFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUhNeEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVJrd0Z3WURWUVFLRXhCdmNtY3hMbVY0WVcxd2JHVXVZMjl0TVJ3d0dnWURWUVFECkV4TmpZUzV2Y21jeExtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUKSmNFQkUvUlF1Ty9JR2h0QWJjSnhnTEhyMHNEbENFS0dpQnA3NXlRYytjdUFQK21NWDI4TGtWTERJaGhELzV5MgpiMTJUM1o4Z1Y1N2tMOHdrUTM1SXpxTnRNR3N3RGdZRFZSMFBBUUgvQkFRREFnR21NQjBHQTFVZEpRUVdNQlFHCkNDc0dBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01Da0dBMVVkRGdRaUJDQlUKR2xOV3JVOGVkUE9qSisyRzczZTJTUUR2NVh0VTNJZitJMkQyajhVZ0VEQUtCZ2dxaGtqT1BRUURBZ05IQURCRQpBaUF3YzVYMVZLVGZQM0ZqL0o1Tk9wVHlNU08vU2MzcnFhbmxOelRUbk5VRmZnSWdNZi9HVWpjcjhra1V2Y2hBCjlzM0NIc1VYZ1loTWNPaHVxWFJmZHdTRC9XRT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWakNDQWYyZ0F3SUJBZ0lRQldDNzUzQUphWXVOSkhqeVpGb1JvVEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTVM1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN4TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN4TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVJcWRZYWtLRmtKRm1NZEhDRnVGdkJIVXZMMFhUa0RJTG40Qm5vdW5GWUFaWmRFNFdpQ1lkcnJsSwpjTmpQWG1pNXZEajcrQmhoWXBaQjRnbHZRbUpDb2FOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQ0JzMVk5ZjhzTjFBYndZdnFzcEdLRzBIenhTQ2RyWEdHdUVvc2dGb3BQcVRBS0JnZ3Foa2pPUFFRRApBZ05IQURCRUFpQnF5VzBOL0xhMTRlTVh4SXIzNWVQbXVXdXpQQnJrd1h4RG9pd1RtdXJzZ1FJZ0JRRkZJdVl5CmRSVk4zOHdZSU5vUU16eW5Uek93NFNBMXpRdUs3QzViZGk0PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "1"
},
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "PEER"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
},
{
"signed_by": 2
}
]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "Org2MSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
},
{
"principal": {
"msp_identifier": "Org2MSP",
"role": "CLIENT"
},
"principal_classification": "ROLE"
}
],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
},
{
"signed_by": 1
}
]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org2.example.com",
"port": 9051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": {
"client_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "client"
},
"enable": true,
"peer_ou_identifier": {
"certificate": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"organizational_unit_identifier": "peer"
}
},
"intermediate_certs": [],
"name": "Org2MSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNVakNDQWZpZ0F3SUJBZ0lSQU1rYVQ5em1XTEo2bFVNUlhRZmgrSTh3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCek1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFWk1CY0dBMVVFQ2hNUWIzSm5NaTVsZUdGdGNHeGxMbU52YlRFY01Cb0dBMVVFCkF4TVRZMkV1YjNKbk1pNWxlR0Z0Y0d4bExtTnZiVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUEKQkpZSEEzOVNmbjczdXFIT0JrTzJ0R1pucFdvR09NRHFmRkJwZG9FOXhCTzNCdFZaT2hMblBYY01MRGErcTdJMQpxbFJHQmh4TTVpLzZ1L0V4Z2psaTVVK2piVEJyTUE0R0ExVWREd0VCL3dRRUF3SUJwakFkQmdOVkhTVUVGakFVCkJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QXBCZ05WSFE0RUlnUWcKbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEVBd0lEU0FBdwpSUUloQUpzYVBHWklWTkU4S09zbjJzZ2ZSSlo1ek1BbEpIS1p3V1lJQTljVVNWS1FBaUF6ZkIrL0VMUFQrbFFmCjZUNmhEQXU1bkwwRGpoUUxqa0ZmL3RXNUVDMzlCQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNWekNDQWYyZ0F3SUJBZ0lRVGV5ZHpHQWVpNzB1ZkZzbTBmTWY1VEFLQmdncWhrak9QUVFEQWpCMk1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWZNQjBHQTFVRUF4TVdkR3h6ClkyRXViM0puTWk1bGVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTAKTURCYU1IWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSApFdzFUWVc0Z1JuSmhibU5wYzJOdk1Sa3dGd1lEVlFRS0V4QnZjbWN5TG1WNFlXMXdiR1V1WTI5dE1SOHdIUVlEClZRUURFeFowYkhOallTNXZjbWN5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMEQKQVFjRFFnQUVadTg3U0JzYWpnNXRTcFZKeGlZaE9YaWpOd3J0TmQvTFpuYkozWjUvY0dhaXZHeTZwQTB6Y1RjcApvdHN1YWJscE9BNHkxREEvbk5xaWtQa1dVVHZQcHFOdE1Hc3dEZ1lEVlIwUEFRSC9CQVFEQWdHbU1CMEdBMVVkCkpRUVdNQlFHQ0NzR0FRVUZCd01DQmdnckJnRUZCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUNrR0ExVWQKRGdRaUJDQUh4MDhOZ1FwdW9IR0tZWU9IYWh3VDg0cW9BN3p3dzFRTVNZT0h2VlZCWERBS0JnZ3Foa2pPUFFRRApBZ05JQURCRkFpRUF5QlBlakFHWjZtNXdWS244WHFhblFsMWUzYjNpUFBJVFdHaVN0dDBuZ3JJQ0lIUUJKRFd6CjFubzBMbnp0Nis4eEw5R25oY1NrZnZsWXR5MmhQcjdnTThFYgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
}
},
"version": "1"
},
"Orderer": {
"groups": {
"OrdererOrg": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "ADMIN"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 1,
"value": {
"identities": [{
"principal": {
"msp_identifier": "OrdererMSP",
"role": "MEMBER"
},
"principal_classification": "ROLE"
}],
"rule": {
"n_out_of": {
"n": 1,
"rules": [{
"signed_by": 0
}]
}
},
"version": 0
}
},
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "Admins",
"value": {
"config": {
"admins": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNDekNDQWJHZ0F3SUJBZ0lSQUtuelI3NFZjN3RqOE5aQ21QV29QaWN3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRll4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJvd0dBWURWUVFEREJGQlpHMXBia0JsZUdGdGNHeGxMbU52YlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHClNNNDlBd0VIQTBJQUJNUGUyNUtPU0hscUVZelJFVE83YXRjYWRvT0xnckRxelJQTjNjQ2RpR3A4QU4wdmdiTnAKTEEvTFJ0alFKbzdQaTZFZFlHaWh1MUNuRWgvNUxnYk90TmVqVFRCTE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBTQpCZ05WSFJNQkFmOEVBakFBTUNzR0ExVWRJd1FrTUNLQUlFQ0pBOWZTbDlUN0xsVWZ3QVhwM1V1cyt2YVpRbkZPCm9PSXJ2cmFrUDE3QU1Bb0dDQ3FHU000OUJBTUNBMGdBTUVVQ0lRQ1BjZVpmekNXa29MZ0N0NTMwbmEramY0a2cKN3BIWWwwY2NMTTZ4QkU0em5RSWdFVU52N2Q3MjQ2N1dnN0pMckZDb0x6eFhWUlMrN2UyWVJYSUlKOS9NVTJRPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
],
"crypto_config": {
"identity_identifier_hash_function": "SHA256",
"signature_hash_family": "SHA2"
},
"fabric_node_ous": null,
"intermediate_certs": [],
"name": "OrdererMSP",
"organizational_unit_identifiers": [],
"revocation_list": [],
"root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNQRENDQWVPZ0F3SUJBZ0lRSm9TdloyVE5oVTZjN0kvZ3VRNHYvVEFLQmdncWhrak9QUVFEQWpCcE1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVVNQklHQTFVRUNoTUxaWGhoYlhCc1pTNWpiMjB4RnpBVkJnTlZCQU1URG1OaExtVjRZVzF3CmJHVXVZMjl0TUI0WERURTVNRGd3TVRBeU1UUXdNRm9YRFRJNU1EY3lPVEF5TVRRd01Gb3dhVEVMTUFrR0ExVUUKQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFZOaGJpQkdjbUZ1WTJsegpZMjh4RkRBU0JnTlZCQW9UQzJWNFlXMXdiR1V1WTI5dE1SY3dGUVlEVlFRREV3NWpZUzVsZUdGdGNHeGxMbU52CmJUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFaUxuejB5TXp6K0dPNTlLZ3NMV0E1SVNaTXgKaUdRMVkrVHB3a1hZQXhjQnZENXZMMGhXcCtwWDdmSCtqaU9TOFBCMDFkamQ0TVJsb0lCQTgzYkxxdktqYlRCcgpNQTRHQTFVZER3RUIvd1FFQXdJQnBqQWRCZ05WSFNVRUZqQVVCZ2dyQmdFRkJRY0RBZ1lJS3dZQkJRVUhBd0V3CkR3WURWUjBUQVFIL0JBVXdBd0VCL3pBcEJnTlZIUTRFSWdRZ1FJa0QxOUtYMVBzdVZSL0FCZW5kUzZ6NjlwbEMKY1U2ZzRpdSt0cVEvWHNBd0NnWUlLb1pJemowRUF3SURSd0F3UkFJZ2JmRWVkMjJGRHFFSStwU0pKTXZrQi9GQQpFRitIUlg5OW91bGRLVlBqcDgwQ0lBS1VISmxlR01HYzF6dHRNSStCcHBldG53UU5nWjRsUDY5MUs0bENnU2hMCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
],
"signing_identity": null,
"tls_intermediate_certs": [],
"tls_root_certs": [
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNRekNDQWVxZ0F3SUJBZ0lSQVBWKy9UOVlSTjV0YmFXVGlETjg5Mmt3Q2dZSUtvWkl6ajBFQXdJd2JERUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJvd0dBWURWUVFERXhGMGJITmpZUzVsCmVHRnRjR3hsTG1OdmJUQWVGdzB4T1RBNE1ERXdNakUwTURCYUZ3MHlPVEEzTWprd01qRTBNREJhTUd3eEN6QUoKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJRXdwRFlXeHBabTl5Ym1saE1SWXdGQVlEVlFRSEV3MVRZVzRnUm5KaApibU5wYzJOdk1SUXdFZ1lEVlFRS0V3dGxlR0Z0Y0d4bExtTnZiVEVhTUJnR0ExVUVBeE1SZEd4elkyRXVaWGhoCmJYQnNaUzVqYjIwd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFSUkxQK2ZLeFpnd2tFa2JIREoKb1JQak5ySXZBWWx2SHBMUTJoSXE1aXJQQnJlcEU4akRNTERyVklZR0NRdDBydGxjWFZTT3dZVTFkMXNOUy9USApSb1JvbzIwd2F6QU9CZ05WSFE4QkFmOEVCQU1DQWFZd0hRWURWUjBsQkJZd0ZBWUlLd1lCQlFVSEF3SUdDQ3NHCkFRVUZCd01CTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3S1FZRFZSME9CQ0lFSUJWM1owTGw1TnpOcUdqVlpmbHIKUHRqRXFrNUtvUWFweXpJOFNrTnJQQVpWTUFvR0NDcUdTTTQ5QkFNQ0EwY0FNRVFDSUQ5Z3U5bU9TejVLaTd5cQpGU2czd1FGdXphb2pRakdiNVN4YUwwVzJTOUxXQWlCSzZIOXhNSENuZm5BV291bVpsdXNHd3RsOU5PVDhkdXFVCnR2eHRPOVY4S1E9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
]
},
"type": 0
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"BlockValidation": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BatchSize": {
"mod_policy": "Admins",
"value": {
"absolute_max_bytes": 103809024,
"max_message_count": 10,
"preferred_max_bytes": 524288
},
"version": "0"
},
"BatchTimeout": {
"mod_policy": "Admins",
"value": {
"timeout": "2s"
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_1": {}
}
},
"version": "0"
},
"ChannelRestrictions": {
"mod_policy": "Admins",
"value": null,
"version": "0"
},
"ConsensusType": {
"mod_policy": "Admins",
"value": {
"metadata": null,
"state": "STATE_NORMAL",
"type": "solo"
},
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "MAJORITY",
"sub_policy": "Admins"
}
},
"version": "0"
},
"Readers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Readers"
}
},
"version": "0"
},
"Writers": {
"mod_policy": "Admins",
"policy": {
"type": 3,
"value": {
"rule": "ANY",
"sub_policy": "Writers"
}
},
"version": "0"
}
},
"values": {
"BlockDataHashingStructure": {
"mod_policy": "Admins",
"value": {
"width": 4294967295
},
"version": "0"
},
"Capabilities": {
"mod_policy": "Admins",
"value": {
"capabilities": {
"V1_3": {}
}
},
"version": "0"
},
"Consortium": {
"mod_policy": "Admins",
"value": {
"name": "SampleConsortium"
},
"version": "0"
},
"HashingAlgorithm": {
"mod_policy": "Admins",
"value": {
"name": "SHA256"
},
"version": "0"
},
"OrdererAddresses": {
"mod_policy": "/Channel/Orderer/Admins",
"value": {
"addresses": [
"orderer.example.com:7050"
]
},
"version": "0"
}
},
"version": "0"
},
"sequence": "3"
},
"last_update": {
"payload": {
"data": {
"config_update": {
"channel_id": "mychannel",
"isolated_data": {},
"read_set": {
"groups": {
"Application": {
"groups": {
"Org2MSP": {
"groups": {},
"mod_policy": "",
"policies": {
"Admins": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Readers": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Writers": {
"mod_policy": "",
"policy": null,
"version": "0"
}
},
"values": {
"MSP": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "0"
}
},
"mod_policy": "Admins",
"policies": {},
"values": {},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
},
"write_set": {
"groups": {
"Application": {
"groups": {
"Org2MSP": {
"groups": {},
"mod_policy": "Admins",
"policies": {
"Admins": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Readers": {
"mod_policy": "",
"policy": null,
"version": "0"
},
"Writers": {
"mod_policy": "",
"policy": null,
"version": "0"
}
},
"values": {
"AnchorPeers": {
"mod_policy": "Admins",
"value": {
"anchor_peers": [{
"host": "peer0.org2.example.com",
"port": 9051
}]
},
"version": "0"
},
"MSP": {
"mod_policy": "",
"value": null,
"version": "0"
}
},
"version": "1"
}
},
"mod_policy": "Admins",
"policies": {},
"values": {},
"version": "1"
}
},
"mod_policy": "",
"policies": {},
"values": {},
"version": "0"
}
},
"signatures": [{
"signature": "MEQCIG3LXxjcsZ+8jSCZEZbUMqWOslJGqFAzNz1zz/JlqssmAiBaO8xvVXT1vusIAQaLsldAXXU9np280n5UwYyJLsuYBw==",
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "InlUsKBi6xaHgTtivM0cOO5Vabiw6oSS"
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:19:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 2,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "5U3mT268xjTWiMCBXcO2+wEJcikv2YJj"
}
}
},
"signature": "MEUCIQCpsp/8oaQEmtp5baG8ogkrXgP+unRtBIQzLgsVf9AlbQIgLrQ6mG1TyxMEdFtL1rjaLLj9iL3A/qzFSh3jTiUH7ME="
}
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": null,
"timestamp": "2019-08-01T02:19:01Z",
"tls_cert_hash": null,
"tx_id": "",
"type": 1,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K",
"mspid": "OrdererMSP"
},
"nonce": "6oiXowzUUpo6JFlSyB50x1Ma12ZoFUba"
}
}
},
"signature": "MEQCIBNyEeC+cWzOCLq9J9haktAwM3e+BBjNeEQc55xiB2yPAiAIF+KkWEh+RARJM/iA15JSfgvCP9zwgIrvSxO/Ki/KQQ=="
}]
},
"header": {
"data_hash": "35xdxe58OecLRLqrdJREGDaw+XEVSDFvN4Vk8v/GNPA=",
"number": "2",
"previous_hash": "6xJ0rP3gVYFZpwcBDW/i84IIkTi8Jj0cWBeX2GXsCUQ="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhig4rR9Ua6PViqus4vI1MI7T5OdGzlRTykSRjBEAiBI+JDzNh3q5fzUahojTbQkWxVBf+QksfhsRNdTtEVYfgIgJqbdOquXXkWQ8kB7xKjub/sfbX1Cnm2Vo2uHlSZAIG4=",
"CgIIAg==",
"AA==",
""
]
}
}

应用通道普通区块

普通区块指不修改配置的区块,里面包含2类交易:

  1. 实例化链码的交易
  2. 调用链码的交易

实例化实际Invoke的是LSCC,调用应用chaincode,也会先用到LSCC,再到应用chaincode,那么怎么区分当前到底是Invoke哪个chaincode?区块中的chaincode_proposal_payload,代表了当前操作的chaincode。

ns_rwset是操作过程的读写集,包含LSCC和应用chaincode的。

实例化chaincode

ns_rwset包含2个collection_hashed_rwset

  • 第1个:说明了当前操作的是lscc,读mycc没结果,然后把mycc放到写集,这是创建合约的典型过程。
  • 第2个:是进行chaincode的初始化,也是只写入,这里是设置a和b的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{
"data": {
"data": [{
"payload": {
"data": {
"actions": [{
"header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "CLJ2euIe3YSE7LhNiTuXdBCS2DnvabH2"
},
"payload": {
"action": {
"endorsements": [{
"endorser": "CgdPcmcyTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVHA5QTREM25oa2NJaVdXRk4xRUZQakFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVSRjBFV0NuQW5oMm8KcnFFazhmRTlLdEFCdmJIc1FYTHhMRnZZdlZYVlFPS1crVDBpVk44eWdQbTZlM0kxcG5FTS9hK3Vha2dtYWNmOApzVnVZVERMSythTk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnVTMwdllnVHpCYS84ZGFaZlpBYThzLzN3RFdZdlRpL1BEY3RuWFJBT043QUNJQmNvQzlXSgpYK28rdHFUUDZybS8vdWxpUU02Rk53cUtBSDlYQWxOcVdhbWsKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEUCIQCRu+F6XBVrfy24JtxuAiy9+3PNYy6tRmKDmgtmGx0OLAIgQeMPTKzPhh9fH39tmZEDjJCGet/ob2BzbRExZIYq2po="
}],
"proposal_response_payload": {
"extension": {
"chaincode_id": {
"name": "lscc",
"path": "",
"version": "1.4.2"
},
"events": null,
"response": {
"message": "",
"payload": "CgRteWNjEgMxLjAaBGVzY2MiBHZzY2MqLBIMEgoIAhICCAASAggBGg0SCwoHT3JnMU1TUBADGg0SCwoHT3JnMk1TUBADMkQKIJJH+4JOIdCHD/55PPBgJOZehvHg5Ytk6QxbGU9uNzQAEiAHHxQpHhUrsq6I5/355ad00xNha6MVSoT2oNRaF19jJjogR2/KGpSSdAAZcfHsKDbLCTIfC3Emizdi1okxyT8hgTRCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBAB",
"status": 200
},
"results": {
"data_model": "KV",
"ns_rwset": [{
"collection_hashed_rwset": [],
"namespace": "lscc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "mycc",
"version": null
}],
"writes": [{
"is_delete": false,
"key": "mycc",
"value": "CgRteWNjEgMxLjAaBGVzY2MiBHZzY2MqLBIMEgoIAhICCAASAggBGg0SCwoHT3JnMU1TUBADGg0SCwoHT3JnMk1TUBADMkQKIJJH+4JOIdCHD/55PPBgJOZehvHg5Ytk6QxbGU9uNzQAEiAHHxQpHhUrsq6I5/355ad00xNha6MVSoT2oNRaF19jJjogR2/KGpSSdAAZcfHsKDbLCTIfC3Emizdi1okxyT8hgTRCLBIMEgoIARICCAASAggBGg0SCwoHT3JnMU1TUBABGg0SCwoHT3JnMk1TUBAB"
}]
}
},
{
"collection_hashed_rwset": [],
"namespace": "mycc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [],
"writes": [{
"is_delete": false,
"key": "a",
"value": "MTAw"
},
{
"is_delete": false,
"key": "b",
"value": "MjAw"
}
]
}
}
]
},
"token_expectation": null
},
"proposal_hash": "QFsKOSAIcsdRQ/3UYkYrUuK36Apj4iulCKW/LKmtAAs="
}
},
"chaincode_proposal_payload": {
"TransientMap": {},
"input": {
"chaincode_spec": {
"chaincode_id": {
"name": "lscc",
"path": "",
"version": ""
},
"input": {
"args": [
"ZGVwbG95",
"bXljaGFubmVs",
"CicIARILEgRteWNjGgMxLjAaFgoEaW5pdAoBYQoDMTAwCgFiCgMyMDA=",
"EgwSCggCEgIIABICCAEaDRILCgdPcmcxTVNQEAMaDRILCgdPcmcyTVNQEAM=",
"ZXNjYw==",
"dnNjYw=="
],
"decorations": {}
},
"timeout": 0,
"type": "GOLANG"
}
}
}
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": "EgYSBGxzY2M=",
"timestamp": "2019-08-01T02:19:04.887742126Z",
"tls_cert_hash": null,
"tx_id": "91c8e4c4e6c91db19eb8e40f95e2b29cea3d916b2a3249459b9fa83b44fc445a",
"type": 3,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "CLJ2euIe3YSE7LhNiTuXdBCS2DnvabH2"
}
}
},
"signature": "MEUCIQD0jJ2zDEbsKIi8ekG3E5xF//EddpMJdKL5nF0anvHp7QIgTNsEJk3N344hq4fklJn/c0fVojzeos4t4w17r7VOJSg="
}]
},
"header": {
"data_hash": "w8fXJmELmqvXJAWXPge6oCH0SxwoR4nube3o//fYz4A=",
"number": "3",
"previous_hash": "qpUWyIdR36SuQa/xqGEnwm1wjpaQxIeU8aaA8RFeN/8="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhjerftAXzK8sFOlQ17D0d7A0oP73xf29PkSRjBEAiAp7vqkuMOhC4XzOjwov/HlydZHpMgKuVfeBITm8T752AIgDl9dQHLeaLXEmjFXgv+38H1tFgv/ohHNxdraFexmlrg=",
"CgIIAg==",
"AA==",
""
]
}
}

调用chaincode

ns_rwset包含2个collection_hashed_rwset

  • 第1个:说明了当前操作的是lscc,从区块3里把mycc读取出来。
  • 第2个:说明操作的是mycc,先把a和b读取出来,然后又把a和b的结果写回mycc。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
{
"data": {
"data": [{
"payload": {
"data": {
"actions": [{
"header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "6Z4RNNIV085Nv/pOYUc5W3EaWcBueAAs"
},
"payload": {
"action": {
"endorsements": [{
"endorser": "CgdPcmcxTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVTlaQzNZWEVySzEybHlSRmx2VW1DREFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTVM1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NUzVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN4CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVGdnVqVG5lMXYyRk4KVENxOTI4WlZrSlhheXRNbVlZcnJ2dndoMEV4b1VVZU1yRjRueU9hZjBRcUo4NjZKeTNibFNQL0xDZFJqOElzdwprdzZmNFBDdEE2Tk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnVkJwVFZxMVBIblR6b3lmdGh1OTN0a2tBNytWN1ZOeUgvaU5nOW8vRklCQXdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnSCtpTHF0THQ4WGYwSzNSVHNZUVJLNFIyY05hMXNSV3BVd05QYkxEdzFZd0NJRTg4c2kyTgprZkMvTzB6SkhDandhdFR1aDFVbEt4eFc4OU16aWx3aXJjWnEKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEQCIEnnXFAiBoxqlqgTRs9aBW5PWv+uyjHEEi1XGWXbX0aaAiBEteVfQu7352r5zokdeKdG5+K5LF+IiAbR+QOomOjF1Q=="
},
{
"endorser": "CgdPcmcyTVNQEqYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNKekNDQWM2Z0F3SUJBZ0lRVHA5QTREM25oa2NJaVdXRk4xRUZQakFLQmdncWhrak9QUVFEQWpCek1Rc3cKQ1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0JNS1EyRnNhV1p2Y201cFlURVdNQlFHQTFVRUJ4TU5VMkZ1SUVaeQpZVzVqYVhOamJ6RVpNQmNHQTFVRUNoTVFiM0puTWk1bGVHRnRjR3hsTG1OdmJURWNNQm9HQTFVRUF4TVRZMkV1CmIzSm5NaTVsZUdGdGNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmEKTUdveEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVApZVzRnUm5KaGJtTnBjMk52TVEwd0N3WURWUVFMRXdSd1pXVnlNUjh3SFFZRFZRUURFeFp3WldWeU1DNXZjbWN5CkxtVjRZVzF3YkdVdVkyOXRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVSRjBFV0NuQW5oMm8KcnFFazhmRTlLdEFCdmJIc1FYTHhMRnZZdlZYVlFPS1crVDBpVk44eWdQbTZlM0kxcG5FTS9hK3Vha2dtYWNmOApzVnVZVERMSythTk5NRXN3RGdZRFZSMFBBUUgvQkFRREFnZUFNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqCkJDUXdJb0FnbVFGMFluM29zOXBQSFdDTWtrdThVdTNiSzRyZjVBSWVRNjBRWDI0eHhyOHdDZ1lJS29aSXpqMEUKQXdJRFJ3QXdSQUlnVTMwdllnVHpCYS84ZGFaZlpBYThzLzN3RFdZdlRpL1BEY3RuWFJBT043QUNJQmNvQzlXSgpYK28rdHFUUDZybS8vdWxpUU02Rk53cUtBSDlYQWxOcVdhbWsKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=",
"signature": "MEUCIQCKz1zIddnNjjoGnzSTuxvVy19TvQSlONS4C9Y3iaJhaAIgY+Zpl003I0X/FsH8+ekifXvugeHCPeZ3UhkO90MWg3U="
}
],
"proposal_response_payload": {
"extension": {
"chaincode_id": {
"name": "mycc",
"path": "",
"version": "1.0"
},
"events": null,
"response": {
"message": "",
"payload": null,
"status": 200
},
"results": {
"data_model": "KV",
"ns_rwset": [{
"collection_hashed_rwset": [],
"namespace": "lscc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "mycc",
"version": {
"block_num": "3",
"tx_num": "0"
}
}],
"writes": []
}
},
{
"collection_hashed_rwset": [],
"namespace": "mycc",
"rwset": {
"metadata_writes": [],
"range_queries_info": [],
"reads": [{
"key": "a",
"version": {
"block_num": "3",
"tx_num": "0"
}
},
{
"key": "b",
"version": {
"block_num": "3",
"tx_num": "0"
}
}
],
"writes": [{
"is_delete": false,
"key": "a",
"value": "OTA="
},
{
"is_delete": false,
"key": "b",
"value": "MjEw"
}
]
}
}
]
},
"token_expectation": null
},
"proposal_hash": "okKS/G4+W9VT9+t6VXJnAClnPvOhtUkiCdqjPmzkqjI="
}
},
"chaincode_proposal_payload": {
"TransientMap": {},
"input": {
"chaincode_spec": {
"chaincode_id": {
"name": "mycc",
"path": "",
"version": ""
},
"input": {
"args": [
"aW52b2tl",
"YQ==",
"Yg==",
"MTA="
],
"decorations": {}
},
"timeout": 0,
"type": "GOLANG"
}
}
}
}
}]
},
"header": {
"channel_header": {
"channel_id": "mychannel",
"epoch": "0",
"extension": "EgYSBG15Y2M=",
"timestamp": "2019-08-01T02:19:38.504172871Z",
"tls_cert_hash": null,
"tx_id": "2377b308f2ea91e58d1b21d71b09370dc3c39bab3b92d2c0bc40801843150d6b",
"type": 3,
"version": 0
},
"signature_header": {
"creator": {
"id_bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNLekNDQWRHZ0F3SUJBZ0lSQUo5YnI3UHJmQWg5cHVVcDFMZkJ3V0V3Q2dZSUtvWkl6ajBFQXdJd2N6RUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhHVEFYQmdOVkJBb1RFRzl5WnpJdVpYaGhiWEJzWlM1amIyMHhIREFhQmdOVkJBTVRFMk5oCkxtOXlaekl1WlhoaGJYQnNaUzVqYjIwd0hoY05NVGt3T0RBeE1ESXhOREF3V2hjTk1qa3dOekk1TURJeE5EQXcKV2pCc01Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQk1LUTJGc2FXWnZjbTVwWVRFV01CUUdBMVVFQnhNTgpVMkZ1SUVaeVlXNWphWE5qYnpFUE1BMEdBMVVFQ3hNR1kyeHBaVzUwTVI4d0hRWURWUVFEREJaQlpHMXBia0J2CmNtY3lMbVY0WVcxd2JHVXVZMjl0TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFZndwdkRmOHoKWVdVaWxwclcxQ2hsc3hBaDhVcjBLQXpUYm9oc0oyQjFNWk40ZkVUakhKTHRnWjZDT0ZRaVFoNy9BK1NyNVhYdApDRnpvNUZWSlNSNmc2YU5OTUVzd0RnWURWUjBQQVFIL0JBUURBZ2VBTUF3R0ExVWRFd0VCL3dRQ01BQXdLd1lEClZSMGpCQ1F3SW9BZ21RRjBZbjNvczlwUEhXQ01ra3U4VXUzYks0cmY1QUllUTYwUVgyNHh4cjh3Q2dZSUtvWkkKemowRUF3SURTQUF3UlFJaEFPVUZRWUMvZE1ZMGZ0U2JNYVFLL213Uy83a0JyRWZicXFNaHBwWjlqWG1uQWlCeQpjczdFalFuZHJ2RHRMZnVPUEY4QnJyVGZPdWZDTTVNN09YSDl4S2ZQdVE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==",
"mspid": "Org2MSP"
},
"nonce": "6Z4RNNIV085Nv/pOYUc5W3EaWcBueAAs"
}
}
},
"signature": "MEUCIQD5A7tvxqe4q/l+D1KOpDDaTZ5DdpsLs+xTA1CqsR+nBAIgSo+cFAzBBDfRJK8bcV6khmqWuhATKHm/7uiJiDkDjKI="
}]
},
"header": {
"data_hash": "9Q8SRPt+L2bvRGXAteiCXOkiutLu1nZBiCCaiwAlFdk=",
"number": "4",
"previous_hash": "vE7W/Qond3H6koHXpoWm2dfcZfZnmMYwF5lfNj6hDlY="
},
"metadata": {
"metadata": [
"CgQKAggCEv0GCrIGCpUGCgpPcmRlcmVyTVNQEoYGLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNEVENDQWJPZ0F3SUJBZ0lSQU0rUFQ2YTJsYm1EY0NpNEVhTEFSeFl3Q2dZSUtvWkl6ajBFQXdJd2FURUwKTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnVENrTmhiR2xtYjNKdWFXRXhGakFVQmdOVkJBY1REVk5oYmlCRwpjbUZ1WTJselkyOHhGREFTQmdOVkJBb1RDMlY0WVcxd2JHVXVZMjl0TVJjd0ZRWURWUVFERXc1allTNWxlR0Z0CmNHeGxMbU52YlRBZUZ3MHhPVEE0TURFd01qRTBNREJhRncweU9UQTNNamt3TWpFME1EQmFNRmd4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxVFlXNGdSbkpoYm1OcApjMk52TVJ3d0dnWURWUVFERXhOdmNtUmxjbVZ5TG1WNFlXMXdiR1V1WTI5dE1Ga3dFd1lIS29aSXpqMENBUVlJCktvWkl6ajBEQVFjRFFnQUVZVGcwdXZJMVZ0dEsrb2o2MG9LRjRtWlc4YlFXN0FIRFpZcFZueEdoTE5Kems2cmwKTU1uS0RiRmxibElETE9qK0tiakVwOW9WdEN6OVBsbk4xTmhHSEtOTk1Fc3dEZ1lEVlIwUEFRSC9CQVFEQWdlQQpNQXdHQTFVZEV3RUIvd1FDTUFBd0t3WURWUjBqQkNRd0lvQWdRSWtEMTlLWDFQc3VWUi9BQmVuZFM2ejY5cGxDCmNVNmc0aXUrdHFRL1hzQXdDZ1lJS29aSXpqMEVBd0lEU0FBd1JRSWhBTnd0cDQzMUdkYkdqb3pXRU1DQWdwNWIKeFdnMHNRMVlGbG9TYzF1MFFRU3FBaUF1TVNkc1AvMXZoclhVcGtoT1Voa3NwRjYrYVl1V2ozazZaVE84RWV3agpqdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KEhhh6UhNerUcr/HbaA9SbvQ3xInOkYF9oKoSRjBEAiAwGjDnt+7TPrPz7fhm/63rUCFlJYzgRXdPautelXme3gIgUXb8ynZgf2rfIA8bL+y992HIgjtDCBppydWmPmXD9g4=",
"CgIIAg==",
"AA==",
""
]
}
}

这篇文章介绍了如何快速的搭建一个fabric网络,然后又把搭建过程分解,针对每一步都做详细解释,希望你能熟练今后到不看文档也能搭建出fabric网络。

本文是Building Your First Network的笔记和实践记录,基于Fabric 1.4,commit id:9dce73。

前提:

  1. 安装了Docker、Go等环境。
  2. 已经下载了fabric仓库,完成make all

下载fabric-samples和准备工作

有2种方式。

方式1:一键下载和编译。

1
curl -sSL http://bit.ly/2ysbOFE | bash -s

方式2:手动clone,放到GOPATH下,然后执行脚本,构建和拉去一些镜像,为搭建网络做准备。方式2只不过是把方式1的工作,手动做掉了。

1
2
3
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples
sh scripts/bootstrap.sh

参考资料:https://hyperledger-fabric.readthedocs.io/en/release-1.4/install.html。

快速启动你的第一个Fabric网络

这一节的目的是用几分钟的时间启动一个网络,并且了解启动一个网络的过程。

启动网络

fabric-samples下有多个示例,本次要使用的是first-network

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  fabric-samples git:(release-1.4) ll | grep ^d
drwxr-xr-x 5 centos centos 193 7月 12 08:21 balance-transfer
drwxr-xr-x 4 centos centos 273 7月 12 08:21 basic-network
drwxrwxr-x 2 centos centos 175 1月 9 2019 bin
drwxr-xr-x 8 centos centos 113 7月 12 08:21 chaincode
drwxr-xr-x 3 centos centos 139 7月 12 08:21 chaincode-docker-devmode
drwxr-xr-x 3 centos centos 44 7月 12 06:47 commercial-paper
drwxrwxr-x 2 centos centos 64 1月 9 2019 config
drwxr-xr-x 2 centos centos 59 7月 12 08:21 docs
drwxr-xr-x 5 centos centos 110 7月 12 08:21 fabcar
drwxr-xr-x 7 centos centos 4.0K 7月 17 03:14 first-network
drwxr-xr-x 4 centos centos 55 7月 12 08:21 high-throughput
drwxr-xr-x 4 centos centos 55 7月 12 08:21 interest_rate_swaps
drwxr-xr-x 4 centos centos 67 7月 17 03:46 scripts

进入first-network然后执行./byfn.sh up,启动操作会持续两三分钟,byfn是Building Your First Network的缩写。

启动过程实际做了这些事:

第一阶段:生成配置文件

  1. 使用加密工具cryptogen生成证书
  2. 使用工具configtxgen生成orderer节点的创世块,即得到genesis.block
  3. 使用工具configtxgen生成配置应用通道channel的交易channel.tx,即得到mychannel.block
  4. 使用工具configtxgen生成Org1的MSP的anchor peer
  5. 使用工具configtxgen生成Org2的MSP的anchor peer

第二阶段:启动网络

这个阶段是启动容器,包含客户端(cli)、peer,每个org有2个peer,peer0和peer1,默认是solo共识算法,还会启动1个orderer。

第三阶段:创建和加入通道,部署和测试链码

  1. 创建应用通道mychannel
  2. peer加入mychannel
  3. 在mychannel上更新Org1和Org2 MSP的anchor peer
  4. 在ogr1好org2的peer0上安装chaincode
  5. 在mychannel中,在peer0.org2上实例化chaincode,1个通道上只需示例化1次chaincode
  6. 在mychannel中,Invoke刚实例化的chaincode
  7. 在peer1.org2上安装chaincode,并查询

byfn.sh中的networkUp()函数是./byfn.sh up的主要执行函数,它的主要功能就是组织上面3个阶段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Generate the needed certificates, the genesis block and start the network.
function networkUp() {
checkPrereqs
# 生成配置:证书、交易、私钥
# generate artifacts if they don't exist
if [ ! -d "crypto-config" ]; then
generateCerts
replacePrivateKey
generateChannelArtifacts
fi
# 启动网络/容器
COMPOSE_FILES="-f ${COMPOSE_FILE}"
if [ "${CERTIFICATE_AUTHORITIES}" == "true" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_CA}"
export BYFN_CA1_PRIVATE_KEY=$(cd crypto-config/peerOrganizations/org1.example.com/ca && ls *_sk)
export BYFN_CA2_PRIVATE_KEY=$(cd crypto-config/peerOrganizations/org2.example.com/ca && ls *_sk)
fi
if [ "${CONSENSUS_TYPE}" == "kafka" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_KAFKA}"
elif [ "${CONSENSUS_TYPE}" == "etcdraft" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_RAFT2}"
fi
if [ "${IF_COUCHDB}" == "couchdb" ]; then
COMPOSE_FILES="${COMPOSE_FILES} -f ${COMPOSE_FILE_COUCH}"
fi
IMAGE_TAG=$IMAGETAG docker-compose ${COMPOSE_FILES} up -d 2>&1
# 检查容器是否启动
docker ps -a
if [ $? -ne 0 ]; then
echo "ERROR !!!! Unable to start network"
exit 1
fi

if [ "$CONSENSUS_TYPE" == "kafka" ]; then
sleep 1
echo "Sleeping 10s to allow $CONSENSUS_TYPE cluster to complete booting"
sleep 9
fi

if [ "$CONSENSUS_TYPE" == "etcdraft" ]; then
sleep 1
echo "Sleeping 15s to allow $CONSENSUS_TYPE cluster to complete booting"
sleep 14
fi

# 执行端到端脚本:创建并加入应用通道,然后测试
# now run the end to end script
docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT $VERBOSE $NO_CHAINCODE
if [ $? -ne 0 ]; then
echo "ERROR !!!! Test failed"
exit 1
fi
}

启动日志,日志中标记了各阶段,建议详读一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
$ cd first-network
➜ first-network git:(release-1.4) ./byfn.sh up
Starting for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds
Continue? [Y/n] y
proceeding ...
LOCAL_VERSION=1.4.0
DOCKER_IMAGE_VERSION=1.4.0
/home/centos/go/src/github.com/hyperledger/fabric-samples/bin/cryptogen

/**** 第1阶段:生成配置文件 ****/

##########################################################
##### Generate certificates using cryptogen tool #########
##########################################################
+ cryptogen generate --config=./crypto-config.yaml
org1.example.com
org2.example.com
+ res=0
+ set +x

/home/centos/go/src/github.com/hyperledger/fabric-samples/bin/configtxgen
##########################################################
######### Generating Orderer Genesis block ##############
##########################################################
CONSENSUS_TYPE=solo
+ '[' solo == solo ']'
+ configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-17 06:34:26.973 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.088 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: solo
2019-07-17 06:34:27.088 UTC [common.tools.configtxgen.localconfig] Load -> INFO 003 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.186 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 004 orderer type: solo
2019-07-17 06:34:27.186 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 005 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.188 UTC [common.tools.configtxgen] doOutputBlock -> INFO 006 Generating genesis block
2019-07-17 06:34:27.189 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Writing genesis block
+ res=0
+ set +x

#################################################################
### Generating channel configuration transaction 'channel.tx' ###
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
2019-07-17 06:34:27.228 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.315 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.422 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 005 Generating new channel configtx
2019-07-17 06:34:27.425 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 006 Writing new channel tx
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org1MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
2019-07-17 06:34:27.477 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.559 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-17 06:34:27.649 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x

#################################################################
####### Generating anchor peer update for Org2MSP ##########
#################################################################
+ configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
2019-07-17 06:34:27.689 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-17 06:34:27.773 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-17 06:34:27.886 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
+ res=0
+ set +x



/**** 第2阶段:启动容器网络 ****/


Creating network "net_byfn" with the default driver
Creating volume "net_orderer.example.com" with default driver
Creating volume "net_peer0.org1.example.com" with default driver
Creating volume "net_peer1.org1.example.com" with default driver
Creating volume "net_peer0.org2.example.com" with default driver
Creating volume "net_peer1.org2.example.com" with default driver
Creating orderer.example.com ... done
Creating peer0.org2.example.com ... done
Creating peer1.org2.example.com ... done
Creating peer0.org1.example.com ... done
Creating peer1.org1.example.com ... done
Creating cli ... done
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8c2ccb5ee443 hyperledger/fabric-tools:latest "/bin/bash" Less than a second ago Up Less than a second cli
5af5a3fb3bb7 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:8051->8051/tcp peer1.org1.example.com
396b363bb6f5 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:7051->7051/tcp peer0.org1.example.com
94be2011d20f hyperledger/fabric-orderer:latest "orderer" 2 seconds ago Up Less than a second 0.0.0.0:7050->7050/tcp orderer.example.com
da8c17df215d hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:9051->9051/tcp peer0.org2.example.com
fcd30620e876 hyperledger/fabric-peer:latest "peer node start" 2 seconds ago Up Less than a second 0.0.0.0:10051->10051/tcp peer1.org2.example.com
10510312db61 dc535406-4013-4141-be9e-e472c1cf24a1-simple-5e32b897538246406863e63956e4c561246725b6fbf114a1fedff16775cf782d "tail -f /dev/null" 22 hours ago Exited (137) 22 hours ago dc535406-4013-4141-be9e-e472c1cf24a1-simple
21a3b8dc137a hyperledger/fabric-buildenv:amd64-latest "/bin/bash" 22 hours ago Exited (0) 22 hours ago musing_swartz
48407948b7d7 hyperledger/fabric-buildenv "/bin/bash" 22 hours ago Exited (130) 22 hours ago affectionate_curie
358f0c0de3e1 92b20cd39f98 "/bin/bash" 23 hours ago Exited (130) 22 hours ago festive_clarke
de5938eccc11 92b20cd39f98 "./scripts/check_dep…" 23 hours ago Exited (127) 23 hours ago quizzical_einstein
324b27de3a34 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago amazing_booth
26a53801f203 92b20cd39f98 "./scripts/check_dep…" 23 hours ago Exited (127) 23 hours ago jolly_saha
94a33ddda70a 92b20cd39f98 "./scripts/golinter.…" 23 hours ago Exited (0) 23 hours ago peaceful_allen
6a0c7fded448 92b20cd39f98 "./scripts/golinter.…" 23 hours ago Created recursing_beaver
233496b4065c 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago wizardly_shockley
f0a255a96610 92b20cd39f98 "/bin/bash" 23 hours ago Exited (0) 23 hours ago jolly_ganguly
664416bc4fee 965663acb7cf "/bin/sh -c 'apt-get…" 24 hours ago Exited (100) 24 hours ago nostalgic_chaplygin
51cf784ef4e1 ba82c6de-50fb-4ffd-989d-0dcf54e14e3b-simple-9961bcae6dad48592af2e9f1c1df3c96b568f9394ec82b2f351e79fa51a4f786 "tail -f /dev/null" 47 hours ago Exited (137) 47 hours ago ba82c6de-50fb-4ffd-989d-0dcf54e14e3b-simple
b7642b085ac1 14669948-7a23-4b3b-aa14-8b0622986e03-simple-f8a7b2e1352d04d884580725c2be9b642dd29df7e3e095a4a9403ac789dde2ac "tail -f /dev/null" 2 days ago Exited (137) 2 days ago 14669948-7a23-4b3b-aa14-8b0622986e03-simple

/**** 第3阶段:创建并加入应用通道,然后测试 ****/

____ _____ _ ____ _____
/ ___| |_ _| / \ | _ \ |_ _|
\___ \ | | / _ \ | |_) | | |
___) | | | / ___ \ | _ < | |
|____/ |_| /_/ \_\ |_| \_\ |_|

Build your first network (BYFN) end-to-end test

Channel name : mychannel
Creating channel...
+ peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:32.113 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:32.190 UTC [cli.common] readBlock -> INFO 002 Received block: 0
===================== Channel 'mychannel' created =====================

Having all peers join the channel...
+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:32.272 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:32.338 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org1 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:35.449 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:35.536 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org1 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:38.617 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:38.673 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer0.org2 joined channel 'mychannel' =====================

+ peer channel join -b mychannel.block
+ res=0
+ set +x
2019-07-17 06:34:41.755 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:41.837 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
===================== peer1.org2 joined channel 'mychannel' =====================

Updating anchor peers for org1...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:44.930 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:44.951 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org1MSP' on channel 'mychannel' =====================

Updating anchor peers for org2...
+ peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
+ res=0
+ set +x
2019-07-17 06:34:48.037 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-17 06:34:48.059 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
===================== Anchor peers updated for org 'Org2MSP' on channel 'mychannel' =====================

Installing chaincode on peer0.org1...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:34:51.167 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.167 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:34:51.462 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer0.org1 =====================

Install chaincode on peer0.org2...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:34:51.542 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.542 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:34:51.817 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer0.org2 =====================

Instantiating chaincode on peer0.org2...
+ peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'
+ res=0
+ set +x
2019-07-17 06:34:51.910 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:34:51.910 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
===================== Chaincode is instantiated on peer0.org2 on channel 'mychannel' =====================

Querying chaincode on peer0.org1...
===================== Querying on peer0.org1 on channel 'mychannel'... =====================
+ peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
Attempting to Query peer0.org1 ...3 secs
+ res=0
+ set +x

100
===================== Query successful on peer0.org1 on channel 'mychannel' =====================
Sending invoke transaction on peer0.org1 peer0.org2...
+ peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --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 -c '{"Args":["invoke","a","b","10"]}'
+ res=0
+ set +x
2019-07-17 06:35:27.719 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
===================== Invoke transaction successful on peer0.org1 peer0.org2 on channel 'mychannel' =====================

Installing chaincode on peer1.org2...
+ peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
+ res=0
+ set +x
2019-07-17 06:35:27.809 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-17 06:35:27.809 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-17 06:35:28.060 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
===================== Chaincode is installed on peer1.org2 =====================

Querying chaincode on peer1.org2...
===================== Querying on peer1.org2 on channel 'mychannel'... =====================
+ peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
Attempting to Query peer1.org2 ...3 secs
+ res=0
+ set +x

90
===================== Query successful on peer1.org2 on channel 'mychannel' =====================

========= All GOOD, BYFN execution completed ===========


_____ _ _ ____
| ____| | \ | | | _ \
| _| | \| | | | | |
| |___ | |\ | | |_| |
|_____| |_| \_| |____/

使用docker查看起来的服务:

1
2
3
4
5
6
7
8
9
10
11
➜  first-network git:(release-1.4) docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fe690a4f3e9f dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer1.org2.example.com-mycc-1.0
03a5f82384a0 dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9 "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer0.org1.example.com-mycc-1.0
a737b47e9de6 dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b "chaincode -peer.add…" 2 hours ago Up 2 hours dev-peer0.org2.example.com-mycc-1.0
8c2ccb5ee443 hyperledger/fabric-tools:latest "/bin/bash" 2 hours ago Up 2 hours cli
5af5a3fb3bb7 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:8051->8051/tcp peer1.org1.example.com
396b363bb6f5 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:7051->7051/tcp peer0.org1.example.com
94be2011d20f hyperledger/fabric-orderer:latest "orderer" 2 hours ago Up 2 hours 0.0.0.0:7050->7050/tcp orderer.example.com
da8c17df215d hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:9051->9051/tcp peer0.org2.example.com
fcd30620e876 hyperledger/fabric-peer:latest "peer node start" 2 hours ago Up 2 hours 0.0.0.0:10051->10051/tcp peer1.org2.example.com

上面有3个dev-peer*.org*.example.com-mycc-1.0容器,它们是链码容器,每一个安装过链码的peer都会创建一个属于自己的链码容器,在调用链码的时候,peer会通过gRPC和自己的链码容器通信。

关闭网络

使用./byfn.sh down命令,关闭first-network

  1. 依次停止channel、客户端、orderer、peer
  2. 删除cli、orderer、peer、netowrk
  3. 删除docker镜像

./byfn.sh up失败时,也需要使用此命令清理数据,以免后面启动网络时出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
➜  first-network git:(release-1.4) ./byfn.sh down
Stopping for channel 'mychannel' with CLI timeout of '10' seconds and CLI delay of '3' seconds
Continue? [Y/n] y
proceeding ...
WARNING: The BYFN_CA1_PRIVATE_KEY variable is not set. Defaulting to a blank string.
WARNING: The BYFN_CA2_PRIVATE_KEY variable is not set. Defaulting to a blank string.
Stopping cli ... done
Stopping orderer.example.com ... done
Stopping peer1.org1.example.com ... done
Stopping peer0.org1.example.com ... done
Stopping peer0.org2.example.com ... done
Stopping peer1.org2.example.com ... done
Removing cli ... done
Removing orderer.example.com ... done
Removing peer1.org1.example.com ... done
Removing peer0.org1.example.com ... done
Removing peer0.org2.example.com ... done
Removing peer1.org2.example.com ... done
Removing network net_byfn
Removing volume net_orderer.example.com
Removing volume net_peer0.org1.example.com
Removing volume net_peer1.org1.example.com
Removing volume net_peer0.org2.example.com
Removing volume net_peer1.org2.example.com
Removing volume net_orderer2.example.com
WARNING: Volume net_orderer2.example.com not found.
Removing volume net_orderer3.example.com
WARNING: Volume net_orderer3.example.com not found.
Removing volume net_orderer4.example.com
WARNING: Volume net_orderer4.example.com not found.
Removing volume net_orderer5.example.com
WARNING: Volume net_orderer5.example.com not found.
Removing volume net_peer0.org3.example.com
WARNING: Volume net_peer0.org3.example.com not found.
Removing volume net_peer1.org3.example.com
WARNING: Volume net_peer1.org3.example.com not found.
05c281ff186c
f3ccbe5e2b80
7f0144ca0eae
Untagged: dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab:latest
Deleted: sha256:9425c8298cafe082ed22c5968d431a6098d53ef2318fb5d286efb96b4bc44915
Deleted: sha256:9005a0d9f52947d9256aa4766d4c26a9bab98f229aab7f2598da05789fc977ef
Deleted: sha256:98602d24729b179952f685f8f83f1effaf3733e7f93354a9d31b15f711bc0fac
Deleted: sha256:fe2b67155487d7e001c8a0b2ef100bb710b1b816897bc9d2a80029f4c7bd0b54
Untagged: dev-peer0.org1.example.com-mycc-1.0-384f11f484b9302df90b453200cfb25174305fce8f53f4e94d45ee3b6cab0ce9:latest
Deleted: sha256:0759f367d73c68e71b6077ebd46611d43a8d9c1c9ebc398b838010268b175d65
Deleted: sha256:2d56a884d5514a4467471cf06b42c0cfa492a80a239d48f79fa48273982d81b7
Deleted: sha256:614c6a2a164cc8afbb7f348fdf6d048834dc0cb2a94a22638b8d4dcd72eaeb14
Deleted: sha256:9a39bc364e8d141bdab60a80946e4af10513cb070c34e4bda1b1cbbf88f9dca3
Untagged: dev-peer0.org2.example.com-mycc-1.0-15b571b3ce849066b7ec74497da3b27e54e0df1345daff3951b94245ce09c42b:latest
Deleted: sha256:63a4ecd2677f62197f547b1cef9041e3f3ad5c929b1dcd139610b106862a92b5
Deleted: sha256:6a30b775f40f687d0ed98fe9d5fdd2da60ce22753b066db599e4308303f16c13
Deleted: sha256:d94c0213bc68b7ee8dfa942c82c07cf8d8b3e7a4d68cf5ca79d372557e5f6567
Deleted: sha256:c720d5e3b8c3b5690da0b10588517592ca11d94b3427cf75e88be0e000352ec9

部署网络步骤详解

准备工作

  1. 以下用到的工具都在fabric-samples/bin目录下,手动执行记得把该目录添加到PATH。
  2. 这些目录下的工具必须是跟当前fabirc项目是版本匹配的,最好把fabric编译生成的工具fabric/build/bin/*,拷贝到fabric-samples/bin目录。
  3. 执行./byfn.sh down清理掉之前启动的数据,不然可能造成错误。

生成证书

byfn.sh中能找到生成证书的命令,手动执行命令可以生成证书,生成的证书在crypto-config目录下。

crypto-config.yaml是证书的配置文件。ordererOrganizations是系统通道组织,peerOrganizations是应用通道组织,包含2个组织:org1.example.comorg2.example.com

各组织的子目录是:证书、MSP、私钥、TLS证书、以及组织下的用户,用户目录会包含所有用户,以及用户的各种证书。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
➜  first-network git:(release-1.4) cryptogen generate --config=./crypto-config.yaml
org1.example.com
org2.example.com
➜ first-network git:(release-1.4) tree crypto-config -L 3
crypto-config
├── ordererOrganizations
│   └── example.com
│   ├── ca
│   ├── msp
│   ├── orderers
│   ├── tlsca
│   └── users
└── peerOrganizations
├── org1.example.com
│   ├── ca
│   ├── msp
│   ├── peers
│   ├── tlsca
│   └── users
└── org2.example.com
├── ca
├── msp
├── peers
├── tlsca
└── users

20 directories, 0 files

生成创世块

系统通道保存的链是系统链,链上的区块都是配置信息,它的第一个区块,被称为创世块genesis.block,用来初始化系统链。

生成创世块的工具是configtxgen,会自动在执行目录下寻找configtx.yaml文件,该文件包含了网络的初始配置,使用-profile指定系统链的配置TwoOrgsOrdererGenesis,该变量定义在configtx.yaml中。

使用-outputBlock指定输出的创世块文件。

命令:

1
configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsOrdererGenesis -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-29 07:17:02.140 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:17:02.229 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: solo
2019-07-29 07:17:02.229 UTC [common.tools.configtxgen.localconfig] Load -> INFO 003 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:17:02.311 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 004 orderer type: solo
2019-07-29 07:17:02.311 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 005 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:17:02.313 UTC [common.tools.configtxgen] doOutputBlock -> INFO 006 Generating genesis block
2019-07-29 07:17:02.313 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Writing genesis block
➜ first-network git:(release-1.4) ls channel-artifacts
genesis.block

以上命令是采用Solo共识算法创世块,如果使用Raft需要使用-profile SampleMultiNodeEtcdRaft选项:

命令:

1
configtxgen -profile SampleMultiNodeEtcdRaft -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block

记录:

1
2
3
4
5
6
7
8
9
➜  first-network git:(release-1.4) configtxgen -profile SampleMultiNodeEtcdRaft -channelID byfn-sys-channel -outputBlock ./channel-artifacts/genesis.block
2019-07-29 08:44:18.348 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 002 orderer type: etcdraft
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 Orderer.EtcdRaft.Options unset, setting to tick_interval:"500ms" election_tick:10 heartbeat_tick:1 max_inflight_blocks:5 snapshot_interval_size:20971520
2019-07-29 08:44:18.444 UTC [common.tools.configtxgen.localconfig] Load -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 08:44:18.552 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 005 orderer type: solo
2019-07-29 08:44:18.553 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 006 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 08:44:18.558 UTC [common.tools.configtxgen] doOutputBlock -> INFO 007 Generating genesis block
2019-07-29 08:44:18.559 UTC [common.tools.configtxgen] doOutputBlock -> INFO 008 Writing genesis block

生成创建应用通道的交易

网络启动后,只有1个系统通道,应用通道需要通过交易生成,这个交易(channel.tx)需要使用configtxgen工具创建,具体命令如下,-outputCreateChannelTx说明了是要生成创建应用通道的交易。

命令:

1
configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputCreateChannelTx ./channel-artifacts/channel.tx -channelID mychannel
2019-07-29 07:26:03.976 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:26:04.072 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:26:04.169 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 005 Generating new channel configtx
2019-07-29 07:26:04.172 UTC [common.tools.configtxgen] doOutputChannelCreateTx -> INFO 006 Writing new channel tx
➜ first-network git:(release-1.4)
➜ first-network git:(release-1.4) ls channel-artifacts
channel.tx genesis.block

生成更新组织1的锚节点交易

组织节点加入到应用通道后,需要更新系统配置,把组织1的锚节点写入到配置块,这个也需要通过1笔交易完成。工具依然是configtxgen-outputAnchorPeersUpdate表明了这是生成更新组织锚节点配置交易的操作。

命令:

1
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP

记录:

1
2
3
4
5
6
7
8
9
10
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org1MSPanchors.tx -channelID mychannel -asOrg Org1MSP
2019-07-29 07:30:26.456 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:30:26.557 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:30:26.640 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-29 07:30:26.641 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
➜ first-network git:(release-1.4)
➜ first-network git:(release-1.4) ls channel-artifacts
channel.tx genesis.block Org1MSPanchors.tx

生成更新组织2的锚节点交易

与上面类似,这是生成组织2锚节点配置的交易。

命令:

1
configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP

记录:

1
2
3
4
5
6
7
8
9
➜  first-network git:(release-1.4) configtxgen -profile TwoOrgsChannel -outputAnchorPeersUpdate ./channel-artifacts/Org2MSPanchors.tx -channelID mychannel -asOrg Org2MSP
2019-07-29 07:32:45.446 UTC [common.tools.configtxgen] main -> INFO 001 Loading configuration
2019-07-29 07:32:45.567 UTC [common.tools.configtxgen.localconfig] Load -> INFO 002 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen.localconfig] completeInitialization -> INFO 003 orderer type: solo
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen.localconfig] LoadTopLevel -> INFO 004 Loaded configuration: /home/centos/go/src/github.com/hyperledger/fabric-samples/first-network/configtx.yaml
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 005 Generating anchor peer update
2019-07-29 07:32:45.662 UTC [common.tools.configtxgen] doOutputAnchorPeersUpdate -> INFO 006 Writing anchor peer update
➜ first-network git:(release-1.4) ls ./channel-artifacts/
channel.tx genesis.block Org1MSPanchors.tx Org2MSPanchors.tx

启动网络

启动网络涉及到docker容器的创建与启动,这部分不挨个手动执行,使用byfn.sh完成,这样可以完成fabric网络的启动,系统通道的启动也在这个阶段完成。

byfn.sh利用scripts/script.sh完成的从创建应用通道到调研合约、查询合约的过程,这部分继续手动执行,需要注释掉byfn.sh中的下面这行:

1
docker exec cli scripts/script.sh $CHANNEL_NAME $CLI_DELAY $LANGUAGE $CLI_TIMEOUT $VERBOSE $NO_CHAINCODE

然后执行:

1
./byfn.sh up

这样启动的是solo共识算法的网络,我启动的是raft共识的网络:

1
./byfn.sh up -o etcdraft

如果是solo,启动起来的容器与快速启动你的第一个Fabric网络中的类似,只不过缺少3个链码容器。如果是raft应当是下面这样:

  • 5个orderer节点,
  • 4个peer节点
  • 1个cli客户端
1
2
3
4
5
6
7
8
9
10
11
12
➜  first-network git:(r1.4-raft) ✗ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fc0891e02afd hyperledger/fabric-tools:latest "/bin/bash" 26 seconds ago Up 25 seconds cli
9363a51d3f68 hyperledger/fabric-orderer:latest "orderer" 29 seconds ago Up 25 seconds 0.0.0.0:10050->7050/tcp orderer4.example.com
7d9c13f964a5 hyperledger/fabric-orderer:latest "orderer" 29 seconds ago Up 25 seconds 0.0.0.0:7050->7050/tcp orderer.example.com
e16a90f3f3fc hyperledger/fabric-peer:latest "peer node start" 29 seconds ago Up 25 seconds 0.0.0.0:7051->7051/tcp peer0.org1.example.com
4c7776287dc7 hyperledger/fabric-peer:latest "peer node start" 29 seconds ago Up 27 seconds 0.0.0.0:8051->8051/tcp peer1.org1.example.com
aaeab5fdb418 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 27 seconds 0.0.0.0:11050->7050/tcp orderer5.example.com
817a3ec7dd9d hyperledger/fabric-peer:latest "peer node start" 30 seconds ago Up 27 seconds 0.0.0.0:10051->10051/tcp peer1.org2.example.com
26524f34f654 hyperledger/fabric-peer:latest "peer node start" 30 seconds ago Up 27 seconds 0.0.0.0:9051->9051/tcp peer0.org2.example.com
2485874c48d1 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 27 seconds 0.0.0.0:8050->7050/tcp orderer2.example.com
5a8142d00432 hyperledger/fabric-orderer:latest "orderer" 30 seconds ago Up 28 seconds 0.0.0.0:9050->7050/tcp orderer3.example.com

创建应用通道

连接cli发送创建mychannel的交易。

命令:

1
peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
➜  first-network git:(r1.4-raft) ✗ docker exec -it cli  /bin/bash
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto scripts
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls channel-artifacts/
Org1MSPanchors.tx Org2MSPanchors.tx channel.tx genesis.block
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel create -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/channel.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-29 11:25:57.987 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 11:25:58.026 UTC [cli.common] readBlock -> INFO 002 Got status: &{NOT_FOUND}
2019-07-29 11:25:58.029 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized
2019-07-29 11:25:58.231 UTC [cli.common] readBlock -> INFO 004 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.234 UTC [channelCmd] InitCmdFactory -> INFO 005 Endorser and orderer connections initialized
2019-07-29 11:25:58.436 UTC [cli.common] readBlock -> INFO 006 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.441 UTC [channelCmd] InitCmdFactory -> INFO 007 Endorser and orderer connections initialized
2019-07-29 11:25:58.643 UTC [cli.common] readBlock -> INFO 008 Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.646 UTC [channelCmd] InitCmdFactory -> INFO 009 Endorser and orderer connections initialized
2019-07-29 11:25:58.848 UTC [cli.common] readBlock -> INFO 00a Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:58.852 UTC [channelCmd] InitCmdFactory -> INFO 00b Endorser and orderer connections initialized
2019-07-29 11:25:59.053 UTC [cli.common] readBlock -> INFO 00c Got status: &{SERVICE_UNAVAILABLE}
2019-07-29 11:25:59.056 UTC [channelCmd] InitCmdFactory -> INFO 00d Endorser and orderer connections initialized
2019-07-29 11:25:59.260 UTC [cli.common] readBlock -> INFO 00e Received block: 0
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

确认应用通道创建成功

连接orderer查看mychannel是否创建,可以看到已经存在mychannel目录,证明mychannel已创建。

命令:

1
ls /var/hyperledger/production/orderer/chains/mychannel/

记录:

1
2
3
4
➜  ~ docker exec -it orderer.example.com bash
root@7d9c13f964a5:/opt/gopath/src/github.com/hyperledger/fabric# ls
root@7d9c13f964a5:/opt/gopath/src/github.com/hyperledger/fabric# ls /var/hyperledger/production/orderer/chains/mychannel/
blockfile_000000

加入应用通道

从cli上可以发起peer0.org1加入mychannel的交易请求,方法是通过让peer channel join读取环境变量信息,环境变量决定了当前为哪个peer进行处理。

CORE_PEER*相关的环境变量,代表了所有和peer相关的配置信息:

1
2
3
4
5
6
7
8
9
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# env | grep "CORE_PEER"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_TLS_KEY_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.key
CORE_PEER_LOCALMSPID=Org1MSP
CORE_PEER_TLS_CERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/server.crt
CORE_PEER_TLS_ENABLED=true
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ID=cli
CORE_PEER_ADDRESS=peer0.org1.example.com:7051

切换peer时,只需修改以下4个环境变量,它们代表当前是哪个peer,以及peer的配置,其他环境变量通用:

  • CORE_PEER_LOCALMSPID
  • CORE_PEER_TLS_ROOTCERT_FILE
  • CORE_PEER_MSPCONFIGPATH
  • CORE_PEER_ADDRESS,最易分辨当前是在为哪个peer操作,比如默认是peer0.org1

以下为加入mychannel和列出当前peer(peer0.org1)所加入的通道:

命令:

1
2
peer channel join -b mychannel.block
peer channel list

记录:

1
2
3
4
5
6
7
8
9
10
11
12
➜  first-network git:(r1.4-raft) ✗ docker exec -it cli  /bin/bash
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls
channel-artifacts crypto mychannel.block scripts
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 11:38:30.638 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 11:38:30.719 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 11:45:29.692 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel

也可以登录peer0.org1去查看peer加入的通道:

1
2
3
4
5
➜  ~ docker exec -it peer0.org1.example.com bash
root@e16a90f3f3fc:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 11:59:51.415 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel

没有命令能够查看某个通道所有的peer。

peer0.org1重复加入会发起proposal失败:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 11:58:25.746 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Error: proposal failed (err: bad proposal response 500: cannot create ledger from genesis block: LedgerID already exists)

修改环境变量的规则见fabric-samples/first-network/scripts/utils.sh中的setGloabls函数。以下是peer1.org0加入mychanel:

1
2
3
4
5
6
7
8
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=peer1.org1.example.com:8051
# 执行加入通道
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-29 12:01:20.752 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 12:01:20.832 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel

确认peer加入的应用通道和数据

登录peer1.org1确认已经加入mychanel,以及同步到了mychannel上的链数据:

1
2
3
4
5
6
7
➜  ~ docker exec -it peer1.org1.example.com bash
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-29 12:02:48.662 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls /var/hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000
/var/hyperledger/production/ledgersData/chains/chains/mychannel/blockfile_000000

更新锚节点配置

使用cli更新peer1.org1的锚节点配置:

因为上一步操作环境变量已经修改成peer1.org1的,所以,就直接让peer1.org1做锚节点好了。

命令:

1
peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem

记录:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org1MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-29 12:05:12.585 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-29 12:05:12.609 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update

安装链码

使用cli为peer1.org1安装链码,安装链码是把链码源码打包和拷贝到peer节点的文件系统上,具体见快速入门Fabric核心概念和框架:安装链码

安装命令为peer channel isntall,包含了以下参数:

  • -n mycc:链码名称
  • -v 1.0:链码版本
  • -l golang:链码语言
  • -p github.com/chaincode/chaincode_example02/go/:本地链码代码所在路径,从$GOPATH/src下开始搜索。

更多选项见peer chaincode install -h

命令:

1
peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/

记录:

1
2
3
4
5
6
7
8
# 查看链码源码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls ../../../../github.com/chaincode/chaincode_example02/go/
chaincode_example02.go
# 安装链码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
2019-07-30 02:09:10.516 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 02:09:10.516 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-30 02:09:10.800 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >

查看安装的链码

2种方式。

1、在cli上执行查询:

命令:

1
peer chaincode list --installed

记录:

1
2
3
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode list --installed
Get installed chaincodes on peer:
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Id: 476fca1a949274001971f1ec2836cb09321f0b71268b3762d68931c93f218134

2、登录到peer1.org1上查询链码文件:

命令:

1
ls /var/hyperledger/production/chaincodes

记录:

1
2
root@4c7776287dc7:/opt/gopath/src/github.com/hyperledger/fabric/peer# ls /var/hyperledger/production/chaincodes/mycc.1.0
/var/hyperledger/production/chaincodes/mycc.1.0

链码安装过程

也可以查看peer1.org1安装链码的日志:

1
2
3
4
5
6
7
8
9
➜  ~ docker logs peer1.org1.example.com
...省略老日志
2019-07-30 02:09:10.796 UTC [endorser] callChaincode -> INFO 04b [][876f7f14] Entry chaincode: name:"lscc"
2019-07-30 02:09:10.799 UTC [lscc] executeInstall -> INFO 04c Installed Chaincode [mycc] Version [1.0] to peer
2019-07-30 02:09:10.799 UTC [endorser] callChaincode -> INFO 04d [][876f7f14] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:09:10.799 UTC [comm.grpc.server] 1 -> INFO 04e unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35232 grpc.code=OK grpc.call_duration=3.276308ms
2019-07-30 02:10:51.409 UTC [endorser] callChaincode -> INFO 04f [][9db45293] Entry chaincode: name:"lscc"
2019-07-30 02:10:51.411 UTC [endorser] callChaincode -> INFO 050 [][9db45293] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:10:51.411 UTC [comm.grpc.server] 1 -> INFO 051 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35240 grpc.code=OK grpc.call_duration=2.667734ms

部署链码

实例化链码也成部署链码,鉴于少1个字,下文使用部署链码。

通过cli部署链码,由于之前设置的环境变量,部署等价于由peer1.org1触发。

在一个通道上,链码只需部署1次,所以无需每个peer都去部署链码。部署链码实际是一笔部署交易,该交易的结果就是部署链码容器。

部署命令是peer chaincode instantiate,需要使用以下标记:

  • -o orderer.example.com:7050:指定要连接的orderer节点
  • --tls true:开启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:使用的CA证书文件,注意使用的是orderer的证书
  • -C mychannel:在哪个通道上部署
  • -n mycc:链码名称
  • -l golang:链码语言
  • -v 1.0:链码版本
  • -c '{"Args":["init","a","100","b","200"]}':链码的构建过程消息,JSON格式,调用init函数,设置a和b的值
  • -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')':指定背书策略,必须由org1和org2同时背书

命令:

1
peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'

部署和快速查询如下:

1
2
3
4
5
6
7
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -l golang -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P 'AND ('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'
2019-07-30 02:18:57.119 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 02:18:57.119 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode list --instantiated -C "mychannel"
Get instantiated chaincodes on channel mychannel:
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Escc: escc, Vscc: vscc

查看部署的链码

链码部署后,会启动链码容器,可以查看:

1
2
3
➜  ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8bab025e153e dev-peer1.org1.example.com-mycc-1.0-cd123150154e6bf2df7ce682e0b1bcbea40499416f37a6da3aae14c4eb51b08d "chaincode -peer.add…" 11 minutes ago Up 11 minutes dev-peer1.org1.example.com-mycc-1.0

查看链码容器日志:

1
2
3
➜  ~ docker logs dev-peer1.org1.example.com-mycc-1.0
ex02 Init
Aval = 100, Bval = 200

部署链码的过程

可以通过peer1.org1的日志查看:

  1. 背书
  2. 生成Docker构建镜像:GenerateDockerBuild
  3. 接收到区块2(前面一步必然被orderer打包了)
  4. 部署链码:HandleStateUpdates
  5. 提交区块2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2019-07-30 02:17:46.522 UTC [endorser] callChaincode -> INFO 052 [mychannel][1c8a3567] Entry chaincode: name:"lscc"
2019-07-30 02:17:46.526 UTC [endorser] callChaincode -> INFO 053 [mychannel][1c8a3567] Exit chaincode: name:"lscc" (3ms)
2019-07-30 02:17:46.527 UTC [comm.grpc.server] 1 -> INFO 054 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35248 grpc.code=OK grpc.call_duration=6.246381ms
2019-07-30 02:18:57.122 UTC [endorser] callChaincode -> INFO 055 [mychannel][5059d46e] Entry chaincode: name:"lscc"
2019-07-30 02:18:57.141 UTC [chaincode.platform.golang] GenerateDockerBuild -> INFO 056 building chaincode with ldflagsOpt: '-ldflags "-linkmode external -extldflags '-static'"'
2019-07-30 02:19:12.363 UTC [endorser] callChaincode -> INFO 057 [mychannel][5059d46e] Exit chaincode: name:"lscc" (15240ms)
2019-07-30 02:19:12.363 UTC [comm.grpc.server] 1 -> INFO 058 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35252 grpc.code=OK grpc.call_duration=15.242059303s
2019-07-30 02:19:14.462 UTC [gossip.privdata] StoreBlock -> INFO 059 [mychannel] Received block [2] from buffer
2019-07-30 02:19:14.464 UTC [committer.txvalidator] Validate -> INFO 05a [mychannel] Validated block [2] in 2ms
2019-07-30 02:19:14.464 UTC [cceventmgmt] HandleStateUpdates -> INFO 05b Channel [mychannel]: Handling deploy or update of chaincode [mycc]
2019-07-30 02:19:14.503 UTC [kvledger] CommitWithPvtData -> INFO 05c [mychannel] Committed block [2] with 1 transaction(s) in 38ms (state_validation=0ms block_and_pvtdata_commit=16ms state_commit=6ms)
2019-07-30 02:20:41.569 UTC [endorser] callChaincode -> INFO 05d [mychannel][3d51a2d1] Entry chaincode: name:"lscc"
2019-07-30 02:20:41.571 UTC [endorser] callChaincode -> INFO 05e [mychannel][3d51a2d1] Exit chaincode: name:"lscc" (2ms)
2019-07-30 02:20:41.571 UTC [comm.grpc.server] 1 -> INFO 05f unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:35260 grpc.code=OK grpc.call_duration=2.998184ms

查询链码

链码查询的命令是peer chaincode query,需要指定通道以及链码,最后是调用参数。

如果key存在可以查询到正确的值,如果key不存在,查询结果提示Error。

命令:

1
peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'

记录:

1
2
3
4
5
6
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
100
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
200
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","c"]}'
Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Nil amount for c\"}"

1次查询操作,链码容器的日志会是这样,可以看到触发Invoke,然后是Query Response,并不是真正去Invoke:

1
2
ex02 Invoke
Query Response:{"Name":"a","Amount":"100"}

调用链码

调用链码是一笔调用交易,需要:

  • 指定orderer节点
  • 使用TLS、指定orderer的CA证书
  • 指定哪个通道的,哪个链码
  • 通过peerAddresses指定背书的peer,以及要使用的证书

只有当背书策略满足要求时,调用交易才会判断为有效,然后修改链码容器内的数据。

不满足背书策略的调用交易

以下为无效的调用交易,因为只指定了peer1.org1进行背书,而mycc的背书策略要求org1和org2的2个peer同时背书才行。

命令:

1
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --peerAddresses peer1.org1.example.com:8051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt  -c '{"Args":["invoke","a","b","10"]}'

-c '{"Args":["invoke","a","b","10"]}'指调用链码的invoke函数,参数为a, b和10,含义是对把a和b的值分别修改为a-10,b+10,具体操作可以见源码:
fabric-samples/chaincode/chaincode_example02/go/chaincode_example02.go。

链码容器日志如下,可以看到触发了Invoke,值进行了变更。

1
2
ex02 Invoke
Aval = 90, Bval = 210

通过查询命令可以发现链码数据并未改变,因为交易proposal不满足背书策略,cli不会发起交易给orderer,数据不会提交。

1
2
3
4
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
100
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
200

有效的调用交易

之前安装链码时,只在peer1.org1进行了安装,但背书要求org2的节点也要进行背书,背书节点需要已经安装链码,否则无法进行背书,会返回错误的背书响应:

1
Error: endorsement failure during invoke. response: status:500 message:"cannot retrieve package for chaincode mycc/1.0, error open /var/hyperledger/production/chaincodes/mycc.1.0: no such file or directory"

本文选择peer1.org2,先让它加入mychannel,然后安装链码,详细过程见加入应用通道安装链码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 设置环境变量
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_LOCALMSPID="Org2MSP"
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# CORE_PEER_ADDRESS=peer1.org2.example.com:10051
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 加入mychannel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel join -b mychannel.block
2019-07-30 03:45:39.637 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-30 03:45:39.731 UTC [channelCmd] executeJoin -> INFO 002 Successfully submitted proposal to join channel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2019-07-30 03:46:09.910 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 设置org2的锚节点
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel update -o orderer.example.com:7050 -c mychannel -f ./channel-artifacts/Org2MSPanchors.tx --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
2019-07-30 03:47:27.412 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
2019-07-30 03:47:27.447 UTC [channelCmd] update -> INFO 002 Successfully submitted channel update
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer#

# 安装链码
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n mycc -v 1.0 -l golang -p github.com/chaincode/chaincode_example02/go/
2019-07-30 03:48:18.903 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2019-07-30 03:48:18.903 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2019-07-30 03:48:19.180 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >

之前已经实例化过1链码,所以无需再次实例化。

接下来重新执行调用链码,并且制定2个背书节点,分别是peer1.org1和peer1.org2。背书启用了TLS,需要在--peerAddresses后面,使用--tlsRootCertFiles指定对应peer的证书文件,使用--cafile指定orderer的证书文件。

命令:

1
2
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc --peerAddresses peer1.org1.example.com:8051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer1.org1.example.com/tls/ca.crt --peerAddresses peer1.org2.example.com:10051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer1.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
2019-07-30 03:52:53.880 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

可以通过命令查询到,链码中的数据已经更新到最新,说明调用链码成功。

1
2
3
4
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","a"]}'
90
root@fc0891e02afd:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode query -C mychannel -n mycc -c '{"Args":["query","b"]}'
210

链码容器的日志可以看到Invoke的日志:

1
2
ex02 Invoke
Aval = 90, Bval = 210

链码FAQ

执行完上面的操作,你是否有这2个疑问:

  1. 有2个背书节点,不应该2个背书节点都Invoke吗,为什么链码容器日志只看到1次Invoke?
  2. 背书节点Invoke链码容器时,链码容器的数据并不会提交,链码容器里的数据是什么时候更新的?

疑问1解答

有2个背书节点,不应该2个背书节点都Invoke吗,为什么链码容器日志只看到1次Invoke?

每个组织进行背书,都必须部署自己的链码容器,背书时通过gRPC和自己组织的链码容器交互,所以上面查看链码容器dev-peer1.org1.example.com-mycc-1.0日志的时候,只看到1次Invoke,另外1次,在org2的链码容器dev-peer1.org2.example.com-mycc-1.0日志里。

有效的调用交易这一节,我们只安装了链码,因为在某个通道内,链码只需部署1次,并且之前peer1.org1已经发送了部署交易,所以无需再发送部署交易。

1
2
3
4
➜  ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0700ebc80246 dev-peer1.org2.example.com-mycc-1.0-26c2ef32838554aac4f7ad6f100aca865e87959c9a126e86d764c8d01f8346ab "chaincode -peer.add…" 3 hours ago Up 3 hours dev-peer1.org2.example.com-mycc-1.0
8bab025e153e dev-peer1.org1.example.com-mycc-1.0-cd123150154e6bf2df7ce682e0b1bcbea40499416f37a6da3aae14c4eb51b08d "chaincode -peer.add…" 4 hours ago Up 4 hours dev-peer1.org1.example.com-mycc-1.0

这里有一点需要注意,当org2的背书节点进行背书时,发现没有链码容器,会自动创建,而不是安装链码后,即可主动部署链码容器,可以从peer1.org2的日志确认以上流程。

1
2
3
4
5
6
7
8
9
10
11
# 安装链码
2019-07-30 03:48:19.177 UTC [endorser] callChaincode -> INFO 063 [][353ca5ab] Entry chaincode: name:"lscc"
2019-07-30 03:48:19.179 UTC [lscc] executeInstall -> INFO 064 Installed Chaincode [mycc] Version [1.0] to peer
2019-07-30 03:48:19.179 UTC [endorser] callChaincode -> INFO 065 [][353ca5ab] Exit chaincode: name:"lscc" (2ms)
2019-07-30 03:48:19.179 UTC [comm.grpc.server] 1 -> INFO 066 unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:38546 grpc.code=OK grpc.call_duration=2.758068ms

# 12s后才部署链码容器
2019-07-30 03:52:37.932 UTC [endorser] callChaincode -> INFO 067 [mychannel][6b87148b] Entry chaincode: name:"mycc"
2019-07-30 03:52:37.948 UTC [chaincode.platform.golang] GenerateDockerBuild -> INFO 068 building chaincode with ldflagsOpt: '-ldflags "-linkmode external -extldflags '-static'"'
2019-07-30 03:52:53.875 UTC [endorser] callChaincode -> INFO 069 [mychannel][6b87148b] Exit chaincode: name:"mycc" (15943ms)
2019-07-30 03:52:53.876 UTC [comm.grpc.server] 1 -> INFO 06a unary call completed grpc.service=protos.Endorser grpc.method=ProcessProposal grpc.peer_address=172.28.0.11:38984 grpc.code=OK grpc.call_duration=15.945466965s

疑问2解答

背书节点Invoke链码容器时,链码容器的数据并不会提交,链码容器里的数据是什么时候更新的?

链码容器只是负责执行调用,不负责存储数据,所以不存在链码容器数据何时更新的问题。

下面是调用链码的流程图,可以看到链码执行时,是通过Shim从fabric账本读取数据的,然后把执行结果返回。如果交易被peer节点验证有效,调用交易的结果会写入当fabric账本,如果无效,不会改变fabric账本,所以你现在是否理解不满足背书策略的调用交易不会改变数据,而有效的调用交易会改变数据。

启动自定义网络

利用byfn启动自定义网络

./byfn.sh不止updown2个命令,还有其他命令,以及更多的参数,比如restart, generate和upgrade。

在上一节,使用了完全默认的参数,启动了网络,这是完全傻瓜式的。基于对fabric的掌握,还可以设置更多的参数,比如使用参数可以指定channel名称,而不是使用默认的mychannel,可以设置超时时间,使用指定docker编排文件创建各容器,指定chaincode的语言是Go还是Java等,还有更多的参数自己探索吧,设置一些参数重新启动网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
➜  first-network git:(release-1.4) ./byfn.sh help
Usage:
byfn.sh <mode> [-c <channel name>] [-t <timeout>] [-d <delay>] [-f <docker-compose-file>] [-s <dbtype>] [-l <language>] [-o <consensus-type>] [-i <imagetag>] [-a] [-n] [-v]
<mode> - one of 'up', 'down', 'restart', 'generate' or 'upgrade'
- 'up' - bring up the network with docker-compose up
- 'down' - clear the network with docker-compose down
- 'restart' - restart the network
- 'generate' - generate required certificates and genesis block
- 'upgrade' - upgrade the network from version 1.3.x to 1.4.0
-c <channel name> - channel name to use (defaults to "mychannel")
-t <timeout> - CLI timeout duration in seconds (defaults to 10)
-d <delay> - delay duration in seconds (defaults to 3)
-f <docker-compose-file> - specify which docker-compose file use (defaults to docker-compose-cli.yaml)
-s <dbtype> - the database backend to use: goleveldb (default) or couchdb
-l <language> - the chaincode language: golang (default) or node
-o <consensus-type> - the consensus-type of the ordering service: solo (default), kafka, or etcdraft
-i <imagetag> - the tag to be used to launch the network (defaults to "latest")
-a - launch certificate authorities (no certificate authorities are launched by default)
-n - do not deploy chaincode (abstore chaincode is deployed by default)
-v - verbose mode
byfn.sh -h (print this message)

Typically, one would first generate the required certificates and
genesis block, then bring up the network. e.g.:

byfn.sh generate -c mychannel
byfn.sh up -c mychannel -s couchdb
byfn.sh up -c mychannel -s couchdb -i 1.4.0
byfn.sh up -l node
byfn.sh down -c mychannel
byfn.sh upgrade -c mychannel

Taking all defaults:
byfn.sh generate
byfn.sh up
byfn.sh down

总结

通过这篇文章能够掌握部署一个Fabric网络需哪些步骤,以及各步骤需要做哪些工作。但这篇文章,缺少了关于docker配置各容器的部分,后面会单独一篇文章介绍。

声明

这是一篇信息整合的文章,80%的内容来自Fabric官方文档和网络文章,在此基础上整理和修改,剩下20%为操作记录。

官方文档资料链接

剩下的官方文档链接都加入到了下面的笔记中

网络文章

天山老妖S:HyperLeger Fabric SDK开发系列文章

陶辉:区块链开源实现hyperledger fabric架构详解

Hyperledger Fabric 开发系列文章

笔记摘录

架构

HyperLeger Fabric架构„

Fabric网络是通过组织(organization)来划分的,每个组织内都包含承担不同功能的Peer 节点,每个Peer节点又可以担任多种角色。所有的组织共用一个统一的Orderer排序服务集群。基于Hyperledger Fabric区块链网络的设计时需要考虑组织之间的业务关系以及内部每个模块之间的联系,统一进行规划。

每个组织通常拥有自己的客户端、Peer节点和CA节点,并且可以根据需要创建一个或多个不同的类型节点。Orderer节点不属于某个组织的实体,属于组织共同维护的节点。

排序节点

orderer负责排序和打包区块。排序服务节点只是决定交易处理的顺序,并不对交易的合法性进行校验,也无需去管之前的交易是否合法,也不负责维护账本信息,只有记账节点才有账本写入权限。peer验证交易后会给交易打上交易是否合法。

排序服务节点接收包含背书签名的交易,对未打包的交易进行排序生成区块,广播给Peer节点中的主节点。排序服务提供的是原子广播,保证同一个链上的节点接收到相同的消息,并且有相同的逻辑顺序。排序服务独立于Peer进程存在并且以先来先服务的方式对Fabric网络上的所有通道进行排序交易。

Tx排序的依据是什么?根据每个通道按时间顺序调用,创建每个通道的交易区块。

出块的依据是什么?交易数、时间?都可以,具体见orderer排序交易

peer节点角色

peer文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/peers/peers.html

peer节点负责背书和验证交易,以及Org之间的通信。

每个Org可以有多个Peer,每个Peer节点都是记账节点,并且可担任多种角色:

  • Endorser Peer(背书节点)
  • Leading Peer(主节点)
  • Committer Peer(记账节点)
  • Anchor Peer(锚节点)

背书节点(Endorser Peer)

部分Peer节点会执行交易并对结果进行签名背书。背书节点是动态的角色,是与具体链码绑定的,由链码的背书策略指定。

只有在应用程序向节点发起交易背书请求时才成为背书节点,其它时候是普通的记账节点,只负责验证交易并记账。

主节点(Leading Peer)

主节点负责和Orderer排序服务节点通信,从排序服务节点处获取最新的区块,并把区块分发到本channel内同组织的其他节点。可以使用配置文件强制设置,也可以选举产生。

注意:主节点不是指Raft中的leader,是指org中的主节点(leading peer)。

记账节点(Committer Peer)

负责验证区块里的交易,然后将区块提交(写入/追加)到其通道账本的副本。记账节点还将每个块中的每个交易标记为有效或无效,通过验证的为有效,否则为无效。

锚节点(Anchor Peer)

在一个通道上可以被所有其它Peer节点发现的Peer节点,通道上的每个组织都有一个或多个锚节点,多个锚节点可以用来防止单点故障,通过锚节点实现不同组织的Peer节点发现通道上的所有组织的锚节点。

举个例子,peer0.org1是org1的锚节点,它连接peer0.org2时,如果peer0.org2知道org3的锚节点peer0.org3,那么会告诉peer0.org1,org3的锚节点是peer0.org3,以后peer0.org1就可以直接和peer0.org3通信了。

客户端

2种:1. CLI,2. SDK.

SDK

Fabric提供了三种语言版本的SDK,分别如下:

  1. Fabric Nodejs SDK
  2. Fabric Java SDK
  3. Fabric Go SDK

Fabric区块链应用可以通过SDK访问Fabric区块链网络中的多种资源,包括账本、交易、链码、事件、权限管理等。应用程序代表用户与Fabric区块链网络进行交互,Fabric SDK API提供了如下功能:

  1. 创建通道
  2. 将peer节点加入通道
  3. 在peer节点安装链码
  4. 在通道实例化链码
  5. 通过链码调用交易
  6. 查询交易或区块的账本

链码

链码即智能合约,链码分系统链码和用户链码,在没有特殊强调的时候,链码就是指用户链码。

链码操作包含3个基本操作:安装(install)、实例化(instantiation)和调用(Invoke),以及其他操作比如打包、签名。

用户链码被编译成一个独立的应用程序,运行于隔离的Docker容器中,在链码部署的时候会自动生成链码的Docker镜像,链码容器通过gRPC协议与相应的Peer节点进行交互,以操作分布式账本中的数据。

一个channel内链码只需实例化1次,所有运行链码的节点都需要安装该链码,如果只需要验证交易,并不需要安装链码,因为实例化后,可以通过gRPC通信与链码容器交互验证链码。

背书策略(Endorser Policy)

背书策略是背书节点如何决策交易是否合法的条件。链码实例化时可指定背书策略,当记账节点接收到交易时,会获知相关链码信息,然后检查链码的背书策略,判断交易是否满足背书策略,若满足则标注交易为合法。

背书策略可分为主体Principal(P)和阈值Threshold(T)两部分,具体如下:

  • Principal指定由哪些成员进行背书。
  • Threshold接受两个输入,分别为阈值t和若干个P的集合n,只要交易中包含了n中t个成员的背书则认为交易合法。

背书策略可以指定某几个组织内的任意成员身份进行背书,或者要求至少有一个管理员身份进行背书等等。

  • T(1, ‘A’, ‘B’) 则需要A,B中任意成员背书。
  • T(1, ‘A’, T(2, ‘B’, ‘C’))则需要A成员背书或B,C成员同时背书。

目前客户端已经实现对背书策略的支持,可以通过-P来指定背书策略,结合AND、OR来组合成员,完成成员身份(admin、member)的集合。

-P OR ( 'Org1.admin' , AND ('Org2.member' , 'Org3.member') )

可以把OR、AND理解为函数,函数内为参数。

上述背书策略指定要么Org1的admin进行背书,或者Org2和Org3的成员同时进行背书,才满足背书策略。

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#endorsement-policy

系统链码

系统链码与用户链码有相同的编程模型,但系统链码运行在Peer节点,用户链码则在隔离的容器中运行。因此,系统链码内置为Peer节点的可执行文件中,不遵循用户链码的生命周期,安装、实例化、升级不适用于系统链码

系统链码用于减少Peer节点与用户链码进行gRPC通信的开销,同时权衡管理的灵活性。系统链码只能通过Peer节点的二进制文件升级,必须通过一组固定的参数进行注册,但不具有背书策略

Hyperledger Fabric系统链码实现了一系列系统功能,以便系统集成人员能够根据需求对其进行修改与替换。

常见系统链码如下:

  • 生命周期系统链码(LSCC ):负责对用户链码的生命周期进行管理。
  • 配置系统链码(CSCC):处理在Peer节点上的通道配置。
  • 查询系统链码(QSCC):提供账本的查询API,例如获取区块以及交易。
  • 背书系统链码(ESCC):背书过程的管理和配置。
  • 验证系统链码(VSCC):处理交易验证,包括检查背书策略以及多进程并发控制。

这样容易记:一共5个SCC,前2个与配置相关,后3个与操作相关,配置有生命周期和配置,操作有背书、验证和查询

在修改或者替换系统链码(LSCC、ESCC、VSCC)时必须注意,因为系统链码在主交易执行的路径中。VSCC在将区块提交至账本前,所有在通道的Peer节点会计算相同的验证以避免账本分歧(不确定性)。如果VSCC被改变或者替换,需要特别小心。

生命周期

通过install安装链码,通过instantiate实例化链码,然后可以通过invoke、query调用链码和查询链码。
如果需要升级链码,则需要先install安装新版本的链码,通过upgrade升级链码。在install安装链码前,可以通过package打包并签名生成打包文件,然后再通过install安装。

链码管理

peer chaincode -h可以列出链码的几种操作:

1
2
3
4
5
6
7
8
install     Package the specified chaincode into a deployment spec and save it on the peer's path.
instantiate Deploy the specified chaincode to the network.
invoke Invoke the specified chaincode.
list Get the instantiated chaincodes on a channel or installed chaincodes on a peer.
package Package the specified chaincode into a deployment spec.
query Query using the specified chaincode.
signpackage Sign the specified chaincode package
upgrade Upgrade chaincode.
  • 安装:把chaincode打包成部署规格,并且保存到peer的某个路径。

  • 实例化:部署chaincode到网络。

  • 调用:调用chaincode。
  • list:列出某个通道上已经实例化的chaincode或已安装到peer上的chaincode。
  • 打包:把chaincode打包成部署规格。
  • 查询:使用chaincode查询,即查询该查询该chaincode上的信息。
  • signpackage:对打包的chaincode签名。
  • 升级:升级已实例化的chaincode。

可以看到安装chaincode实际已经包含了打包的过程。

打包链码

链码包由三个部分组成:

  1. 由ChaincodeDeploymentSpec(CDS)格式定义的链码。CDS根据代码及其它属性(如名称与版本)定义链码包;
  2. 一个可选的实例化策略,能够被用作背书的策略进行描述
  3. 拥有链码的实体的一组签名。

其中,链码的签名主要目的如下:

  1. 建立链码的所有权;
  2. 允许验证链码包中的内容;
  3. 允许检测链码包是否被篡改。

通道上的链码的实例化交易的创建者能够被链码的实例化策略验证。

链码打包的方法由两种,一种是打包被多个所有者所拥有的链码,需要初始化创建一个被签名的链码包(SignedCDS),然后将其按顺序的传递给其它所有者进行签名;一种是打包单个所有者持有的链码。

创建一个被签名的链码包的命令如下:

1
peer chaincode package -n sacc -p chaincodedev/chaincode/sacc -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out
  • -s选项创建一个能被多个所有者签名的链码包,而不是简单的创建一个原始的CDS。一旦-s被指定,如果其它所有者想要签名CDS,则-S选项必须被指定。否则,将会创建一个SignedCDS,除CDS外仅仅包括实例化策略。
  • -S选项使用被在core.yaml文件中定义的localMspid属性的值标识的MSP对链码包进行签名。
  • -S选项是可选的。如果创建了一个没有签名的链码包,不能被其它所有者使用signpackage命令进行签名。
  • -i选项是可选的,允许为链码指定实例化策略。实例化策略与背书策略有相同的格式,指定哪些身份能够实例化链码。本例中仅OrgA的admin能够实例化链码。如果没有实例化策略被指定,将会使用默认的策略,仅允许拥有Peer的MSP的管理员身份实例化链码。

签名链码

在创建阶段就被签名的链码包能够交给其它所有者进行检查与签名,支持带外对链码进行签名。
ChaincodeDeploymentSpec可以选择由所有者集合进行签名,从而创建一个SignedChaincodeDeploymentSpec(SignedCDS)。SignedCDS包括3个部分:

  1. CDS包括源代码,链码的名称与版本号;

  2. 链码的实例化策略,表示为背书策略;

  3. 链码所有者的列表,由背书策略定义。

当在某些通道上实例化链码时,背书策略是在带外确定的,用于提供合适的MSP主体。如果没指定实例化策略,则默认的策略就是通道的任何MSP管理员。

每一个链码的所有者通过将SignedCDS与链码所有者的身份(例如证书)组合并签署组合结果来背书ChaincodeDeploymentSpec。

一个链码的所有者能够对自己所创建的签名过的链码包进行签名,需要使用如下命令:

1
peer chaincode signpackage ccpack.out signedccpack.out

ccpack.out、signedccpack.out分别是输入与输出包。signedccpack.out包括一个对链码包附加的签名(通过local msp进行的签名)。

安装链码

将链码的源代码打包成ChaincodeDeploymentSpec(CDS)的规定的格式,然后安装到通道中的背书节点上

当安装的链码包只包含一个ChaincodeDeploymentSpec时,将使用默认初始化策略并包括一个空的所有者列表。
链码应该仅仅被安装在链码所有者成员的背书节点上,用于实现链码对于网络中其它成员在逻辑上是隔离的。

安装链码会发送一个SignedProposal到生命周期系统链码(LSCC) ,也就是说会发送调用系统链码的交易。

使用CLI安装sacc链码的命令如下:

1
peer chaincode install -n sacc -v 1.0 -p sacc
  • -n选项指定链码实例名称
  • -v选项指定链码的版本
  • -p选项指定链码所在路径,必须在GOPATH路径下

CLI内部创建sacc的SignedChaincodeDeploymentSpec,然后将其发送给本地Peer节点,Peer节点会调用LSCC上的安装方法。为了在Peer节点上安装链码,SignedProposal的签名必须来自于Peer节点的本地MSP管理员之一

未安装 chaincode 的节点不能执行 chaincode,但仍可以验证交易并提交到账本中。所以背书节点必须按照链码,记账节点并非必须安装链码。

实例化链码

链码和通道是低耦合的:实例化调用生命周期系统链码(LSCC)用于创建及初始化通道上的链码。链码能够被绑定到任意数量的通道,以及在每个通道上单独的操作。无论链码安装及实例化到多少个通道上,每个通道的状态都是隔离的。

实例化的创建者必须满足包含在SignedCDS内链码的实例化策略,而且还必须是通道的写入器(作为通道创建的一部分被配置)。可以防止部署链码的流氓实体或者欺骗者在未被绑定的通道上执行链码。

默认的实例化策略是任意的通道MSP的管理员,因此链码实例化交易的创建者必须是通道管理员之一。当交易提案到达背书节点后,背书节点会根据实例化策略验证创建者的签名。在提交实例化交易到账本前,在交易验证时再一次完成该操作。

实例化交易同样设置了通道上的链码的背书策略 。背书策略描述了交易被通道上成员接受的认证要求。
使用CLI去实例化sacc的链码并初始化状态为user1与0,命令如下:

1
peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["user1","0"]}' -P "OR ('Org1.member','Org2.member')"
  • -n选项指定链码实例名称
  • -v选项指定链码的版本
  • -c 选项指定链码的调用参数
  • -P选项指定链码的背书策略

链码的背书策略表示,org1.member或者org2.member必须对调用使用sacc(这名字起的让我以为是某种SCC)的交易进行签名,以保障交易是有效的。在成功实例化后,通道的链码进入激活状态,可以处理任意的交易提案。交易到达背书节点时,会同时被处理。

实例化选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
root@5af5a3fb3bb7:/var/hyperledger/production# peer chaincode instantiate -h
Deploy the specified chaincode to the network.

Usage:
peer chaincode instantiate [flags]

Flags:
-C, --channelID string The channel on which this command should be executed
--collections-config string The fully qualified path to the collection JSON file including the file name
--connectionProfile string Connection profile that provides the necessary connection information for the network. Note: currently only supported for providing peer connection information
-c, --ctor string Constructor message for the chaincode in JSON format (default "{}")
-E, --escc string The name of the endorsement system chaincode to be used for this chaincode
-h, --help help for instantiate
-l, --lang string Language the chaincode is written in (default "golang")
-n, --name string Name of the chaincode
--peerAddresses stringArray The addresses of the peers to connect to
-P, --policy string The endorsement policy associated to this chaincode
--tlsRootCertFiles stringArray If TLS is enabled, the paths to the TLS root cert files of the peers to connect to. The order and number of certs specified should match the --peerAddresses flag
-v, --version string Version of the chaincode specified in install/instantiate/upgrade commands
-V, --vscc string The name of the verification system chaincode to be used for this chaincode

Global Flags:
--cafile string Path to file containing PEM-encoded trusted certificate(s) for the ordering endpoint
--certfile string Path to file containing PEM-encoded X509 public key to use for mutual TLS communication with the orderer endpoint
--clientauth Use mutual TLS when communicating with the orderer endpoint
--connTimeout duration Timeout for client to connect (default 3s)
--keyfile string Path to file containing PEM-encoded private key to use for mutual TLS communication with the orderer endpoint
-o, --orderer string Ordering service endpoint
--ordererTLSHostnameOverride string The hostname override to use when validating the TLS connection to the orderer.
--tls Use TLS when communicating with the orderer endpoint
--transient string Transient map of arguments in JSON encoding

调用链码

调用链码:

1
peer chaincode invoke -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C mychannel -n sacc -c '{"Args":["invoke","user1","user2","10"]}'

查询链码

1
peer chaincode query -C mychannel -n sacc -c '{"Args":["query","user1"]}'

调用链码的前提是链码已经实例化。

链码实例化之后存在了运行了一个容器,这个容器一直在运行,所以调用1个链码,实际是peer容器和链码容器交互的过程。

链码与Peer节点的交互过程如下:

  1. 链码通过gRPC与Peer节点交互,当Peer节点收到客户端的交易提案请求后,会发送一个链码消息对象(包含交易提案信息、调用者信息)给对应的链码。
  2. 链码调用Invoke方法,通过发送获取数据(GetState)和写入数据(PutState)消息,向Peer节点获取账本状态信息和发送预提交状态。
  3. 链码发送模拟执行结果给Peer节点,Peer节点对交易提案和模拟执行结果进行背书签名。

升级链码

链码的升级通过改变其版本号(作为SignedCDS的一部分)。SignedCDS另外的部分,如所有者及实例化策略都是可选的。然而,链码的名称必须是一致的,否则会被当做另外一个新的链码
在升级前,必须将新版本的链码安装到有需求的背书节点上。升级也是一种交易,会把新版本的链码绑定到通道中。升级只能在一个时间点对一个通道产生影响,其它通道仍然运行旧版本的链码
由于可能存在多个版本的链码同时存在,升级过程不会自动删除老版本链码,用户必须手动操作删除过程
升级与实例化transaction有一点不同的是:通过现有的chaincode实例化策略检查升级transaction,而不是用新的策略检查。这是为了确保只有当前实例化策略中指定的成员能够升级chaincode。
在升级期间,链码的Init函数也会被调用,执行有关升级的数据或者使用数据重新进行初始化,在升级链码的期间避免对状态进行重置。
安装新版本的链码
peer chaincode install -n sacc -v 1 -p path/to/my/chaincode/v1
upgrade升级链码
peer chaincode upgrade -n sacc -v 1 -c '{"Args":["d", "e", "f"]}' -C mychannel

都是交易

安装、实例化、调用、升级链码,都是创建一笔交易,通过交易实现。

链码开发

github.com/hyperledger/fabric/core/chaincode/shim包是开发Go语言链码的API。shim 包提供了链码与账本交互的中间层。链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态。

链码启动必须通过调用 shim 包中的 Start 函数,而 Start 函数被调用时需要传递一个类型为 Chaincode 的参数,这个参数 Chaincode 是一个接口类型,该接口中有两个重要的函数 Init 与 Invoke 。

Chaincode 接口定义如下:

1
2
3
4
type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}

Init 与 Invoke 方法

编写链码,关键是实现 Init 与 Invoke 两个方法,必须由所有链码实现。Fabric 通过调用指定的函数来运行事务。

  • Init:在链码实例化或升级时被调用, 完成初始化数据的工作。
  • invoke:更新或查询提案事务中的分类帐本数据状态时,Invoke 方法被调用, 因此响应调用或查询的业务实现逻辑都需要在此方法中编写实现。

示例:https://blog.51cto.com/9291927/2318364

通道

通道由排序服务管理,排序服务节点还负责对通道中的交易进行排序。。

目前通道分为系统通道(System Channel)和应用通道(Application Channel)。排序服务通过系统通道来管理应用通道,用户的交易信息通过应用通道传递。

每个组织可以有多个节点加入同一个通道,组织内的节点中可以指定一个锚节点或多个锚节点(增强系统可靠性,避免单点故障)。另外,同一组织的节点会选举或指定主导节点(leading peer),主导节点负责接收从排序服务发来的区块,然后转发给本组织的其它节点。主导节点可以通过特定的算法选出,可以保证在节点数量不断变动的情况下仍能维持整个网络的稳定性。

在Fabric网络中,可能同时存在多条彼此隔离的通道,每条通道包含一条私有的区块链和一个私有账本,通道中可以实例化一个或多个链码,以操作区块链上的数据

通道是共识服务提供的一种通讯机制,基于发布-订阅关系,将Peer节点和排序节点根据某个Topic连接在一起,形成一个具有保密性的通讯链路(虚拟),实现业务隔离的要求。

排序服务提供了供Peer节点订阅的主题(如发布-订阅消息队列),每个主题是一个通道。Peer节点可以订阅多个通道,并且只能访问自己所订阅通道上的交易,因此一个Peer节点可以通过接入多个通道参与到多条链中。

排序服务支持多通道,提供了通向客户端和Peer节点的共享通信通道,提供了包含交易的消息广播服务(broadcast和deliver)。客户端可以通过通道向连接到通道的所有节点广播(broadcast)消息,向连接到通道的所有节点投递(deliver)消息。多通道使得Peer节点可以基于应用访问控制策略来订阅任意数量的通道,应用程序根据业务逻辑决定将交易发送到1个或多个通道。

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/channels.html

创建

在创建通道的时候,需要定义通道的成员和组织、锚节点(anchor peer)和排序服务节点,一条与通道对应的区块链会同时生成,用于记录账本的交易,通道的初始配置信息记录在区块链的创世区块中,可以通过增加一个新的配置区块来更改通道的配置信息。

创建通道的时候定义了成员,只有通过成员MSP验证的实体,才能够加入到通道并访问通道的数据。在通道中一般包含有若干成员(组织),若两个网络实体的×××书能够追溯到同一个根CA,则认为这两个实体属于同一组织。

配置

通道的配置信息都被打包到一个区块中,并存放在通道的共享账本中,成为通道的配置区块,配置区块除了配置信息外不包含其它交易信息。通道可以使用配置区块来更新配置,因此在账本中每新添加一个配置区块,通道就按照最新配置区块的定义来修改配置。通道账本的首个区块一定是配置区块,也称为创世区块(Genesis Block)。

交易

交易的过程,实际是共识的3步:背书、排序和校验。

类型

Fabric区块链的交易分两种,部署交易和调用交易

部署交易把链码部署到Peer节点上并准备好被调用,当一个部署交易成功执行时,链码就被部署到各个背书节点上。(部署指实例化)

调用交易是客户端应用程序通过Fabric提供的API调用先前已部署好的某个链码的某个函数执行交易,并相应地读取和写入KV数据库,返回是否成功或者失败。

流程

详读这篇文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/txflow.html

背书节点校验客户端的签名,然后执行智能合约代码模拟交易。交易处理完成后,对交易信息签名,返回给客户端。客户端收到签名后的交易信息后,发给排序服务节点排序。排序服务节点将交易信息排序打包成区块后,广播发给确认节点,写入区块链中。

客户端提案

客户端应用程序利用SDK(Node.js,Java,Python)构造交易提案(Proposal),提案有2种:

  1. 实例化chaincode。
  2. 调用chaincode。

客户端把交易提案(Proposal)根据背书策略发送给一个或多个背书节点,交易提案中包含本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等。
SDK将交易提案打包为可识别的格式(如gRPC上的protobuf),并使用用户的加密凭证为该交易提案生成唯一的签名。

背书策略定义需要哪些节点背书交易才有效,例如需要5个成员的背书节点中至少3个同意;或者某个特殊身份的成员支持等。客户端只有在收集足够多的背书节点的交易提案签名,交易才能被视为有效。

背书节点为提案背书

背书节点(endorser)收到交易提案后,验证签名并确定提交者是否有权执行操作。背书节点将交易提案的参数作为输入,在当前状态KV数据库上执行交易,生成响应返回给客户端,响应包含执行返回值、读写集的交易结果(此时不会更新账本),交易结果集、背书节点的签名和背书结果(YES/NO)作为提案的结果,客户端SDK解析信息判断是否应用于后续的交易。

在所有合法性校验通过后,背书节点按照交易提案调用链码模拟执行交易。链码执行时,读取的数据(键值对)是背书节点中本地的状态数据库,链码读取过的数据回被归总到读集(Read Set);链码对状态数据库的写操作并不会对账本做改变,所有的写操作将归总到一个写入集(Write Set)中记录下来。读集和写集将在确认节点中用于确定交易是否最终写入账本。

资料:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#endorsement

客户端收集提案背书

客户端验证背书节点签名,并比较各节点返回的提案结果,判断提案结果是否一致以及是否参照指定的背书策略执行。

当背书结果都通过验证,并且满足背书策略时,客户端生成一笔交易发送给排序节点。交易包含交易签名、proposal、读写集、背书结果和通道ID。

orderer排序交易

排序服务节点对接收到的交易进行共识排序,然后按照区块生成策略,将一批交易打包到一起,生成新的区块,调用deliver API投递消息,发送给确认节点。

区块的广播有两种触发条件,一种是当通道的交易数量达到某个预设的阈值,另一种是在交易数量没有超过阈值但距离上次广播的时间超过某个特定阈值,也可触发广播数据块。两种方式相结合,使得经过排序的交易及时生成区块并广播给通道的Leader节点(记账节点),Leader节点验证后,再发送给同channel同组织的其他记账节点。

peer验证区块

peer需要对区块内的所有交易进行验证,验证交易是否按背书策略执行以及根据读写集把交易打上有效或者无效的标签。最后把区块追加到本地的区块链,修改世界状态。

记账节点收到排序服务节点发来的区块后,逐笔检查区块中的交易:

  1. 先检查交易的合法性以及该交易是否曾经出现过。
  2. 然后调用校验系统链码(VSCC,Validation System Chaincode)检验交易的签名背书是否合法,
  3. 以及背书的数量是否满足背书策略的要求。
  4. 记账节点对交易进行多版本并发控制(MVCC)检查,即校验交易的读集(Read Set)是否和当前账本中的版本一致(即没有变化)。如果没有改变,说明交易写集(Write Set)中对数据的修改有效,把该交易标注为有效,交易的写集更新到状态数据库中。如果当前账本的数据和读集版本不一致,则该交易被标注为无效,不更新状态数据库。

交易流程中,采用MVCC的乐观锁模型,提高了系统的并发能力。但MVCC也带来了一些局限性。例如,在同一个区块中若有两个交易先后对某个数据项做更新,顺序在后的交易将失败,因为后序交易的读集版本和当前数据项版本已经不一致。

区块写入账本

每个peer都把区块追加到对应channel的账本上,每个有效交易的write set会被提交到状态数据库。

客户端获取交易结果

客户端可以通过事件订阅交易的结果:是否添加到数据库,是否有效。

结构

账本

账本由区块链和状态数据库两部分组成

  1. 区块链是一组不可更改的有序的区块(数据块),记录着全部交易的日志。
  2. 状态数据库记录了账本中所有键值对的当前值(世界状态),相当于对当前账本的交易日志做了索引。链码执行交易的时候需要读取账本的当前状态,从状态数据库可以迅速获取键值的最新状态。

数据库

Fabric区块链网络中,每个通道都有其账本,每个Peer节点都保存着其所加入通道的账本,Peer节点的账本包含如下数据:

  1. 账本编号,用于快速查询存在哪些账本
  2. 账本数据,用于区块数据存储
  3. 区块索引,用于快速查询区块/交易
  4. 状态数据,用于最新的世界状态数据
  5. 历史数据,用于跟踪键的历史

Fabric的Peer节点账本中有四种数据库,idStore(ledgerID数据库)、blkstorage(block文件存储)、statedb(状态数据库)、historydb(历史数据库)。

账本数据库基于文件系统,将区块存储在文件块中,然后在区块索引LevelDB中存储区块交易对应的文件块及其偏移,即将区块索引LevelDB作为账本数据库的索引。目前支持的区块索引有:区块编号、区块哈希、交易ID、区块交易编号

状态数据库存储的是所有曾经在交易中出现的键值对的最新值(世界状态)。调用链码执行交易可以改变状态数据,为了高效的执行链码调用,所有数据的最新值都被存放在状态数据库中;状态数据库是有序交易日志的快照,任何时候都可以根据交易日志重新生成状态数据库;状态数据库会在Peer节点启动的时候自动恢复或重构,未完备前,本Peer节点不会接受新的交易;状态数据库可以使用LevelDB或者CouchDB,CouchDB能够存储任意的二进制数据,支持富文本查询。链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#world-state

历史状态数据库用于查询某个key的历史修改记录,历史状态数据库并不存储key具体的值,而只记录在某个区块的某个交易里,某key变动了一次。后续需要查询的时候,根据变动历史去查询实际变动的值。

ledgerID数据库存储chainID,用于快速查询节点存在哪些账本。

ledgersData是Peer节点账本的根目录,Peer节点的账本存储在Peer节点容器的/var/hyperledger/production/ledgersData目录下,通过命令行可以进入Peer节点容器进行查看,命令如下:

1
docker exec -it peer0.org1.example.com bash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
root@5af5a3fb3bb7:/var/hyperledger/production# ls -R chaincodes
chaincodes:
root@5af5a3fb3bb7:/var/hyperledger/production# ls -R transientStore/
transientStore/:
000001.log CURRENT LOCK LOG MANIFEST-000000




root@5af5a3fb3bb7:/var/hyperledger/production# ls -R ledgersData/
ledgersData/:
bookkeeper chains configHistory historyLeveldb ledgerProvider pvtdataStore stateLeveldb

ledgersData/bookkeeper:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/chains:
chains index

ledgersData/chains/chains:
mychannel

ledgersData/chains/chains/mychannel:
blockfile_000000

ledgersData/chains/index:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/configHistory:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/historyLeveldb:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/ledgerProvider:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/pvtdataStore:
000001.log CURRENT LOCK LOG MANIFEST-000000

ledgersData/stateLeveldb:
000001.log CURRENT LOCK LOG MANIFEST-000000
  • chains/chains目录下的mychannel目录channel的名称,Fabric支持多通道的机制,而通道之间的账本是隔离的,每个通道都有自己的账本空间。
  • chains/index目录包含levelDB数据库文件,存储区块索引数据库,使用leveldb实现。
  • historyLeveldb目录存储智能合约中写入的key的历史记录的索引地址,使用leveldb实现。
  • ledgerProvider目录存储当前节点所包含channel的信息(已经创建的channel id 和正在创建中的channel id),使用leveldb实现。
  • stateLeveldb目录存储智能合约写入的数据,可选择使用leveldb或couchDB,即状态数据库。

索引

区块索引用于快速定位区块。索引键可以是区块高度、区块哈希、交易哈希。索引值为区块文件编号+文件内偏移量+区块数据长度。

Hyperledger Fabric提供了多种区块索引的方式,以便能快速找到区块。索引的内容是文件位置指针(File Location Pointer)。文件位置指针由三个部分组成:所在文件的编号(fileSuffixNum)、文件内的偏移量(offset)、区块占用的字节数(bytesLength)。

整个网络有一条系统链,保存有网络的配置信息,比如MSP、各种策略、各种配置项,系统链在排序服务中(即,peer上没有?),保存在ordering节点的系统channel中,任何改变整个网络配置的,都会产生一个新的配置块添加到系统链上去。

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#system-chain

共识/排序服务

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/orderer/ordering_service.html

kafka

共识集群由多个排序服务节点(OSN)和一个Kafka集群组成。排序节点之间不直接通信,仅仅与Kafka集群通信。

在排序节点的实现里,通道(Channel)在Kafka中是以主题topic的形式隔离。

每个排序节点内部,针对每个通道都会建立与Kafka集群对应topic的生产者及消费者。生产者将排序节点收到的交易发送到Kafka集群进行排序,在生产的同时,消费者也同步消费排序后的交易。

Raft

fabric的raft基于etcd的raft,raft是CFT,是leader-follower模型。

需要注意的一点是,所有channel共用排序集群,但每个channel都有各自的Raft实例,所以每个channel可以选举自己的leader。

虽然所有的 Raft 节点都必须包含在系统通道中,但他们并不需要都包含在应用通道中。通道创建者(和通道管理员)拥有选择可用排序节点子集的能力并且可以根据需要增加或移除排序节点(只要每次只增加会移除一个节点)。

where a leader node is elected (per channel) and its decisions are replicated by the followers.

链接:https://hyperledger-fabric.readthedocs.io/en/release-1.4/glossary.html#raft

通信

Gossip

gossip是数据扩散协议,有3个功能:

  1. 管理节点发现和channel的成员关系
  2. 同channel上的所有peer扩散账本数据
  3. 同channel上的所有peer间同步账本状态

更多信息:https://hyperledger-fabric.readthedocs.io/en/release-1.4/gossip.html

Gossip 协议最终的目的是将数据分发到网络中的每一个节点,Gossip数据分发协议实现了两种数据传输方式。

推送数据
  1. 网络中的某个节点随机选择N个节点作为数据接收对象,N配置在配置文件中
  2. 该节点向其选中的N个节点传输相应的信息
  3. 接收到信息的节点处理它接收到的数据
  4. 接收到数据的节点再从第一步开始重复执行

拉去数据
  1. 某个节点周期性地选择随机N个节点询问有没有最新的信息
  2. 收到请求的节点回复请求节点其最近未收到的信息

数据同步

节点之间使用数据同步保证账本数据和状态数据的即使更新,数据同步主要有2类:

  1. 主动广播,比如广播新打包的区块,排序节点把区块发送给主节点,它基于Gossip的推送数据
  2. 主动请求,比如新加入的节点,向已存在的其他组织锚节点请求数据,锚节点给他响应,它基于Gossip的拉去数据

应用开发

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/developapps/developing_applications.html

Fabric网络

文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/network/network.html

文档里介绍了以上各种概念在网络中的位置。

由于你知我知的网络原因,开发者遇到了以下问题:

  1. brew/apt-get/yum等安装软件慢、更新慢
  2. docker下载镜像慢
  3. go get某些package无法访问、超时

怎么解决?

  1. 挂代理,实现科学上网
  2. 换镜像,曲线救国

镜像都在国内,所以镜像效果比代理好。

换代理请看让终端科学上网

接下来看几个常用的镜像。

Linux发行版镜像

阿里镜像首页列出了所有发行版的镜像状态,以及【帮助】,展示了如何更换源。

这里不仅包含了发行版的镜像,还有homebrew、docker,但我认为这2个阿里的镜像不太好用,但列出来了。

Brew镜像

你需要让Homebrew飞

Docker镜像

看如何配置Docker镜像加速器

推荐使用七牛或DaoCloud的镜像。

Go modules代理

现在国内已经有第三方的Go modules代理服务了,比如:

  1. goproxy.io,是即将毕业的盛奥飞小哥捐给了七牛搭建的Go modules代理服务。
  2. aliyun goproxy,阿里云昨天(大概2019年07月15日)刚开放了Go modules代理服务。

fabric使用vendor,下载各种东西的时候需要翻墙,即便是可以翻墙,也是有缺点的:

  1. 慢。
  2. 翻墙有流量限制。

Homebrew源

homebrew默认使用的是Github,虽然已经科学上网了,速度依然是KB级别的,相当的慢。使用国内的源,速度有质的提升,推荐2个国内的:

1
2
https://mirrors.cloud.tencent.com/homebrew/brew.git
git://mirrors.ustc.edu.cn/brew.git

腾讯源更多信息见:https://mirrors.cloud.tencent.com/help/homebrew-bottles.html

建议ping一下以上2个源,选延时小的。

下载和修改安装脚本

下载官方安装脚本:

1
curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install >> brew_install

修改官方脚本,把Github源替换为腾讯源:

1
2
3
4
5
6
7
8
9
#!/usr/bin/ruby
# This script installs to /usr/local only. To install elsewhere (which is
# unsupported) you can untar https://github.com/Homebrew/brew/tarball/master
# anywhere you like.
HOMEBREW_PREFIX = "/usr/local".freeze
HOMEBREW_REPOSITORY = "/usr/local/Homebrew".freeze
HOMEBREW_CORE_TAP = "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core".freeze
HOMEBREW_CACHE = "#{ENV["HOME"]}/Library/Caches/Homebrew".freeze
BREW_REPO = "https://github.com/Homebrew/brew.git".freeze

BREW_REPO = "https://github.com/Homebrew/brew.git".freeze替换为BREW_REPO = "https://mirrors.cloud.tencent.com/homebrew/brew.git".freeze

运行脚本安装

1
/usr/bin/ruby ~/brew_install

这个版本的安装脚本已经没有CORE_TAP_REPO了,所以下载homebrew core的时候依然去Github下载,非常慢,可以在brew.git下载完,control-c结束掉。

把仓库https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git克隆到/usr/local/Homebrew/Library/Taps/homebrew/目录,然后再执行上面的安装脚本。

更换brew源

如果brew已经安装了,直接修改源就行了。

1、替换brew.git和homebrew-core.git的源:

1
2
3
4
5
6
cd "$(brew --repo)"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/brew.git


cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git

2、更新 bottles源

对于bash用户:

1
2
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile

对于zsh用户

1
2
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc

代码Review的时候,遇到过一些log滥用的情况,今天聊一聊滥用(过渡使用)日志。

好的log能够帮助开发人员快速定位bug,而差的log各有各的不同。

你滥用日志了吗?

是什么导致了滥用log?是不是存在这些误解:

1. 害怕出了问题,现有的log无法定位,要多加一些log,恨不得每段都有一个log,log数简直越多越好,看日志有一种,每一步都非常清晰的错觉。

2. 不知道log多了,定位效率更低,试问你有没有经历过几分钟刷出了G级别日志文件?在这种日志文件里定位bug,简直是大海捞针,这让log的价值非常低。

3. 不知道log多了会影响性能,log自身涉及格式化和文件读写,虽然现在各log库都已经比较高效了,但是,这也扛不住“海量”的log啊,积少成多,势必影响程序性能。

4. 对log级别错误的认知:日志级别设置为Info,Debug、Trace级别的日志不会打印,Debug、Trace级别日志多没关系。虽然日志不会输出,并不代表相关代码没执行啊。

第4点重点解释一下:

debug-demo

这是一个打印Debug级别的日志,它还有1项日志信息,是来自func()的结果,请问:

  1. 日志级别设置为Info,log.Debug会执行吗?func()还会执行吗?
  2. 如果这行日志频繁被执行,是不是浪费了CPU做无用功?

如果你认为不会执行,看下面的Demo,log使用zap。

log-test

结果:

log-ret

事实证明无论限制的日志级别是什么,log.***一定会被调用,它入参中的函数也一定会被调用,只不过是日记级别不满足打印时,不会打印而已。被调函数的结果只被这条log.***使用,结果这个日志根本不打印,这就浪费了CPU。

日志级别都设置为Info了,Debug级别的日志为何还会打印?

如果你有这个问题,你可能没有理解2个地方。

日志级别设置为Info,不代表log.Debug函数不执行。log.Debug函数一定会执行,看下图,log.Info,Error等接口会调用相同的真实实现函数log.loglog.log的入参包含了log.Info等接口的入参,以及当前的log_level,比如以下2种是等价的:

log-log

所以,无论设置的是什么日志级别控制,log.Debug一定会被执行,至于当前日志是否会打印,会在log.log里决定。

image-20190712072742045

日志为Warn级别,Debug日志不会打印,func()会不会执行?

日志打印本质是函数调用,会先计算入参,再调用函数。比如:

log-debug

所以func()一定会被调用。

总结

针对滥用日志的情况给几点建议:

  1. 1条日志描述清when、where、what,提供有效信息,这就对定位很有帮助了。
  2. 只在“可能”出问题的地方打印日志,一些能根据上下文日志推断的地方,就无需再增加日志。
  3. 日志打印不要调用函数。
  1. 如果这篇文章对你有帮助,不妨关注下我的Github,有文章会收到通知。
  2. 本文作者:大彬
  3. 如果喜欢本文,随意转载,但请保留此原文链接:http://lessisbetter.site/2019/07/12/do-not-abuse-of-log/

关注公众号,获取最新Golang文章