Hyperleger--共识算法 (2)

共识算法(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阶段协议

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

上图是发生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-smax-s之间每个序列号n都生成一个PRE-PREPARE消息。这可能有两种情况: - P的VIEW-CHANGE消息中至少存在一个集合,序列号是n - 不存在上面的集合

第一种情况,会生成新的PRE-PREPARE消息<PRE-PREPARE, v+1, nd>

Hyperledger Fabric是一个面向企业应用的区块链平台,智能合约也称为链码(Chaincode),是Hyperledger Fabric的核心概念之一。在Hyperledger Fabric中,链码是一段运行在区块链节点上的代码,它可以实现各种业务逻辑,例如资产转移、身份验证、规则检查等等。本文将介绍Hyperledger Fabric的链码开发。 一、链码类型 在Hyperledger Fabric中,链码分为两种类型:Go和Java。Go链码采用Go语言编写,Java链码则采用Java语言编写。Go链码的优点是运行速度快、部署简单;Java链码的优点是易于开发和维护。 二、链码开发 链码是Hyperledger Fabric应用的核心组件,它实现了业务逻辑、数据存储和访问控制等功能。链码开发的步骤如下: 1、创建链码项目 使用命令行工具创建一个新的链码项目: ``` $ mkdir mychaincode $ cd mychaincode $ touch main.go ``` 2、编写链码 在main.go文件中编写链码的业务逻辑。下面是一个简单的示例代码: ```go package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type MyChaincode struct { } func (t *MyChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (t *MyChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() if function == "hello" { return t.hello(stub, args) } return shim.Error("Invalid function name.") } func (t *MyChaincode) hello(stub shim.ChaincodeStubInterface, args []string) pb.Response { name := args[0] message := fmt.Sprintf("Hello, %s!", name) return shim.Success([]byte(message)) } func main() { err := shim.Start(new(MyChaincode)) if err != nil { fmt.Printf("Error starting chaincode: %s", err) } } ``` 在上面的代码中,我们定义了一个名为MyChaincode的结构体,实现了Init和Invoke两个方法。Init方法用于初始化链码,Invoke方法用于处理链码的各种请求。在Invoke方法中,我们根据不同的请求类型来调用不同的函数。例如,当请求类型为hello时,会调用hello函数来返回一条问候消息。 3、编译链码 使用以下命令编译链码: ``` $ go build ``` 4、部署链码 使用以下命令将链码部署到Hyperledger Fabric网络中: ``` $ peer chaincode deploy -n mychaincode -v 1.0 -p /path/to/chaincode -c '{"Args":[]}' -C mychannel ``` 在上面的命令中,-n参数指定链码的名称,-v参数指定链码的版本号,-p参数指定链码的路径,-c参数指定链码的初始化参数,-C参数指定链码所属的通道。 5、调用链码 使用以下命令调用链码: ``` $ peer chaincode invoke -n mychaincode -c '{"Args":["hello", "World"]}' -C mychannel ``` 在上面的命令中,-n参数指定链码的名称,-c参数指定调用链码的参数,-C参数指定链码所属的通道。 三、总结 本文介绍了Hyperledger Fabric的链码开发,包括链码类型、链码开发步骤等。链码是Hyperledger Fabric应用的核心组件,它实现了业务逻辑、数据存储和访问控制等功能。在实际应用中,我们可以根据实际需求开发不同类型的链码,以实现各种业务逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值