mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-02 22:21:44 +00:00
docs/spec: blockchain and consensus dirs
This commit is contained in:
424
docs/spec/blockchain/blockchain.md
Normal file
424
docs/spec/blockchain/blockchain.md
Normal file
@ -0,0 +1,424 @@
|
||||
# Tendermint Blockchain
|
||||
|
||||
Here we describe the data structures in the Tendermint blockchain and the rules for validating them.
|
||||
|
||||
## Data Structures
|
||||
|
||||
The Tendermint blockchains consists of a short list of basic data types:
|
||||
|
||||
- `Block`
|
||||
- `Header`
|
||||
- `Vote`
|
||||
- `BlockID`
|
||||
- `Signature`
|
||||
- `Evidence`
|
||||
|
||||
## Block
|
||||
|
||||
A block consists of a header, a list of transactions, a list of votes (the commit),
|
||||
and a list of evidence of malfeasance (ie. signing conflicting votes).
|
||||
|
||||
```go
|
||||
type Block struct {
|
||||
Header Header
|
||||
Txs [][]byte
|
||||
LastCommit []Vote
|
||||
Evidence []Evidence
|
||||
}
|
||||
```
|
||||
|
||||
## Header
|
||||
|
||||
A block header contains metadata about the block and about the consensus, as well as commitments to
|
||||
the data in the current block, the previous block, and the results returned by the application:
|
||||
|
||||
```go
|
||||
type Header struct {
|
||||
// block metadata
|
||||
Version string // Version string
|
||||
ChainID string // ID of the chain
|
||||
Height int64 // Current block height
|
||||
Time int64 // UNIX time, in millisconds
|
||||
|
||||
// current block
|
||||
NumTxs int64 // Number of txs in this block
|
||||
TxHash []byte // SimpleMerkle of the block.Txs
|
||||
LastCommitHash []byte // SimpleMerkle of the block.LastCommit
|
||||
|
||||
// previous block
|
||||
TotalTxs int64 // prevBlock.TotalTxs + block.NumTxs
|
||||
LastBlockID BlockID // BlockID of prevBlock
|
||||
|
||||
// application
|
||||
ResultsHash []byte // SimpleMerkle of []abci.Result from prevBlock
|
||||
AppHash []byte // Arbitrary state digest
|
||||
ValidatorsHash []byte // SimpleMerkle of the ValidatorSet
|
||||
ConsensusParamsHash []byte // SimpleMerkle of the ConsensusParams
|
||||
|
||||
// consensus
|
||||
Proposer []byte // Address of the block proposer
|
||||
EvidenceHash []byte // SimpleMerkle of []Evidence
|
||||
}
|
||||
```
|
||||
|
||||
Further details on each of these fields is described below.
|
||||
|
||||
## BlockID
|
||||
|
||||
The `BlockID` contains two distinct Merkle roots of the block.
|
||||
The first, used as the block's main hash, is the Merkle root
|
||||
of all the fields in the header. The second, used for secure gossipping of
|
||||
the block during consensus, is the Merkle root of the complete serialized block
|
||||
cut into parts. The `BlockID` includes these two hashes, as well as the number of
|
||||
parts.
|
||||
|
||||
```go
|
||||
type BlockID struct {
|
||||
Hash []byte
|
||||
Parts PartsHeader
|
||||
}
|
||||
|
||||
type PartsHeader struct {
|
||||
Hash []byte
|
||||
Total int32
|
||||
}
|
||||
```
|
||||
|
||||
## Vote
|
||||
|
||||
A vote is a signed message from a validator for a particular block.
|
||||
The vote includes information about the validator signing it.
|
||||
|
||||
```go
|
||||
type Vote struct {
|
||||
Timestamp int64
|
||||
Address []byte
|
||||
Index int
|
||||
Height int64
|
||||
Round int
|
||||
Type int8
|
||||
BlockID BlockID
|
||||
Signature Signature
|
||||
}
|
||||
```
|
||||
|
||||
There are two types of votes:
|
||||
a *prevote* has `vote.Type == 1` and
|
||||
a *precommit* has `vote.Type == 2`.
|
||||
|
||||
## Signature
|
||||
|
||||
Tendermint allows for multiple signature schemes to be used by prepending a single type-byte
|
||||
to the signature bytes. Different signatures may also come with fixed or variable lengths.
|
||||
Currently, Tendermint supports Ed25519 and Secp256k1.
|
||||
|
||||
### ED25519
|
||||
|
||||
An ED25519 signature has `Type == 0x1`. It looks like:
|
||||
|
||||
```go
|
||||
// Implements Signature
|
||||
type Ed25519Signature struct {
|
||||
Type int8 = 0x1
|
||||
Signature [64]byte
|
||||
}
|
||||
```
|
||||
|
||||
where `Signature` is the 64 byte signature.
|
||||
|
||||
### Secp256k1
|
||||
|
||||
A `Secp256k1` signature has `Type == 0x2`. It looks like:
|
||||
|
||||
```go
|
||||
// Implements Signature
|
||||
type Secp256k1Signature struct {
|
||||
Type int8 = 0x2
|
||||
Signature []byte
|
||||
}
|
||||
```
|
||||
|
||||
where `Signature` is the DER encoded signature, ie:
|
||||
|
||||
```hex
|
||||
0x30 <length of whole message> <0x02> <length of R> <R> 0x2 <length of S> <S>.
|
||||
```
|
||||
|
||||
## Evidence
|
||||
|
||||
TODO
|
||||
|
||||
## Validation
|
||||
|
||||
Here we describe the validation rules for every element in a block.
|
||||
Blocks which do not satisfy these rules are considered invalid.
|
||||
|
||||
We abuse notation by using something that looks like Go, supplemented with English.
|
||||
A statement such as `x == y` is an assertion - if it fails, the item is invalid.
|
||||
|
||||
We refer to certain globally available objects:
|
||||
`block` is the block under consideration,
|
||||
`prevBlock` is the `block` at the previous height,
|
||||
and `state` keeps track of the validator set, the consensus parameters
|
||||
and other results from the application.
|
||||
Elements of an object are accessed as expected,
|
||||
ie. `block.Header`. See [here](state.md) for the definition of `state`.
|
||||
|
||||
### Header
|
||||
|
||||
A Header is valid if its corresponding fields are valid.
|
||||
|
||||
### Version
|
||||
|
||||
Arbitrary string.
|
||||
|
||||
### ChainID
|
||||
|
||||
Arbitrary constant string.
|
||||
|
||||
### Height
|
||||
|
||||
```go
|
||||
block.Header.Height > 0
|
||||
block.Header.Height == prevBlock.Header.Height + 1
|
||||
```
|
||||
|
||||
The height is an incrementing integer. The first block has `block.Header.Height == 1`.
|
||||
|
||||
### Time
|
||||
|
||||
The median of the timestamps of the valid votes in the block.LastCommit.
|
||||
Corresponds to the number of nanoseconds, with millisecond resolution, since January 1, 1970.
|
||||
|
||||
Note: the timestamp of a vote must be greater by at least one millisecond than that of the
|
||||
block being voted on.
|
||||
|
||||
### NumTxs
|
||||
|
||||
```go
|
||||
block.Header.NumTxs == len(block.Txs)
|
||||
```
|
||||
|
||||
Number of transactions included in the block.
|
||||
|
||||
### TxHash
|
||||
|
||||
```go
|
||||
block.Header.TxHash == SimpleMerkleRoot(block.Txs)
|
||||
```
|
||||
|
||||
Simple Merkle root of the transactions in the block.
|
||||
|
||||
### LastCommitHash
|
||||
|
||||
```go
|
||||
block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit)
|
||||
```
|
||||
|
||||
Simple Merkle root of the votes included in the block.
|
||||
These are the votes that committed the previous block.
|
||||
|
||||
The first block has `block.Header.LastCommitHash == []byte{}`
|
||||
|
||||
### TotalTxs
|
||||
|
||||
```go
|
||||
block.Header.TotalTxs == prevBlock.Header.TotalTxs + block.Header.NumTxs
|
||||
```
|
||||
|
||||
The cumulative sum of all transactions included in this blockchain.
|
||||
|
||||
The first block has `block.Header.TotalTxs = block.Header.NumberTxs`.
|
||||
|
||||
### LastBlockID
|
||||
|
||||
LastBlockID is the previous block's BlockID:
|
||||
|
||||
```go
|
||||
prevBlockParts := MakeParts(prevBlock, state.LastConsensusParams.BlockGossip.BlockPartSize)
|
||||
block.Header.LastBlockID == BlockID {
|
||||
Hash: SimpleMerkleRoot(prevBlock.Header),
|
||||
PartsHeader{
|
||||
Hash: SimpleMerkleRoot(prevBlockParts),
|
||||
Total: len(prevBlockParts),
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Note: it depends on the ConsensusParams,
|
||||
which are held in the `state` and may be updated by the application.
|
||||
|
||||
The first block has `block.Header.LastBlockID == BlockID{}`.
|
||||
|
||||
### ResultsHash
|
||||
|
||||
```go
|
||||
block.ResultsHash == SimpleMerkleRoot(state.LastResults)
|
||||
```
|
||||
|
||||
Simple Merkle root of the results of the transactions in the previous block.
|
||||
|
||||
The first block has `block.Header.ResultsHash == []byte{}`.
|
||||
|
||||
### AppHash
|
||||
|
||||
```go
|
||||
block.AppHash == state.AppHash
|
||||
```
|
||||
|
||||
Arbitrary byte array returned by the application after executing and commiting the previous block.
|
||||
|
||||
The first block has `block.Header.AppHash == []byte{}`.
|
||||
|
||||
### ValidatorsHash
|
||||
|
||||
```go
|
||||
block.ValidatorsHash == SimpleMerkleRoot(state.Validators)
|
||||
```
|
||||
|
||||
Simple Merkle root of the current validator set that is committing the block.
|
||||
This can be used to validate the `LastCommit` included in the next block.
|
||||
May be updated by the application.
|
||||
|
||||
### ConsensusParamsHash
|
||||
|
||||
```go
|
||||
block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams)
|
||||
```
|
||||
|
||||
Simple Merkle root of the consensus parameters.
|
||||
May be updated by the application.
|
||||
|
||||
### Proposer
|
||||
|
||||
```go
|
||||
block.Header.Proposer in state.Validators
|
||||
```
|
||||
|
||||
Original proposer of the block. Must be a current validator.
|
||||
|
||||
NOTE: we also need to track the round.
|
||||
|
||||
## EvidenceHash
|
||||
|
||||
```go
|
||||
block.EvidenceHash == SimpleMerkleRoot(block.Evidence)
|
||||
```
|
||||
|
||||
Simple Merkle root of the evidence of Byzantine behaviour included in this block.
|
||||
|
||||
## Txs
|
||||
|
||||
Arbitrary length array of arbitrary length byte-arrays.
|
||||
|
||||
## LastCommit
|
||||
|
||||
The first height is an exception - it requires the LastCommit to be empty:
|
||||
|
||||
```go
|
||||
if block.Header.Height == 1 {
|
||||
len(b.LastCommit) == 0
|
||||
}
|
||||
```
|
||||
|
||||
Otherwise, we require:
|
||||
|
||||
```go
|
||||
len(block.LastCommit) == len(state.LastValidators)
|
||||
talliedVotingPower := 0
|
||||
for i, vote := range block.LastCommit{
|
||||
if vote == nil{
|
||||
continue
|
||||
}
|
||||
vote.Type == 2
|
||||
vote.Height == block.LastCommit.Height()
|
||||
vote.Round == block.LastCommit.Round()
|
||||
vote.BlockID == block.LastBlockID
|
||||
|
||||
val := state.LastValidators[i]
|
||||
vote.Verify(block.ChainID, val.PubKey) == true
|
||||
|
||||
talliedVotingPower += val.VotingPower
|
||||
}
|
||||
|
||||
talliedVotingPower > (2/3) * TotalVotingPower(state.LastValidators)
|
||||
```
|
||||
|
||||
Includes one (possibly nil) vote for every current validator.
|
||||
Non-nil votes must be Precommits.
|
||||
All votes must be for the same height and round.
|
||||
All votes must be for the previous block.
|
||||
All votes must have a valid signature from the corresponding validator.
|
||||
The sum total of the voting power of the validators that voted
|
||||
must be greater than 2/3 of the total voting power of the complete validator set.
|
||||
|
||||
### Vote
|
||||
|
||||
A vote is a signed message broadcast in the consensus for a particular block at a particular height and round.
|
||||
When stored in the blockchain or propagated over the network, votes are encoded in TMBIN.
|
||||
For signing, votes are encoded in JSON, and the ChainID is included, in the form of the `CanonicalSignBytes`.
|
||||
|
||||
We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the CanonicalSignBytes
|
||||
using the given ChainID:
|
||||
|
||||
```go
|
||||
func (v Vote) Verify(chainID string, pubKey PubKey) bool {
|
||||
return pubKey.Verify(v.Signature, CanonicalSignBytes(chainID, v))
|
||||
}
|
||||
```
|
||||
|
||||
where `pubKey.Verify` performs the appropriate digital signature verification of the `pubKey`
|
||||
against the given signature and message bytes.
|
||||
|
||||
## Evidence
|
||||
|
||||
TODO
|
||||
|
||||
```
|
||||
TODO
|
||||
```
|
||||
|
||||
Every piece of evidence contains two conflicting votes from a single validator that
|
||||
was active at the height indicated in the votes.
|
||||
The votes must not be too old.
|
||||
|
||||
|
||||
# Execution
|
||||
|
||||
Once a block is validated, it can be executed against the state.
|
||||
|
||||
The state follows this recursive equation:
|
||||
|
||||
```go
|
||||
state(1) = InitialState
|
||||
state(h+1) <- Execute(state(h), ABCIApp, block(h))
|
||||
```
|
||||
|
||||
where `InitialState` includes the initial consensus parameters and validator set,
|
||||
and `ABCIApp` is an ABCI application that can return results and changes to the validator
|
||||
set (TODO). Execute is defined as:
|
||||
|
||||
```go
|
||||
Execute(s State, app ABCIApp, block Block) State {
|
||||
TODO: just spell out ApplyBlock here
|
||||
and remove ABCIResponses struct.
|
||||
abciResponses := app.ApplyBlock(block)
|
||||
|
||||
return State{
|
||||
LastResults: abciResponses.DeliverTxResults,
|
||||
AppHash: abciResponses.AppHash,
|
||||
Validators: UpdateValidators(state.Validators, abciResponses.ValidatorChanges),
|
||||
LastValidators: state.Validators,
|
||||
ConsensusParams: UpdateConsensusParams(state.ConsensusParams, abci.Responses.ConsensusParamChanges),
|
||||
}
|
||||
}
|
||||
|
||||
type ABCIResponses struct {
|
||||
DeliverTxResults []Result
|
||||
ValidatorChanges []Validator
|
||||
ConsensusParamChanges ConsensusParams
|
||||
AppHash []byte
|
||||
}
|
||||
```
|
||||
|
||||
|
320
docs/spec/blockchain/encoding.md
Normal file
320
docs/spec/blockchain/encoding.md
Normal file
@ -0,0 +1,320 @@
|
||||
# Tendermint Encoding
|
||||
|
||||
## Amino
|
||||
|
||||
Tendermint uses the Protobuf3 derrivative [Amino]() for all data structures.
|
||||
Think of Amino as an object-oriented Protobuf3 with native JSON support.
|
||||
The goal of the Amino encoding protocol is to bring parity between application
|
||||
logic objects and persistence objects.
|
||||
|
||||
Please see the [Amino
|
||||
specification](https://github.com/tendermint/go-amino#amino-encoding-for-go) for
|
||||
more details.
|
||||
|
||||
Notably, every object that satisfies an interface (eg. a particular kind of p2p message,
|
||||
or a particular kind of pubkey) is registered with a global name, the hash of
|
||||
which is included in the object's encoding as the so-called "prefix bytes".
|
||||
|
||||
We define the `func AminoEncode(obj interface{}) []byte` function to take an
|
||||
arbitrary object and return the Amino encoded bytes.
|
||||
|
||||
## Byte Arrays
|
||||
|
||||
The encoding of a byte array is simply the raw-bytes prefixed with the length of
|
||||
the array as a `UVarint` (what Protobuf calls a `Varint`).
|
||||
|
||||
For details on varints, see the [protobuf
|
||||
spec](https://developers.google.com/protocol-buffers/docs/encoding#varints).
|
||||
|
||||
For example, the byte-array `[0xA, 0xB]` would be encoded as `0x020A0B`,
|
||||
while a byte-array containing 300 entires beginning with `[0xA, 0xB, ...]` would
|
||||
be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300.
|
||||
|
||||
## Public Key Cryptography
|
||||
|
||||
Tendermint uses Amino to distinguish between different types of private keys,
|
||||
public keys, and signatures. Additionally, for each public key, Tendermint
|
||||
defines an Address function that can be used as a more compact identifier in
|
||||
place of the public key. Here we list the concrete types, their names,
|
||||
and prefix bytes for public keys and signatures, as well as the address schemes
|
||||
for each PubKey. Note for brevity we don't
|
||||
include details of the private keys beyond their type and name, as they can be
|
||||
derrived the same way as the others using Amino.
|
||||
|
||||
All registered objects are encoded by Amino using a 4-byte PrefixBytes that
|
||||
uniquely identifies the object and includes information about its underlying
|
||||
type. For details on how PrefixBytes are computed, see the [Amino
|
||||
spec](https://github.com/tendermint/go-amino#computing-the-prefix-and-disambiguation-bytes).
|
||||
|
||||
In what follows, we provide the type names and prefix bytes directly.
|
||||
Notice that when encoding byte-arrays, the length of the byte-array is appended
|
||||
to the PrefixBytes. Thus the encoding of a byte array becomes `<PrefixBytes>
|
||||
<Length> <ByteArray>`
|
||||
|
||||
(NOTE: the remainder of this section on Public Key Cryptography can be generated
|
||||
from [this script](./scripts/crypto.go))
|
||||
|
||||
### PubKeyEd25519
|
||||
|
||||
```
|
||||
// Name: tendermint/PubKeyEd25519
|
||||
// PrefixBytes: 0x1624DE62
|
||||
// Length: 0x20
|
||||
// Notes: raw 32-byte Ed25519 pubkey
|
||||
type PubKeyEd25519 [32]byte
|
||||
|
||||
func (pubkey PubKeyEd25519) Address() []byte {
|
||||
// NOTE: hash of the Amino encoded bytes!
|
||||
return RIPEMD160(AminoEncode(pubkey))
|
||||
}
|
||||
```
|
||||
|
||||
For example, the 32-byte Ed25519 pubkey
|
||||
`CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E` would be
|
||||
encoded as
|
||||
`1624DE6220CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E`.
|
||||
|
||||
The address would then be
|
||||
`RIPEMD160(0x1624DE6220CCACD52F9B29D04393F01CD9AF6535455668115641F3D8BAEFD2295F24BAF60E)`
|
||||
or `430FF75BAF1EC4B0D51BB3EEC2955479D0071605`
|
||||
|
||||
### SignatureEd25519
|
||||
|
||||
```
|
||||
// Name: tendermint/SignatureKeyEd25519
|
||||
// PrefixBytes: 0x3DA1DB2A
|
||||
// Length: 0x40
|
||||
// Notes: raw 64-byte Ed25519 signature
|
||||
type SignatureEd25519 [64]byte
|
||||
```
|
||||
|
||||
For example, the 64-byte Ed25519 signature
|
||||
`1B6034A8ED149D3C94FDA13EC03B26CC0FB264D9B0E47D3FA3DEF9FCDE658E49C80B35F9BE74949356401B15B18FB817D6E54495AD1C4A8401B248466CB0DB0B`
|
||||
would be encoded as
|
||||
`3DA1DB2A401B6034A8ED149D3C94FDA13EC03B26CC0FB264D9B0E47D3FA3DEF9FCDE658E49C80B35F9BE74949356401B15B18FB817D6E54495AD1C4A8401B248466CB0DB0B`
|
||||
|
||||
### PrivKeyEd25519
|
||||
|
||||
```
|
||||
// Name: tendermint/PrivKeyEd25519
|
||||
// Notes: raw 32-byte priv key concatenated to raw 32-byte pub key
|
||||
type PrivKeyEd25519 [64]byte
|
||||
```
|
||||
|
||||
### PubKeySecp256k1
|
||||
|
||||
```
|
||||
// Name: tendermint/PubKeySecp256k1
|
||||
// PrefixBytes: 0xEB5AE982
|
||||
// Length: 0x21
|
||||
// Notes: OpenSSL compressed pubkey prefixed with 0x02 or 0x03
|
||||
type PubKeySecp256k1 [33]byte
|
||||
|
||||
func (pubkey PubKeySecp256k1) Address() []byte {
|
||||
// NOTE: hash of the raw pubkey bytes (not Amino encoded!).
|
||||
// Compatible with Bitcoin addresses.
|
||||
return RIPEMD160(SHA256(pubkey[:]))
|
||||
}
|
||||
```
|
||||
|
||||
For example, the 33-byte Secp256k1 pubkey
|
||||
`020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` would be
|
||||
encoded as
|
||||
`EB5AE98221020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9`
|
||||
|
||||
The address would then be
|
||||
`RIPEMD160(SHA256(0x020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9))`
|
||||
or `0AE5BEE929ABE51BAD345DB925EEA652680783FC`
|
||||
|
||||
### SignatureSecp256k1
|
||||
|
||||
```
|
||||
// Name: tendermint/SignatureKeySecp256k1
|
||||
// PrefixBytes: 0x16E1FEEA
|
||||
// Length: Variable
|
||||
// Encoding prefix: Variable
|
||||
// Notes: raw bytes of the Secp256k1 signature
|
||||
type SignatureSecp256k1 []byte
|
||||
```
|
||||
|
||||
For example, the Secp256k1 signature
|
||||
`304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||
would be encoded as
|
||||
`16E1FEEA46304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7`
|
||||
|
||||
### PrivKeySecp256k1
|
||||
|
||||
```
|
||||
// Name: tendermint/PrivKeySecp256k1
|
||||
// Notes: raw 32-byte priv key
|
||||
type PrivKeySecp256k1 [32]byte
|
||||
```
|
||||
|
||||
## Other Common Types
|
||||
|
||||
### BitArray
|
||||
|
||||
The BitArray is used in block headers and some consensus messages to signal
|
||||
whether or not something was done by each validator. BitArray is represented
|
||||
with a struct containing the number of bits (`Bits`) and the bit-array itself
|
||||
encoded in base64 (`Elems`).
|
||||
|
||||
```go
|
||||
type BitArray struct {
|
||||
Bits int
|
||||
Elems []uint64
|
||||
}
|
||||
```
|
||||
|
||||
This type is easily encoded directly by Amino.
|
||||
|
||||
Note BitArray receives a special JSON encoding in the form of `x` and `_`
|
||||
representing `1` and `0`. Ie. the BitArray `10110` would be JSON encoded as
|
||||
`"x_xx_"`
|
||||
|
||||
### Part
|
||||
|
||||
Part is used to break up blocks into pieces that can be gossiped in parallel
|
||||
and securely verified using a Merkle tree of the parts.
|
||||
|
||||
Part contains the index of the part in the larger set (`Index`), the actual
|
||||
underlying data of the part (`Bytes`), and a simple Merkle proof that the part is contained in
|
||||
the larger set (`Proof`).
|
||||
|
||||
```go
|
||||
type Part struct {
|
||||
Index int
|
||||
Bytes byte[]
|
||||
Proof byte[]
|
||||
}
|
||||
```
|
||||
|
||||
### MakeParts
|
||||
|
||||
Encode an object using Amino and slice it into parts.
|
||||
|
||||
```go
|
||||
func MakeParts(obj interface{}, partSize int) []Part
|
||||
```
|
||||
|
||||
## Merkle Trees
|
||||
|
||||
Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure.
|
||||
|
||||
RIPEMD160 is always used as the hashing function.
|
||||
|
||||
### Simple Merkle Root
|
||||
|
||||
The function `SimpleMerkleRoot` is a simple recursive function defined as follows:
|
||||
|
||||
```go
|
||||
func SimpleMerkleRoot(hashes [][]byte) []byte{
|
||||
switch len(hashes) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return hashes[0]
|
||||
default:
|
||||
left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2])
|
||||
right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:])
|
||||
return SimpleConcatHash(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
func SimpleConcatHash(left, right []byte) []byte{
|
||||
left = encodeByteSlice(left)
|
||||
right = encodeByteSlice(right)
|
||||
return RIPEMD160 (append(left, right))
|
||||
}
|
||||
```
|
||||
|
||||
Note that the leaves are Amino encoded as byte-arrays (ie. simple Uvarint length
|
||||
prefix) before being concatenated together and hashed.
|
||||
|
||||
Note: we will abuse notion and invoke `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`.
|
||||
For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to
|
||||
field name and then hashing them.
|
||||
For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements.
|
||||
|
||||
### Simple Merkle Proof
|
||||
|
||||
Proof that a leaf is in a Merkle tree consists of a simple structure:
|
||||
|
||||
|
||||
```
|
||||
type SimpleProof struct {
|
||||
Aunts [][]byte
|
||||
}
|
||||
```
|
||||
|
||||
Which is verified using the following:
|
||||
|
||||
```
|
||||
func (proof SimpleProof) Verify(index, total int, leafHash, rootHash []byte) bool {
|
||||
computedHash := computeHashFromAunts(index, total, leafHash, proof.Aunts)
|
||||
return computedHash == rootHash
|
||||
}
|
||||
|
||||
func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byte) []byte{
|
||||
assert(index < total && index >= 0 && total > 0)
|
||||
|
||||
if total == 1{
|
||||
assert(len(proof.Aunts) == 0)
|
||||
return leafHash
|
||||
}
|
||||
|
||||
assert(len(innerHashes) > 0)
|
||||
|
||||
numLeft := (total + 1) / 2
|
||||
if index < numLeft {
|
||||
leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
assert(leftHash != nil)
|
||||
return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1])
|
||||
}
|
||||
rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
assert(rightHash != nil)
|
||||
return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash)
|
||||
}
|
||||
```
|
||||
|
||||
## JSON
|
||||
|
||||
### Amino
|
||||
|
||||
TODO: improve this
|
||||
|
||||
Amino also supports JSON encoding - registered types are simply encoded as:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "<DisfixBytes>",
|
||||
"value": <JSON>
|
||||
}
|
||||
|
||||
For instance, an ED25519 PubKey would look like:
|
||||
|
||||
```
|
||||
{
|
||||
"type": "AC26791624DE60",
|
||||
"value": "uZ4h63OFWuQ36ZZ4Bd6NF+/w9fWUwrOncrQsackrsTk="
|
||||
}
|
||||
```
|
||||
|
||||
Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the
|
||||
`"type"` is the full disfix bytes for Ed25519 pubkeys.
|
||||
|
||||
|
||||
### Signed Messages
|
||||
|
||||
Signed messages (eg. votes, proposals) in the consensus are encoded using Amino-JSON, rather than in the standard binary format.
|
||||
|
||||
When signing, the elements of a message are sorted by key and the sorted message is embedded in an
|
||||
outer JSON that includes a `chain_id` field.
|
||||
We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look
|
||||
like:
|
||||
|
||||
```json
|
||||
{"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2}
|
||||
```
|
||||
|
||||
Note how the fields within each level are sorted.
|
114
docs/spec/blockchain/light-client.md
Normal file
114
docs/spec/blockchain/light-client.md
Normal file
@ -0,0 +1,114 @@
|
||||
# Light client
|
||||
|
||||
A light client is a process that connects to the Tendermint Full Node(s) and then tries to verify the Merkle proofs
|
||||
about the blockchain application. In this document we describe mechanisms that ensures that the Tendermint light client
|
||||
has the same level of security as Full Node processes (without being itself a Full Node).
|
||||
|
||||
To be able to validate a Merkle proof, a light client needs to validate the blockchain header that contains the root app hash.
|
||||
Validating a blockchain header in Tendermint consists in verifying that the header is committed (signed) by >2/3 of the
|
||||
voting power of the corresponding validator set. As the validator set is a dynamic set (it is changing), one of the
|
||||
core functionality of the light client is updating the current validator set, that is then used to verify the
|
||||
blockchain header, and further the corresponding Merkle proofs.
|
||||
|
||||
For the purpose of this light client specification, we assume that the Tendermint Full Node exposes the following functions over
|
||||
Tendermint RPC:
|
||||
|
||||
```golang
|
||||
Header(height int64) (SignedHeader, error) // returns signed header for the given height
|
||||
Validators(height int64) (ResultValidators, error) // returns validator set for the given height
|
||||
LastHeader(valSetNumber int64) (SignedHeader, error) // returns last header signed by the validator set with the given validator set number
|
||||
|
||||
type SignedHeader struct {
|
||||
Header Header
|
||||
Commit Commit
|
||||
ValSetNumber int64
|
||||
}
|
||||
|
||||
type ResultValidators struct {
|
||||
BlockHeight int64
|
||||
Validators []Validator
|
||||
// time the current validator set is initialised, i.e, time of the last validator change before header BlockHeight
|
||||
ValSetTime int64
|
||||
}
|
||||
```
|
||||
|
||||
We assume that Tendermint keeps track of the validator set changes and that each time a validator set is changed it is
|
||||
being assigned the next sequence number. We can call this number the validator set sequence number. Tendermint also remembers
|
||||
the Time from the header when the next validator set is initialised (starts to be in power), and we refer to this time
|
||||
as validator set init time.
|
||||
Furthermore, we assume that each validator set change is signed (committed) by the current validator set. More precisely,
|
||||
given a block `H` that contains transactions that are modifying the current validator set, the Merkle root hash of the next
|
||||
validator set (modified based on transactions from block H) will be in block `H+1` (and signed by the current validator
|
||||
set), and then starting from the block `H+2`, it will be signed by the next validator set.
|
||||
|
||||
Note that the real Tendermint RPC API is slightly different (for example, response messages contain more data and function
|
||||
names are slightly different); we shortened (and modified) it for the purpose of this document to make the spec more
|
||||
clear and simple. Furthermore, note that in case of the third function, the returned header has `ValSetNumber` equals to
|
||||
`valSetNumber+1`.
|
||||
|
||||
|
||||
Locally, light client manages the following state:
|
||||
|
||||
```golang
|
||||
valSet []Validator // current validator set (last known and verified validator set)
|
||||
valSetNumber int64 // sequence number of the current validator set
|
||||
valSetHash []byte // hash of the current validator set
|
||||
valSetTime int64 // time when the current validator set is initialised
|
||||
```
|
||||
|
||||
The light client is initialised with the trusted validator set, for example based on the known validator set hash,
|
||||
validator set sequence number and the validator set init time.
|
||||
The core of the light client logic is captured by the VerifyAndUpdate function that is used to 1) verify if the given header is valid,
|
||||
and 2) update the validator set (when the given header is valid and it is more recent than the seen headers).
|
||||
|
||||
```golang
|
||||
VerifyAndUpdate(signedHeader SignedHeader):
|
||||
assertThat signedHeader.valSetNumber >= valSetNumber
|
||||
if isValid(signedHeader) and signedHeader.Header.Time <= valSetTime + UNBONDING_PERIOD then
|
||||
setValidatorSet(signedHeader)
|
||||
return true
|
||||
else
|
||||
updateValidatorSet(signedHeader.ValSetNumber)
|
||||
return VerifyAndUpdate(signedHeader)
|
||||
|
||||
isValid(signedHeader SignedHeader):
|
||||
valSetOfTheHeader = Validators(signedHeader.Header.Height)
|
||||
assertThat Hash(valSetOfTheHeader) == signedHeader.Header.ValSetHash
|
||||
assertThat signedHeader is passing basic validation
|
||||
if votingPower(signedHeader.Commit) > 2/3 * votingPower(valSetOfTheHeader) then return true
|
||||
else
|
||||
return false
|
||||
|
||||
setValidatorSet(signedHeader SignedHeader):
|
||||
nextValSet = Validators(signedHeader.Header.Height)
|
||||
assertThat Hash(nextValSet) == signedHeader.Header.ValidatorsHash
|
||||
valSet = nextValSet.Validators
|
||||
valSetHash = signedHeader.Header.ValidatorsHash
|
||||
valSetNumber = signedHeader.ValSetNumber
|
||||
valSetTime = nextValSet.ValSetTime
|
||||
|
||||
votingPower(commit Commit):
|
||||
votingPower = 0
|
||||
for each precommit in commit.Precommits do:
|
||||
if precommit.ValidatorAddress is in valSet and signature of the precommit verifies then
|
||||
votingPower += valSet[precommit.ValidatorAddress].VotingPower
|
||||
return votingPower
|
||||
|
||||
votingPower(validatorSet []Validator):
|
||||
for each validator in validatorSet do:
|
||||
votingPower += validator.VotingPower
|
||||
return votingPower
|
||||
|
||||
updateValidatorSet(valSetNumberOfTheHeader):
|
||||
while valSetNumber != valSetNumberOfTheHeader do
|
||||
signedHeader = LastHeader(valSetNumber)
|
||||
if isValid(signedHeader) then
|
||||
setValidatorSet(signedHeader)
|
||||
else return error
|
||||
return
|
||||
```
|
||||
|
||||
Note that in the logic above we assume that the light client will always go upward with respect to header verifications,
|
||||
i.e., that it will always be used to verify more recent headers. In case a light client needs to be used to verify older
|
||||
headers (go backward) the same mechanisms and similar logic can be used. In case a call to the FullNode or subsequent
|
||||
checks fail, a light client need to implement some recovery strategy, for example connecting to other FullNode.
|
246
docs/spec/blockchain/pre-amino.md
Normal file
246
docs/spec/blockchain/pre-amino.md
Normal file
@ -0,0 +1,246 @@
|
||||
# Tendermint Encoding (Pre-Amino)
|
||||
|
||||
## PubKeys and Addresses
|
||||
|
||||
PubKeys are prefixed with a type-byte, followed by the raw bytes of the public
|
||||
key.
|
||||
|
||||
Two keys are supported with the following type bytes:
|
||||
|
||||
```
|
||||
TypeByteEd25519 = 0x1
|
||||
TypeByteSecp256k1 = 0x2
|
||||
```
|
||||
|
||||
```
|
||||
// TypeByte: 0x1
|
||||
type PubKeyEd25519 [32]byte
|
||||
|
||||
func (pub PubKeyEd25519) Encode() []byte {
|
||||
return 0x1 | pub
|
||||
}
|
||||
|
||||
func (pub PubKeyEd25519) Address() []byte {
|
||||
// NOTE: the length (0x0120) is also included
|
||||
return RIPEMD160(0x1 | 0x0120 | pub)
|
||||
}
|
||||
|
||||
// TypeByte: 0x2
|
||||
// NOTE: OpenSSL compressed pubkey (x-cord with 0x2 or 0x3)
|
||||
type PubKeySecp256k1 [33]byte
|
||||
|
||||
func (pub PubKeySecp256k1) Encode() []byte {
|
||||
return 0x2 | pub
|
||||
}
|
||||
|
||||
func (pub PubKeySecp256k1) Address() []byte {
|
||||
return RIPEMD160(SHA256(pub))
|
||||
}
|
||||
```
|
||||
|
||||
See https://github.com/tendermint/go-crypto/blob/v0.5.0/pub_key.go for more.
|
||||
|
||||
## Binary Serialization (go-wire)
|
||||
|
||||
Tendermint aims to encode data structures in a manner similar to how the corresponding Go structs
|
||||
are laid out in memory.
|
||||
Variable length items are length-prefixed.
|
||||
While the encoding was inspired by Go, it is easily implemented in other languages as well, given its intuitive design.
|
||||
|
||||
XXX: This is changing to use real varints and 4-byte-prefixes.
|
||||
See https://github.com/tendermint/go-wire/tree/sdk2.
|
||||
|
||||
### Fixed Length Integers
|
||||
|
||||
Fixed length integers are encoded in Big-Endian using the specified number of bytes.
|
||||
So `uint8` and `int8` use one byte, `uint16` and `int16` use two bytes,
|
||||
`uint32` and `int32` use 3 bytes, and `uint64` and `int64` use 4 bytes.
|
||||
|
||||
Negative integers are encoded via twos-complement.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode(uint8(6)) == [0x06]
|
||||
encode(uint32(6)) == [0x00, 0x00, 0x00, 0x06]
|
||||
|
||||
encode(int8(-6)) == [0xFA]
|
||||
encode(int32(-6)) == [0xFF, 0xFF, 0xFF, 0xFA]
|
||||
```
|
||||
|
||||
### Variable Length Integers
|
||||
|
||||
Variable length integers are encoded as length-prefixed Big-Endian integers.
|
||||
The length-prefix consists of a single byte and corresponds to the length of the encoded integer.
|
||||
|
||||
Negative integers are encoded by flipping the leading bit of the length-prefix to a `1`.
|
||||
|
||||
Zero is encoded as `0x00`. It is not length-prefixed.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode(uint(6)) == [0x01, 0x06]
|
||||
encode(uint(70000)) == [0x03, 0x01, 0x11, 0x70]
|
||||
|
||||
encode(int(-6)) == [0xF1, 0x06]
|
||||
encode(int(-70000)) == [0xF3, 0x01, 0x11, 0x70]
|
||||
|
||||
encode(int(0)) == [0x00]
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
An encoded string is length-prefixed followed by the underlying bytes of the string.
|
||||
The length-prefix is itself encoded as an `int`.
|
||||
|
||||
The empty string is encoded as `0x00`. It is not length-prefixed.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode("") == [0x00]
|
||||
encode("a") == [0x01, 0x01, 0x61]
|
||||
encode("hello") == [0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F]
|
||||
encode("¥") == [0x01, 0x02, 0xC2, 0xA5]
|
||||
```
|
||||
|
||||
### Arrays (fixed length)
|
||||
|
||||
An encoded fix-lengthed array is the concatenation of the encoding of its elements.
|
||||
There is no length-prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode([4]int8{1, 2, 3, 4}) == [0x01, 0x02, 0x03, 0x04]
|
||||
encode([4]int16{1, 2, 3, 4}) == [0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04]
|
||||
encode([4]int{1, 2, 3, 4}) == [0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x04]
|
||||
encode([2]string{"abc", "efg"}) == [0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x65, 0x66, 0x67]
|
||||
```
|
||||
|
||||
### Slices (variable length)
|
||||
|
||||
An encoded variable-length array is length-prefixed followed by the concatenation of the encoding of
|
||||
its elements.
|
||||
The length-prefix is itself encoded as an `int`.
|
||||
|
||||
An empty slice is encoded as `0x00`. It is not length-prefixed.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode([]int8{}) == [0x00]
|
||||
encode([]int8{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x02, 0x03, 0x04]
|
||||
encode([]int16{1, 2, 3, 4}) == [0x01, 0x04, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04]
|
||||
encode([]int{1, 2, 3, 4}) == [0x01, 0x04, 0x01, 0x01, 0x01, 0x02, 0x01, 0x03, 0x01, 0x4]
|
||||
encode([]string{"abc", "efg"}) == [0x01, 0x02, 0x01, 0x03, 0x61, 0x62, 0x63, 0x01, 0x03, 0x65, 0x66, 0x67]
|
||||
```
|
||||
|
||||
### BitArray
|
||||
|
||||
BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode
|
||||
value of each array element.
|
||||
|
||||
```go
|
||||
type BitArray struct {
|
||||
Bits int
|
||||
Elems []uint64
|
||||
}
|
||||
```
|
||||
|
||||
### Time
|
||||
|
||||
Time is encoded as an `int64` of the number of nanoseconds since January 1, 1970,
|
||||
rounded to the nearest millisecond.
|
||||
|
||||
Times before then are invalid.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
encode(time.Time("Jan 1 00:00:00 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
encode(time.Time("Jan 1 00:00:01 UTC 1970")) == [0x00, 0x00, 0x00, 0x00, 0x3B, 0x9A, 0xCA, 0x00] // 1,000,000,000 ns
|
||||
encode(time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")) == [0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00]
|
||||
```
|
||||
|
||||
### Structs
|
||||
|
||||
An encoded struct is the concatenation of the encoding of its elements.
|
||||
There is no length-prefix.
|
||||
|
||||
Examples:
|
||||
|
||||
```go
|
||||
type MyStruct struct{
|
||||
A int
|
||||
B string
|
||||
C time.Time
|
||||
}
|
||||
encode(MyStruct{4, "hello", time.Time("Mon Jan 2 15:04:05 -0700 MST 2006")}) ==
|
||||
[0x01, 0x04, 0x01, 0x05, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x0F, 0xC4, 0xBB, 0xC1, 0x53, 0x03, 0x12, 0x00]
|
||||
```
|
||||
|
||||
## Merkle Trees
|
||||
|
||||
Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure.
|
||||
|
||||
RIPEMD160 is always used as the hashing function.
|
||||
|
||||
The function `SimpleMerkleRoot` is a simple recursive function defined as follows:
|
||||
|
||||
```go
|
||||
func SimpleMerkleRoot(hashes [][]byte) []byte{
|
||||
switch len(hashes) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return hashes[0]
|
||||
default:
|
||||
left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2])
|
||||
right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:])
|
||||
return RIPEMD160(append(left, right))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note: we abuse notion and call `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`.
|
||||
For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to
|
||||
field name and then hashing them.
|
||||
For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements.
|
||||
|
||||
## JSON (TMJSON)
|
||||
|
||||
Signed messages (eg. votes, proposals) in the consensus are encoded in TMJSON, rather than TMBIN.
|
||||
TMJSON is JSON where `[]byte` are encoded as uppercase hex, rather than base64.
|
||||
|
||||
When signing, the elements of a message are sorted by key and the sorted message is embedded in an
|
||||
outer JSON that includes a `chain_id` field.
|
||||
We call this encoding the CanonicalSignBytes. For instance, CanonicalSignBytes for a vote would look
|
||||
like:
|
||||
|
||||
```json
|
||||
{"chain_id":"my-chain-id","vote":{"block_id":{"hash":DEADBEEF,"parts":{"hash":BEEFDEAD,"total":3}},"height":3,"round":2,"timestamp":1234567890, "type":2}
|
||||
```
|
||||
|
||||
Note how the fields within each level are sorted.
|
||||
|
||||
## Other
|
||||
|
||||
### MakeParts
|
||||
|
||||
Encode an object using TMBIN and slice it into parts.
|
||||
|
||||
```go
|
||||
MakeParts(object, partSize)
|
||||
```
|
||||
|
||||
### Part
|
||||
|
||||
```go
|
||||
type Part struct {
|
||||
Index int
|
||||
Bytes byte[]
|
||||
Proof byte[]
|
||||
}
|
||||
```
|
81
docs/spec/blockchain/state.md
Normal file
81
docs/spec/blockchain/state.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Tendermint State
|
||||
|
||||
## State
|
||||
|
||||
The state contains information whose cryptographic digest is included in block headers, and thus is
|
||||
necessary for validating new blocks. For instance, the set of validators and the results of
|
||||
transactions are never included in blocks, but their Merkle roots are - the state keeps track of them.
|
||||
|
||||
Note that the `State` object itself is an implementation detail, since it is never
|
||||
included in a block or gossipped over the network, and we never compute
|
||||
its hash. However, the types it contains are part of the specification, since
|
||||
their Merkle roots are included in blocks.
|
||||
|
||||
For details on an implementation of `State` with persistence, see TODO
|
||||
|
||||
```go
|
||||
type State struct {
|
||||
LastResults []Result
|
||||
AppHash []byte
|
||||
|
||||
Validators []Validator
|
||||
LastValidators []Validator
|
||||
|
||||
ConsensusParams ConsensusParams
|
||||
}
|
||||
```
|
||||
|
||||
### Result
|
||||
|
||||
```go
|
||||
type Result struct {
|
||||
Code uint32
|
||||
Data []byte
|
||||
Tags []KVPair
|
||||
}
|
||||
|
||||
type KVPair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
```
|
||||
|
||||
`Result` is the result of executing a transaction against the application.
|
||||
It returns a result code, an arbitrary byte array (ie. a return value),
|
||||
and a list of key-value pairs ordered by key. The key-value pairs, or tags,
|
||||
can be used to index transactions according to their "effects", which are
|
||||
represented in the tags.
|
||||
|
||||
### Validator
|
||||
|
||||
A validator is an active participant in the consensus with a public key and a voting power.
|
||||
Validator's also contain an address which is derived from the PubKey:
|
||||
|
||||
```go
|
||||
type Validator struct {
|
||||
Address []byte
|
||||
PubKey PubKey
|
||||
VotingPower int64
|
||||
}
|
||||
```
|
||||
|
||||
The `state.Validators` and `state.LastValidators` must always by sorted by validator address,
|
||||
so that there is a canonical order for computing the SimpleMerkleRoot.
|
||||
|
||||
We also define a `TotalVotingPower` function, to return the total voting power:
|
||||
|
||||
```go
|
||||
func TotalVotingPower(vals []Validators) int64{
|
||||
sum := 0
|
||||
for v := range vals{
|
||||
sum += v.VotingPower
|
||||
}
|
||||
return sum
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### ConsensusParams
|
||||
|
||||
TODO:
|
||||
|
Reference in New Issue
Block a user