forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 36
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
Initial SMT store type #12
Closed
Closed
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
1945a92
Initial SMT store type
tzdybal dd21010
Add iteration support to SMT
tzdybal f8fd015
Migrate to smt v0.1.1
tzdybal 19e07ee
Extra test for SMT iterator
tzdybal dde3c29
ci: fix liveness test / rbuilder script (#15)
tac0turtle e89cbb5
CommitStore implementation for SMT store
tzdybal 8d79be8
Merge remote-tracking branch 'origin/master' into tzdybal/add_smt
tzdybal 40ce86d
Use interface instead of concrete type
tzdybal 04dc9ee
Add telemetry to SMT store
tzdybal 54e3909
SMT: version->root mapping, cleanup
tzdybal f1ac2cc
SMT proofs - initial code
tzdybal 122a1bc
Tests for SMT store ProofOp implementation
tzdybal 9eb31c8
Merge remote-tracking branch 'origin/master' into tzdybal/add_smt
tzdybal 6171e35
Fix linter errors
tzdybal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package smt | ||
|
||
import ( | ||
"bytes" | ||
|
||
dbm "github.com/tendermint/tm-db" | ||
) | ||
|
||
type Iterator struct { | ||
store *Store | ||
iter dbm.Iterator | ||
} | ||
|
||
var ( | ||
indexPrefix = []byte("smt-ordering-idx-") | ||
afterIndex = []byte("smt-ordering-idx.") // '.' is next after '-' in ASCII | ||
) | ||
|
||
func indexKey(key []byte) []byte { | ||
return append(indexPrefix, key...) | ||
} | ||
|
||
func plainKey(key []byte) []byte { | ||
return key[len(indexPrefix):] | ||
} | ||
|
||
func startKey(key []byte) []byte { | ||
if key == nil { | ||
return indexPrefix | ||
} | ||
return indexKey(key) | ||
} | ||
|
||
func endKey(key []byte) []byte { | ||
if key == nil { | ||
return afterIndex | ||
} | ||
return indexKey(key) | ||
} | ||
|
||
func newIterator(s *Store, start, end []byte, reverse bool) (*Iterator, error) { | ||
start = startKey(start) | ||
end = endKey(end) | ||
var i dbm.Iterator | ||
var err error | ||
if reverse { | ||
i, err = s.db.ReverseIterator(start, end) | ||
} else { | ||
i, err = s.db.Iterator(start, end) | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Iterator{store: s, iter: i}, nil | ||
} | ||
|
||
// Domain returns the start (inclusive) and end (exclusive) limits of the iterator. | ||
// CONTRACT: start, end readonly []byte | ||
func (i *Iterator) Domain() (start []byte, end []byte) { | ||
start, end = i.iter.Domain() | ||
if bytes.Equal(start, indexPrefix) { | ||
start = nil | ||
} else { | ||
start = plainKey(start) | ||
} | ||
if bytes.Equal(end, afterIndex) { | ||
end = nil | ||
} else { | ||
end = plainKey(end) | ||
} | ||
return start, end | ||
} | ||
|
||
// Valid returns whether the current iterator is valid. Once invalid, the Iterator remains | ||
// invalid forever. | ||
func (i *Iterator) Valid() bool { | ||
return i.iter.Valid() | ||
} | ||
|
||
// Next moves the iterator to the next key in the database, as defined by order of iteration. | ||
// If Valid returns false, this method will panic. | ||
func (i *Iterator) Next() { | ||
i.iter.Next() | ||
} | ||
|
||
// Key returns the key at the current position. Panics if the iterator is invalid. | ||
// CONTRACT: key readonly []byte | ||
func (i *Iterator) Key() (key []byte) { | ||
return plainKey(i.iter.Key()) | ||
} | ||
|
||
// Value returns the value at the current position. Panics if the iterator is invalid. | ||
// CONTRACT: value readonly []byte | ||
func (i *Iterator) Value() (value []byte) { | ||
return i.store.Get(i.Key()) | ||
} | ||
|
||
// Error returns the last error encountered by the iterator, if any. | ||
func (i *Iterator) Error() error { | ||
return i.iter.Error() | ||
} | ||
|
||
// Close closes the iterator, relasing any allocated resources. | ||
func (i *Iterator) Close() error { | ||
return i.iter.Close() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package smt_test | ||
|
||
import ( | ||
"bytes" | ||
"sort" | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/store/smt" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
dbm "github.com/tendermint/tm-db" | ||
) | ||
|
||
func TestIteration(t *testing.T) { | ||
pairs := []struct{ key, val []byte }{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Table driven tests |
||
{[]byte("foo"), []byte("bar")}, | ||
{[]byte("lorem"), []byte("ipsum")}, | ||
{[]byte("alpha"), []byte("beta")}, | ||
{[]byte("gamma"), []byte("delta")}, | ||
{[]byte("epsilon"), []byte("zeta")}, | ||
{[]byte("eta"), []byte("theta")}, | ||
{[]byte("iota"), []byte("kappa")}, | ||
} | ||
|
||
s := smt.NewStore(dbm.NewMemDB()) | ||
|
||
for _, p := range pairs { | ||
s.Set(p.key, p.val) | ||
} | ||
|
||
// sort test data by key, to get "expected" ordering | ||
sort.Slice(pairs, func(i, j int) bool { | ||
return bytes.Compare(pairs[i].key, pairs[j].key) < 0 | ||
}) | ||
|
||
iter := s.Iterator([]byte("alpha"), []byte("omega")) | ||
for _, p := range pairs { | ||
require.True(t, iter.Valid()) | ||
assert.Equal(t, p.key, iter.Key()) | ||
assert.Equal(t, p.val, iter.Value()) | ||
iter.Next() | ||
} | ||
assert.False(t, iter.Valid()) | ||
assert.NoError(t, iter.Error()) | ||
assert.NoError(t, iter.Close()) | ||
|
||
iter = s.Iterator(nil, nil) | ||
for _, p := range pairs { | ||
require.True(t, iter.Valid()) | ||
assert.Equal(t, p.key, iter.Key()) | ||
assert.Equal(t, p.val, iter.Value()) | ||
iter.Next() | ||
} | ||
assert.False(t, iter.Valid()) | ||
assert.NoError(t, iter.Error()) | ||
assert.NoError(t, iter.Close()) | ||
|
||
iter = s.Iterator([]byte("epsilon"), []byte("gamma")) | ||
for _, p := range pairs[1:4] { | ||
require.True(t, iter.Valid()) | ||
assert.Equal(t, p.key, iter.Key()) | ||
assert.Equal(t, p.val, iter.Value()) | ||
iter.Next() | ||
} | ||
assert.False(t, iter.Valid()) | ||
assert.NoError(t, iter.Error()) | ||
assert.NoError(t, iter.Close()) | ||
|
||
rIter := s.ReverseIterator(nil, nil) | ||
for i := len(pairs) - 1; i >= 0; i-- { | ||
require.True(t, rIter.Valid()) | ||
assert.Equal(t, pairs[i].key, rIter.Key()) | ||
assert.Equal(t, pairs[i].val, rIter.Value()) | ||
rIter.Next() | ||
} | ||
assert.False(t, rIter.Valid()) | ||
assert.NoError(t, rIter.Error()) | ||
assert.NoError(t, rIter.Close()) | ||
|
||
// delete something, and ensure that iteration still works | ||
s.Delete([]byte("eta")) | ||
|
||
iter = s.Iterator(nil, nil) | ||
for _, p := range pairs { | ||
if !bytes.Equal([]byte("eta"), p.key) { | ||
require.True(t, iter.Valid()) | ||
assert.Equal(t, p.key, iter.Key()) | ||
assert.Equal(t, p.val, iter.Value()) | ||
iter.Next() | ||
} | ||
} | ||
assert.False(t, iter.Valid()) | ||
assert.NoError(t, iter.Error()) | ||
assert.NoError(t, iter.Close()) | ||
} | ||
|
||
func TestDomain(t *testing.T) { | ||
s := smt.NewStore(dbm.NewMemDB()) | ||
|
||
iter := s.Iterator(nil, nil) | ||
start, end := iter.Domain() | ||
assert.Nil(t, start) | ||
assert.Nil(t, end) | ||
|
||
iter = s.Iterator([]byte("foo"), []byte("bar")) | ||
start, end = iter.Domain() | ||
assert.Equal(t, []byte("foo"), start) | ||
assert.Equal(t, []byte("bar"), end) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package smt | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
"encoding/gob" | ||
"hash" | ||
|
||
"github.com/cosmos/cosmos-sdk/store/types" | ||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/lazyledger/lazyledger-core/crypto/merkle" | ||
tmmerkle "github.com/lazyledger/lazyledger-core/proto/tendermint/crypto" | ||
"github.com/lazyledger/smt" | ||
) | ||
|
||
type HasherType byte | ||
|
||
const ( | ||
SHA256 HasherType = iota | ||
) | ||
|
||
const ( | ||
ProofType = "smt" | ||
) | ||
|
||
type ProofOp struct { | ||
Root []byte | ||
Key []byte | ||
Hasher HasherType | ||
Proof smt.SparseMerkleProof | ||
} | ||
|
||
var _ merkle.ProofOperator = &ProofOp{} | ||
|
||
func NewProofOp(root, key []byte, hasher HasherType, proof smt.SparseMerkleProof) *ProofOp { | ||
return &ProofOp{ | ||
Root: root, | ||
Key: key, | ||
Hasher: hasher, | ||
Proof: proof, | ||
} | ||
} | ||
|
||
func (p *ProofOp) Run(args [][]byte) ([][]byte, error) { | ||
switch len(args) { | ||
case 0: // non-membership proof | ||
if !smt.VerifyProof(p.Proof, p.Root, p.Key, []byte{}, getHasher(p.Hasher)) { | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidProof, "proof did not verify absence of key: %s", p.Key) | ||
} | ||
case 1: // membership proof | ||
if !smt.VerifyProof(p.Proof, p.Root, p.Key, args[0], getHasher(p.Hasher)) { | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidProof, "proof did not verify existence of key %s with given value %x", p.Key, args[0]) | ||
} | ||
default: | ||
return nil, sdkerrors.Wrapf(types.ErrInvalidProof, "args must be length 0 or 1, got: %d", len(args)) | ||
} | ||
return [][]byte{p.Root}, nil | ||
} | ||
|
||
func (p *ProofOp) GetKey() []byte { | ||
return p.Key | ||
} | ||
|
||
func (p *ProofOp) ProofOp() tmmerkle.ProofOp { | ||
var data bytes.Buffer | ||
enc := gob.NewEncoder(&data) | ||
enc.Encode(p) | ||
return tmmerkle.ProofOp{ | ||
Type: "smt", | ||
liamsi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Key: p.Key, | ||
Data: data.Bytes(), | ||
} | ||
} | ||
|
||
func ProofDecoder(pop tmmerkle.ProofOp) (merkle.ProofOperator, error) { | ||
dec := gob.NewDecoder(bytes.NewBuffer(pop.Data)) | ||
var proof ProofOp | ||
err := dec.Decode(&proof) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &proof, nil | ||
} | ||
|
||
func getHasher(hasher HasherType) hash.Hash { | ||
switch hasher { | ||
case SHA256: | ||
return sha256.New() | ||
default: | ||
return nil | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really self-explanatory. Add a brief explanation (even if it is unexported).
Nit: I'd prefer using
string
const
ants instead and wrap them with[]byte()
where they are used.