From b96fd8a031f837b420d3a5a5e6c1c7683d8944ba Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 7 Aug 2015 10:43:24 -0700 Subject: [PATCH] Beginning of complete merkle proofs --- consensus/state.go | 1 + merkle/iavl_node.go | 2 +- merkle/simple_tree.go | 55 ++++++++++++++++++++++++++- merkle/simple_tree_test.go | 36 ++++++++++++++++++ state/state.go | 15 ++++---- state/state_test.go | 1 + types/block.go | 76 ++++++++++++++++++++++---------------- 7 files changed, 145 insertions(+), 41 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 07843978e..7b2137bc7 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -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) diff --git a/merkle/iavl_node.go b/merkle/iavl_node.go index faf86890f..96d8a19ca 100644 --- a/merkle/iavl_node.go +++ b/merkle/iavl_node.go @@ -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 diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go index af642a682..5a7afdf68 100644 --- a/merkle/simple_tree.go +++ b/merkle/simple_tree.go @@ -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 { diff --git a/merkle/simple_tree_test.go b/merkle/simple_tree_test.go index 5cbf732e4..af6ff4df9 100644 --- a/merkle/simple_tree_test.go +++ b/merkle/simple_tree_test.go @@ -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") + } + +} diff --git a/state/state.go b/state/state.go index 5e8767961..20f6d134c 100644 --- a/state/state.go +++ b/state/state.go @@ -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. diff --git a/state/state_test.go b/state/state_test.go index 7078cea0b..6eda39b82 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -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) diff --git a/types/block.go b/types/block.go index bbb5f74a4..718b6a5a5 100644 --- a/types/block.go +++ b/types/block.go @@ -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 {