我们在前文讲述了许多区块链这几年发展演进过来的共识机制。在之前的内容中,我们讲述的共识多属于区块链1.o与2.0的知识。这次,我们着重讲述下区块链3.0时代的HyperLedger Fabric中的共识机制以及相关特性。而今,比特币与以太坊多用于币圈的应用,然而根据行业的发现现状来看,超级账本的未来发展更倾向于商业落地项目,所以对这类机制的深入研究对我们后续的发展大有裨益。
Fabric是Hyperledger项目组的一个项目。从区块链的演进过程中看,Fabric属于区块链3.0的技术范畴内。但是其用时具有区块链1.0与2.0系统的特性,比如其可以共享账本、具有智能合约,可以通过共识算法确保数据的安全。但是作为一个典型的区块链3.0技术平台,Fabric中存在和其他区块链不同的技术来保证更适合项目的落地。
在Fabric中不同的会员发起的交易是按照一定的顺序写入区块链中的,区块链之间连接起来之后形成区块链。在交易排序的过程中需要防止恶意篡改。对于比特币而已,其采用了Pow,并解决了这个问题。而对于Fabric而言,它支持Solo(单节点共识)、Kafka(分布式队列)以及SBFT(简单拜占庭容错)三种共识方式。而前两种已经被应用于Fabric中,第三种会在未来被应用实践。
在Fabric中,共识过程意味着多个Peer节点对于某一批交易的发生顺序、合法性以及它们对账本状态的更新达成一致的观点。
要想真正的了解Fabric共识是如何协作的,我们就要了解其中各个组成部分。在Fabric中,共识是通过背书、排序和验证三个环节的保障。
背书过程:背书节点对收到的来自客户端的请求(交易提案)按照自身的逻辑进行检查,以决策是否予以支持的过程。
对于调用某个链码的交易来讲,它需要获得一定条件的背书才被认为合法。例如,必须是来自某些特定身份成员的一致同意;或者某个组织中超过一定数目的成员的支持等等;这些背书策略可以由链码在实例化之前来指定。
排序过程:对一段时间内的一批交易达成一个网络内全局一致的顺序。
Fabric中,采用可插拔式的架构,solo模式、Kafka在内的CFT类型后端、BFT类型后端。
验证过程:对排序后的一批交易进行提交到账本之前最终检查的过程。
验证过程包括验证交易结构自身完整性,背书签名是否满足背书策略,交易的读写集是否满足多版本并发控制等。
而其中最重要的当属排序服务 。在Fabric中所有交易在交付给Committer进行验证接受之前,需要先经过排序服务进行全局排序。排序服务提供了原子广播排序功能。 只有经过了排序,各个交易才有其顺序,任务才能被合理的执行。
Solo方法是指在单个节点中完成的排序算法,这种模式的安全性和稳定性都比较差,如果单点出现问题,那么整个区块链系统将无法正常运行。因此Solo也就是被用在演示系统与本机开发中。
Solo机制是一个非常容易部署的非生产环境的共识排序节点。它只有一个客户服务端单节点,所以不需要进行“共识”。因为其有中央权威机构,所以相应的其并不具备高可用性或可扩展性。order-solo模式作为单节点通信模式,所有从peer收到的消息都在本节点进行排序与生成数据块。
Kafka是一种高吞吐的分布式发布订阅消息系统。在Fabric的Kafka模式中,排序节点从kafka集群中获取了相应的topic数据,用以保证交易的有序性。借助Kafka的特性,排序节点还可以进行集群化处理,能够有效的避免单点因故障而导致整个网络的崩溃问题。具体来说,其是一种支持多通道分区的集群时序服务,可以容忍部分节点失效(crash),但不能容忍恶意节点,其基于zookeeper进行Paxos算法选举,支持2f+1节点集群,f代表失效节点个数。即kafka可以容忍少于半数的共识节点失效。
具体过程分为以下两步,①在任务下发后,节点向endorser节点发送背书请求。节点针对身份、签名和读写集的验证后标记其为正确信息。而背书节点会预先执行一遍相关任务,包括验证任务是否合法,模拟任务执行后的结果等。之后我们进入第二步。②之后任务会下发到orderer节点中,而在单节点的solo中,此orderer只有一个,所以不涉及集群的协作排序。但是kafka中涉及到了多个orderer节点,所以其核心作用就是将全局的tx(通过背书请求后的)作为输入,在集群内形成统一的、唯一的、确定的、经过排序的tx输出。
具体的内容可以参考Kafka共识机制剖析
在Fabric诞生之前的区块链平台均只有一个账本,所有的记录均在一个账本中。这存在许多弊端,例如这会导致这个账本存储量十分巨大,例如现在的比特币账本已经有160G。随着时间的推移,这种设计是有很大的问题的。在Fabric中,我们又私有链、联盟链与公有链。对于公有链而言,其弊端与比特币系统一致,但是对于其他链的模式而言,Fabric采用了多账本的设计模式。
在Fabric中有通道的概念。而一个通道包含若干成员,这些成员共享同一个账本并且共享账本数据以及维护账本。对于不同的通道,其账本的数据格式是不同的。在Fabric中,账本的存储方式被设计成插件的模式,不同的会员可以根据情况选择不同的存储方式。
其有如下特点:
1 使用了键值对查询,包括范围查询以及复合键查询来更新账本。
2 使用丰富的查询语句(CouchDB)。
3 拥有只读的历史查询——实现数据追溯。
4 交易被打包排序成区块,并通过通道从共识节点传递至对等节点。然后包含背书节点,机器提交至排序节点进行签名。
5 通道包含了会员服务提供商,因此加密证书能传递到不同的证书颁发机构。
Fabric中的智能合约被称为链码(Chaincode),而Chaincode是一段使用计算机语言编写的程序。其运行在容器中,Fabric通过调用链码来读取和修改账本的数据,同时将交易的日志保存在状态数据库。其可以使用Go、java、node.js进行开发。
下面向读者展示下使用GO语言所编写的链码。
GO的包的引入
package main
import (
//"bytes"
"encoding/json"
"fmt"
"strconv"
//"strings"
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
"time"
)
这些是经常用的包
Main
main是入口
// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}
// ===================================================================================
// Main
// ===================================================================================
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
Init
chaincode 包含一个Init和一个Invoke函数。
Init是初始化的地方,还需要注意以后版本升级时能够通用
// Init initializes chaincode
// ===========================
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
Invoke
所有的调用都进入到这里,然后分发出去。
// Invoke - Our entry point
// ========================================
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
function, args := stub.GetFunctionAndParameters()
fmt.Println("invoke is running " + function)
// Handle different functions
if function == "initProvider" { // create Provider
return t.initProvider(stub, args)
} else if function == "initBeneficiary" { // create beneficiary
return t.initBeneficiary(stub, args)
}
fmt.Println("invoke did not find func: " + function) //error
return shim.Error("Received unknown function ")
}
在链码中,我们首先需要init
函数进行初始化,之后可以调用invoke
函数进行数据的查询或者插入。
在Fabric系统中,我们要知道它是依照什么机制来保证安全性的。我们知道Fabric拥有多通道的机制,那其安全性应该如何保证呢?对此,我们这里讲述一下其权限系统。
Fabric与其他区块链最大的区别就是其网络是不公开的,如想进入网络中必须获得授权,因此Fabric是联盟链的唯一代表。而我们知道比特币、以太坊是没有身份验证系统的,任何人都可以在任何时间、任何地点连接入网络中。而连入后就可以遵循Pow机制同其他人一起竞争记账权。
而在Fabric中却没有类似Pow这样的机制存在,因此成员如果想加入网络必须获取到合法的授权,否则无论你有多大的算力都不可能进入网络。为了解决成员授权的问题,Fabric中存在会员服务系统MSP。MSP是基于PKI规范的一个用户证书与私钥体系。MSP是Fabric中非常重要的内容,其包括了Fabric账号体系,并依据此生成了相应的私钥签名来保证能够顺利获得联盟的认证,并顺利收到区块链中的相应交易信息。
Casper是以太坊提出的下一代的共识机制,从原理上说,Casper属于POS。Casper的共识是按块达成的,而不是像POS那样按照链来生产。
Casper核心是基于保证金的经济激励共识协议。协议中的节点在参加系统的正常流程过程中,需要作为“锁定保证金的验证人”,必须先缴纳保证金(这一步叫做锁定保证金,"bonding")才可以参与出块和共识形成。
而保证金在这里的作用巨大。协议中的节点会通过对这些保证金的控制来直接约束参与验证人的行为举止。简单来说,如果一个验证人作出了任何Casper认为“无效”的事情,他的保证金将被罚没,出块和参与共识的权利也会被取消。这样对于节点来说代价是十分巨大的。在经典的Pos协议中做坏事的代价很低,然而保证金的引入很好的解决了"nothing at stake"问题。如今有了代价,那些做坏事的节点将会为自己的行为付出相应的代价。
在Casper共识中,我们不得不提到:下注共识 (Gambling on Consensus)的概念。
Casper共识要求参与验证的验证人将交出的大部分保证金对共识结果进行下注。共识结果又通过验证人的下注情况进行形成。类似于博弈行为,验证人需要猜测其他人会赌哪个块胜出,同时也下注这个块。如果赌对了,他们就可以拿回保证金外加交易费用,也许还会有一些新发的货币作为收益。倘若下注的结果并没有迅速达成,他们也可以拿回部分保证金。
为了防止验证人在不同世界提供不同的投注,这里还有一个简单严格的条款:如果你有两次投注序号相同,或者说你提交了一个无法让Casper合约处理的投注,你将失去所有的保证金。这种惩罚机制时刻对节点带来警示。
在经过上述的过程后,整个合约来到了最后一步:交易最终确认。
当锁定保证金的验证人中的绝大多数(满足协议定义阈值的一群验证人:保证金比例达到67%到90%之间某个百分比)以非常高的概率(例如,> 99.9%)下注某个块时,此时其他的分叉块都不可能有胜出的可能,也就是说此块已经被最终确认。
此时,所有的客户端均会接受到高度为H的块被最终确认的消息。那么用户倘若接收到高度小于H的块和顺序执行这些完全块得到的状态不一样的分叉,此时节点可以对其不采取信任的态度。
简单来讲述一下时间戳的概念:
时间戳通常是一个字符序列,能够唯一地标识某一刻时间,并能表示一份数据在某个特定时间之前是否已经存在、 是否完整、 是否是可验证的。
项目数据时间戳通常被用来调用随机性函数、根据时间戳来锁定一段时间的资金以及根据时间的各种变化的条件语句。
而根据以太坊规定,矿工处理一个新的区块时,如果新的区块的时间戳大于上一个区块,并且时间戳之差小于900秒,那么这个新区块的时间戳就是合法的。时间戳依赖顾名思义就是指智能合约的执行依赖当前区块的时间戳,随着时间戳的不同,合约的执行结果也有差别。
而矿工可以对区块的时间进行篡改,并通过设置区块的时间戳来尽可能满足有利于他的条件,从中获利。例如:
function play() public {
require(now > 999999 && neverPlayed == true);
//规定了时间要>999999,所以这里需要进行篡改处理。
neverPlayed = false;
msg.sender.transfer(999 ether);
}
下面我们可以举相关例子进行论述:
假如有一个抽奖的合约,其规则规定要根据当前的时间戳和一些变量来计算出一个“幸运数字”。而奖励规则是此数字与官方提出的数字相同则可以获得奖品。那么矿工在挖矿过程中可以提前尝试不同的时间戳来计算好这个“幸运数”,从而将奖品送给自己想给的获奖者。
对于其检测方法:我们可以为时间戳变量设置特殊符号,检测是否有依赖于该符号的路径。而时间通常用于条件判断或随机数生成,编程时用不可更改的属性来替代时间戳 (如block index)。
余额被锁死在合约中无法分发 : Ether分发函数所依赖的库函数失效。
简单来说,这是指那些永远停留在以太坊的智能合约。例如 Parity 漏洞正是一种贪婪合约,它会把智能合约所涉及的商品以及加密货币锁定在以太坊中,交易双方均无法得到,也不能取消。
漏洞特征:合约余额大于0 时没有调用CALL
函数(或调用CALL但没有发生Ether交换),DELEGATECALL
或SUICIDE
。
在那些已完成或者被关闭的智能合约中,虽然他们的代码和全局变量被清楚了,但是其中一部分仍然在继续执行。遗嘱合约和贪婪合约一样,均是由以太坊的错误引起,目前并不能被黑客利用。