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

feat: ADR-040: Implement KV Store with decoupled storage and SMT #9892

Merged
merged 39 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c59cd90
Clarify db interface godoc
roysc Aug 27, 2021
c4b4553
Add prefixed db read/writers
roysc Aug 17, 2021
94c2c17
Add db adapter
roysc Aug 12, 2021
c164920
Add SMT store type (#8507)
tzdybal May 14, 2021
51bc84b
Add godoc comments
roysc Jul 8, 2021
ce3ff55
update go.mod - smt
roysc Aug 26, 2021
94d1a2e
Refactor SMT, move to store/v2
roysc Jun 23, 2021
14643aa
revise store/v2 types
roysc Sep 8, 2021
72b0501
Introduce decoupled.Store
roysc Aug 26, 2021
9b12233
store op telemetry
roysc Sep 22, 2021
ef4b843
rollback commit on failure
roysc Sep 22, 2021
e498b9b
handle initial version
roysc Sep 22, 2021
456b67f
complete store interface
roysc Sep 22, 2021
f558557
docs: describe new store package
roysc Sep 22, 2021
a254ea9
update go.mod
roysc Sep 22, 2021
78cdaef
error on block height limit
roysc Sep 28, 2021
fbd7ced
remove access to SC store
roysc Sep 28, 2021
d4f8fab
close txns on error + test case
roysc Sep 28, 2021
f83869c
store nits
roysc Sep 29, 2021
41d3da1
store nit
roysc Sep 29, 2021
e83d6dd
vmgr patches
roysc Oct 4, 2021
382b21b
impl, test store commit rollback
roysc Oct 4, 2021
a19d6a5
remove unneeded files
roysc Oct 6, 2021
2a70da1
decoupled Store - consolidate constructors
roysc Oct 12, 2021
20e9fb9
fix test
roysc Oct 12, 2021
97bad68
fill out test coverage and tweak errors
roysc Oct 15, 2021
f9130d9
smt store test coverage
roysc Oct 15, 2021
b6cd409
clean up test
roysc Oct 15, 2021
9754394
store test coverage, cleanup
roysc Oct 18, 2021
27d3020
Merge remote-tracking branch 'upstream/master' into adr-040-wip-store
roysc Oct 18, 2021
af607a6
Merge branch 'master' into roysc/adr-040-store
roysc Oct 18, 2021
3a093ab
unneeded method
roysc Oct 18, 2021
fe26dbb
test cleanup
roysc Oct 19, 2021
c956d9c
remove telemetry
roysc Oct 19, 2021
3d740f2
remove telemetry
roysc Oct 19, 2021
15c5118
PR revisions
roysc Oct 19, 2021
5fc15d5
rename decoupled -> flat
roysc Oct 19, 2021
ec86707
changelog entry
roysc Oct 19, 2021
526272e
Merge branch 'master' into roysc/adr-040-store
roysc Oct 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#9848](https://github.com/cosmos/cosmos-sdk/pull/9848) ADR-040: Implement BadgerDB backend
* [\#9851](https://github.com/cosmos/cosmos-sdk/pull/9851) ADR-040: Implement RocksDB backend
* [\#10308](https://github.com/cosmos/cosmos-sdk/pull/10308) ADR-040: Implement DBConnection.Revert
* [\#9892](https://github.com/cosmos/cosmos-sdk/pull/9892) ADR-040: KV Store with decoupled storage and state commitment


### Client Breaking Changes
Expand Down
23 changes: 23 additions & 0 deletions db/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package db

type readerRWAdapter struct{ DBReader }

// ReaderAsReadWriter returns a ReadWriter that forwards to a reader and errors if writes are
// attempted. Can be used to pass a Reader when a ReadWriter is expected
// but no writes will actually occur.
func ReaderAsReadWriter(r DBReader) DBReadWriter {
return readerRWAdapter{r}
}

func (readerRWAdapter) Set([]byte, []byte) error {
return ErrReadOnly
}

func (readerRWAdapter) Delete([]byte) error {
return ErrReadOnly
}

func (rw readerRWAdapter) Commit() error {
rw.Discard()
return nil
}
52 changes: 52 additions & 0 deletions db/dbtest/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package dbtest

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

dbm "github.com/cosmos/cosmos-sdk/db"
)

func AssertNext(t *testing.T, itr dbm.Iterator, expected bool) {
t.Helper()
require.Equal(t, expected, itr.Next())
}

func AssertDomain(t *testing.T, itr dbm.Iterator, start, end []byte) {
t.Helper()
ds, de := itr.Domain()
assert.Equal(t, start, ds, "checkDomain domain start incorrect")
assert.Equal(t, end, de, "checkDomain domain end incorrect")
}

func AssertItem(t *testing.T, itr dbm.Iterator, key, value []byte) {
t.Helper()
assert.Exactly(t, itr.Key(), k)
assert.Exactly(t, itr.Value(), v)
}

func AssertInvalid(t *testing.T, itr dbm.Iterator) {
t.Helper()
AssertNext(t, itr, false)
AssertKeyPanics(t, itr)
AssertValuePanics(t, itr)
}

func AssertKeyPanics(t *testing.T, itr dbm.Iterator) {
t.Helper()
assert.Panics(t, func() { itr.Key() }, "checkKeyPanics expected panic but didn't")
}

func AssertValue(t *testing.T, db dbm.DBReader, key, valueWanted []byte) {
t.Helper()
valueGot, err := db.Get(key)
assert.NoError(t, err)
assert.Equal(t, valueWanted, valueGot)
}

func AssertValuePanics(t *testing.T, itr dbm.Iterator) {
t.Helper()
assert.Panics(t, func() { itr.Value() })
}
2 changes: 1 addition & 1 deletion db/internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func ValidateKv(key, value []byte) error {
func CombineErrors(ret error, also error, desc string) error {
if also != nil {
if ret != nil {
ret = fmt.Errorf("%w; %v: %v", ret, desc, also)
ret = fmt.Errorf("%w; %s: %v", ret, desc, also)
} else {
ret = also
}
Expand Down
159 changes: 159 additions & 0 deletions db/prefix/prefix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package prefix

import (
dbm "github.com/cosmos/cosmos-sdk/db"
)

// Prefix Reader/Writer lets you namespace multiple DBs within a single DB.
type prefixR struct {
db dbm.DBReader
prefix []byte
}

type prefixRW struct {
db dbm.DBReadWriter
prefix []byte
}

var _ dbm.DBReader = (*prefixR)(nil)
var _ dbm.DBReadWriter = (*prefixRW)(nil)

func NewPrefixReader(db dbm.DBReader, prefix []byte) prefixR {
return prefixR{
prefix: prefix,
db: db,
}
}

func NewPrefixReadWriter(db dbm.DBReadWriter, prefix []byte) prefixRW {
return prefixRW{
prefix: prefix,
db: db,
}
}

func prefixed(prefix, key []byte) []byte {
return append(prefix, key...)
}

// Get implements DBReader.
func (pdb prefixR) Get(key []byte) ([]byte, error) {
if len(key) == 0 {
return nil, dbm.ErrKeyEmpty
}
return pdb.db.Get(prefixed(pdb.prefix, key))
}

// Has implements DBReader.
func (pdb prefixR) Has(key []byte) (bool, error) {
if len(key) == 0 {
return false, dbm.ErrKeyEmpty
}
return pdb.db.Has(prefixed(pdb.prefix, key))
}

// Iterator implements DBReader.
func (pdb prefixR) Iterator(start, end []byte) (dbm.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, dbm.ErrKeyEmpty
}

var pend []byte
if end == nil {
pend = cpIncr(pdb.prefix)
} else {
pend = prefixed(pdb.prefix, end)
}
itr, err := pdb.db.Iterator(prefixed(pdb.prefix, start), pend)
if err != nil {
return nil, err
}
return newPrefixIterator(pdb.prefix, start, end, itr), nil
}

// ReverseIterator implements DBReader.
func (pdb prefixR) ReverseIterator(start, end []byte) (dbm.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, dbm.ErrKeyEmpty
}

var pend []byte
if end == nil {
pend = cpIncr(pdb.prefix)
} else {
pend = prefixed(pdb.prefix, end)
}
ritr, err := pdb.db.ReverseIterator(prefixed(pdb.prefix, start), pend)
if err != nil {
return nil, err
}
return newPrefixIterator(pdb.prefix, start, end, ritr), nil
}

// Discard implements DBReader.
func (pdb prefixR) Discard() error { return pdb.db.Discard() }

// Set implements DBReadWriter.
func (pdb prefixRW) Set(key []byte, value []byte) error {
if len(key) == 0 {
return dbm.ErrKeyEmpty
}
return pdb.db.Set(prefixed(pdb.prefix, key), value)
}

// Delete implements DBReadWriter.
func (pdb prefixRW) Delete(key []byte) error {
if len(key) == 0 {
return dbm.ErrKeyEmpty
}
return pdb.db.Delete(prefixed(pdb.prefix, key))
}

// Get implements DBReadWriter.
func (pdb prefixRW) Get(key []byte) ([]byte, error) {
return NewPrefixReader(pdb.db, pdb.prefix).Get(key)
}

// Has implements DBReadWriter.
func (pdb prefixRW) Has(key []byte) (bool, error) {
return NewPrefixReader(pdb.db, pdb.prefix).Has(key)
}

// Iterator implements DBReadWriter.
func (pdb prefixRW) Iterator(start, end []byte) (dbm.Iterator, error) {
return NewPrefixReader(pdb.db, pdb.prefix).Iterator(start, end)
}

// ReverseIterator implements DBReadWriter.
func (pdb prefixRW) ReverseIterator(start, end []byte) (dbm.Iterator, error) {
return NewPrefixReader(pdb.db, pdb.prefix).ReverseIterator(start, end)
}

// Close implements DBReadWriter.
func (pdb prefixRW) Commit() error { return pdb.db.Commit() }

// Discard implements DBReadWriter.
func (pdb prefixRW) Discard() error { return pdb.db.Discard() }

// Returns a slice of the same length (big endian), but incremented by one.
// Returns nil on overflow (e.g. if bz bytes are all 0xFF)
// CONTRACT: len(bz) > 0
func cpIncr(bz []byte) (ret []byte) {
if len(bz) == 0 {
panic("cpIncr expects non-zero bz length")
}
ret = make([]byte, len(bz))
copy(ret, bz)
for i := len(bz) - 1; i >= 0; i-- {
if ret[i] < byte(0xFF) {
ret[i]++
return
}
ret[i] = byte(0x00)
if i == 0 {
// Overflow
return nil
}
}
return nil
}
112 changes: 112 additions & 0 deletions db/prefix/prefix_iterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package prefix

import (
"bytes"
"fmt"

dbm "github.com/cosmos/cosmos-sdk/db"
)

// IteratePrefix is a convenience function for iterating over a key domain
// restricted by prefix.
func IteratePrefix(db dbm.DBReader, prefix []byte) (dbm.Iterator, error) {
var start, end []byte
if len(prefix) != 0 {
start = prefix
end = cpIncr(prefix)
}
itr, err := db.Iterator(start, end)
if err != nil {
return nil, err
}
return itr, nil
}

// Strips prefix while iterating from Iterator.
type prefixDBIterator struct {
prefix []byte
start []byte
end []byte
source dbm.Iterator
err error
}

var _ dbm.Iterator = (*prefixDBIterator)(nil)

func newPrefixIterator(prefix, start, end []byte, source dbm.Iterator) *prefixDBIterator {
return &prefixDBIterator{
prefix: prefix,
start: start,
end: end,
source: source,
}
}

// Domain implements Iterator.
func (itr *prefixDBIterator) Domain() (start, end []byte) {
return itr.start, itr.end
}

func (itr *prefixDBIterator) valid() bool {
if itr.err != nil {
return false
}

key := itr.source.Key()
if len(key) < len(itr.prefix) || !bytes.Equal(key[:len(itr.prefix)], itr.prefix) {
itr.err = fmt.Errorf("received invalid key from backend: %x (expected prefix %x)",
key, itr.prefix)
return false
}

return true
}

// Next implements Iterator.
func (itr *prefixDBIterator) Next() bool {
if !itr.source.Next() {
return false
}
key := itr.source.Key()
if !bytes.HasPrefix(key, itr.prefix) {
return false
}
// Empty keys are not allowed, so if a key exists in the database that exactly matches the
// prefix we need to skip it.
if bytes.Equal(key, itr.prefix) {
return itr.Next()
}
return true
}

// Next implements Iterator.
func (itr *prefixDBIterator) Key() []byte {
itr.assertIsValid()
key := itr.source.Key()
return key[len(itr.prefix):] // we have checked the key in Valid()
}

// Value implements Iterator.
func (itr *prefixDBIterator) Value() []byte {
itr.assertIsValid()
return itr.source.Value()
}

// Error implements Iterator.
func (itr *prefixDBIterator) Error() error {
if err := itr.source.Error(); err != nil {
return err
}
return itr.err
}

// Close implements Iterator.
func (itr *prefixDBIterator) Close() error {
return itr.source.Close()
}

func (itr *prefixDBIterator) assertIsValid() {
if !itr.valid() {
panic("iterator is invalid")
}
}
Loading