mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-19 22:42:24 +00:00
Beginning of complete merkle proofs
This commit is contained in:
@@ -640,6 +640,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts
|
||||
Txs: txs,
|
||||
},
|
||||
}
|
||||
block.FillHeader()
|
||||
|
||||
// Set the block.Header.StateHash.
|
||||
err := cs.state.ComputeBlockStateHash(block)
|
||||
|
||||
+1
-1
@@ -5,8 +5,8 @@ import (
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/code.google.com/p/go.crypto/ripemd160"
|
||||
"io"
|
||||
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
// Node
|
||||
|
||||
+54
-1
@@ -27,11 +27,12 @@ package merkle
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/tendermint/tendermint/Godeps/_workspace/src/code.google.com/p/go.crypto/ripemd160"
|
||||
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
func SimpleHashFromTwoHashes(left []byte, right []byte) []byte {
|
||||
@@ -89,6 +90,58 @@ func SimpleHashFromHashables(items []Hashable) []byte {
|
||||
return SimpleHashFromHashes(hashes)
|
||||
}
|
||||
|
||||
// Convenience for SimpleHashFromHashes.
|
||||
func SimpleHashFromMap(m map[string]interface{}) []byte {
|
||||
kpPairsH := MakeSortedKVPairs(m)
|
||||
return SimpleHashFromHashables(kpPairsH)
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
/* Convenience struct for key-value pairs.
|
||||
A list of KVPairs is hashed via `SimpleHashFromHashables`.
|
||||
NOTE: Each `Value` is encoded for hashing without extra type information,
|
||||
so the user is presumed to be aware of the Value types.
|
||||
*/
|
||||
type KVPair struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (kv KVPair) Hash() []byte {
|
||||
hasher, n, err := ripemd160.New(), new(int64), new(error)
|
||||
wire.WriteString(kv.Key, hasher, n, err)
|
||||
if kvH, ok := kv.Value.(Hashable); ok {
|
||||
wire.WriteByteSlice(kvH.Hash(), hasher, n, err)
|
||||
} else {
|
||||
wire.WriteBinary(kv.Value, hasher, n, err)
|
||||
}
|
||||
if *err != nil {
|
||||
PanicSanity(*err)
|
||||
}
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
type KVPairs []KVPair
|
||||
|
||||
func (kvps KVPairs) Len() int { return len(kvps) }
|
||||
func (kvps KVPairs) Less(i, j int) bool { return kvps[i].Key < kvps[j].Key }
|
||||
func (kvps KVPairs) Swap(i, j int) { kvps[i], kvps[j] = kvps[j], kvps[i] }
|
||||
func (kvps KVPairs) Sort() { sort.Sort(kvps) }
|
||||
|
||||
func MakeSortedKVPairs(m map[string]interface{}) []Hashable {
|
||||
kvPairs := []KVPair{}
|
||||
for k, v := range m {
|
||||
kvPairs = append(kvPairs, KVPair{k, v})
|
||||
}
|
||||
KVPairs(kvPairs).Sort()
|
||||
kvPairsH := []Hashable{}
|
||||
for _, kvp := range kvPairs {
|
||||
kvPairsH = append(kvPairsH, kvp)
|
||||
}
|
||||
return kvPairsH
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
type SimpleProof struct {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package merkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
. "github.com/tendermint/tendermint/common/test"
|
||||
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -81,3 +84,36 @@ func TestSimpleProof(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVPairs(t *testing.T) {
|
||||
// NOTE: in alphabetical order for convenience.
|
||||
m := map[string]interface{}{}
|
||||
m["bytez"] = []byte("hizz") // 0
|
||||
m["light"] = "shadow" // 1
|
||||
m["one"] = 1 // 2
|
||||
m["one_u64"] = uint64(1) // 3
|
||||
m["struct"] = struct { // 4
|
||||
A int
|
||||
B int
|
||||
}{0, 1}
|
||||
|
||||
kvPairsH := MakeSortedKVPairs(m)
|
||||
// rootHash := SimpleHashFromHashables(kvPairsH)
|
||||
proofs := SimpleProofsFromHashables(kvPairsH)
|
||||
|
||||
// Some manual tests
|
||||
if !bytes.Equal(proofs[1].LeafHash, KVPair{"light", "shadow"}.Hash()) {
|
||||
t.Errorf("\"light\": proof failed")
|
||||
fmt.Printf("%v\n%X", proofs[0], KVPair{"light", "shadow"}.Hash())
|
||||
}
|
||||
if !bytes.Equal(proofs[2].LeafHash, KVPair{"one", 1}.Hash()) {
|
||||
t.Errorf("\"one\": proof failed")
|
||||
}
|
||||
if !bytes.Equal(proofs[4].LeafHash, KVPair{"struct", struct {
|
||||
A int
|
||||
B int
|
||||
}{0, 1}}.Hash()) {
|
||||
t.Errorf("\"struct\": proof failed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-8
@@ -123,14 +123,13 @@ func (s *State) Copy() *State {
|
||||
|
||||
// Returns a hash that represents the state data, excluding Last*
|
||||
func (s *State) Hash() []byte {
|
||||
hashables := []merkle.Hashable{
|
||||
s.BondedValidators,
|
||||
s.UnbondingValidators,
|
||||
s.accounts,
|
||||
s.validatorInfos,
|
||||
s.nameReg,
|
||||
}
|
||||
return merkle.SimpleHashFromHashables(hashables)
|
||||
return merkle.SimpleHashFromMap(map[string]interface{}{
|
||||
"BondedValidators": s.BondedValidators,
|
||||
"UnbondingValidators": s.UnbondingValidators,
|
||||
"Accounts": s.accounts,
|
||||
"ValidatorInfos": s.validatorInfos,
|
||||
"NameRegistry": s.nameReg,
|
||||
})
|
||||
}
|
||||
|
||||
// Mutates the block in place and updates it with new state hash.
|
||||
|
||||
@@ -91,6 +91,7 @@ func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []t
|
||||
Txs: txs,
|
||||
},
|
||||
}
|
||||
block.FillHeader()
|
||||
|
||||
// Fill in block StateHash
|
||||
err := state.ComputeBlockStateHash(block)
|
||||
|
||||
+45
-31
@@ -8,9 +8,9 @@ import (
|
||||
"time"
|
||||
|
||||
acm "github.com/tendermint/tendermint/account"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
. "github.com/tendermint/tendermint/common"
|
||||
"github.com/tendermint/tendermint/merkle"
|
||||
"github.com/tendermint/tendermint/wire"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
@@ -28,6 +28,14 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash
|
||||
if b.Height != lastBlockHeight+1 {
|
||||
return errors.New(Fmt("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height))
|
||||
}
|
||||
/* TODO: Determine bounds for Time
|
||||
See blockchain/reactor "stopSyncingDurationMinutes"
|
||||
|
||||
if !b.Time.After(lastBlockTime) {
|
||||
return errors.New("Invalid Block.Header.Time")
|
||||
}
|
||||
*/
|
||||
// TODO: validate Fees
|
||||
if b.NumTxs != len(b.Data.Txs) {
|
||||
return errors.New(Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", len(b.Data.Txs), b.NumTxs))
|
||||
}
|
||||
@@ -37,22 +45,26 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash
|
||||
if !b.LastBlockParts.Equals(lastBlockParts) {
|
||||
return errors.New(Fmt("Wrong Block.Header.LastBlockParts. Expected %v, got %v", lastBlockParts, b.LastBlockParts))
|
||||
}
|
||||
/* TODO: Determine bounds
|
||||
See blockchain/reactor "stopSyncingDurationMinutes"
|
||||
|
||||
if !b.Time.After(lastBlockTime) {
|
||||
return errors.New("Invalid Block.Header.Time")
|
||||
}
|
||||
*/
|
||||
if !bytes.Equal(b.LastValidationHash, b.LastValidation.Hash()) {
|
||||
return errors.New(Fmt("Wrong Block.Header.LastValidationHash. Expected %X, got %X", b.LastValidationHash, b.LastValidation.Hash()))
|
||||
}
|
||||
if b.Header.Height != 1 {
|
||||
if err := b.LastValidation.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// XXX more validation
|
||||
if !bytes.Equal(b.DataHash, b.Data.Hash()) {
|
||||
return errors.New(Fmt("Wrong Block.Header.DataHash. Expected %X, got %X", b.DataHash, b.Data.Hash()))
|
||||
}
|
||||
// NOTE: the StateHash is validated later.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Block) FillHeader() {
|
||||
b.LastValidationHash = b.LastValidation.Hash()
|
||||
b.DataHash = b.Data.Hash()
|
||||
}
|
||||
|
||||
// Computes and returns the block hash.
|
||||
// If the block is incomplete (e.g. missing Header.StateHash)
|
||||
// then the hash is nil, to prevent the usage of that hash.
|
||||
@@ -60,18 +72,8 @@ func (b *Block) Hash() []byte {
|
||||
if b.Header == nil || b.Data == nil || b.LastValidation == nil {
|
||||
return nil
|
||||
}
|
||||
hashHeader := b.Header.Hash()
|
||||
hashData := b.Data.Hash()
|
||||
hashLastValidation := b.LastValidation.Hash()
|
||||
|
||||
// If hashHeader is nil, required fields are missing.
|
||||
if len(hashHeader) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merkle hash from subhashes.
|
||||
hashes := [][]byte{hashHeader, hashData, hashLastValidation}
|
||||
return merkle.SimpleHashFromHashes(hashes)
|
||||
b.FillHeader()
|
||||
return b.Header.Hash()
|
||||
}
|
||||
|
||||
func (b *Block) MakePartSet() *PartSet {
|
||||
@@ -121,14 +123,16 @@ func (b *Block) StringShort() string {
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
type Header struct {
|
||||
ChainID string `json:"chain_id"`
|
||||
Height int `json:"height"`
|
||||
Time time.Time `json:"time"`
|
||||
Fees int64 `json:"fees"`
|
||||
NumTxs int `json:"num_txs"`
|
||||
LastBlockHash []byte `json:"last_block_hash"`
|
||||
LastBlockParts PartSetHeader `json:"last_block_parts"`
|
||||
StateHash []byte `json:"state_hash"`
|
||||
ChainID string `json:"chain_id"`
|
||||
Height int `json:"height"`
|
||||
Time time.Time `json:"time"`
|
||||
Fees int64 `json:"fees"`
|
||||
NumTxs int `json:"num_txs"`
|
||||
LastBlockHash []byte `json:"last_block_hash"`
|
||||
LastBlockParts PartSetHeader `json:"last_block_parts"`
|
||||
LastValidationHash []byte `json:"last_validation_hash"`
|
||||
DataHash []byte `json:"data_hash"`
|
||||
StateHash []byte `json:"state_hash"`
|
||||
}
|
||||
|
||||
// NOTE: hash is nil if required fields are missing.
|
||||
@@ -136,8 +140,18 @@ func (h *Header) Hash() []byte {
|
||||
if len(h.StateHash) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return wire.BinaryRipemd160(h)
|
||||
return merkle.SimpleHashFromMap(map[string]interface{}{
|
||||
"ChainID": h.ChainID,
|
||||
"Height": h.Height,
|
||||
"Time": h.Time,
|
||||
"Fees": h.Fees,
|
||||
"NumTxs": h.NumTxs,
|
||||
"LastBlock": h.LastBlockHash,
|
||||
"LastBlockParts": h.LastBlockParts,
|
||||
"LastValidation": h.LastValidationHash,
|
||||
"Data": h.DataHash,
|
||||
"State": h.StateHash,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Header) StringIndented(indent string) string {
|
||||
|
||||
Reference in New Issue
Block a user