diff --git a/CHANGELOG.md b/CHANGELOG.md
index d36d777951..0b5d406386 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@
* [#1670](https://github.com/crypto-org-chain/cronos/pull/1670) Fix state overwrite in debug trace APIs.
* [#1679](https://github.com/crypto-org-chain/cronos/pull/1679) Include no trace detail on insufficient balance fix.
+* [#1685](https://github.com/crypto-org-chain/cronos/pull/1685) Add command to fix versiondb corrupted data.
+* [#1688](https://github.com/crypto-org-chain/cronos/pull/1688) Add timestamp api to versiondb iterator.
+* [#1686](https://github.com/crypto-org-chain/cronos/pull/1686) Update rocksdb to 9.7.4.
*Oct 14, 2024*
diff --git a/app/versiondb.go b/app/versiondb.go
index 10b0ad76ad..381db1027b 100644
--- a/app/versiondb.go
+++ b/app/versiondb.go
@@ -23,6 +23,7 @@ func (app *App) setupVersionDB(
if err := os.MkdirAll(dataDir, os.ModePerm); err != nil {
return nil, err
}
+
versionDB, err := tsrocksdb.NewStore(dataDir)
if err != nil {
return nil, err
@@ -34,6 +35,9 @@ func (app *App) setupVersionDB(
exposeStoreKeys = append(exposeStoreKeys, storeKey)
}
+ // see: https://github.com/crypto-org-chain/cronos/issues/1683
+ versionDB.SetSkipVersionZero(true)
+
service := versiondb.NewStreamingService(versionDB, exposeStoreKeys)
app.SetStreamingService(service)
diff --git a/go.mod b/go.mod
index 386ce5014e..cc60d20c95 100644
--- a/go.mod
+++ b/go.mod
@@ -27,7 +27,7 @@ require (
github.com/golang/protobuf v1.5.4
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
- github.com/linxGnu/grocksdb v1.9.2
+ github.com/linxGnu/grocksdb v1.9.7
github.com/peggyjv/gravity-bridge/module/v2 v2.0.0-20220420162017-838c0d25e974
github.com/spf13/cast v1.6.0
github.com/spf13/cobra v1.8.0
diff --git a/go.sum b/go.sum
index 2231f62066..d48b77ff32 100644
--- a/go.sum
+++ b/go.sum
@@ -1085,8 +1085,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8=
-github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
+github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw=
+github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
diff --git a/gomod2nix.toml b/gomod2nix.toml
index 345984dccd..1cff51efb6 100644
--- a/gomod2nix.toml
+++ b/gomod2nix.toml
@@ -403,8 +403,8 @@ schema = 3
version = "v0.1.0"
hash = "sha256-wQqGTtRWsfR9n0O/SXHVgECebbnNmHddxJIbG63OJBQ="
[mod."github.com/linxGnu/grocksdb"]
- version = "v1.9.2"
- hash = "sha256-ThXtaXx6LvRIFW4xLHsMrVWdsN2qobLPA0InLmlADOM="
+ version = "v1.9.7"
+ hash = "sha256-ZSomnYZRo7gHB9/FW55MebNkNzn0DuR96RfsVpAwjIQ="
[mod."github.com/magiconair/properties"]
version = "v1.8.7"
hash = "sha256-XQ2bnc2s7/IH3WxEO4GishZurMyKwEclZy1DXg+2xXc="
diff --git a/nix/rocksdb.nix b/nix/rocksdb.nix
index 66e37dd642..32e1e0720e 100644
--- a/nix/rocksdb.nix
+++ b/nix/rocksdb.nix
@@ -21,13 +21,13 @@
stdenv.mkDerivation (finalAttrs: {
pname = "rocksdb";
- version = "9.2.1";
+ version = "9.7.4";
src = fetchFromGitHub {
owner = "facebook";
repo = finalAttrs.pname;
rev = "v${finalAttrs.version}";
- hash = "sha256-Zifn5Gu/4h6TaEqSaWQ2mFdryeAarqbHWW3fKUGGFac=";
+ hash = "sha256-u5uuShM2SxHc9/zL4UU56IhCcR/ZQbzde0LgOYS44bM=";
};
nativeBuildInputs = [
diff --git a/versiondb/client/cmd.go b/versiondb/client/cmd.go
index 4bf36343e8..5955037f71 100644
--- a/versiondb/client/cmd.go
+++ b/versiondb/client/cmd.go
@@ -28,6 +28,7 @@ func ChangeSetGroupCmd(opts Options) *cobra.Command {
ChangeSetToVersionDBCmd(),
RestoreAppDBCmd(opts),
RestoreVersionDBCmd(),
+ FixDataCmd(opts.DefaultStores),
)
return cmd
}
diff --git a/versiondb/client/fixdata.go b/versiondb/client/fixdata.go
new file mode 100644
index 0000000000..efd95fbff4
--- /dev/null
+++ b/versiondb/client/fixdata.go
@@ -0,0 +1,59 @@
+package client
+
+import (
+ "github.com/crypto-org-chain/cronos/versiondb/tsrocksdb"
+ "github.com/linxGnu/grocksdb"
+ "github.com/spf13/cobra"
+)
+
+const (
+ FlagDryRun = "dry-run"
+ FlagStore = "store-name"
+)
+
+func FixDataCmd(defaultStores []string) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "fixdata
",
+ Args: cobra.ExactArgs(1),
+ Short: "Fix wrong data in versiondb, see: https://github.com/crypto-org-chain/cronos/issues/1683",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ dir := args[0]
+ dryRun, err := cmd.Flags().GetBool(FlagDryRun)
+ if err != nil {
+ return err
+ }
+ stores, err := cmd.Flags().GetStringArray(FlagStore)
+ if err != nil {
+ return err
+ }
+ if len(stores) == 0 {
+ stores = defaultStores
+ }
+
+ var (
+ db *grocksdb.DB
+ cfHandle *grocksdb.ColumnFamilyHandle
+ )
+
+ if dryRun {
+ db, cfHandle, err = tsrocksdb.OpenVersionDBForReadOnly(dir, false)
+ } else {
+ db, cfHandle, err = tsrocksdb.OpenVersionDB(dir)
+ }
+ if err != nil {
+ return err
+ }
+
+ versionDB := tsrocksdb.NewStoreWithDB(db, cfHandle)
+ if err := versionDB.FixData(stores, dryRun); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ }
+
+ cmd.Flags().Bool(FlagDryRun, false, "Dry run, do not write to the database, open the database in read-only mode.")
+ cmd.Flags().StringArray(FlagStore, []string{}, "Store names to fix, if not specified, all stores will be fixed.")
+ return cmd
+}
diff --git a/versiondb/go.mod b/versiondb/go.mod
index 9b1f0454f6..9fb00f47f5 100644
--- a/versiondb/go.mod
+++ b/versiondb/go.mod
@@ -14,7 +14,7 @@ require (
github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583
github.com/crypto-org-chain/cronos/memiavl v0.0.3
github.com/golang/snappy v0.0.4
- github.com/linxGnu/grocksdb v1.9.2
+ github.com/linxGnu/grocksdb v1.9.7
github.com/spf13/cast v1.5.0
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.9.0
diff --git a/versiondb/go.sum b/versiondb/go.sum
index 5f08098e34..cb73e9e5a9 100644
--- a/versiondb/go.sum
+++ b/versiondb/go.sum
@@ -515,8 +515,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
-github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8=
-github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
+github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw=
+github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
diff --git a/versiondb/tsrocksdb/iterator.go b/versiondb/tsrocksdb/iterator.go
index f4c4b3c777..9cb15e8a62 100644
--- a/versiondb/tsrocksdb/iterator.go
+++ b/versiondb/tsrocksdb/iterator.go
@@ -2,8 +2,9 @@ package tsrocksdb
import (
"bytes"
+ "encoding/binary"
- "github.com/cosmos/cosmos-sdk/store/types"
+ "github.com/crypto-org-chain/cronos/versiondb"
"github.com/linxGnu/grocksdb"
)
@@ -12,11 +13,14 @@ type rocksDBIterator struct {
prefix, start, end []byte
isReverse bool
isInvalid bool
+
+ // see: https://github.com/crypto-org-chain/cronos/issues/1683
+ skipVersionZero bool
}
-var _ types.Iterator = (*rocksDBIterator)(nil)
+var _ versiondb.Iterator = (*rocksDBIterator)(nil)
-func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool) *rocksDBIterator {
+func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool, skipVersionZero bool) *rocksDBIterator {
if isReverse {
if end == nil {
source.SeekToLast()
@@ -39,14 +43,18 @@ func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, is
source.Seek(start)
}
}
- return &rocksDBIterator{
- source: source,
- prefix: prefix,
- start: start,
- end: end,
- isReverse: isReverse,
- isInvalid: false,
+ it := &rocksDBIterator{
+ source: source,
+ prefix: prefix,
+ start: start,
+ end: end,
+ isReverse: isReverse,
+ isInvalid: false,
+ skipVersionZero: skipVersionZero,
}
+
+ it.trySkipZeroVersion()
+ return it
}
// Domain implements Iterator.
@@ -114,6 +122,21 @@ func (itr rocksDBIterator) Next() {
} else {
itr.source.Next()
}
+
+ itr.trySkipZeroVersion()
+}
+
+func (itr rocksDBIterator) Timestamp() []byte {
+ itr.assertIsValid()
+ return moveSliceToBytes(itr.source.Timestamp())
+}
+
+func (itr rocksDBIterator) trySkipZeroVersion() {
+ if itr.skipVersionZero {
+ for itr.Valid() && binary.LittleEndian.Uint64(itr.Timestamp()) == 0 {
+ itr.Next()
+ }
+ }
}
// Error implements Iterator.
diff --git a/versiondb/tsrocksdb/opts.go b/versiondb/tsrocksdb/opts.go
index 604c203b72..8218cd9692 100644
--- a/versiondb/tsrocksdb/opts.go
+++ b/versiondb/tsrocksdb/opts.go
@@ -64,6 +64,20 @@ func OpenVersionDB(dir string) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, erro
return db, cfHandles[1], nil
}
+// OpenVersionDBForReadOnly open versiondb in readonly mode
+func OpenVersionDBForReadOnly(dir string, errorIfWalFileExists bool) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) {
+ opts := grocksdb.NewDefaultOptions()
+ db, cfHandles, err := grocksdb.OpenDbForReadOnlyColumnFamilies(
+ opts, dir, []string{"default", VersionDBCFName},
+ []*grocksdb.Options{opts, NewVersionDBOpts(false)},
+ errorIfWalFileExists,
+ )
+ if err != nil {
+ return nil, nil, err
+ }
+ return db, cfHandles[1], nil
+}
+
// OpenVersionDBAndTrimHistory opens versiondb similar to `OpenVersionDB`,
// but it also trim the versions newer than target one, can be used for rollback.
func OpenVersionDBAndTrimHistory(dir string, version int64) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) {
diff --git a/versiondb/tsrocksdb/store.go b/versiondb/tsrocksdb/store.go
index 7d4176c5d0..706c357b98 100644
--- a/versiondb/tsrocksdb/store.go
+++ b/versiondb/tsrocksdb/store.go
@@ -1,6 +1,7 @@
package tsrocksdb
import (
+ "bytes"
"encoding/binary"
"errors"
"fmt"
@@ -38,6 +39,9 @@ func init() {
type Store struct {
db *grocksdb.DB
cfHandle *grocksdb.ColumnFamilyHandle
+
+ // see: https://github.com/crypto-org-chain/cronos/issues/1683
+ skipVersionZero bool
}
func NewStore(dir string) (Store, error) {
@@ -58,6 +62,10 @@ func NewStoreWithDB(db *grocksdb.DB, cfHandle *grocksdb.ColumnFamilyHandle) Stor
}
}
+func (s *Store) SetSkipVersionZero(skip bool) {
+ s.skipVersionZero = skip
+}
+
func (s Store) SetLatestVersion(version int64) error {
var ts [TimestampSize]byte
binary.LittleEndian.PutUint64(ts[:], uint64(version))
@@ -86,11 +94,23 @@ func (s Store) PutAtVersion(version int64, changeSet []types.StoreKVPair) error
}
func (s Store) GetAtVersionSlice(storeKey string, key []byte, version *int64) (*grocksdb.Slice, error) {
- return s.db.GetCF(
+ value, ts, err := s.db.GetCFWithTS(
newTSReadOptions(version),
s.cfHandle,
prependStoreKey(storeKey, key),
)
+ if err != nil {
+ return nil, err
+ }
+ defer ts.Free()
+
+ if value.Exists() && s.skipVersionZero {
+ if binary.LittleEndian.Uint64(ts.Data()) == 0 {
+ return grocksdb.NewSlice(nil, 0), nil
+ }
+ }
+
+ return value, err
}
// GetAtVersion implements VersionStore interface
@@ -127,29 +147,25 @@ func (s Store) GetLatestVersion() (int64, error) {
}
// IteratorAtVersion implements VersionStore interface
-func (s Store) IteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) {
- if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
- return nil, errKeyEmpty
- }
-
- prefix := storePrefix(storeKey)
- start, end = iterateWithPrefix(prefix, start, end)
-
- itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle)
- return newRocksDBIterator(itr, prefix, start, end, false), nil
+func (s Store) IteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) {
+ return s.iteratorAtVersion(storeKey, start, end, version, false)
}
// ReverseIteratorAtVersion implements VersionStore interface
-func (s Store) ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) {
+func (s Store) ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) {
+ return s.iteratorAtVersion(storeKey, start, end, version, true)
+}
+
+func (s Store) iteratorAtVersion(storeKey string, start, end []byte, version *int64, reverse bool) (versiondb.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, errKeyEmpty
}
prefix := storePrefix(storeKey)
- start, end = iterateWithPrefix(storePrefix(storeKey), start, end)
+ start, end = iterateWithPrefix(prefix, start, end)
itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle)
- return newRocksDBIterator(itr, prefix, start, end, true), nil
+ return newRocksDBIterator(itr, prefix, start, end, reverse, s.skipVersionZero), nil
}
// FeedChangeSet is used to migrate legacy change sets into versiondb
@@ -216,6 +232,103 @@ func (s Store) Flush() error {
)
}
+// FixData fixes wrong data written in versiondb due to rocksdb upgrade, the operation is idempotent.
+// see: https://github.com/crypto-org-chain/cronos/issues/1683
+// call this before `SetSkipVersionZero(true)`.
+func (s Store) FixData(storeNames []string, dryRun bool) error {
+ for _, storeName := range storeNames {
+ if err := s.fixDataStore(storeName, dryRun); err != nil {
+ return err
+ }
+ }
+
+ if !dryRun {
+ return s.Flush()
+ }
+
+ return nil
+}
+
+// fixDataStore iterate the wrong data at version 0, parse the timestamp from the key and write it again.
+func (s Store) fixDataStore(storeName string, dryRun bool) error {
+ pairs, err := s.loadWrongData(storeName)
+ if err != nil {
+ return err
+ }
+
+ batch := grocksdb.NewWriteBatch()
+ defer batch.Destroy()
+
+ prefix := storePrefix(storeName)
+ readOpts := grocksdb.NewDefaultReadOptions()
+ defer readOpts.Destroy()
+ for _, pair := range pairs {
+ realKey := cloneAppend(prefix, pair.Key)
+
+ readOpts.SetTimestamp(pair.Timestamp)
+ oldValue, err := s.db.GetCF(readOpts, s.cfHandle, realKey)
+ if err != nil {
+ return err
+ }
+
+ clean := bytes.Equal(oldValue.Data(), pair.Value)
+ oldValue.Free()
+
+ if clean {
+ continue
+ }
+
+ if dryRun {
+ fmt.Printf("fix data: %s, key: %X, ts: %X\n", storeName, pair.Key, pair.Timestamp)
+ } else {
+ batch.PutCFWithTS(s.cfHandle, realKey, pair.Timestamp, pair.Value)
+ }
+ }
+
+ if !dryRun {
+ return s.db.Write(defaultSyncWriteOpts, batch)
+ }
+
+ return nil
+}
+
+type KVPairWithTS struct {
+ Key []byte
+ Value []byte
+ Timestamp []byte
+}
+
+func (s Store) loadWrongData(storeName string) ([]KVPairWithTS, error) {
+ var version int64
+ iter, err := s.IteratorAtVersion(storeName, nil, nil, &version)
+ if err != nil {
+ return nil, err
+ }
+ defer iter.Close()
+
+ var pairs []KVPairWithTS
+ for ; iter.Valid(); iter.Next() {
+ ts := iter.Timestamp()
+ if binary.LittleEndian.Uint64(ts) != 0 {
+ // FIXME: https://github.com/crypto-org-chain/cronos/issues/1689
+ continue
+ }
+
+ key := iter.Key()
+ if len(key) < TimestampSize {
+ return nil, fmt.Errorf("invalid key length: %X, store: %s", key, storeName)
+ }
+
+ pairs = append(pairs, KVPairWithTS{
+ Key: key[:len(key)-TimestampSize],
+ Timestamp: key[len(key)-TimestampSize:],
+ Value: iter.Value(),
+ })
+ }
+
+ return pairs, nil
+}
+
func newTSReadOptions(version *int64) *grocksdb.ReadOptions {
var ver uint64
if version == nil {
diff --git a/versiondb/tsrocksdb/store_test.go b/versiondb/tsrocksdb/store_test.go
index a5328977a6..f3ec3617fb 100644
--- a/versiondb/tsrocksdb/store_test.go
+++ b/versiondb/tsrocksdb/store_test.go
@@ -4,6 +4,8 @@ import (
"encoding/binary"
"testing"
+ dbm "github.com/cometbft/cometbft-db"
+ "github.com/cosmos/cosmos-sdk/store/types"
"github.com/crypto-org-chain/cronos/versiondb"
"github.com/linxGnu/grocksdb"
"github.com/stretchr/testify/require"
@@ -153,3 +155,88 @@ func TestUserTimestampPruning(t *testing.T) {
require.Equal(t, []byte{100}, bz.Data())
bz.Free()
}
+
+func TestSkipVersionZero(t *testing.T) {
+ storeKey := "test"
+
+ var wrongTz [8]byte
+ binary.LittleEndian.PutUint64(wrongTz[:], 100)
+
+ key1 := []byte("hello1")
+ key2 := []byte("hello2")
+ key2Wrong := cloneAppend(key2, wrongTz[:])
+ key3 := []byte("hello3")
+
+ store, err := NewStore(t.TempDir())
+ require.NoError(t, err)
+
+ err = store.PutAtVersion(0, []types.StoreKVPair{
+ {StoreKey: storeKey, Key: key2Wrong, Value: []byte{2}},
+ })
+ require.NoError(t, err)
+ err = store.PutAtVersion(100, []types.StoreKVPair{
+ {StoreKey: storeKey, Key: key1, Value: []byte{1}},
+ })
+ require.NoError(t, err)
+ err = store.PutAtVersion(100, []types.StoreKVPair{
+ {StoreKey: storeKey, Key: key3, Value: []byte{3}},
+ })
+ require.NoError(t, err)
+
+ i := int64(999)
+ bz, err := store.GetAtVersion(storeKey, key2Wrong, &i)
+ require.NoError(t, err)
+ require.Equal(t, []byte{2}, bz)
+
+ it, err := store.IteratorAtVersion(storeKey, nil, nil, &i)
+ require.NoError(t, err)
+ require.Equal(t,
+ []kvPair{
+ {Key: key1, Value: []byte{1}},
+ {Key: key2Wrong, Value: []byte{2}},
+ {Key: key3, Value: []byte{3}},
+ },
+ consumeIterator(it),
+ )
+
+ store.SetSkipVersionZero(true)
+
+ bz, err = store.GetAtVersion(storeKey, key2Wrong, &i)
+ require.NoError(t, err)
+ require.Empty(t, bz)
+ bz, err = store.GetAtVersion(storeKey, key1, &i)
+ require.NoError(t, err)
+ require.Equal(t, []byte{1}, bz)
+
+ it, err = store.IteratorAtVersion(storeKey, nil, nil, &i)
+ require.NoError(t, err)
+ require.Equal(t,
+ []kvPair{
+ {Key: key1, Value: []byte{1}},
+ {Key: key3, Value: []byte{3}},
+ },
+ consumeIterator(it),
+ )
+
+ store.SetSkipVersionZero(false)
+ err = store.FixData([]string{storeKey}, false)
+ require.NoError(t, err)
+
+ bz, err = store.GetAtVersion(storeKey, key2, &i)
+ require.NoError(t, err)
+ require.Equal(t, []byte{2}, bz)
+}
+
+type kvPair struct {
+ Key []byte
+ Value []byte
+}
+
+func consumeIterator(it dbm.Iterator) []kvPair {
+ var result []kvPair
+ for ; it.Valid(); it.Next() {
+ result = append(result, kvPair{it.Key(), it.Value()})
+ }
+ it.Close()
+ return result
+}
diff --git a/versiondb/types.go b/versiondb/types.go
index 6c69fbbf94..9f5eab3637 100644
--- a/versiondb/types.go
+++ b/versiondb/types.go
@@ -4,14 +4,20 @@ import (
"github.com/cosmos/cosmos-sdk/store/types"
)
+type Iterator interface {
+ types.Iterator
+
+ Timestamp() []byte
+}
+
// VersionStore is a versioned storage of a flat key-value pairs.
// it don't need to support merkle proof, so could be implemented in a much more efficient way.
// `nil` version means the latest version.
type VersionStore interface {
GetAtVersion(storeKey string, key []byte, version *int64) ([]byte, error)
HasAtVersion(storeKey string, key []byte, version *int64) (bool, error)
- IteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error)
- ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error)
+ IteratorAtVersion(storeKey string, start, end []byte, version *int64) (Iterator, error)
+ ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (Iterator, error)
GetLatestVersion() (int64, error)
// Persist the change set of a block,