Hyperleger源码分析--共识算法
Posted 李赫的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hyperleger源码分析--共识算法相关的知识,希望对你有一定的参考价值。
共识算法(consensus)
peer节点启动的时候根据配置文件core.yaml文件配置项peer.validator.consensus.plugin选择采用哪种共识算法。目前Fabric实现了两种共识算法NOOPS和PBFT,默认是NOOPS:
- NOOPS:是一个供开发和测试使用的插件,会处理所有收到的消息。
- PBFT:PBFT算法实现。
0x01 插件接口
- Consenter
// ExecutionConsumer allows callbacks from asycnhronous execution and statetransfer
type ExecutionConsumer interface
Executed(tag interface) // Called whenever Execute completes
Committed(tag interface, target *pb.BlockchainInfo) // Called whenever Commit completes
RolledBack(tag interface) // Called whenever a Rollback completes
StateUpdated(tag interface, target *pb.BlockchainInfo) // Called when state transfer completes, if target is nil, this indicates a failure and a new target should be supplied
// Consenter is used to receive messages from the network
// Every consensus plugin needs to implement this interface
type Consenter interface
RecvMsg(msg *pb.Message, senderHandle *pb.PeerID) error // Called serially with incoming messages from gRPC
ExecutionConsumer
每个共识插件都需要实现Consenter接口,包括RecvMsg函数和ExecutionConsumer接口里的函数(可以直接返回)。
Consenter是EngineImpl的一个成员,EngineImpl是接口Engine的一个实例,是在peer启动的时候创建的,连同Impl的其他成员一起注册到gRPC服务中。当通过gRPC收到ProcessTransaction消息时,最终会调用Consenter的RecvMsg处理交易信息。
ExecutionConsumer接口是专门处理事件消息的,它是Stack的成员Executor的一个接口。coordinatorImpl是Executor的一个实例,在实例化coordinatorImpl的时候同时设置自身为成员变量Manager的事件消息接收者,然后启动一个协程循环处理接收到的事件,根据不同的事件类型,调用ExecutionConsumer的不同函数。特别说明一下,事件在内部是channel实现的生产者/消费者模型,只有一个缓冲区,如果处理不及时会出现消息等待的情况,在实际产品化过程中需要进行优化。
- Stack
// Stack is the set of stack-facing methods available to the consensus plugin
type Stack interface
NetworkStack // 网络消息发送和接收接口
SecurityUtils // Sign和Verify接口
Executor // 事件消息处理接口
LegacyExecutor // 交易处理接口
LedgerManager // 控制ledger的状态
ReadOnlyLedger // 操作blockchain
StatePersistor // 操作共识状态
这个接口的实现都是在helper中实现的,这里只是统一的抽象出来便于实现共识算法插件的时候调用。
- newTimerImpl
// timerStart is used to deliver the start request to the eventTimer thread
type timerStart struct
hard bool // Whether to reset the timer if it is running
event Event // What event to push onto the event queue
duration time.Duration // How long to wait before sending the event
// timerImpl is an implementation of Timer
type timerImpl struct
threaded // Gives us the exit chan
timerChan <-chan time.Time // When non-nil, counts down to preparing to do the event
startChan chan *timerStart // Channel to deliver the timer start events to the service go routine
stopChan chan struct // Channel to deliver the timer stop events to the service go routine
manager Manager // The event manager to deliver the event to after timer expiration
timerStart指定了几个参数:timer还未到时间前是否可以重置、消息队列、超时时间。timerImpl在初始化的时候,启动一个协程,循环检测startChan、stopChan、timerChan、event、exit等是否消息,对timer进行操作,比如停止、重启等。
- MessageFan
// Message encapsulates an OpenchainMessage with sender information
type Message struct
Msg *pb.Message
Sender *pb.PeerID
// MessageFan contains the reference to the peer's MessageHandlerCoordinator
type MessageFan struct
ins map[*pb.PeerID]<-chan *Message
out chan *Message
lock sync.Mutex
MessageFan类似风扇,把不同PeerID的消息汇聚到一个通道统一输出。
0x02 NOOPS
NOOPS是为了演示共识算法的,要实现一个共识算法的插件,可以看看它都实现了哪些功能:
-
GetNoops返回一个插件对象 NOOPS和PBFT都是单例模式,输入参数是Stack接口:
// GetNoops returns a singleton of NOOPS func GetNoops(c consensus.Stack) consensus.Consenter if iNoops != nil iNoops = newNoops(c) return iNoops
-
实现Consenter接口 包括RecvMsg、Executed、Committed、RolledBack、StateUpdated等。
NOOPS只实现了RecvMsg接口,处理了Message_CHAIN_TRANSACTION和Message_CONSENSUS消息。对Message_CHAIN_TRANSACTION消息的处理就是把消息类型修改成Message_CONSENSUS再广播出去,对Message_CONSENSUS消息的处理就是保存下来。
0x03 PBFT
PBFT协议
#### 前提假设 #### * 分布式节点通过网络是连接在一起的 * 网络节点发送的消息可能会丢,可能会延迟到达,也可能会重复,到达顺序也可能是乱的
为什么至少要3f+1个节点
- 最坏的情况是:f个节点是有问题的,由于到达顺序的问题,有可能f个有问题的节点比正常的f个节点先返回消息,又要保证收到的正常的节点比有问题的节点多,所以需要满足N-f-f>f => N>3f,所以至少3f+1个节点
术语
- client:发出调用请求的实体
- view:连续的编号
- replica:网络节点
- primary:主节点,负责生成消息序列号
- backup:支撑节点
- state:节点状态
3阶段协议
从primary收到消息开始,每个消息都会有view的编号,每个节点都会检查是否和自己的view是相同的,代表是哪个节点发送出来的消息,源头在哪里,client收到消息也会检查该请求返回的所有消息是否是相同的view。如果过程中发现view不相同,消息就不会被处理。除了检查view之外,每个节点收到消息的时候都会检查对应的序列号n是否匹配,还会检查相同view和n的PRE-PREPARE、PREPARE消息是否匹配,从协议的连续性上提供了一定程度的安全。
每个节点收到其他节点发送的消息,能够验证其签名确认发送来源,但并不能确认发送节点是否伪造了消息,PBFT采用的办法就是数数,看有多少节点发送了相同的消息,在有问题的节点数有限的情况下,就能判断哪些节点发送的消息是真实的。REQUEST和PRE-PREPARE阶段还不涉及到消息的真实性,只是独立的生成或者确认view和序列号n,所以收到消息判断来源后就广播出去了。PREPARE阶段开始会汇总消息,通过数数判断消息的真实性。PREPARE消息是收到PRE-PREPARE消息的节点发送出来的,primary收到REQUEST消息后不会给自己发送PRE-PREPARE消息,也不会发送PRE-PREPARE消息,所以一个节点收到的消息数满足2f+1-1=2f个就能满足没问题的节点数比有问题节点多了(包括自身节点)。COMMIT阶段primary节点也会在收到PREPARE消息后发送COMMIT消息,所以收到的消息数满足2f+1个就能满足没问题的节点数比有问题节点多了(包括自身节点)。
PRE-PREPARE和PREPARE阶段保证了所有正常的节点对请求的处理顺序达成一致,它能够保证如果PREPARE(m, v, n, i) 是真的话,PREPARE(m’, v, n, j) 就一定是假的,其中j是任意一个正常节点的编号,只要D(m) != D(m’)。因为如果有3f+1个节点,至少有f+1个正常的节点发送了PRE-PREPARE和PREPARE消息,所以如果PREPARE(m’, v, n, j) 是真的话,这些节点中就至少有一个节点发了不同的PRE-PREPARE或者PREPARE消息,这和它是正常的节点不一致。当然,还有一个假设是安全强度是足够的,能够保证m != m’时,D(m) != D(m’),D(m) 是消息m的摘要。
确定好了每个请求的处理顺序,怎么能保证按照顺序执行呢?网络消息都是无序到达的,每个节点达成一致的顺序也是不一样的,有可能在某个节点上n比n-1先达成一致。其实每个节点都会把PRE-PREPARE、PREPARE和COMMIT消息缓存起来,它们都会有一个状态来标识现在处理的情况,然后再按顺序处理。而且序列号n在不同view中也是连续的,所以n-1处理完了,处理n就好了。
VIEW-CHANGE
上图是发生VIEW-CHANGE的一种情况,就是节点正常收到PRE-PREPARE消息以后都会启动一个定时器,如果在设置的时间内都没有收到回复,就会触发VIEW-CHANGE,该节点就不会再接收除CHECKPOINT 、VIEW-CHANGE和NEW-VIEW等消息外的其他消息了。NEW-VIEW是由新一轮的primary节点发送的,O是不包含捎带的REQUEST的PRE-PREPARE消息集合,计算方法如下: * primary节点确定V中最新的稳定检查点序列号min-s和PRE-PREPARE消息中最大的序列号max-s * 对min-s和max-s之间每个序列号n都生成一个PRE-PREPARE消息。这可能有两种情况: - P的VIEW-CHANGE消息中至少存在一个集合,序列号是n - 不存在上面的集合
第一种情况,会生成新的PRE-PREPARE消息<PRE-PREPARE, v+1, n, d>
以上是关于Hyperleger源码分析--共识算法的主要内容,如果未能解决你的问题,请参考以下文章