Tendermint共识引擎,将绝大多数node的共识记录到一条区块链中,并在所有node间复制。这条区块链可以通过各种RPC访问命令,比如从获取整条区块数据的/block?height= 命令,到获取区块头列表的/blockchain?minHeight=&maxHeight=命令,应有尽有。那么这些区块中到底存储了哪些东西呢?
区块链的魔力就在于区块中包含了一批交易、区块描述信息,并链接之前的区块。而这个“链”由两种形式组成:前区块散列;以及一组使得前区块得以commit的precommits数据(也就是下面要谈到的区块组成部分“LastCommit”)。那么,一个区块将包含三个主要部分:区块头、交易列表以及LastCommit。
另外,如果在区块链中发现一次对多个区块进行propose或vote的恶意行为,其他validator可以以交易的形式发布违规evidence,Tendermint将删除违规者,为validator清理门户。因此,区块还将包含evidence数据,这个区块就是下图中的模样。
为探讨区块实体结构,在接下来的部分,将主要围绕组成区块的实体结构展开。Block结构体定义如下所示:
type Block struct {
Header `json:"header"`
Data `json:"data"`
Evidence EvidenceData `json:"evidence"`
LastCommit *Commit `json:"last_commit"`
}
组成区块的最重要内容就是交易数据了,就从Data结构体开始。
说个题外话,根据官方测算,64个validators在跨互联网的环境中,Tendermint的交易吞吐量能达到4000Txs/S。这对于我这种心心念念要把区块链应用在信息安全领域的用户来说,是个好消息。相信长期关注本订阅号的读者对下图不陌生:
是的,在《也许,只有信息安全才是区块链的未来(上)》中提到:“比特币网络每秒只能处理约7笔交易”。有人会说“你这才64个节点,当然快啊”。我想回答的是“服务于信息安全的私有链无需太多的节点”。至于如何将私有链服务于信息安全,想清楚并有了最佳实践后,《也许,只有信息安全才是区块链的未来(下)》就可以提笔了。
而在不跨互联网的数据中心环境下,随着设备性能(32 vCPU,60GB RAM)的提高,交易吞吐量也会跟着增长。
话题转回来,下面是Data结构体定义,Data结构体是由Txs字段组成。
type Data struct {
// 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.
type Txs []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 ?
type Tx []byte
LastCommit代表前一个区块的投票信息。是Commit的指针变量,Commit则是一个简单的votes列表包装器(Precommits),每个validator对应一个vote。Commit还包含与其相关的BlockID字段。
type Commit struct {
// 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在包含上述两种散列值的同时还记录了区块的片段数。
type BlockID struct {
Hash cmn.HexBytes `json:"hash"`
PartsHeader PartSetHeader `json:"parts"`
}
而在PartSetHeader结构体中,Total字段是32位有符号整数,用于记录PartSet的总数,另外,Hash字段还记录了这些PartSet的Merkle根散列值。对于PartSet的介绍可参考《Tendermint:拜占庭容错算法》。
type PartSetHeader struct {
Total int `json:"total"`
Hash cmn.HexBytes `json:"hash"`
}
现在跳出BlockID结构体,看看Precommits字段,该字段是Vote结构体的指针数组。
type Vote struct {
Type SignedMsgType `json:"type"`
Height int64 `json:"height"`
Round int `json:"round"`
Timestamp time.Time `json:"timestamp"`
BlockID BlockID `json:"block_id"` // zero if vote is nil.
ValidatorAddress Address `json:"validator_address"`
ValidatorIndex int `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结构体由四个部分构成:区块基本信息、前区块信息、区块数据散列、前区块散列以及共识信息。
type Header struct {
// basic block info
Version version.Consensus `json:"version"`
ChainID string `json:"chain_id"`
Height int64 `json:"height"`
Time time.Time `json:"time"`
NumTxs int64 `json:"num_txs"`
TotalTxs int64 `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)。
type Consensus struct {
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共识引擎管理,遵从如下原则:
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的地址。
type EvidenceData struct {
Evidence EvidenceList `json:"evidence"`
// contains filtered or unexported fields
}
EvidenceData结构体由EvidenceList构成,是一个Evidence类型(type EvidenceList []Evidence)。而Evidence实际上是个接口。
type Evidence interface {
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(chainID string, 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结构体。
func RegisterEvidences(cdc *amino.Codec) {
cdc.RegisterInterface((*Evidence)(nil), nil)
cdc.RegisterConcrete(&DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil)
}
// DuplicateVoteEvidence contains evidence a validator signed two conflicting
// votes.
type DuplicateVoteEvidence struct {
PubKey crypto.PubKey
VoteA *Vote
VoteB *Vote
}
var _ Evidence = &DuplicateVoteEvidence{}
// String returns a string representation of the evidence.
func (dve *DuplicateVoteEvidence) String() string {
return fmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)
}
......
...
DuplicateVoteEvidence实现了检测某个具体validator进行两次存在冲突投票的拜占庭行为的能力。
本文转载于:https://zhuanlan.zhihu.com/p/51432128
作者:Rosen Jiang