feat: ADR 040: Implement in-memory DB backend (#9952)
## Description Implements an in-memory backend for the DB interface introduced by https://github.com/cosmos/cosmos-sdk/pull/9573 and specified by [ADR-040](https://github.com/cosmos/cosmos-sdk/blob/eb7d939f86c6cd7b4218492364cdda3f649f06b5/docs/architecture/adr-040-storage-and-smt-state-commitments.md). This expands on the [btree](https://pkg.go.dev/github.com/google/btree)-based [`MemDB`](https://github.com/tendermint/tm-db/tree/master/memdb) from `tm-db` by using copy-on-write clones to implement versioning. Resolves: https://github.com/vulcanize/cosmos-sdk/issues/2 Will move out of draft once https://github.com/cosmos/cosmos-sdk/pull/9573 is merged and rebased on. ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [x] added `!` to the type prefix if API or client breaking change - [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) n/a - [x] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] added a changelog entry to `CHANGELOG.md` - [x] included comments for [documenting Go code](https://blog.golang.org/godoc) - [x] updated the relevant documentation or specification - [x] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package memdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
tmdb "github.com/cosmos/cosmos-sdk/db"
|
||||
"github.com/google/btree"
|
||||
)
|
||||
|
||||
const (
|
||||
// Size of the channel buffer between traversal goroutine and iterator. Using an unbuffered
|
||||
// channel causes two context switches per item sent, while buffering allows more work per
|
||||
// context switch. Tuned with benchmarks.
|
||||
chBufferSize = 64
|
||||
)
|
||||
|
||||
// memDBIterator is a memDB iterator.
|
||||
type memDBIterator struct {
|
||||
ch <-chan *item
|
||||
cancel context.CancelFunc
|
||||
item *item
|
||||
start []byte
|
||||
end []byte
|
||||
}
|
||||
|
||||
var _ tmdb.Iterator = (*memDBIterator)(nil)
|
||||
|
||||
// newMemDBIterator creates a new memDBIterator.
|
||||
// A visitor is passed to the btree which streams items to the iterator over a channel. Advancing
|
||||
// the iterator pulls items from the channel, returning execution to the visitor.
|
||||
// The reverse case needs some special handling, since we use [start, end) while btree uses (start, end]
|
||||
func newMemDBIterator(tx *dbTxn, start []byte, end []byte, reverse bool) *memDBIterator {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ch := make(chan *item, chBufferSize)
|
||||
iter := &memDBIterator{
|
||||
ch: ch,
|
||||
cancel: cancel,
|
||||
start: start,
|
||||
end: end,
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
// Because we use [start, end) for reverse ranges, while btree uses (start, end], we need
|
||||
// the following variables to handle some reverse iteration conditions ourselves.
|
||||
var (
|
||||
skipEqual []byte
|
||||
abortLessThan []byte
|
||||
)
|
||||
visitor := func(i btree.Item) bool {
|
||||
item := i.(*item)
|
||||
if skipEqual != nil && bytes.Equal(item.key, skipEqual) {
|
||||
skipEqual = nil
|
||||
return true
|
||||
}
|
||||
if abortLessThan != nil && bytes.Compare(item.key, abortLessThan) == -1 {
|
||||
return false
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
case ch <- item:
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case start == nil && end == nil && !reverse:
|
||||
tx.btree.Ascend(visitor)
|
||||
case start == nil && end == nil && reverse:
|
||||
tx.btree.Descend(visitor)
|
||||
case end == nil && !reverse:
|
||||
// must handle this specially, since nil is considered less than anything else
|
||||
tx.btree.AscendGreaterOrEqual(newKey(start), visitor)
|
||||
case !reverse:
|
||||
tx.btree.AscendRange(newKey(start), newKey(end), visitor)
|
||||
case end == nil:
|
||||
// abort after start, since we use [start, end) while btree uses (start, end]
|
||||
abortLessThan = start
|
||||
tx.btree.Descend(visitor)
|
||||
default:
|
||||
// skip end and abort after start, since we use [start, end) while btree uses (start, end]
|
||||
skipEqual = end
|
||||
abortLessThan = start
|
||||
tx.btree.DescendLessOrEqual(newKey(end), visitor)
|
||||
}
|
||||
}()
|
||||
|
||||
return iter
|
||||
}
|
||||
|
||||
// Close implements Iterator.
|
||||
func (i *memDBIterator) Close() error {
|
||||
i.cancel()
|
||||
for range i.ch { // drain channel
|
||||
}
|
||||
i.item = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Domain implements Iterator.
|
||||
func (i *memDBIterator) Domain() ([]byte, []byte) {
|
||||
return i.start, i.end
|
||||
}
|
||||
|
||||
// Next implements Iterator.
|
||||
func (i *memDBIterator) Next() bool {
|
||||
item, ok := <-i.ch
|
||||
switch {
|
||||
case ok:
|
||||
i.item = item
|
||||
default:
|
||||
i.item = nil
|
||||
}
|
||||
return i.item != nil
|
||||
}
|
||||
|
||||
// Error implements Iterator.
|
||||
func (i *memDBIterator) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key implements Iterator.
|
||||
func (i *memDBIterator) Key() []byte {
|
||||
i.assertIsValid()
|
||||
return i.item.key
|
||||
}
|
||||
|
||||
// Value implements Iterator.
|
||||
func (i *memDBIterator) Value() []byte {
|
||||
i.assertIsValid()
|
||||
return i.item.value
|
||||
}
|
||||
|
||||
func (i *memDBIterator) assertIsValid() {
|
||||
if i.item == nil {
|
||||
panic("iterator is invalid")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user