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) [\#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
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue