前段时间写了篇博客说了说自己对“初链”白皮书和黄皮书的解读,其中一部分涉及到对“初链”混合共识和双链技术的解读,由于是从说明文档中获取的信息,难免会有误解,虽然大体上和实际的处理逻辑相符合,但在细节上还是有一定的出入。出于对混合共识技术和双链逻辑的好奇,博主我下来抽空余时间仔细阅读了下“初链”项目共识部分的代码,感悟颇深,遂写文分享。本篇博客的目的是让对区块链有一定了解且有一定技术开发经验的朋友快速、深入地了解“初链”混合共识机制的处理逻辑,主要以源代码翻译伪代码(基于“初链”Beta版本)的形式讲解各个处理过程,因为只有用伪代码的方式才能直观的表述一个处理过程的细节,但我并不会过分深入每一个细节。本篇博客包括以下内容:
- 快链(FastChain)、慢链(SnailChain)的区块结构、区块验证和交互机制
- 慢链(SnailChain)的区块打包、挖矿和奖励分配机制
- 快链(FastChain)的区块打包和拜占庭委员会(PBFT)的共识机制
- 拜占庭委员会(PBFT)的选举和验证机制
- 抗ASIC专用矿机的POW挖矿算法(TrueHash)
- “初链”和以太坊的关系
- “初链”的代码审查意见
- 总结
> 快链(FastChain)、慢链(SnailChain)的区块结构、区块验证和交互机制
“初链”采用双链结构设计,目的是为了分离交易确认和算力保护。快链区块直接打包交易,交易打包成区块后经过拜占庭委员会(PBFT)的共识即被确认,此过程很快。慢链区块包含快链区块的内容,通过挖矿完成慢链区块的打包,慢链采用工作量证明(POW)机制,旨在通过算力保护整个区块链和拜占庭委员会的安全。
快链区块结构(FastBlock:code/types/block.go)<
// Header represents a block header in the true Fastblockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
SnailHash common.Hash `json:"snailHash" gencodec:"required"`
SnailNumber *big.Int `json:"snailNumber" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time *big.Int `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
}
// FastBlock represents an entire block in the Ethereum blockchain.
type Block struct {
header *Header
transactions Transactions
uncles []*Header // reserved for compile
signs PbftSigns
// caches
hash atomic.Value
size atomic.Value
// Td is used by package core to store the total difficulty
// of the chain up to and including the block.
// td *big.Int
// These fields are used by package etrue to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{
}
}
在快链区块结构中,最重要的两个属性就是"transactions"和"signs",在区块头中也可以看到有“TxHash”、“GasLimit”和“GasUsed”这三个属性,这表明了快链区块的主要作用是执行交易、收集交易和收集拜占庭委员会成员的签名。
慢链区块结构(FastBlock:code/types/block.go)<
// Header represents a block header in the Ethereum truechain.
type SnailHeader struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
PointerHash common.Hash `json:"PointerHash" gencodec:"required"`
FruitsHash common.Hash `json:"fruitsHash" gencodec:"required"`
FastHash common.Hash `json:"fastHash" gencodec:"required"`
FastNumber *big.Int `json:"fastNumber" gencodec:"required"`
SignHash common.Hash `json:"signHash" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
Publickey []byte `json:"Publickey" gencodec:"required"`
ToElect bool `json:"ToElect" gencodec:"required"`
Time *big.Int `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash" gencodec:"required"`
Nonce BlockNonce `json:"nonce" gencodec:"required"`
Fruit bool
}
// Block represents an entire block in the Ethereum blockchain.
type SnailBlock struct {
header *SnailHeader
fruits SnailBlocks
signs PbftSigns
uncles []*SnailHeader
// caches
hash atomic.Value
size atomic.Value
// Td is used by package core to store the total difficulty
// of the chain up to and including the block.
td *big.Int
// These fields are used by package etrue to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{
}
}
慢链区块有点特殊,在“初链”中包含“水果(Fruit)”的概念,而水果也是通过慢链区块来表示的。在慢链区块中包含“fruits”、“signs”属性,在区块头中则包含很多属性,其中包括有“Coinbase”、“PointerHash”、“FruitsHash”、“FastHash”、“SignHash”、“ToElect”和“Fruit”属性。这些里面最能直观反应慢链区块的作用的就是块中的“fruits”、“signs”属性和头中“Fruit”属性。区块头中的“Fruit”布尔属性表明该区块是不是一个水果,如果是水果,那在块中就有“signs”属性填充,否则就是一个普通区块,在块中有“fruits”属性填充。这三个属性直观地表明了“水果”和“区块”的关系:水果是包含了一个快链区块(FastBlock)中拜占庭委员会成员签名的区块,而真正意义上的区块则是包含了多个连续排列的水果(头中“FastNumber”属性顺序排列)的区块。
快链区块的验证(VerifyFastBlock:etrue/pbft_agent.go)<
拜占庭共识委员会在收集P2P网络中的交易并产生快链区块后,或者收到其他委员会广播的区块后,会进行区块验证,该算法的流程大致是:
- 验证父区块是否存在
- 验证区块头
- 验证区块体
- 验证交易和状态数据库
伪代码如下:
// 传入快链区块 => fb type.FastBlock,bc type.BlockChain
// 验证父区块
parent = bc.GetBlock(fb.ParentHash(), fb.Number()-1)
if parent == nil {
return error("height not yet")
}
// 验证区块头
header = fb.Header()
if len(header.Extra) > 32 {
return error("extra too long")
}
if header.Time - Time.Now() > 15 * Time.Second {
return error("future block")
}
if header.Time < parent.Time {
return error("zero block time")
}
if header.GasLimit > 0x7fffffffffffffff {
return error("invalid gas used")
}
if abs(parent.GasLimit - header.GasLimit) >= (parent.GasLimit / 1024) || header.GasLimit < 5000 {
return error("invalid gas limit")
}
if header.Number - parent.Number != 1 {
return error("invalid number")
}
// 验证区块体
if Hash(fb.Transactions()) != header.TxHash {
return error("transaction root hash mismatch")
}
// 验证交易和状态数据库
stateDB = fb.State()
receipts, usedGas = bc.Process(fb.Transactions(), stateDB)
if fb.GasUsed() != usedGas {
return error("invalid gas used")
}
if CreateBloomHash(receipts) != header.Bloom {
return error("invalid bloom")
}
if Hash(receipts) != header.ReceiptHash {
return error("nvalid receipt root hash")
}
if Hash(state) != header.Root {
return error("invalid merkle root")
}
return nil
慢链区块的验证(ValidateFruit:core/snailchain/block_validator.go、VerifySnailHeader:consensus/minerva/consensus.go)<
慢链区块由于分为水果和区块,所以有两种验证过程:
- 区块
a) 验证区块头
b) 验证区块体- 水果
a) 验证委员会签名
b) 验证水果新鲜度
c) 验证区块头
验证区块伪代码如下:
// 传入慢链区块 => sb type.SnailBlock,bc type.SnailBlockChain
// 验证区块头
parent = bc.GetBlock(sb.ParentHash(), sb.Number()-1)
if parent == nil {
return error("unkown ancestor")
}
header = fb.Header()
if len(header.Extra) > 32 {
return error("extra too long")
}
if header.Time - Time.Now() > 15 * Time.Second {
return error("future block")
}
if header.Time < parent.Time {
return error("zero block time")
}
expectedDifficulty = CalcSnailDifficulty(bc, header.Time, parent)
if expectedDifficulty != header.Difficulty {
return error("invalid difficulty")
}
digest, hashResult = truehash(header.HashWithoutNonce(), header.Nonce) // 挖矿时计算的数据指纹和哈希结果
if header.MixDigest != Hash(digest) {
return error("invalid mixdigest")
}
maxUint128 = 2 ^ 128 - 1
if hashResult.SubBytes(0, 15) > (maxUint128 / header.Difficulty) {
// hashResult取前16字节
return error("invalid pow")
}
// 验证区块体
for fruit in range sb.Fruits() {
if err = ValidateFruit(fruit, bc); err != nil {
return err
}
}
return nil
验证水果伪代码如下:
// 传入水果 => f type.SnailBlock,bc type.SnailBlockChain
// 验证委员会签名
header = f.Header()
if Hash(f.Signs()) != header.SignHash {
return error("invalid sign")
}
// 验证水果新鲜度
pointer = bc.GetBlockByHash(f.PointerHash()) // pointer区块是在水果生成时从当前慢链区块往前数7个的那个区块
if pointer == nil {
return error("invalid pointer")
}
if bc.CurrentBlock().Number() - pointer.Number() > 17 {
// 生成水果时指定的区块高高度比最新的区块高度低17位时则水果过期
return error("invalid freshness") // 水果的生存周期只有(17-7)=10个区块,每个区块生产周期为10分钟,所以水果的新鲜度只有平均1个小时40分钟左右
}
// 验证区块头
parent = bc.GetBlock(f.ParentHash(), f.Number()-1)
if parent == nil {
return error("unkown ancestor")
}
if len(header.Extra) > 32 {
return error("extra too long")
}
digest, hashResult = truehash(header.HashWithoutNonce(), header.Nonce)
if header.MixDigest != Hash(digest) {
return error("invalid mixdigest")
}
fruitDifficulty = pointer