区块链导航-超级账本

区块链导航系列-超级账本

磨链(mochain)区块链技术社区整理(投河自尽的鱼、南瓜地、清源、John)

区块链导航也是一个入门区块链的入门学习路线。下图是一个区块链相关内容的整理分享,内容还是比较繁琐,当然在学习区块链的过程中并不需要那么复杂。钻研琢磨某些方面可能对这个学习过程更为深刻。 

所有内容不需要科学上网,当然通过科学上网能获取更多信息,这里暂时只收录了不通过科学上网能获取的内容

磨链社区-区块链导航目录:


超级账本相关导航

超级账本(hyperledger)是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目,加入成员包括:荷兰银行(ABN AMRO)、埃森哲(Accenture)等十几个不同利益体,目标是让成员共同合作,共建开放平台,满足来自多个不同行业各种用户案例,并简化业务流程。由于点对点网络的特性,分布式账本技术是完全共享、透明和去中心化的,故非常适合于在金融行业的应用,以及其他的例如制造、银行、保险、物联网等无数个其他行业。通过创建分布式账本的公开标准,实现虚拟和数字形式的价值交换,例如资产合约、能源交易、结婚证书、能够安全和高效低成本的进行追踪和交易。

超级账本导航如图:


相关官网及网络资料

超级账本官网:

超级账本官方文档 :

超级账本中文文档 :

巴比特专栏 :

区块链技术指南:

Hyperledger Fabric 从零开始:

Hyperledger详解:

趣链科技技术团队:

孔壹学院:

区块链兄弟:

IBM Fabric系列微讲堂 :

超级账本白皮书:

github源码 :

Fabric源码分析 :

相关词汇概念

Anchor(锚点):一般指作为刚启动时候的初始联络元素或与其它结构的沟通元素。如刚加入一个 channel 的节点,需要通过某个锚点节点来快速获取 channel 内的情况(如其它节点的存在信息)。

Auditability(审计性):在一定权限和许可下,可以对链上的交易进行审计和检查。

Block(区块):代表一批得到确认的交易信息的整体,准备被共识加入到区块链中。

Blockchain(区块链):由多个区块链接而成的链表结构,除了初始区块,每个区块头部都包括前继区块内容的 hash 值。

Chaincode(链码):区块链上的应用代码,扩展自“智能合约”概念,支持 golang、nodejs 等语言,多为图灵完备。

Channel(通道):Fabric 网络上的私有隔离。通道中的 chaincode 和交易只有加入该通道的节点可见。同一个节点可以加入多个通道,并为每个通道内容维护一个账本。

Committer(提交节点):1.0 架构中一种 peer 节点角色,负责对 orderer 排序后的交易进行检查,选择合法的交易执行并写入存储。

Commitment(提交):提交节点完成对排序后交易的验证,将交易内容写到区块,并更新世界观的过程。

Confidentiality(保密):只有交易相关方可以看到交易内容,其它人未经授权则无法看到。

Endorser(推荐节点或背书节点):1.0 架构中一种 peer 节点角色,负责检验某个交易是否合法,是否愿意为之背书、签名。

Endorsement:背书过程。按照 chaincode 部署时候的 endorsement 策略,相关 peer 对交易提案进行模拟和检查,决策是否为之背书。如果交易提案获得了足够多的背书,则可以构造正式交易进行进一步的共识。

Invoke(调用):一种交易类型,对 chaincode 中的某个方法进行调用,一般需要包括调用方法和调用参数。

Ledger(账本):包括区块链结构(带有所有的交易信息)和当前的世界观(world state)。

Member(成员):代表某个具体的实体身份,在网络中有用自己的根证书。节点和应用都必须属于某个成员身份。同一个成员可以在同一个通道中拥有多个 peer 节点,其中一个为 leader 节点,代表成员与排序节点进行交互,并分发排序后的区块给属于同一成员的其它节点。

MSP(Member Service Provider,成员服务提供者):抽象的实现成员服务(身份验证,证书管理等)的组件,实现对不同类型的成员服务的可拔插支持。

Non-validating Peer(非验证节点):不参与账本维护,仅作为交易代理响应客户端的 REST 请求,并对交易进行一些基本的有效性检查,之后转发给验证节点。

Orderer(排序节点):1.0 架构中的共识服务角色,负责排序看到的交易,提供全局确认的顺序。

Permissioned Ledger(带权限的账本):网络中所有节点必须是经过许可的,非许可过的节点则无法加入网络。

Privacy(隐私保护):交易员可以隐藏交易的身份,其它成员在无特殊权限的情况下,只能对交易进行验证,而无法获知身份信息。

System Chain(系统链):由对网络中配置进行变更的配置区块组成,一般可以用来作为组成网络成员们形成的联盟约定。

Transaction(交易):执行账本上的某个函数调用或者部署 chaincode。调用的具体函数在 chaincode 中实现。

Transactor(交易者):发起交易调用的客户端。

Validating Peer(验证节点):维护账本的核心节点,参与一致性维护、对交易的验证和执行。

World State(世界状态):即最新的全局账本状态。Fabric 用它来存储历史交易发生后产生的最新的状态,可以用键值或文档数据库实现。 

(链接:https://www.jianshu.com/p/12020c87c946

学习目录

Hyperledger Fabric 环境搭建及使⽤ 

(1)搭建开发环境 

(2)搭建⾃己第一个网络 

(3)编写⾃己第一个应⽤

Hyperledger Fabric 架构与设计 

(1)整体架构的概览 

(2)Hyperledger Fabric 核⼼组件 

(3)Hyperledger Fabric 权限管理 

(4)Hyperledger Fabric gRPC消息协议 

(5)Hyperledger Fabric 排序服务

Hyperledger Fabric 链码开发 

(1)链码的简介、结构 

(2)链码开发 API 

(3)链码开发案例例解析

Hyperledger Fabric SDK的使⽤ 

(1)Fabric-sdk-node 介绍及api 

(2)Fabric-sdk-node 调用链码 

(3)Fabric-sdk-node 开发区块链应用 

(4)Fabric-sdk-java 介绍及调用链码

Hyperledger Fabric 部署 

(1)创建Fabric多节点集群 

(2)启动Fabric多节点集群 

(3)Fabric多节点集群⽣生产部署及启动

相关基础内容(来自趣链科技清源知乎)

清源知乎

Fabric学习笔记-架构初探

本文介绍了Fabric的架构,以及通过一个简单的Demo来熟悉整个交易流程。 

Hyperledger fabric V1.0的架构 

如下图所示: 

application提供各种语言的SDK接口。

membership也就是fabric-ca提供成员服务,用来管理身份,提供授权和认证。

peer负责模拟交易和记账

Endorser(背书)用来当peer执行一个交易以后返回yes/no。 

Committer将验证过的区块追加到通道上各个账本的副本。 

Ledger就是账本啦。 

Chaincode用来编写业务逻辑,交易指令用来修改资产,可以理解为 fabric 网络对外提供的一个交互接口(智能合约)。 

Event是fabric提供的一个事件框架,比如链代码事件,拒绝事件,注册事件等,在编写代码的时候可以订阅这些事件来实现自己的业务逻辑。 

o-service用来实现共识。

交易流程

交易过程如下图所示: 

Application向一个或多个peer节点发送对交易的背书请求。 

Peer的endorse执行背书,但并不将结果提交到本地账本,只是将结果返回给应用。 

应用收集所有背书节点的结果后,将结果广播给orderers,orderers执行共识,生成block,通过消息通道批量的将block发布给peer节点,更新lerdger。 

可以看一下下面这张图,一样的过程,更加突出了多个peer背书的过程。 

在介绍一下在交易过程中扮演重要角色的channel 

channel是构建在Fabric网络上的私有区块链,实现了数据的隔离和保密。channel是由特定的peer所共享的,并且交易方必须通过该通道的正确验证才能与账本进行交互。 

如下图所示: 


可以看到不同颜色的channel隔离了不同的peer之间的通信。

构建Fabric网络 

Fabric提供了一个first-network的demo来学习整个流程。 

first-network有两个组织,每个组织各有两个个peer节点,以及一个排序服务。两个peer节点无法达成共识,三个peer节点无法容错,四个peer节点刚好完成演示。 

Demo启动以后会自动完成一笔转账,并且打印出整个操作过程。 

启动网络以后可以看到非常多的日志,这里我们只追踪关键步骤。

##########################################################

#########  Generating Orderer Genesis block ##############

##########################################################

2018-02-05 15:13:08.760 CST [common/configtx/tool] main -> INFO 001 Loading configuration

2018-02-05 15:13:08.816 CST [common/configtx/tool] doOutputBlock -> INFO 002 Generating genesis block

2018-02-05 15:13:08.819 CST [common/configtx/tool] doOutputBlock -> INFO 003 Writing genesis block

#################################################################

### Generating channel configuration transaction 'channel.tx' ###

#################################################################

2018-02-05 15:13:08.845 CST [common/configtx/tool] main -> INFO 001 Loading configuration

2018-02-05 15:13:08.849 CST [common/configtx/tool] doOutputChannelCreateTx -> INFO 002 Generating new channel configtx

2018-02-05 15:13:08.850 CST [common/configtx/tool] doOutputChannelCreateTx -> INFO 003 Writing new channel tx

#################################################################

#######    Generating anchor peer update for Org1MSP  ##########

#################################################################

2018-02-05 15:13:08.876 CST [common/configtx/tool] main -> INFO 001 Loading configuration

2018-02-05 15:13:08.880 CST [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 002 Generating anchor peer update

2018-02-05 15:13:08.881 CST [common/configtx/tool] doOutputAnchorPeersUpdate -> INFO 003 Writing anchor peer update

可以看到,在转账之前首先进行了fabric网络的初始化过程,创建了创世区块,配置了channel和生成了membership(MSP)身份服务。

Channel name : mychannel

Creating channel...

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_VM_ENDPOINT=unix:///host/var/run/docker.sock

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_LOGGING_LEVEL=DEBUG

CORE_PEER_ADDRESS=peer0.org1.example.com:7051

2018-02-05 07:13:13.382 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP

2018-02-05 07:13:13.382 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity

2018-02-05 07:13:13.406 UTC [channelCmd] InitCmdFactory -> INFO 003 Endorser and orderer connections initialized

2018-02-05 07:13:13.409 UTC [msp] GetLocalMSP -> DEBU 004 Returning existing local MSP

dd

接着可以看到在转账前首先创建了channel,其中包括了身份认证的ca文件,key以及通信的socket信息和MSP的信息。

Attempting to Query PEER0 ...3 secs

2018-02-05 07:13:55.998 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP

2018-02-05 07:13:55.998 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity

2018-02-05 07:13:55.998 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc

2018-02-05 07:13:55.998 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc

2018-02-05 07:13:55.999 UTC [msp/identity] Sign -> DEBU 005 Sign: plaintext: 0A95070A6708031A0C08B388E0D30510...6D7963631A0A0A0571756572790A0161

2018-02-05 07:13:55.999 UTC [msp/identity] Sign -> DEBU 006 Sign: digest: D6BB27BAC40E5A58ED3CF0AFB147280B60E305053D9B97A38461A398736ED7C7

Query Result: 100

接着在很不起眼的地方看到了上面这些日志,差点看瞎我~重点来了~

在转账之前使用默认的ESCC(背书链码),VSCC(验证链码),以及自身的签名查询了Peer0得到余额是100。

2018-02-05 07:14:14.950 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> DEBU 009 ESCC invoke result: version:1 response: payload:"\n \370\316\5...,k\363_(\n\212\027" >

2018-02-05 07:14:14.952 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 00a Chaincode invoke successful. result: status:200

2018-02-05 07:14:14.953 UTC [main] main -> INFO 00b Exiting.....

===================== Invoke transaction on PEER0 on channel 'mychannel' is successful =====================

调用链码进行转账操作,发现两边的状态码都是200表示转账成功,其中payload是发送转账的一些具体信息。

Attempting to Query PEER3 ...3 secs

2018-02-05 07:14:18.566 UTC [msp] GetLocalMSP -> DEBU 001 Returning existing local MSP

2018-02-05 07:14:18.566 UTC [msp] GetDefaultSigningIdentity -> DEBU 002 Obtaining default signing identity

2018-02-05 07:14:18.566 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 003 Using default escc

2018-02-05 07:14:18.566 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 004 Using default vscc

2018-02-05 07:14:18.567 UTC [msp/identity] Sign -> DEBU 005 Sign: plaintext: 0A95070A6708031A0C08CA88E0D30510...6D7963631A0A0A0571756572790A0161

2018-02-05 07:14:18.567 UTC [msp/identity] Sign -> DEBU 006 Sign: digest: 9E93A6D0549C627788C5074633DE28C9D08F36A4763EFF55479613B2DD32CADB

Query Result: 90

2018-02-05 07:14:37.014 UTC [main] main -> INFO 007 Exiting.....

最后查询Peer3发现余额只有90了,这样就完成了一个最小区块链网络的交易示例。

Fabric学习笔记-智能合约

本文介绍了什么是智能合约,以及如何在Fabric下编写一个简单的智能合约Demo。

什么是智能合约? 

智能合约是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易。这些交易可追踪且不可逆转,Fabric为智能合约提供了一个可信的执行环境。

Fabric下的智能合约 

Chaincode是Fabric下智能合约的代码实现,是一段在区块链上验证,存储和执行的代码,只有通过Chaincode才能与Fabric网络进行交互。 

在Fabric下又分为系统Chaincode和普通Chaincode。

系统Chaincode运行于peer节点内而非一个隔离的容器中,没有生命周期的概念,用于实现一些系统行为。

普通Chaincode运行在一个单独的容器中,对外提供操作Fabric网络的接口,有打包、安装、实例化和升级四个生命周期并且有相应的函数提供调用,操作更加的灵活也是我们通常所指Chaincode。

Chaincode的运行过程

Channel负责连接不同的Peer,同步连接到的Peer对Chaincode的执行结果,相当于将一些节点划分为一个子集同时同步Chaincode的执行结果。

Endorser负责执行Chaincode。

Orderer负责对Chaincode的执行结果进行共识,目前支持solo/kafka/sBFT三种共识方式。

Committer负责将Chaincode经过共识后的结果写入Ledger。

编写Chaincode

Chaincode必须实现两个接口。

type Chaincode interface { 

    //  初始化⼯作,⼀般情况下仅被调⽤⼀次,升级时也会调用该函数

    Init(stub ChaincodeStubInterface) pb.Response

    //  查询或更新world state,可多次被调⽤

    Invoke(stub ChaincodeStubInterface) pb.Response

}

下面是一个最小智能合约所需要的结构。

package main   

import  ( 

    "fmt" 

    "github.com/hyperledger/fabric/core/chaincode/shim"

)

type SimpleAsset struct {}

func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {}

func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {}

func main() {

    if err := shim.Start(new(SimpleAsset)); err != nil {

        fmt.Printf("Error starting SimpleAsset chaincode: %s", err)

    }

}

具体的业务逻辑都在Invoke这个函数里面进行编写。 

首先,我们完善一下Init函数

func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {

  args := stub.GetStringArgs()

  if len(args) != 2 {

    return shim.Error("Incorrect arguments. Expecting a key and a value")

  }

  // 实例化的时候初始化一下状态

  err := stub.PutState(args[0], []byte(args[1]))

  if err != nil {

    return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))

  }

  return shim.Success(nil)

}

接着在Invoke里编写业务逻辑

func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {

    fn, args := stub.GetFunctionAndParameters()

    var result string

    var err error

    if fn == "set" {

        result, err = set(stub, args)

    } else {

        result, err = get(stub, args)

    }

    if err != nil {

        return shim.Error(err.Error())

    }

    return shim.Success([]byte(result))

}

Invoke根据不同的参数决定调用查询或者是设置状态。

编写set和get函数

func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {

    if len(args) != 2 {

        return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")

    }

    err := stub.PutState(args[0], []byte(args[1]))

    if err != nil {

        return "", fmt.Errorf("Failed to set asset: %s", args[0])

    }

    return args[1], nil

}

func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {

    if len(args) != 1 {

        return "", fmt.Errorf("Incorrect arguments. Expecting a key")

    }

    value, err := stub.GetState(args[0])

    if err != nil {

        return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)

    }

    if value == nil {

        return "", fmt.Errorf("Asset not found: %s", args[0])

    }

    return string(value), nil

}

github上有完整代码,代码非常简单就是根据命令查询或者设置世界状态。 

执行效果如下图: 

这样就在Fabric上实现了一个简单的智能合约。

Fabric学习笔记-PBFT算法

本文介绍了实用拜占庭容错算法(PBFT)。

Fabric在v0.6中采用的是PBFT算法,在v1.0.0-preview中是SBFT算法,在v1.0.0-release中文档上说PBFT还在开发中,项目中目前还没有实现(⊙o⊙)…但是万变不离其宗都是对BFT算法的一些优化改进。

解决的问题

拜占庭算法主要解决了缺少可信的中央节点和可信任的通道的情况下,分布在网络中的各个节点如何达成共识的问题,实用拜占庭算法是拜占庭算法的改进,主要改进了拜占庭算法效率不高的问题,将算法复杂度由指数级降低到多项式级,使得拜占庭容错算法在实际系统应用中变得可行。 

基本概念

客户端(client)负责发送请求。

副本(replica)所有参与提供服务的节点。

备份节点(backup)主节点外的所有节点。

主节点(primary)从副本中选出提供主要服务的节点。

视图(view)主节点和副本之间编号的一次快照。

PBFT是一种状态机副本复制算法,即服务作为确定有限状态机进行建模,状态机在分布式系统的不同节点进行副本复制。每个状态机的副本都保存了服务的状态,同时也实现了服务的操作。

在确定有限自动机中,每个状态对每个可能输入只有精确的一个转移,在输入顺序一致,起始状态一致的情况下输出也必然一致。这也就对PBFT算法中的副本提出了两个限定 :

所有节点必须是确定性的。也就是说,在给定状态和参数相同的情况下,操作执行的结果必须相同。

所有节点必须从相同的状态开始执行。

在这两个限定条件下,即使失效的副本节点存在,PBFT算法对所有非失效副本节点的请求执行总顺序达成一致,从而保证安全性。

将所有的副本组成的集合使用大写字母R表示,使用0到|R|-1的整数表示每一个副本。为了描述方便,假设|R|=3f+1,这里f是有可能失效的副本的最大个数。尽管可以存在多于3f+1个副本,但是额外的副本除了降低性能之外不能提高可靠性。 

算法流程

请求阶段:客户端向主节点发送请求。

预准备阶段:主节点分配一个序列号n给收到的请求,然后向所有备份节点广播预准备消息,预准备消息的格式为<,m>,这里v是视图编号,m是客户端发送的请求消息,d是请求消息m的摘要。

准备阶段:备份节点i接受了预准备消息<,m>,则进入准备阶段。在准备阶段的同时,该节点向所有副本节点发送准备消息,并且将预准备消息和准备消息写入自己的消息日志。

确认阶段:副本节点收到2f个从不同副本节点发来一致的预准备消息,一共2f+1个一致的预准备消息确认了消息的正确性,然后按照序号n依次执行请求。

预准备阶段和准备阶段确保所有正常节点对同一个视图中的请求序号达成一致。

准备阶段和确认阶段确保了节点执行了大多数都认可的操作。

PBFT算法流程: 

PBFT算法流程

主要介绍了PBFT算法的主要流程,其中还有一些问题例如:主节点是如何选择,主节点失效,怎么确定消息的正确性等都不在此赘述,可以参考:https://www.jianshu.com/p/fb5edf031afd 区块链核心技术:拜占庭共识算法之PBFT。

Fabric源码分析-共识模块

正好这些天要有一个需求要帮客户魔改Fabric-v0.6,把一些hyperchain的高级特性移植过去,借此机会把之前看过的源码在梳理一下。 

下面就是对Fabric共识模块的源码分析和梳理,代码都是以Fabric-v0.6-preview为例,在1.0及后续版本中都移除了PBFT部分,用了更好的SBFT,目前这一部分还在开发中。 

目录结构 

可以看到共识模块目录如下。

consensus

├── controller

├── executor

├── helper

│  └── persist

├── noops

├── pbft

└── util

    └── events

目录含义如下

controller 用来控制Fabric选择什么样的共识算法,默认是noops。

executor 封装了消息队列中对交易的处理。

helper 对外提供接口调用和数据持久化接口。

noops 提供了如何编写Fabric共识算法的Demo。

pbft PBFT算法的具体实现。

util 实现了一个peer节点到共识算法的一个消息通道,和一个消息队列。

流程概览

Fabric网络通过一个EventLoop和共识算法进行交互,所有的操作都通过对事件循环中的事件监听进行推进。

整体流程如下图所示。 

Consensus模块接口

fabric/consensus/consensus.go对外提供共识模块的方法调用。

其中最核心也是每个算法必须实现的接口是Consenter。

type ExecutionConsumer interface {

    Executed(tag interface{})                               

    Committed(tag interface{}, target *pb.BlockchainInfo)   

    RolledBack(tag interface{})                             

    StateUpdated(tag interface{}, target *pb.BlockchainInfo)

}

type Consenter interface {

    RecvMsg(msg *pb.Message, senderHandle *pb.PeerID) error

    ExecutionConsumer

}

接口的具体实现在fabric/consensus/pbft/external.go。

因为对交易的操作都是异步的,所以必须手动实现Executed,Committed,RolledBack,StateUpdated方法来监听对应动作的完成。

RecvMsg方法用来从不用的peer节点接收消息。

初始化共识模块

共识算法引擎在peer启动的时候初始化,初始化的具体函数如下所示。

// consensus/helper/engine.go

func GetEngine(coord peer.MessageHandlerCoordinator) (peer.Engine, error) {

    var err error

    engineOnce.Do(func() {

        engine = new(EngineImpl)

        engine.helper = NewHelper(coord)

        engine.consenter = controller.NewConsenter(engine.helper)

        engine.helper.setConsenter(engine.consenter)

        engine.peerEndpoint, err = coord.GetPeerEndpoint()

        engine.consensusFan = util.NewMessageFan()

        go func() {

            logger.Debug("Starting up message thread for consenter")

            for msg := range engine.consensusFan.GetOutChannel() {

                engine.consenter.RecvMsg(msg.Msg, msg.Sender)

            }

        }()

    })

    return engine, err

}

GetEngine的作用是进行共识模块的初始化,同时启动一个goroutine等待消息进入。

具体的engine.consenter是在consensus/controller/controller.go里选择。

// consensus/controller/controller.go

func NewConsenter(stack consensus.Stack) consensus.Consenter {

    plugin := strings.ToLower(viper.GetString("peer.validator.consensus.plugin"))

    if plugin == "pbft" {

        logger.Infof("Creating consensus plugin %s", plugin)

        return pbft.GetPlugin(stack)

    }

    logger.Info("Creating default consensus plugin (noops)")

    return noops.GetNoops(stack)

}

默认选择的是noops,如果需要添加自己编写的共识模块需要在这里自行添加判断。

noops 只是演示如何编写Fabric共识模块,不要用在生产环境。

如果选择了PBFT则会调用consensus/pbft/pbft.go进行初始化。

使用PBFT的batch模式启动时会调用newObcBatch进行PBFT算法初始化。

PBFT只有batch一种模式。

// consensus/pbft/batch.go

func newObcBatch(id uint64, config *viper.Viper, stack consensus.Stack) *obcBatch {

    var err error

    ...

    op.manager = events.NewManagerImpl()   

    op.manager.SetReceiver(op)

    etf := events.NewTimerFactoryImpl(op.manager)

    op.pbft = newPbftCore(id, config, op, etf)

    op.manager.Start()

    blockchainInfoBlob := stack.GetBlockchainInfoBlob()

    op.externalEventReceiver.manager = op.manager

    ...

    return op

}

newObcBatch主要做了这几项工作

初始化了eventLoop的消息队列。

设置了消息的接收者,用来处理对应的消息。

创建监听消息超时的定时器。

初始化pbft算法。

启动消息队列,不断监听事件的到来并且分发给接收者处理。

消息处理

Fabric的共识消息是通过eventLoop注射给对应处理函数的。

// consensus/util/events/events.go

func SendEvent(receiver Receiver, event Event) {

    next := event

    for {

        next = receiver.ProcessEvent(next)

        if next == nil {

            break

        }

    }

}

func (em *managerImpl) Inject(event Event) {

    if em.receiver != nil {

        SendEvent(em.receiver, event)

    }

}

func (em *managerImpl) eventLoop() {

    for {

        select {

        case next := <-em.events:

            em.Inject(next)

        case <-em.exit:

            logger.Debug("eventLoop told to exit")

            return

        }

    }

}

eventLoop函数不断的从em.events里取出事件,通过Inject注射给对应的接收者,注意,通过SendEvent注射给接收者的ProcessEvent方法。

SendEvent函数实现非常有意思,如果receiver.ProcessEvent的返回不为nil则不断的调用receiver.ProcessEvent直到找到对应的消息处理函数,在ProcessEvent函数中,其余case均为事件处理函数,唯独pbftMessage依赖SendEvent发送消息给其余函数处理。

// consensus/pbft/pbft-core.go

func (instance *pbftCore) ProcessEvent(e events.Event) events.Event {

    ...

    case *pbftMessage:

        return pbftMessageEvent(*et)

    case pbftMessageEvent:

        msg := et

        logger.Debugf("Replica %d received incoming message from %v", instance.id, msg.sender)

        next, err := instance.recvMsg(msg.msg, msg.sender)

        if err != nil {

            break

        }

        return next

    case *RequestBatch:

        err = instance.recvRequestBatch(et)

    case *PrePrepare:

        err = instance.recvPrePrepare(et)

    ...

}

可以看到*pbftMessage和pbftMessageEvent这两个case通过recvMsg的返回值又把消息分发给其余case,非常巧妙。 

PBFT算法的不同阶段都会按着上面的流程映射到不同的处理函数往前推进,本质上是一个状态机。 

至此Fabric的Consensus模块主要流程已经梳理清楚,熟悉了这个流程以后再结合PBFT算法的过程就可以很容易在此基础上添加新的功能了。

推荐阅读更多精彩内容