Skip to content

Commit

Permalink
store/rootmulti: add SimpleProofsFromMap (#6481)
Browse files Browse the repository at this point in the history
* add sipleproofs from map to sdk

* bring changes from tendermint pr

* bring back tmhash

* add changelog

* Update CHANGELOG.md

Co-authored-by: Federico Kunze <[email protected]>
  • Loading branch information
tac0turtle and fedekunze authored Jun 23, 2020
1 parent 4716260 commit 67d079e
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ pagination.
* (types) [\#6128](https://github.com/cosmos/cosmos-sdk/pull/6137) Add `String()` method to `GasMeter`.
* (types) [\#6195](https://github.com/cosmos/cosmos-sdk/pull/6195) Add codespace to broadcast(sync/async) response.
* (baseapp) [\#6053](https://github.com/cosmos/cosmos-sdk/pull/6053) Customizable panic recovery handling added for `app.runTx()` method (as proposed in the [ADR 22](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-022-custom-panic-handling.md)). Adds ability for developers to register custom panic handlers extending standard ones.
* (store) [\#6481](https://github.com/cosmos/cosmos-sdk/pull/6481) Move `SimpleProofsFromMap` from Tendermint into the SDK.

## [v0.38.4] - 2020-05-21

Expand Down
88 changes: 88 additions & 0 deletions store/rootmulti/merkle_map.go → store/rootmulti/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,91 @@ func hashKVPairs(kvs kv.Pairs) []byte {

return merkle.SimpleHashFromByteSlices(kvsH)
}

// ---------------------------------------------

// Merkle tree from a map.
// Leaves are `hash(key) | hash(value)`.
// Leaves are sorted before Merkle hashing.
type simpleMap struct {
kvs kv.Pairs
sorted bool
}

func newSimpleMap() *simpleMap {
return &simpleMap{
kvs: nil,
sorted: false,
}
}

// Set creates a kv pair of the key and the hash of the value,
// and then appends it to simpleMap's kv pairs.
func (sm *simpleMap) Set(key string, value []byte) {
sm.sorted = false

// The value is hashed, so you can
// check for equality with a cached value (say)
// and make a determination to fetch or not.
vhash := tmhash.Sum(value)

sm.kvs = append(sm.kvs, kv.Pair{
Key: []byte(key),
Value: vhash,
})
}

// Hash Merkle root hash of items sorted by key
// (UNSTABLE: and by value too if duplicate key).
func (sm *simpleMap) Hash() []byte {
sm.Sort()
return hashKVPairs(sm.kvs)
}

func (sm *simpleMap) Sort() {
if sm.sorted {
return
}
sm.kvs.Sort()
sm.sorted = true
}

// Returns a copy of sorted KVPairs.
// NOTE these contain the hashed key and value.
func (sm *simpleMap) KVPairs() kv.Pairs {
sm.Sort()
kvs := make(kv.Pairs, len(sm.kvs))
copy(kvs, sm.kvs)
return kvs
}

//----------------------------------------

// A local extension to KVPair that can be hashed.
// Key and value are length prefixed and concatenated,
// then hashed.
type KVPair kv.Pair

// NewKVPair takes in a key and value and creates a kv.Pair
// wrapped in the local extension KVPair
func NewKVPair(key, value []byte) KVPair {
return KVPair(kv.Pair{
Key: key,
Value: value,
})
}

// Bytes returns key || value, with both the
// key and value length prefixed.
func (kv KVPair) Bytes() []byte {
var b bytes.Buffer
err := encodeByteSlice(&b, kv.Key)
if err != nil {
panic(err)
}
err = encodeByteSlice(&b, kv.Value)
if err != nil {
panic(err)
}
return b.Bytes()
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
)

func TestSimpleMap(t *testing.T) {
func TestMerkleMap(t *testing.T) {
tests := []struct {
keys []string
values []string // each string gets converted to []byte in test
Expand Down Expand Up @@ -48,3 +48,44 @@ func TestSimpleMap(t *testing.T) {
assert.Equal(t, tc.want, fmt.Sprintf("%x", got), "Hash didn't match on tc %d", i)
}
}

func TestSimpleMap(t *testing.T) {
tests := []struct {
keys []string
values []string // each string gets converted to []byte in test
want string
}{
{[]string{"key1"}, []string{"value1"}, "a44d3cc7daba1a4600b00a2434b30f8b970652169810d6dfa9fb1793a2189324"},
{[]string{"key1"}, []string{"value2"}, "0638e99b3445caec9d95c05e1a3fc1487b4ddec6a952ff337080360b0dcc078c"},
// swap order with 2 keys
{
[]string{"key1", "key2"},
[]string{"value1", "value2"},
"8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3",
},
{
[]string{"key2", "key1"},
[]string{"value2", "value1"},
"8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3",
},
// swap order with 3 keys
{
[]string{"key1", "key2", "key3"},
[]string{"value1", "value2", "value3"},
"1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc",
},
{
[]string{"key1", "key3", "key2"},
[]string{"value1", "value3", "value2"},
"1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc",
},
}
for i, tc := range tests {
db := newSimpleMap()
for i := 0; i < len(tc.keys); i++ {
db.Set(tc.keys[i], []byte(tc.values[i]))
}
got := db.Hash()
assert.Equal(t, tc.want, fmt.Sprintf("%x", got), "Hash didn't match on tc %d", i)
}
}
29 changes: 27 additions & 2 deletions store/rootmulti/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,13 +613,13 @@ func (ci commitInfo) Hash() []byte {
if len(ci.StoreInfos) == 0 {
return nil
}
rootHash, _, _ := merkle.SimpleProofsFromMap(ci.toMap())
rootHash, _, _ := SimpleProofsFromMap(ci.toMap())
return rootHash
}

func (ci commitInfo) ProofOp(storeName string) merkle.ProofOp {
cmap := ci.toMap()
_, proofs, _ := merkle.SimpleProofsFromMap(cmap)
_, proofs, _ := SimpleProofsFromMap(cmap)
proof := proofs[storeName]
if proof == nil {
panic(fmt.Sprintf("ProofOp for %s but not registered store name", storeName))
Expand Down Expand Up @@ -793,3 +793,28 @@ func SimpleHashFromMap(m map[string][]byte) []byte {

return mm.hash()
}

// SimpleProofsFromMap generates proofs from a map. The keys/values of the map will be used as the keys/values
// in the underlying key-value pairs.
// The keys are sorted before the proofs are computed.
func SimpleProofsFromMap(m map[string][]byte) (rootHash []byte, proofs map[string]*merkle.SimpleProof, keys []string) {
sm := newSimpleMap()
for k, v := range m {
sm.Set(k, v)
}
sm.Sort()
kvs := sm.kvs
kvsBytes := make([][]byte, len(kvs))
for i, kvp := range kvs {
kvsBytes[i] = KVPair(kvp).Bytes()
}

rootHash, proofList := merkle.SimpleProofsFromByteSlices(kvsBytes)
proofs = make(map[string]*merkle.SimpleProof)
keys = make([]string, len(proofList))
for i, kvp := range kvs {
proofs[string(kvp.Key)] = proofList[i]
keys[i] = string(kvp.Key)
}
return
}

0 comments on commit 67d079e

Please sign in to comment.