Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

store/rootmulti: add SimpleProofsFromMap #6481

Merged
merged 9 commits into from
Jun 23, 2020
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
}