store/rootmulti: add SimpleProofsFromMap (#6481)
* add sipleproofs from map to sdk * bring changes from tendermint pr * bring back tmhash * add changelog * Update CHANGELOG.md Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
parent
4716260a6e
commit
67d079e68a
|
@ -296,6 +296,7 @@ pagination.
|
||||||
* (types) [\#6128](https://github.com/cosmos/cosmos-sdk/pull/6137) Add `String()` method to `GasMeter`.
|
* (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.
|
* (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.
|
* (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
|
## [v0.38.4] - 2020-05-21
|
||||||
|
|
||||||
|
|
|
@ -100,3 +100,91 @@ func hashKVPairs(kvs kv.Pairs) []byte {
|
||||||
|
|
||||||
return merkle.SimpleHashFromByteSlices(kvsH)
|
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()
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSimpleMap(t *testing.T) {
|
func TestMerkleMap(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
keys []string
|
keys []string
|
||||||
values []string // each string gets converted to []byte in test
|
values []string // each string gets converted to []byte in test
|
||||||
|
@ -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)
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -613,13 +613,13 @@ func (ci commitInfo) Hash() []byte {
|
||||||
if len(ci.StoreInfos) == 0 {
|
if len(ci.StoreInfos) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rootHash, _, _ := merkle.SimpleProofsFromMap(ci.toMap())
|
rootHash, _, _ := SimpleProofsFromMap(ci.toMap())
|
||||||
return rootHash
|
return rootHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ci commitInfo) ProofOp(storeName string) merkle.ProofOp {
|
func (ci commitInfo) ProofOp(storeName string) merkle.ProofOp {
|
||||||
cmap := ci.toMap()
|
cmap := ci.toMap()
|
||||||
_, proofs, _ := merkle.SimpleProofsFromMap(cmap)
|
_, proofs, _ := SimpleProofsFromMap(cmap)
|
||||||
proof := proofs[storeName]
|
proof := proofs[storeName]
|
||||||
if proof == nil {
|
if proof == nil {
|
||||||
panic(fmt.Sprintf("ProofOp for %s but not registered store name", storeName))
|
panic(fmt.Sprintf("ProofOp for %s but not registered store name", storeName))
|
||||||
|
@ -793,3 +793,28 @@ func SimpleHashFromMap(m map[string][]byte) []byte {
|
||||||
|
|
||||||
return mm.hash()
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue