feat: Remove the store/internal module (#18944)
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
parent
7fbca40059
commit
014cbbcb8b
@ -2,9 +2,8 @@ package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/store/v2/internal/maps"
|
||||
)
|
||||
|
||||
type (
|
||||
@ -14,6 +13,7 @@ type (
|
||||
Version uint64
|
||||
StoreInfos []StoreInfo
|
||||
Timestamp time.Time
|
||||
CommitHash []byte
|
||||
}
|
||||
|
||||
// StoreInfo defines store-specific commit information. It contains a reference
|
||||
@ -37,25 +37,44 @@ func (si StoreInfo) GetHash() []byte {
|
||||
|
||||
// Hash returns the root hash of all committed stores represented by CommitInfo,
|
||||
// sorted by store name/key.
|
||||
func (ci CommitInfo) Hash() []byte {
|
||||
func (ci *CommitInfo) Hash() []byte {
|
||||
if len(ci.StoreInfos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootHash, _, _ := maps.ProofsFromMap(ci.toMap())
|
||||
if len(ci.CommitHash) != 0 {
|
||||
return ci.CommitHash
|
||||
}
|
||||
|
||||
rootHash, _, _ := ci.GetStoreProof("")
|
||||
return rootHash
|
||||
}
|
||||
|
||||
func (ci CommitInfo) toMap() map[string][]byte {
|
||||
m := make(map[string][]byte, len(ci.StoreInfos))
|
||||
for _, storeInfo := range ci.StoreInfos {
|
||||
m[storeInfo.Name] = storeInfo.GetHash()
|
||||
func (ci *CommitInfo) GetStoreProof(storeKey string) ([]byte, *CommitmentOp, error) {
|
||||
sort.Slice(ci.StoreInfos, func(i, j int) bool {
|
||||
return ci.StoreInfos[i].Name < ci.StoreInfos[j].Name
|
||||
})
|
||||
|
||||
index := 0
|
||||
leaves := make([][]byte, len(ci.StoreInfos))
|
||||
for i, si := range ci.StoreInfos {
|
||||
var err error
|
||||
leaves[i], err = LeafHash([]byte(si.Name), si.GetHash())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if si.Name == storeKey {
|
||||
index = i
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
rootHash, inners := ProofFromByteSlices(leaves, index)
|
||||
commitmentOp := ConvertCommitmentOp(inners, []byte(storeKey), ci.StoreInfos[index].GetHash())
|
||||
|
||||
return rootHash, &commitmentOp, nil
|
||||
}
|
||||
|
||||
func (ci CommitInfo) CommitID() CommitID {
|
||||
func (ci *CommitInfo) CommitID() CommitID {
|
||||
return CommitID{
|
||||
Version: ci.Version,
|
||||
Hash: ci.Hash(),
|
||||
|
||||
@ -6,7 +6,6 @@ require (
|
||||
cosmossdk.io/core v0.12.0
|
||||
cosmossdk.io/errors v1.0.0
|
||||
cosmossdk.io/log v1.2.1
|
||||
cosmossdk.io/math v1.2.0
|
||||
github.com/cockroachdb/errors v1.11.1
|
||||
github.com/cockroachdb/pebble v1.0.0
|
||||
github.com/cometbft/cometbft v0.38.2
|
||||
|
||||
@ -4,8 +4,6 @@ cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04=
|
||||
cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0=
|
||||
cosmossdk.io/log v1.2.1 h1:Xc1GgTCicniwmMiKwDxUjO4eLhPxoVdI9vtMW8Ti/uk=
|
||||
cosmossdk.io/log v1.2.1/go.mod h1:GNSCc/6+DhFIj1aLn/j7Id7PaO8DzNylUZoOYBL9+I4=
|
||||
cosmossdk.io/math v1.2.0 h1:8gudhTkkD3NxOP2YyyJIYYmt6dQ55ZfJkDOaxXpy7Ig=
|
||||
cosmossdk.io/math v1.2.0/go.mod h1:l2Gnda87F0su8a/7FEKJfFdJrM0JZRXQaohlgJeyQh0=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
package kv
|
||||
|
||||
import "fmt"
|
||||
|
||||
// AssertKeyAtLeastLength panics when store key length is less than the given length.
|
||||
func AssertKeyAtLeastLength(bz []byte, length int) {
|
||||
if len(bz) < length {
|
||||
panic(fmt.Sprintf("expected key of length at least %d, got %d", length, len(bz)))
|
||||
}
|
||||
}
|
||||
|
||||
// AssertKeyLength panics when store key length is not equal to the given length.
|
||||
func AssertKeyLength(bz []byte, length int) {
|
||||
if len(bz) != length {
|
||||
panic(fmt.Sprintf("unexpected key length; got: %d, expected: %d", len(bz), length))
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type (
|
||||
Pair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
Pairs struct {
|
||||
Pairs []Pair
|
||||
}
|
||||
)
|
||||
|
||||
func (kvs Pairs) Len() int { return len(kvs.Pairs) }
|
||||
func (kvs Pairs) Less(i, j int) bool {
|
||||
switch bytes.Compare(kvs.Pairs[i].Key, kvs.Pairs[j].Key) {
|
||||
case -1:
|
||||
return true
|
||||
|
||||
case 0:
|
||||
return bytes.Compare(kvs.Pairs[i].Value, kvs.Pairs[j].Value) < 0
|
||||
|
||||
case 1:
|
||||
return false
|
||||
|
||||
default:
|
||||
panic("invalid comparison result")
|
||||
}
|
||||
}
|
||||
|
||||
func (kvs Pairs) Swap(i, j int) { kvs.Pairs[i], kvs.Pairs[j] = kvs.Pairs[j], kvs.Pairs[i] }
|
||||
|
||||
// Sort invokes sort.Sort on kvs.
|
||||
func (kvs Pairs) Sort() { sort.Sort(kvs) }
|
||||
@ -1,13 +0,0 @@
|
||||
package maps
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkKVPairBytes(b *testing.B) {
|
||||
kvp := NewKVPair(make([]byte, 128), make([]byte, 1e6))
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.SetBytes(int64(len(kvp.Bytes())))
|
||||
}
|
||||
}
|
||||
@ -1,216 +0,0 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cometbft/cometbft/crypto/merkle"
|
||||
"github.com/cometbft/cometbft/crypto/tmhash"
|
||||
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
|
||||
|
||||
"cosmossdk.io/store/v2/internal/kv"
|
||||
"cosmossdk.io/store/v2/internal/tree"
|
||||
)
|
||||
|
||||
// merkleMap defines a merkle-ized tree from a map. Leave values are treated as
|
||||
// hash(key) | hash(value). Leaves are sorted before Merkle hashing.
|
||||
type merkleMap struct {
|
||||
kvs kv.Pairs
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newMerkleMap() *merkleMap {
|
||||
return &merkleMap{
|
||||
kvs: kv.Pairs{},
|
||||
sorted: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Set creates a kv.Pair from the provided key and value. The value is hashed prior
|
||||
// to creating a kv.Pair. The created kv.Pair is appended to the MerkleMap's slice
|
||||
// of kv.Pairs. Whenever called, the MerkleMap must be resorted.
|
||||
func (sm *merkleMap) set(key string, value []byte) {
|
||||
byteKey := []byte(key)
|
||||
assertValidKey(byteKey)
|
||||
|
||||
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.Pairs = append(sm.kvs.Pairs, kv.Pair{
|
||||
Key: byteKey,
|
||||
Value: vhash,
|
||||
})
|
||||
}
|
||||
|
||||
// Hash returns the merkle root of items sorted by key. Note, it is unstable.
|
||||
func (sm *merkleMap) hash() []byte {
|
||||
sm.sort()
|
||||
return hashKVPairs(sm.kvs)
|
||||
}
|
||||
|
||||
func (sm *merkleMap) sort() {
|
||||
if sm.sorted {
|
||||
return
|
||||
}
|
||||
|
||||
sm.kvs.Sort()
|
||||
sm.sorted = true
|
||||
}
|
||||
|
||||
// hashKVPairs hashes a kvPair and creates a merkle tree where the leaves are
|
||||
// byte slices.
|
||||
func hashKVPairs(kvs kv.Pairs) []byte {
|
||||
kvsH := make([][]byte, len(kvs.Pairs))
|
||||
for i, kvp := range kvs.Pairs {
|
||||
kvsH[i] = KVPair(kvp).Bytes()
|
||||
}
|
||||
|
||||
return tree.HashFromByteSlices(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: kv.Pairs{},
|
||||
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) {
|
||||
byteKey := []byte(key)
|
||||
assertValidKey(byteKey)
|
||||
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.Pairs = append(sm.Kvs.Pairs, kv.Pair{
|
||||
Key: byteKey,
|
||||
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 := kv.Pairs{
|
||||
Pairs: make([]kv.Pair, len(sm.Kvs.Pairs)),
|
||||
}
|
||||
|
||||
copy(kvs.Pairs, sm.Kvs.Pairs)
|
||||
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 {
|
||||
// In the worst case:
|
||||
// * 8 bytes to Uvarint encode the length of the key
|
||||
// * 8 bytes to Uvarint encode the length of the value
|
||||
// So preallocate for the worst case, which will in total
|
||||
// be a maximum of 14 bytes wasted, if len(key)=1, len(value)=1,
|
||||
// but that's going to rare.
|
||||
buf := make([]byte, 8+len(kv.Key)+8+len(kv.Value))
|
||||
|
||||
// Encode the key, prefixed with its length.
|
||||
nlk := binary.PutUvarint(buf, uint64(len(kv.Key)))
|
||||
nk := copy(buf[nlk:], kv.Key)
|
||||
|
||||
// Encode the value, prefixing with its length.
|
||||
nlv := binary.PutUvarint(buf[nlk+nk:], uint64(len(kv.Value)))
|
||||
nv := copy(buf[nlk+nk+nlv:], kv.Value)
|
||||
|
||||
return buf[:nlk+nk+nlv+nv]
|
||||
}
|
||||
|
||||
// HashFromMap computes a merkle tree from sorted map and returns the merkle
|
||||
// root.
|
||||
func HashFromMap(m map[string][]byte) []byte {
|
||||
mm := newMerkleMap()
|
||||
for k, v := range m {
|
||||
mm.set(k, v)
|
||||
}
|
||||
|
||||
return mm.hash()
|
||||
}
|
||||
|
||||
// ProofsFromMap 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 ProofsFromMap(m map[string][]byte) ([]byte, map[string]*cmtprotocrypto.Proof, []string) {
|
||||
sm := newSimpleMap()
|
||||
for k, v := range m {
|
||||
sm.Set(k, v)
|
||||
}
|
||||
|
||||
sm.Sort()
|
||||
kvs := sm.Kvs
|
||||
kvsBytes := make([][]byte, len(kvs.Pairs))
|
||||
for i, kvp := range kvs.Pairs {
|
||||
kvsBytes[i] = KVPair(kvp).Bytes()
|
||||
}
|
||||
|
||||
rootHash, proofList := merkle.ProofsFromByteSlices(kvsBytes)
|
||||
proofs := make(map[string]*cmtprotocrypto.Proof)
|
||||
keys := make([]string, len(proofList))
|
||||
|
||||
for i, kvp := range kvs.Pairs {
|
||||
proofs[string(kvp.Key)] = proofList[i].ToProto()
|
||||
keys[i] = string(kvp.Key)
|
||||
}
|
||||
|
||||
return rootHash, proofs, keys
|
||||
}
|
||||
|
||||
func assertValidKey(key []byte) {
|
||||
if len(key) == 0 {
|
||||
panic("key is nil")
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEmptyKeyMerkleMap(t *testing.T) {
|
||||
db := newMerkleMap()
|
||||
require.Panics(t, func() { db.set("", []byte("value")) }, "setting an empty key should panic")
|
||||
}
|
||||
|
||||
func TestMerkleMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
keys []string
|
||||
values []string // each string gets converted to []byte in test
|
||||
want string
|
||||
}{
|
||||
{[]string{}, []string{}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
|
||||
{[]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 := newMerkleMap()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyKeySimpleMap(t *testing.T) {
|
||||
db := newSimpleMap()
|
||||
require.Panics(t, func() { db.Set("", []byte("value")) }, "setting an empty key should panic")
|
||||
}
|
||||
|
||||
func TestSimpleMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
keys []string
|
||||
values []string // each string gets converted to []byte in test
|
||||
want string
|
||||
}{
|
||||
{[]string{}, []string{}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
|
||||
{[]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)
|
||||
}
|
||||
}
|
||||
@ -1,98 +0,0 @@
|
||||
package proofs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/bits"
|
||||
|
||||
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
)
|
||||
|
||||
// ConvertExistenceProof will convert the given proof into a valid
|
||||
// existence proof, if that's what it is.
|
||||
//
|
||||
// This is the simplest case of the range proof and we will focus on
|
||||
// demoing compatibility here
|
||||
func ConvertExistenceProof(p *cmtprotocrypto.Proof, key, value []byte) (*ics23.ExistenceProof, error) {
|
||||
path, err := convertInnerOps(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proof := &ics23.ExistenceProof{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Leaf: convertLeafOp(),
|
||||
Path: path,
|
||||
}
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
// this is adapted from merkle/hash.go:leafHash()
|
||||
// and merkle/simple_map.go:KVPair.Bytes()
|
||||
func convertLeafOp() *ics23.LeafOp {
|
||||
prefix := []byte{0}
|
||||
|
||||
return &ics23.LeafOp{
|
||||
Hash: ics23.HashOp_SHA256,
|
||||
PrehashKey: ics23.HashOp_NO_HASH,
|
||||
PrehashValue: ics23.HashOp_SHA256,
|
||||
Length: ics23.LengthOp_VAR_PROTO,
|
||||
Prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func convertInnerOps(p *cmtprotocrypto.Proof) ([]*ics23.InnerOp, error) {
|
||||
inners := make([]*ics23.InnerOp, 0, len(p.Aunts))
|
||||
path := buildPath(p.Index, p.Total)
|
||||
|
||||
if len(p.Aunts) != len(path) {
|
||||
return nil, fmt.Errorf("calculated a path different length (%d) than provided by SimpleProof (%d)", len(path), len(p.Aunts))
|
||||
}
|
||||
|
||||
for i, aunt := range p.Aunts {
|
||||
auntRight := path[i]
|
||||
|
||||
// combine with: 0x01 || lefthash || righthash
|
||||
inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256}
|
||||
if auntRight {
|
||||
inner.Prefix = []byte{1}
|
||||
inner.Suffix = aunt
|
||||
} else {
|
||||
inner.Prefix = append([]byte{1}, aunt...)
|
||||
}
|
||||
inners = append(inners, inner)
|
||||
}
|
||||
return inners, nil
|
||||
}
|
||||
|
||||
// buildPath returns a list of steps from leaf to root
|
||||
// in each step, true means index is left side, false index is right side
|
||||
// code adapted from merkle/simple_proof.go:computeHashFromAunts
|
||||
func buildPath(idx, total int64) []bool {
|
||||
if total < 2 {
|
||||
return nil
|
||||
}
|
||||
numLeft := getSplitPoint(total)
|
||||
goLeft := idx < numLeft
|
||||
|
||||
// we put goLeft at the end of the array, as we recurse from top to bottom,
|
||||
// and want the leaf to be first in array, root last
|
||||
if goLeft {
|
||||
return append(buildPath(idx, numLeft), goLeft)
|
||||
}
|
||||
return append(buildPath(idx-numLeft, total-numLeft), goLeft)
|
||||
}
|
||||
|
||||
func getSplitPoint(length int64) int64 {
|
||||
if length < 1 {
|
||||
panic("Trying to split a tree with size < 1")
|
||||
}
|
||||
uLength := uint(length)
|
||||
bitlen := bits.Len(uLength)
|
||||
k := int64(1 << uint(bitlen-1))
|
||||
if k == length {
|
||||
k >>= 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
package proofs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLeafOp(t *testing.T) {
|
||||
proof := GenerateRangeProof(20, Middle)
|
||||
|
||||
converted, err := ConvertExistenceProof(proof.Proof, proof.Key, proof.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
leaf := converted.GetLeaf()
|
||||
if leaf == nil {
|
||||
t.Fatalf("Missing leaf node")
|
||||
}
|
||||
|
||||
hash, err := leaf.Apply(converted.Key, converted.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(hash, proof.Proof.LeafHash) {
|
||||
t.Errorf("Calculated: %X\nExpected: %X", hash, proof.Proof.LeafHash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPath(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
idx int64
|
||||
total int64
|
||||
expected []bool
|
||||
}{
|
||||
"pair left": {
|
||||
idx: 0,
|
||||
total: 2,
|
||||
expected: []bool{true},
|
||||
},
|
||||
"pair right": {
|
||||
idx: 1,
|
||||
total: 2,
|
||||
expected: []bool{false},
|
||||
},
|
||||
"power of 2": {
|
||||
idx: 3,
|
||||
total: 8,
|
||||
expected: []bool{false, false, true},
|
||||
},
|
||||
"size of 7 right most": {
|
||||
idx: 6,
|
||||
total: 7,
|
||||
expected: []bool{false, false},
|
||||
},
|
||||
"size of 6 right-left (from top)": {
|
||||
idx: 4,
|
||||
total: 6,
|
||||
expected: []bool{true, false},
|
||||
},
|
||||
"size of 6 left-right-left (from top)": {
|
||||
idx: 2,
|
||||
total: 7,
|
||||
expected: []bool{true, false, true},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
path := buildPath(tc.idx, tc.total)
|
||||
if len(path) != len(tc.expected) {
|
||||
t.Fatalf("Got %v\nExpected %v", path, tc.expected)
|
||||
}
|
||||
for i := range path {
|
||||
if path[i] != tc.expected[i] {
|
||||
t.Fatalf("Differ at %d\nGot %v\nExpected %v", i, path, tc.expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertProof(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("Run %d", i), func(t *testing.T) {
|
||||
proof := GenerateRangeProof(57, Left)
|
||||
|
||||
converted, err := ConvertExistenceProof(proof.Proof, proof.Key, proof.Value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
calc, err := converted.Calculate()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(calc, proof.RootHash) {
|
||||
t.Errorf("Calculated: %X\nExpected: %X", calc, proof.RootHash)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
package proofs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
|
||||
"cosmossdk.io/store/v2/internal/maps"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyKey = errors.New("key is empty")
|
||||
ErrEmptyKeyInData = errors.New("data contains empty key")
|
||||
)
|
||||
|
||||
/*
|
||||
CreateMembershipProof will produce a CommitmentProof that the given key (and queries value) exists in the map.
|
||||
If the key doesn't exist in the tree, this will return an error.
|
||||
*/
|
||||
func CreateMembershipProof(data map[string][]byte, key []byte) (*ics23.CommitmentProof, error) {
|
||||
if len(key) == 0 {
|
||||
return nil, ErrEmptyKey
|
||||
}
|
||||
exist, err := createExistenceProof(data, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proof := &ics23.CommitmentProof{
|
||||
Proof: &ics23.CommitmentProof_Exist{
|
||||
Exist: exist,
|
||||
},
|
||||
}
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
/*
|
||||
CreateNonMembershipProof will produce a CommitmentProof that the given key doesn't exist in the map.
|
||||
If the key exists in the tree, this will return an error.
|
||||
*/
|
||||
func CreateNonMembershipProof(data map[string][]byte, key []byte) (*ics23.CommitmentProof, error) {
|
||||
if len(key) == 0 {
|
||||
return nil, ErrEmptyKey
|
||||
}
|
||||
// ensure this key is not in the store
|
||||
if _, ok := data[string(key)]; ok {
|
||||
return nil, errors.New("cannot create non-membership proof if key is in map")
|
||||
}
|
||||
|
||||
keys := SortedKeys(data)
|
||||
rightidx := sort.SearchStrings(keys, string(key))
|
||||
|
||||
var err error
|
||||
nonexist := &ics23.NonExistenceProof{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
// include left proof unless key is left of entire map
|
||||
if rightidx >= 1 {
|
||||
leftkey := keys[rightidx-1]
|
||||
nonexist.Left, err = createExistenceProof(data, []byte(leftkey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// include right proof unless key is right of entire map
|
||||
if rightidx < len(keys) {
|
||||
rightkey := keys[rightidx]
|
||||
nonexist.Right, err = createExistenceProof(data, []byte(rightkey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
proof := &ics23.CommitmentProof{
|
||||
Proof: &ics23.CommitmentProof_Nonexist{
|
||||
Nonexist: nonexist,
|
||||
},
|
||||
}
|
||||
return proof, nil
|
||||
}
|
||||
|
||||
func createExistenceProof(data map[string][]byte, key []byte) (*ics23.ExistenceProof, error) {
|
||||
for k := range data {
|
||||
if k == "" {
|
||||
return nil, ErrEmptyKeyInData
|
||||
}
|
||||
}
|
||||
value, ok := data[string(key)]
|
||||
if !ok {
|
||||
return nil, errors.New("cannot make existence proof if key is not in map")
|
||||
}
|
||||
|
||||
_, proofs, _ := maps.ProofsFromMap(data)
|
||||
proof := proofs[string(key)]
|
||||
if proof == nil {
|
||||
return nil, errors.New("returned no proof for key")
|
||||
}
|
||||
|
||||
return ConvertExistenceProof(proof, key, value)
|
||||
}
|
||||
@ -1,125 +0,0 @@
|
||||
package proofs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreateMembership(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
size int
|
||||
loc Where
|
||||
}{
|
||||
"small left": {size: 100, loc: Left},
|
||||
"small middle": {size: 100, loc: Middle},
|
||||
"small right": {size: 100, loc: Right},
|
||||
"big left": {size: 5431, loc: Left},
|
||||
"big middle": {size: 5431, loc: Middle},
|
||||
"big right": {size: 5431, loc: Right},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
data := BuildMap(tc.size)
|
||||
allkeys := SortedKeys(data)
|
||||
key := GetKey(allkeys, tc.loc)
|
||||
nonKey := GetNonKey(allkeys, tc.loc)
|
||||
|
||||
// error if the key does not exist
|
||||
proof, err := CreateMembershipProof(data, []byte(nonKey))
|
||||
assert.EqualError(t, err, "cannot make existence proof if key is not in map")
|
||||
assert.Nil(t, proof)
|
||||
|
||||
val := data[key]
|
||||
proof, err = CreateMembershipProof(data, []byte(key))
|
||||
if err != nil {
|
||||
t.Fatalf("Creating Proof: %+v", err)
|
||||
}
|
||||
if proof.GetExist() == nil {
|
||||
t.Fatal("Unexpected proof format")
|
||||
}
|
||||
|
||||
root := CalcRoot(data)
|
||||
err = proof.GetExist().Verify(ics23.TendermintSpec, root, []byte(key), val)
|
||||
if err != nil {
|
||||
t.Fatalf("Verifying Proof: %+v", err)
|
||||
}
|
||||
|
||||
valid := ics23.VerifyMembership(ics23.TendermintSpec, root, proof, []byte(key), val)
|
||||
if !valid {
|
||||
t.Fatalf("Membership Proof Invalid")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNonMembership(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
size int
|
||||
loc Where
|
||||
}{
|
||||
"small left": {size: 100, loc: Left},
|
||||
"small middle": {size: 100, loc: Middle},
|
||||
"small right": {size: 100, loc: Right},
|
||||
"big left": {size: 5431, loc: Left},
|
||||
"big middle": {size: 5431, loc: Middle},
|
||||
"big right": {size: 5431, loc: Right},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
data := BuildMap(tc.size)
|
||||
allkeys := SortedKeys(data)
|
||||
nonKey := GetNonKey(allkeys, tc.loc)
|
||||
key := GetKey(allkeys, tc.loc)
|
||||
|
||||
// error if the key exists
|
||||
proof, err := CreateNonMembershipProof(data, []byte(key))
|
||||
assert.EqualError(t, err, "cannot create non-membership proof if key is in map")
|
||||
assert.Nil(t, proof)
|
||||
|
||||
proof, err = CreateNonMembershipProof(data, []byte(nonKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Creating Proof: %+v", err)
|
||||
}
|
||||
if proof.GetNonexist() == nil {
|
||||
t.Fatal("Unexpected proof format")
|
||||
}
|
||||
|
||||
root := CalcRoot(data)
|
||||
err = proof.GetNonexist().Verify(ics23.TendermintSpec, root, []byte(nonKey))
|
||||
if err != nil {
|
||||
t.Fatalf("Verifying Proof: %+v", err)
|
||||
}
|
||||
|
||||
valid := ics23.VerifyNonMembership(ics23.TendermintSpec, root, proof, []byte(nonKey))
|
||||
if !valid {
|
||||
t.Fatalf("Non Membership Proof Invalid")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
f func(data map[string][]byte, key []byte) (*ics23.CommitmentProof, error)
|
||||
data map[string][]byte
|
||||
key []byte
|
||||
err error
|
||||
}{
|
||||
{"CreateMembershipProof empty key", CreateMembershipProof, map[string][]byte{"": nil}, []byte(""), ErrEmptyKey},
|
||||
{"CreateMembershipProof empty key in data", CreateMembershipProof, map[string][]byte{"": nil, " ": nil}, []byte(" "), ErrEmptyKeyInData},
|
||||
{"CreateNonMembershipProof empty key", CreateNonMembershipProof, map[string][]byte{" ": nil}, []byte(""), ErrEmptyKey},
|
||||
{"CreateNonMembershipProof empty key in data", CreateNonMembershipProof, map[string][]byte{"": nil}, []byte(" "), ErrEmptyKeyInData},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, err := tc.f(tc.data, tc.key)
|
||||
assert.True(t, errors.Is(err, tc.err))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
package proofs
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
cmtprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"cosmossdk.io/math/unsafe"
|
||||
internalmaps "cosmossdk.io/store/v2/internal/maps"
|
||||
)
|
||||
|
||||
// SimpleResult contains a merkle.SimpleProof along with all data needed to build the confio/proof
|
||||
type SimpleResult struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
Proof *cmtprotocrypto.Proof
|
||||
RootHash []byte
|
||||
}
|
||||
|
||||
// GenerateRangeProof makes a tree of size and returns a range proof for one random element
|
||||
//
|
||||
// returns a range proof and the root hash of the tree
|
||||
func GenerateRangeProof(size int, loc Where) *SimpleResult {
|
||||
data := BuildMap(size)
|
||||
root, proofs, allkeys := internalmaps.ProofsFromMap(data)
|
||||
|
||||
key := GetKey(allkeys, loc)
|
||||
proof := proofs[key]
|
||||
|
||||
res := &SimpleResult{
|
||||
Key: []byte(key),
|
||||
Value: toValue(key),
|
||||
Proof: proof,
|
||||
RootHash: root,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Where selects a location for a key - Left, Right, or Middle
|
||||
type Where int
|
||||
|
||||
const (
|
||||
Left Where = iota
|
||||
Right
|
||||
Middle
|
||||
)
|
||||
|
||||
func SortedKeys(data map[string][]byte) []string {
|
||||
keys := maps.Keys(data)
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func CalcRoot(data map[string][]byte) []byte {
|
||||
root, _, _ := internalmaps.ProofsFromMap(data)
|
||||
return root
|
||||
}
|
||||
|
||||
// GetKey this returns a key, on Left/Right/Middle
|
||||
func GetKey(allkeys []string, loc Where) string {
|
||||
if loc == Left {
|
||||
return allkeys[0]
|
||||
}
|
||||
if loc == Right {
|
||||
return allkeys[len(allkeys)-1]
|
||||
}
|
||||
// select a random index between 1 and allkeys-2
|
||||
idx := unsafe.NewRand().Int()%(len(allkeys)-2) + 1
|
||||
return allkeys[idx]
|
||||
}
|
||||
|
||||
// GetNonKey returns a missing key - Left of all, Right of all, or in the Middle
|
||||
func GetNonKey(allkeys []string, loc Where) string {
|
||||
if loc == Left {
|
||||
return string([]byte{1, 1, 1, 1})
|
||||
}
|
||||
if loc == Right {
|
||||
return string([]byte{0xff, 0xff, 0xff, 0xff})
|
||||
}
|
||||
// otherwise, next to an existing key (copy before mod)
|
||||
key := GetKey(allkeys, loc)
|
||||
key = key[:len(key)-2] + string([]byte{255, 255})
|
||||
return key
|
||||
}
|
||||
|
||||
func toValue(key string) []byte {
|
||||
return []byte("value_for_" + key)
|
||||
}
|
||||
|
||||
// BuildMap creates random key/values and stores in a map,
|
||||
// returns a list of all keys in sorted order
|
||||
func BuildMap(size int) map[string][]byte {
|
||||
data := make(map[string][]byte)
|
||||
// insert lots of info and store the bytes
|
||||
for i := 0; i < size; i++ {
|
||||
key := unsafe.Str(20)
|
||||
data[key] = toValue(key)
|
||||
}
|
||||
return data
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
package tree
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
var (
|
||||
leafPrefix = []byte{0}
|
||||
innerPrefix = []byte{1}
|
||||
)
|
||||
|
||||
// HashFromByteSlices computes a Merkle tree where the leaves are the byte slice,
|
||||
// in the provided order. It follows RFC-6962.
|
||||
func HashFromByteSlices(items [][]byte) []byte {
|
||||
return hashFromByteSlices(sha256.New(), items)
|
||||
}
|
||||
|
||||
func hashFromByteSlices(sha hash.Hash, items [][]byte) []byte {
|
||||
switch len(items) {
|
||||
case 0:
|
||||
return emptyHash()
|
||||
case 1:
|
||||
return leafHashOpt(sha, items[0])
|
||||
default:
|
||||
k := getSplitPoint(int64(len(items)))
|
||||
left := hashFromByteSlices(sha, items[:k])
|
||||
right := hashFromByteSlices(sha, items[k:])
|
||||
return innerHashOpt(sha, left, right)
|
||||
}
|
||||
}
|
||||
|
||||
// returns tmhash(0x00 || leaf)
|
||||
func leafHashOpt(s hash.Hash, leaf []byte) []byte {
|
||||
s.Reset()
|
||||
s.Write(leafPrefix)
|
||||
s.Write(leaf)
|
||||
return s.Sum(nil)
|
||||
}
|
||||
|
||||
func innerHashOpt(s hash.Hash, left, right []byte) []byte {
|
||||
s.Reset()
|
||||
s.Write(innerPrefix)
|
||||
s.Write(left)
|
||||
s.Write(right)
|
||||
return s.Sum(nil)
|
||||
}
|
||||
|
||||
// returns tmhash(<empty>)
|
||||
func emptyHash() []byte {
|
||||
h := sha256.Sum256([]byte{})
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// getSplitPoint returns the largest power of 2 less than length
|
||||
func getSplitPoint(length int64) int64 {
|
||||
if length < 1 {
|
||||
panic("Trying to split a tree with size < 1")
|
||||
}
|
||||
uLength := uint(length)
|
||||
bitlen := bits.Len(uLength)
|
||||
k := int64(1 << uint(bitlen-1))
|
||||
if k == length {
|
||||
k >>= 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
|
||||
ics23 "github.com/cosmos/ics23/go"
|
||||
|
||||
errorsmod "cosmossdk.io/errors"
|
||||
@ -13,6 +15,29 @@ const (
|
||||
ProofOpSMTCommitment = "ics23:smt"
|
||||
)
|
||||
|
||||
var (
|
||||
leafPrefix = []byte{0}
|
||||
innerPrefix = []byte{1}
|
||||
|
||||
// SimpleMerkleSpec is the ics23 proof spec for simple merkle proofs.
|
||||
SimpleMerkleSpec = &ics23.ProofSpec{
|
||||
LeafSpec: &ics23.LeafOp{
|
||||
Prefix: leafPrefix,
|
||||
PrehashKey: ics23.HashOp_NO_HASH,
|
||||
PrehashValue: ics23.HashOp_NO_HASH,
|
||||
Hash: ics23.HashOp_SHA256,
|
||||
Length: ics23.LengthOp_VAR_PROTO,
|
||||
},
|
||||
InnerSpec: &ics23.InnerSpec{
|
||||
ChildOrder: []int32{0, 1},
|
||||
MinPrefixLength: 1,
|
||||
MaxPrefixLength: 1,
|
||||
ChildSize: 32,
|
||||
Hash: ics23.HashOp_SHA256,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// CommitmentOp implements merkle.ProofOperator by wrapping an ics23 CommitmentProof.
|
||||
// It also contains a Key field to determine which key the proof is proving.
|
||||
// NOTE: CommitmentProof currently can either be ExistenceProof or NonexistenceProof
|
||||
@ -39,7 +64,7 @@ func NewIAVLCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp
|
||||
func NewSimpleMerkleCommitmentOp(key []byte, proof *ics23.CommitmentProof) CommitmentOp {
|
||||
return CommitmentOp{
|
||||
Type: ProofOpSimpleMerkleCommitment,
|
||||
Spec: ics23.TendermintSpec,
|
||||
Spec: SimpleMerkleSpec,
|
||||
Key: key,
|
||||
Proof: proof,
|
||||
}
|
||||
@ -96,3 +121,69 @@ func (op CommitmentOp) Run(args [][]byte) ([][]byte, error) {
|
||||
|
||||
return [][]byte{root}, nil
|
||||
}
|
||||
|
||||
// ProofFromByteSlices computes the proof from the given leaves.
|
||||
func ProofFromByteSlices(leaves [][]byte, index int) (rootHash []byte, inners []*ics23.InnerOp) {
|
||||
if len(leaves) == 0 {
|
||||
return emptyHash(), nil
|
||||
}
|
||||
|
||||
n := len(leaves)
|
||||
for n > 1 {
|
||||
if index < n-1 || index&1 == 1 {
|
||||
inner := &ics23.InnerOp{Hash: ics23.HashOp_SHA256}
|
||||
if index&1 == 0 {
|
||||
inner.Prefix = innerPrefix
|
||||
inner.Suffix = leaves[index^1]
|
||||
} else {
|
||||
inner.Prefix = append(innerPrefix, leaves[index^1]...)
|
||||
}
|
||||
inners = append(inners, inner)
|
||||
}
|
||||
for i := 0; i < n/2; i++ {
|
||||
leaves[i] = InnerHash(leaves[2*i], leaves[2*i+1])
|
||||
}
|
||||
if n&1 == 1 {
|
||||
leaves[n/2] = leaves[n-1]
|
||||
}
|
||||
n = (n + 1) / 2
|
||||
index /= 2
|
||||
}
|
||||
|
||||
rootHash = leaves[0]
|
||||
return
|
||||
}
|
||||
|
||||
// ConvertCommitmentOp converts the given merkle proof into an CommitmentOp.
|
||||
func ConvertCommitmentOp(inners []*ics23.InnerOp, key, value []byte) CommitmentOp {
|
||||
return NewSimpleMerkleCommitmentOp(key, &ics23.CommitmentProof{
|
||||
Proof: &ics23.CommitmentProof_Exist{
|
||||
Exist: &ics23.ExistenceProof{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Leaf: SimpleMerkleSpec.LeafSpec,
|
||||
Path: inners,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func emptyHash() []byte {
|
||||
h := sha256.Sum256([]byte{})
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// LeafHash computes the hash of a leaf node.
|
||||
func LeafHash(key, value []byte) ([]byte, error) {
|
||||
return SimpleMerkleSpec.LeafSpec.Apply(key, value)
|
||||
}
|
||||
|
||||
// InnerHash computes the hash of an inner node.
|
||||
func InnerHash(left, right []byte) []byte {
|
||||
data := make([]byte, len(innerPrefix)+len(left)+len(right))
|
||||
n := copy(data, innerPrefix)
|
||||
n += copy(data[n:], left)
|
||||
copy(data[n:], right)
|
||||
h := sha256.Sum256(data)
|
||||
return h[:]
|
||||
}
|
||||
|
||||
61
store/proof_test.go
Normal file
61
store/proof_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProofFromBytesSlices(t *testing.T) {
|
||||
tests := []struct {
|
||||
keys []string
|
||||
values []string
|
||||
want string
|
||||
}{
|
||||
{[]string{}, []string{}, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
|
||||
{[]string{"key1"}, []string{"value1"}, "09c468a07fe9bc1f14e754cff0acbad4faf9449449288be8e1d5d1199a247034"},
|
||||
{[]string{"key1"}, []string{"value2"}, "2131d85de3a8ded5d3a72bfc657f7324138540c520de7401ac8594785a3082fb"},
|
||||
// swap order with 2 keys
|
||||
{
|
||||
[]string{"key1", "key2"},
|
||||
[]string{"value1", "value2"},
|
||||
"017788f37362dd0687beb59c0b3bfcc17a955120a4cb63dbdd4a0fdf9e07730e",
|
||||
},
|
||||
{
|
||||
[]string{"key2", "key1"},
|
||||
[]string{"value2", "value1"},
|
||||
"ad2b0c23dbd3376440a5347fba02ff35cfad7930daa5e733930315b6fbb03b26",
|
||||
},
|
||||
// swap order with 3 keys
|
||||
{
|
||||
[]string{"key1", "key2", "key3"},
|
||||
[]string{"value1", "value2", "value3"},
|
||||
"68f41a8a3508cb5f8eb3f1c7534a86fea9f59aa4898a5aac2f1bb92834ae2a36",
|
||||
},
|
||||
{
|
||||
[]string{"key1", "key3", "key2"},
|
||||
[]string{"value1", "value3", "value2"},
|
||||
"92cd50420c22d0c79f64dd1b04bfd5f5d73265f7ac37e65cf622f3cf8b963805",
|
||||
},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
var err error
|
||||
leaves := make([][]byte, len(tc.keys))
|
||||
for j, key := range tc.keys {
|
||||
leaves[j], err = LeafHash([]byte(key), []byte(tc.values[j]))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
for j := range leaves {
|
||||
buf := make([][]byte, len(leaves))
|
||||
copy(buf, leaves)
|
||||
rootHash, inners := ProofFromByteSlices(buf, j)
|
||||
require.Equal(t, tc.want, fmt.Sprintf("%x", rootHash), "test case %d", i)
|
||||
commitmentOp := ConvertCommitmentOp(inners, []byte(tc.keys[j]), []byte(tc.values[j]))
|
||||
expRoots, err := commitmentOp.Run([][]byte{[]byte(tc.values[j])})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.want, fmt.Sprintf("%x", expRoots[0]), "test case %d", i)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -420,10 +420,10 @@ func (s *Store) commitSC() error {
|
||||
return fmt.Errorf("failed to commit SC store: %w", err)
|
||||
}
|
||||
|
||||
commitHash := store.CommitInfo{
|
||||
commitHash := (&store.CommitInfo{
|
||||
Version: s.lastCommitInfo.Version,
|
||||
StoreInfos: commitStoreInfos,
|
||||
}.Hash()
|
||||
}).Hash()
|
||||
|
||||
workingHash, err := s.WorkingHash()
|
||||
if err != nil {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user