Tendermint:实体结构

Tendermint共识引擎,将绝大多数node的共识记录到一条区块链中,并在所有node间复制。这条区块链可以通过各种RPC访问命令,比如从获取整条区块数据的/block?height= 命令,到获取区块头列表的/blockchain?minHeight=&maxHeight=命令,应有尽有。那么这些区块中到底存储了哪些东西呢?

区块链的魔力就在于区块中包含了一批交易、区块描述信息,并链接之前的区块。而这个“链”由两种形式组成:前区块散列;以及一组使得前区块得以commit的precommits数据(也就是下面要谈到的区块组成部分“LastCommit”)。那么,一个区块将包含三个主要部分:区块头、交易列表以及LastCommit。

另外,如果在区块链中发现一次对多个区块进行propose或vote的恶意行为,其他validator可以以交易的形式发布违规evidence,Tendermint将删除违规者,为validator清理门户。因此,区块还将包含evidence数据,这个区块就是下图中的模样。

为探讨区块实体结构,在接下来的部分,将主要围绕组成区块的实体结构展开。Block结构体定义如下所示:

typeBlockstruct{

Header`json:"header"`

Data`json:"data"`

Evidence EvidenceData`json:"evidence"`

LastCommit *Commit`json:"last_commit"`

}

Data

组成区块的最重要内容就是交易数据了,就从Data结构体开始。

说个题外话,根据官方测算,64个validators在跨互联网的环境中,Tendermint的交易吞吐量能达到4000Txs/S。这对于我这种心心念念要把区块链应用在信息安全领域的用户来说,是个好消息。相信长期关注本订阅号的读者对下图不陌生:

是的,在《

也许,只有信息安全才是区块链的未来(上)

》中提到:“比特币网络每秒只能处理约7笔交易”。有人会说“你这才64个节点,当然快啊”。我想回答的是“服务于信息安全的私有链无需太多的节点”。至于如何将私有链服务于信息安全,想清楚并有了最佳实践后,《也许,只有信息安全才是区块链的未来(下)》就可以提笔了。

而在不跨互联网的数据中心环境下,随着设备性能(32 vCPU,60GB RAM)的提高,交易吞吐量也会跟着增长。

话题转回来,下面是Data结构体定义,Data结构体是由Txs字段组成。

typeDatastruct{

// Txs that will be applied by state @ block.Height+1.

//NOTE:not all txs here are valid. We're just agreeing on the order first.

// This means that block.AppHash does not include these txs.

Txs Txs`json:"txs"`

}

而通过进一步观察代码会发现,类型Txs是Tx数组的切片。

// Txs is a slice of Tx.

typeTxs []Tx

Tx又是什么呢,Tx是由任意字节数组构成的类型。到这里就完全明白了,区块中的交易数据内容是可变的。再将这个原则联系到比特币网络呢,人们常说比特币网络实际上是分布式账本,的确,其交易内容就是转账记录。

// Tx is an arbitrary byte array.

//NOTE:Tx has no types at this level, so when wire encoded it's just length-prefixed.

// Might we want types here ?

typeTx []byte

LastCommit

LastCommit代表前一个区块的投票信息。是Commit的指针变量,Commit则是一个简单的votes列表包装器(Precommits),每个validator对应一个vote。Commit还包含与其相关的BlockID字段。

typeCommitstruct{

//NOTE:The Precommits are in order of address to preserve the bonded ValidatorSet order.

// Any peer with a block can gossip precommits by index with a peer without recalculating the

// active ValidatorSet.

BlockID BlockID`json:"block_id"`

Precommits []*Vote`json:"precommits"`

// contains filtered or unexported fields

}

BlockID包含了区块的两种不同Merkle根散列值。第一种是做为区块的主散列(hash),它是header中所有字段的Merkle根散列值;第二种是服务于区块共识阶段的安全协商(包含在PartsHeader字段中),是将整个区块序列化后切分成片段的Merkle根散列值。所以,BlockID在包含上述两种散列值的同时还记录了区块的片段数。

typeBlockIDstruct{

Hash cmn.HexBytes`json:"hash"`

PartsHeader PartSetHeader`json:"parts"`

}

而在PartSetHeader结构体中,Total字段是32位有符号整数,用于记录PartSet的总数,另外,Hash字段还记录了这些PartSet的Merkle根散列值。对于PartSet的介绍可参考《Tendermint:拜占庭容错算法》。

typePartSetHeaderstruct{

Totalint`json:"total"`

Hash cmn.HexBytes`json:"hash"`

}

现在跳出BlockID结构体,看看Precommits字段,该字段是Vote结构体的指针数组。

typeVotestruct{

Type SignedMsgType`json:"type"`

Heightint64`json:"height"`

Roundint`json:"round"`

Timestamp time.Time`json:"timestamp"`

BlockID BlockID`json:"block_id"`// zero if vote is nil.

ValidatorAddress Address`json:"validator_address"`

ValidatorIndexint`json:"validator_index"`

Signature []byte`json:"signature"`

}

vote包含了validator的签名信息。其中,字段Type是字节类型SignedMsgType(type SignedMsgType byte),代表vote的类别,例如vote.Type == 1是prevote、vote.Type == 2是precommit。

Height字段表示链上顺序增长的区块位置编号,是64位有符号整数。

Round字段表示当前round的情况,是32位有符号整数。

Timestamp字段是64位有符号整数,以毫秒为单位的UNIX时间。

ValidatorAddress字段是crypto包的类型Address,是16进制编码的字节数组,用于标识validator的地址。

ValidatorIndex是32位有符号整数,用于标识validator的序号。

Signature是字节数组,用于标识validator的数字签名。Tendermint目前仅支持ED25519算法。

Header

这部分内容多,是区块链“链”的奥妙所在,Header既区块头。Header结构体由四个部分构成:区块基本信息、前区块信息、区块数据散列、前区块散列以及共识信息。

typeHeaderstruct{

// basic block info

Version version.Consensus`json:"version"`

ChainIDstring`json:"chain_id"`

Heightint64`json:"height"`

Time time.Time`json:"time"`

NumTxsint64`json:"num_txs"`

TotalTxsint64`json:"total_txs"`

// prev block info

LastBlockID BlockID`json:"last_block_id"`

// hashes of block data

LastCommitHash cmn.HexBytes`json:"last_commit_hash"`// commit from validators from the last block

DataHash cmn.HexBytes`json:"data_hash"`// transactions

// hashes from the app output from the prev block

ValidatorsHash cmn.HexBytes`json:"validators_hash"`// validators for the current block

NextValidatorsHash cmn.HexBytes`json:"next_validators_hash"`// validators for the next block

ConsensusHash cmn.HexBytes`json:"consensus_hash"`// consensus params for current block

AppHash cmn.HexBytes`json:"app_hash"`// state after txs from the previous block

LastResultsHash cmn.HexBytes`json:"last_results_hash"`// root hash of all results from the txs from the previous block

// consensus info

EvidenceHash cmn.HexBytes`json:"evidence_hash"`// evidence included in the block

ProposerAddress Address`json:"proposer_address"`// original proposer of the block

}

区块基本信息

Version字段用来标识区块链和应用程序协议的版本。由version包的Consensus结构体定义。而类型Protocol则是64位无符号整数(type Protocol uint64)。

typeConsensusstruct{

Block Protocol`json:"block"`

App Protocol`json:"app"`

}

ChainID字段是最大长度为50的UTF-8字符串,该字段在“genesis.json”文件中定义,例如ChainID为“test-chain-nlXLFL”的区块链。

Height字段表示链上顺序增长的区块位置编号,是64位有符号整数。注意,第一个区块遵从“block.Header.Height == 1”。

Time字段是64位有符号整数,以毫秒为单位的UNIX时间。受Tendermint共识引擎管理,遵从如下原则:

时间单调性:时间单调增加,例如分配header H1给height为h1的区块,则header H2满足“height h2=h1+1、H1.Time

时间有效性:从block.LastCommit字段给定一组Commit votes,区块头中的Time字段值的有效范围仅由正确进程发送的Precommit消息定义(来自LastCommit字段),也就是错误的进程不能任意增加Time的值。

NumTxs字段是64位有符号整数,记录本区块中的交易数量。

TotalTxs字段是64位有符号整数,记录本区块链中包含所有交易数量的总和。注意,第一个区块遵从“block.Header.TotalTxs = block.Header.NumTxs”。

前区块信息

为了将区块链接在一起,LastBlockID字段是前区块的BlockID。注意,第一个区块遵从“block.Header.LastBlockID == BlockID{}”。

区块数据散列

LastCommitHash字段是16进制编码的字节数组,其值和LastCommitHash的Merkle根散列值一致。注意,在第一个区块中,遵从“block.Header.LastCommitHash == []byte{}”。

DataHash字段是16进制编码的字节数组,代表当前区块交易数据的Merkle根散列值。

前区块散列

ValidatorsHash字段是16进制编码的字节数组,代表当前区块validator列表的Merkle根散列值。还可以用于验证下一个区块中包含的LastCommit字段。

NextValidatorsHash字段是16进制编码的字节数组,代表可参与下一区块共识的validator列表。

ConsensusHash字段是16进制编码的字节数组,代表当前区块共识参数的amino编码散列值。

AppHash字段是16进制编码的字节数组,代表Tendermint网络在执行和提交前一个区块后返回的任意字节数组。它是验证来自ABCI接口的任何merkle证明的基础,反映的是实际应用的状态,而不是区块链本身的状态。第一个区块遵从“block.Header.AppHash == []byte{}”。

LastResultsHash字段是16进制编码的字节数组,代表前一个区块交易结果的Merkle散列值。

共识信息

EvidenceHash字段是16进制编码的字节数组,代表本区块中有拜占庭行为,也就是evidence的Merkle根散列值。

ProposerAddress字段是crypto包的类型Address,是16进制编码的字节数组,代表本区块proposer的地址。

Evidence

typeEvidenceDatastruct{

Evidence EvidenceList`json:"evidence"`

// contains filtered or unexported fields

}

EvidenceData结构体由EvidenceList构成,是一个Evidence类型(type EvidenceList []Evidence)。而Evidence实际上是个接口。

typeEvidenceinterface{

Height()int64// height of the equivocation

Address() []byte// address of the equivocating validator

Bytes() []byte// bytes which compromise the evidence

Hash() []byte// hash of the evidence

Verify(chainIDstring, pubKey crypto.PubKey) error// verify the evidence

Equal(Evidence)bool// check equality of evidence

ValidateBasic() error

String()string

}

这意味着任何Evidence的实现都可以用Amino前缀编码。Amino是一个编码库,可以很好地处理接口(和protobuf的”oneof”一样)。通过在每个“具体类型”加前缀加字节来实现。通过下面的操作就能实现符合Evidence接口的DuplicateVoteEvidence结构体。

funcRegisterEvidences(cdc *amino.Codec){

cdc.RegisterInterface((*Evidence)(nil),nil)

cdc.RegisterConcrete(&DuplicateVoteEvidence{},"tendermint/DuplicateVoteEvidence",nil)

}

// DuplicateVoteEvidence contains evidence a validator signed two conflicting

// votes.

typeDuplicateVoteEvidencestruct{

PubKey crypto.PubKey

VoteA *Vote

VoteB *Vote

}

var_ Evidence = &DuplicateVoteEvidence{}

// String returns a string representation of the evidence.

func(dve *DuplicateVoteEvidence)String()string{

returnfmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)

}

......

...

DuplicateVoteEvidence实现了检测某个具体validator进行两次存在冲突投票的拜占庭行为的能力。

PS:作者保留对本文任何形式的勘误。

AD Time

关于作者: 十年网络安全行业,八年Java EE研发、架构、管理,三年开源对象数据库社区运营,一年大数据、机器学习产线管理,知乎@rosenjiang。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181202G18G3Y00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券