【区块链】Tendermint——实体结构

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

组成区块的最重要内容就是交易数据了,就从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

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既区块头。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共识引擎管理,遵从如下原则:

  • 时间单调性:时间单调增加,例如分配header H1给height为h1的区块,则header H2满足“height h2=h1+1、H1.Time<H2.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

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

发布了27 篇原创文章 · 获赞 6 · 访问量 3688

猜你喜欢

转载自blog.csdn.net/xk_xx/article/details/104133178
今日推荐