feat!: Add hooks to allow app modules to add things to state-sync (#10961)
## Description Closes: #7340 - Support registering multiple snapshotters in snapshot manager. - Append the extension snapshotters to existing snapshot stream. ~TODO: testing.~ - existing tests are fixed --- ### 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... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] 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) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] 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:
parent
c9a37fe4be
commit
7e18e9f1bf
File diff suppressed because it is too large
Load Diff
|
@ -260,6 +260,22 @@ func DefaultStoreLoader(ms sdk.CommitMultiStore) error {
|
||||||
return ms.LoadLatestVersion()
|
return ms.LoadLatestVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitMultiStore returns the root multi-store.
|
||||||
|
// App constructor can use this to access the `cms`.
|
||||||
|
// UNSAFE: only safe to use during app initialization.
|
||||||
|
func (app *BaseApp) CommitMultiStore() sdk.CommitMultiStore {
|
||||||
|
if app.sealed {
|
||||||
|
panic("cannot call CommitMultiStore() after baseapp is sealed")
|
||||||
|
}
|
||||||
|
return app.cms
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotManager returns the snapshot manager.
|
||||||
|
// application use this to register extra extension snapshotters.
|
||||||
|
func (app *BaseApp) SnapshotManager() *snapshots.Manager {
|
||||||
|
return app.snapshotManager
|
||||||
|
}
|
||||||
|
|
||||||
// LoadVersion loads the BaseApp application version. It will panic if called
|
// LoadVersion loads the BaseApp application version. It will panic if called
|
||||||
// more than once on a running baseapp.
|
// more than once on a running baseapp.
|
||||||
func (app *BaseApp) LoadVersion(version int64) error {
|
func (app *BaseApp) LoadVersion(version int64) error {
|
||||||
|
|
|
@ -210,7 +210,7 @@ func (app *BaseApp) SetSnapshotStore(snapshotStore *snapshots.Store) {
|
||||||
app.snapshotManager = nil
|
app.snapshotManager = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.snapshotManager = snapshots.NewManager(snapshotStore, app.cms)
|
app.snapshotManager = snapshots.NewManager(snapshotStore, app.cms, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSnapshotInterval sets the snapshot interval.
|
// SetSnapshotInterval sets the snapshot interval.
|
||||||
|
|
|
@ -17,4 +17,41 @@ message Snapshot {
|
||||||
// Metadata contains SDK-specific snapshot metadata.
|
// Metadata contains SDK-specific snapshot metadata.
|
||||||
message Metadata {
|
message Metadata {
|
||||||
repeated bytes chunk_hashes = 1; // SHA-256 chunk hashes
|
repeated bytes chunk_hashes = 1; // SHA-256 chunk hashes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SnapshotItem is an item contained in a rootmulti.Store snapshot.
|
||||||
|
message SnapshotItem {
|
||||||
|
// item is the specific type of snapshot item.
|
||||||
|
oneof item {
|
||||||
|
SnapshotStoreItem store = 1;
|
||||||
|
SnapshotIAVLItem iavl = 2 [(gogoproto.customname) = "IAVL"];
|
||||||
|
SnapshotExtensionMeta extension = 3;
|
||||||
|
SnapshotExtensionPayload extension_payload = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotStoreItem contains metadata about a snapshotted store.
|
||||||
|
message SnapshotStoreItem {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotIAVLItem is an exported IAVL node.
|
||||||
|
message SnapshotIAVLItem {
|
||||||
|
bytes key = 1;
|
||||||
|
bytes value = 2;
|
||||||
|
// version is block height
|
||||||
|
int64 version = 3;
|
||||||
|
// height is depth of the tree.
|
||||||
|
int32 height = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotExtensionMeta contains metadata about an external snapshotter.
|
||||||
|
message SnapshotExtensionMeta {
|
||||||
|
string name = 1;
|
||||||
|
uint32 format = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotExtensionPayload contains payloads of an external snapshotter.
|
||||||
|
message SnapshotExtensionPayload {
|
||||||
|
bytes payload = 1;
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package cosmos.base.store.v1beta1;
|
|
||||||
|
|
||||||
import "gogoproto/gogo.proto";
|
|
||||||
|
|
||||||
option go_package = "github.com/cosmos/cosmos-sdk/store/types";
|
|
||||||
|
|
||||||
// SnapshotItem is an item contained in a rootmulti.Store snapshot.
|
|
||||||
message SnapshotItem {
|
|
||||||
// item is the specific type of snapshot item.
|
|
||||||
oneof item {
|
|
||||||
SnapshotStoreItem store = 1;
|
|
||||||
SnapshotIAVLItem iavl = 2 [(gogoproto.customname) = "IAVL"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SnapshotStoreItem contains metadata about a snapshotted store.
|
|
||||||
message SnapshotStoreItem {
|
|
||||||
string name = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SnapshotIAVLItem is an exported IAVL node.
|
|
||||||
message SnapshotIAVLItem {
|
|
||||||
bytes key = 1;
|
|
||||||
bytes value = 2;
|
|
||||||
int64 version = 3;
|
|
||||||
int32 height = 4;
|
|
||||||
}
|
|
|
@ -3,8 +3,10 @@ package mock
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
dbm "github.com/tendermint/tm-db"
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
@ -122,13 +124,13 @@ func (ms multiStore) SetInitialVersion(version int64) error {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms multiStore) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error) {
|
func (ms multiStore) Snapshot(height uint64, protoWriter protoio.Writer) error {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms multiStore) Restore(
|
func (ms multiStore) Restore(
|
||||||
height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{},
|
height uint64, format uint32, protoReader protoio.Reader,
|
||||||
) error {
|
) (snapshottypes.SnapshotItem, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package snapshots_test
|
package snapshots_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
@ -9,11 +11,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
db "github.com/tendermint/tm-db"
|
db "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/snapshots"
|
"github.com/cosmos/cosmos-sdk/snapshots"
|
||||||
"github.com/cosmos/cosmos-sdk/snapshots/types"
|
"github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checksums(slice [][]byte) [][]byte {
|
func checksums(slice [][]byte) [][]byte {
|
||||||
|
@ -56,45 +61,85 @@ func readChunks(chunks <-chan io.ReadCloser) [][]byte {
|
||||||
return bodies
|
return bodies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// snapshotItems serialize a array of bytes as SnapshotItem_ExtensionPayload, and return the chunks.
|
||||||
|
func snapshotItems(items [][]byte) [][]byte {
|
||||||
|
// copy the same parameters from the code
|
||||||
|
snapshotChunkSize := uint64(10e6)
|
||||||
|
snapshotBufferSize := int(snapshotChunkSize)
|
||||||
|
|
||||||
|
ch := make(chan io.ReadCloser)
|
||||||
|
go func() {
|
||||||
|
chunkWriter := snapshots.NewChunkWriter(ch, snapshotChunkSize)
|
||||||
|
bufWriter := bufio.NewWriterSize(chunkWriter, snapshotBufferSize)
|
||||||
|
zWriter, _ := zlib.NewWriterLevel(bufWriter, 7)
|
||||||
|
protoWriter := protoio.NewDelimitedWriter(zWriter)
|
||||||
|
for _, item := range items {
|
||||||
|
types.WriteExtensionItem(protoWriter, item)
|
||||||
|
}
|
||||||
|
protoWriter.Close()
|
||||||
|
zWriter.Close()
|
||||||
|
bufWriter.Flush()
|
||||||
|
chunkWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var chunks [][]byte
|
||||||
|
for chunkBody := range ch {
|
||||||
|
chunk, err := io.ReadAll(chunkBody)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
chunks = append(chunks, chunk)
|
||||||
|
}
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
type mockSnapshotter struct {
|
type mockSnapshotter struct {
|
||||||
chunks [][]byte
|
items [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockSnapshotter) Restore(
|
func (m *mockSnapshotter) Restore(
|
||||||
height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{},
|
height uint64, format uint32, protoReader protoio.Reader,
|
||||||
) error {
|
) (snapshottypes.SnapshotItem, error) {
|
||||||
if format == 0 {
|
if format == 0 {
|
||||||
return types.ErrUnknownFormat
|
return snapshottypes.SnapshotItem{}, types.ErrUnknownFormat
|
||||||
}
|
}
|
||||||
if m.chunks != nil {
|
if m.items != nil {
|
||||||
return errors.New("already has contents")
|
return snapshottypes.SnapshotItem{}, errors.New("already has contents")
|
||||||
}
|
|
||||||
if ready != nil {
|
|
||||||
close(ready)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.chunks = [][]byte{}
|
m.items = [][]byte{}
|
||||||
for reader := range chunks {
|
for {
|
||||||
chunk, err := io.ReadAll(reader)
|
item := &snapshottypes.SnapshotItem{}
|
||||||
if err != nil {
|
err := protoReader.ReadMsg(item)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message")
|
||||||
|
}
|
||||||
|
payload := item.GetExtensionPayload()
|
||||||
|
if payload == nil {
|
||||||
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message")
|
||||||
|
}
|
||||||
|
m.items = append(m.items, payload.Payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshottypes.SnapshotItem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockSnapshotter) Snapshot(height uint64, protoWriter protoio.Writer) error {
|
||||||
|
for _, item := range m.items {
|
||||||
|
if err := types.WriteExtensionItem(protoWriter, item); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.chunks = append(m.chunks, chunk)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockSnapshotter) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error) {
|
func (m *mockSnapshotter) SnapshotFormat() uint32 {
|
||||||
if format == 0 {
|
return 1
|
||||||
return nil, types.ErrUnknownFormat
|
}
|
||||||
}
|
func (m *mockSnapshotter) SupportedFormats() []uint32 {
|
||||||
ch := make(chan io.ReadCloser, len(m.chunks))
|
return []uint32{1}
|
||||||
for _, chunk := range m.chunks {
|
|
||||||
ch <- io.NopCloser(bytes.NewReader(chunk))
|
|
||||||
}
|
|
||||||
close(ch)
|
|
||||||
return ch, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupBusyManager creates a manager with an empty store that is busy creating a snapshot at height 1.
|
// setupBusyManager creates a manager with an empty store that is busy creating a snapshot at height 1.
|
||||||
|
@ -110,7 +155,7 @@ func setupBusyManager(t *testing.T) *snapshots.Manager {
|
||||||
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
|
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hung := newHungSnapshotter()
|
hung := newHungSnapshotter()
|
||||||
mgr := snapshots.NewManager(store, hung)
|
mgr := snapshots.NewManager(store, hung, nil)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_, err := mgr.Create(1)
|
_, err := mgr.Create(1)
|
||||||
|
@ -137,15 +182,13 @@ func (m *hungSnapshotter) Close() {
|
||||||
close(m.ch)
|
close(m.ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *hungSnapshotter) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error) {
|
func (m *hungSnapshotter) Snapshot(height uint64, protoWriter protoio.Writer) error {
|
||||||
<-m.ch
|
<-m.ch
|
||||||
ch := make(chan io.ReadCloser, 1)
|
return nil
|
||||||
ch <- io.NopCloser(bytes.NewReader([]byte{}))
|
|
||||||
return ch, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *hungSnapshotter) Restore(
|
func (m *hungSnapshotter) Restore(
|
||||||
height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{},
|
height uint64, format uint32, protoReader protoio.Reader,
|
||||||
) error {
|
) (snapshottypes.SnapshotItem, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ package snapshots
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/snapshots/types"
|
"github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
|
@ -17,6 +20,8 @@ const (
|
||||||
opRestore operation = "restore"
|
opRestore operation = "restore"
|
||||||
|
|
||||||
chunkBufferSize = 4
|
chunkBufferSize = 4
|
||||||
|
|
||||||
|
snapshotMaxItemSize = int(64e6) // SDK has no key/value size limit, so we set an arbitrary limit
|
||||||
)
|
)
|
||||||
|
|
||||||
// operation represents a Manager operation. Only one operation can be in progress at a time.
|
// operation represents a Manager operation. Only one operation can be in progress at a time.
|
||||||
|
@ -42,8 +47,9 @@ type restoreDone struct {
|
||||||
// 2) io.ReadCloser streams automatically propagate IO errors, and can pass arbitrary
|
// 2) io.ReadCloser streams automatically propagate IO errors, and can pass arbitrary
|
||||||
// errors via io.Pipe.CloseWithError().
|
// errors via io.Pipe.CloseWithError().
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
store *Store
|
store *Store
|
||||||
target types.Snapshotter
|
multistore types.Snapshotter
|
||||||
|
extensions map[string]types.ExtensionSnapshotter
|
||||||
|
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
operation operation
|
operation operation
|
||||||
|
@ -54,13 +60,29 @@ type Manager struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new manager.
|
// NewManager creates a new manager.
|
||||||
func NewManager(store *Store, target types.Snapshotter) *Manager {
|
func NewManager(store *Store, multistore types.Snapshotter, extensions map[string]types.ExtensionSnapshotter) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
store: store,
|
store: store,
|
||||||
target: target,
|
multistore: multistore,
|
||||||
|
extensions: extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterExtensions register extension snapshotters to manager
|
||||||
|
func (m *Manager) RegisterExtensions(extensions ...types.ExtensionSnapshotter) error {
|
||||||
|
for _, extension := range extensions {
|
||||||
|
name := extension.SnapshotName()
|
||||||
|
if _, ok := m.extensions[name]; ok {
|
||||||
|
return fmt.Errorf("duplicated snapshotter name: %s", name)
|
||||||
|
}
|
||||||
|
if !IsFormatSupported(extension, extension.SnapshotFormat()) {
|
||||||
|
return fmt.Errorf("snapshotter don't support it's own snapshot format: %s %d", name, extension.SnapshotFormat())
|
||||||
|
}
|
||||||
|
m.extensions[name] = extension
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// begin starts an operation, or errors if one is in progress. It manages the mutex itself.
|
// begin starts an operation, or errors if one is in progress. It manages the mutex itself.
|
||||||
func (m *Manager) begin(op operation) error {
|
func (m *Manager) begin(op operation) error {
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
|
@ -99,6 +121,17 @@ func (m *Manager) endLocked() {
|
||||||
m.restoreChunkIndex = 0
|
m.restoreChunkIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sortedExtensionNames sort extension names for deterministic iteration.
|
||||||
|
func (m *Manager) sortedExtensionNames() []string {
|
||||||
|
names := make([]string, 0, len(m.extensions))
|
||||||
|
for name := range m.extensions {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
// Create creates a snapshot and returns its metadata.
|
// Create creates a snapshot and returns its metadata.
|
||||||
func (m *Manager) Create(height uint64) (*types.Snapshot, error) {
|
func (m *Manager) Create(height uint64) (*types.Snapshot, error) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
|
@ -119,11 +152,45 @@ func (m *Manager) Create(height uint64) (*types.Snapshot, error) {
|
||||||
"a more recent snapshot already exists at height %v", latest.Height)
|
"a more recent snapshot already exists at height %v", latest.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
chunks, err := m.target.Snapshot(height, types.CurrentFormat)
|
// Spawn goroutine to generate snapshot chunks and pass their io.ReadClosers through a channel
|
||||||
if err != nil {
|
ch := make(chan io.ReadCloser)
|
||||||
return nil, err
|
go m.createSnapshot(height, ch)
|
||||||
|
|
||||||
|
return m.store.Save(height, types.CurrentFormat, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSnapshot do the heavy work of snapshotting after the validations of request are done
|
||||||
|
// the produced chunks are written to the channel.
|
||||||
|
func (m *Manager) createSnapshot(height uint64, ch chan<- io.ReadCloser) {
|
||||||
|
streamWriter := NewStreamWriter(ch)
|
||||||
|
if streamWriter == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer streamWriter.Close()
|
||||||
|
if err := m.multistore.Snapshot(height, streamWriter); err != nil {
|
||||||
|
streamWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, name := range m.sortedExtensionNames() {
|
||||||
|
extension := m.extensions[name]
|
||||||
|
// write extension metadata
|
||||||
|
err := streamWriter.WriteMsg(&types.SnapshotItem{
|
||||||
|
Item: &types.SnapshotItem_Extension{
|
||||||
|
Extension: &types.SnapshotExtensionMeta{
|
||||||
|
Name: name,
|
||||||
|
Format: extension.SnapshotFormat(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
streamWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := extension.Snapshot(height, streamWriter); err != nil {
|
||||||
|
streamWriter.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return m.store.Save(height, types.CurrentFormat, chunks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List lists snapshots, mirroring ABCI ListSnapshots. It can be concurrent with other operations.
|
// List lists snapshots, mirroring ABCI ListSnapshots. It can be concurrent with other operations.
|
||||||
|
@ -169,6 +236,19 @@ func (m *Manager) Restore(snapshot types.Snapshot) error {
|
||||||
}
|
}
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
|
// check multistore supported format preemptive
|
||||||
|
if snapshot.Format != types.CurrentFormat {
|
||||||
|
return sdkerrors.Wrapf(types.ErrUnknownFormat, "snapshot format %v", snapshot.Format)
|
||||||
|
}
|
||||||
|
if snapshot.Height == 0 {
|
||||||
|
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at height 0")
|
||||||
|
}
|
||||||
|
if snapshot.Height > uint64(math.MaxInt64) {
|
||||||
|
return sdkerrors.Wrapf(types.ErrInvalidMetadata,
|
||||||
|
"snapshot height %v cannot exceed %v", snapshot.Height, int64(math.MaxInt64))
|
||||||
|
}
|
||||||
|
|
||||||
err := m.beginLocked(opRestore)
|
err := m.beginLocked(opRestore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -176,10 +256,10 @@ func (m *Manager) Restore(snapshot types.Snapshot) error {
|
||||||
|
|
||||||
// Start an asynchronous snapshot restoration, passing chunks and completion status via channels.
|
// Start an asynchronous snapshot restoration, passing chunks and completion status via channels.
|
||||||
chChunks := make(chan io.ReadCloser, chunkBufferSize)
|
chChunks := make(chan io.ReadCloser, chunkBufferSize)
|
||||||
chReady := make(chan struct{}, 1)
|
|
||||||
chDone := make(chan restoreDone, 1)
|
chDone := make(chan restoreDone, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err := m.target.Restore(snapshot.Height, snapshot.Format, chChunks, chReady)
|
err := m.restoreSnapshot(snapshot, chChunks)
|
||||||
chDone <- restoreDone{
|
chDone <- restoreDone{
|
||||||
complete: err == nil,
|
complete: err == nil,
|
||||||
err: err,
|
err: err,
|
||||||
|
@ -187,17 +267,6 @@ func (m *Manager) Restore(snapshot types.Snapshot) error {
|
||||||
close(chDone)
|
close(chDone)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Check for any initial errors from the restore, before any chunks are fed.
|
|
||||||
select {
|
|
||||||
case done := <-chDone:
|
|
||||||
m.endLocked()
|
|
||||||
if done.err != nil {
|
|
||||||
return done.err
|
|
||||||
}
|
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "restore ended unexpectedly")
|
|
||||||
case <-chReady:
|
|
||||||
}
|
|
||||||
|
|
||||||
m.chRestore = chChunks
|
m.chRestore = chChunks
|
||||||
m.chRestoreDone = chDone
|
m.chRestoreDone = chDone
|
||||||
m.restoreChunkHashes = snapshot.Metadata.ChunkHashes
|
m.restoreChunkHashes = snapshot.Metadata.ChunkHashes
|
||||||
|
@ -205,6 +274,42 @@ func (m *Manager) Restore(snapshot types.Snapshot) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restoreSnapshot do the heavy work of snapshot restoration after preliminary checks on request have passed.
|
||||||
|
func (m *Manager) restoreSnapshot(snapshot types.Snapshot, chChunks <-chan io.ReadCloser) error {
|
||||||
|
streamReader, err := NewStreamReader(chChunks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer streamReader.Close()
|
||||||
|
|
||||||
|
next, err := m.multistore.Restore(snapshot.Height, snapshot.Format, streamReader)
|
||||||
|
if err != nil {
|
||||||
|
return sdkerrors.Wrap(err, "multistore restore")
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if next.Item == nil {
|
||||||
|
// end of stream
|
||||||
|
break
|
||||||
|
}
|
||||||
|
metadata := next.GetExtension()
|
||||||
|
if metadata == nil {
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "unknown snapshot item %T", next.Item)
|
||||||
|
}
|
||||||
|
extension, ok := m.extensions[metadata.Name]
|
||||||
|
if !ok {
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "unknown extension snapshotter %s", metadata.Name)
|
||||||
|
}
|
||||||
|
if !IsFormatSupported(extension, metadata.Format) {
|
||||||
|
return sdkerrors.Wrapf(types.ErrUnknownFormat, "format %v for extension %s", metadata.Format, metadata.Name)
|
||||||
|
}
|
||||||
|
next, err = extension.Restore(snapshot.Height, metadata.Format, streamReader)
|
||||||
|
if err != nil {
|
||||||
|
return sdkerrors.Wrapf(err, "extension %s restore", metadata.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// RestoreChunk adds a chunk to an active snapshot restoration, mirroring ABCI ApplySnapshotChunk.
|
// RestoreChunk adds a chunk to an active snapshot restoration, mirroring ABCI ApplySnapshotChunk.
|
||||||
// Chunks must be given until the restore is complete, returning true, or a chunk errors.
|
// Chunks must be given until the restore is complete, returning true, or a chunk errors.
|
||||||
func (m *Manager) RestoreChunk(chunk []byte) (bool, error) {
|
func (m *Manager) RestoreChunk(chunk []byte) (bool, error) {
|
||||||
|
@ -256,3 +361,13 @@ func (m *Manager) RestoreChunk(chunk []byte) (bool, error) {
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFormatSupported returns if the snapshotter supports restoration from given format.
|
||||||
|
func IsFormatSupported(snapshotter types.ExtensionSnapshotter, format uint32) bool {
|
||||||
|
for _, i := range snapshotter.SupportedFormats() {
|
||||||
|
if i == format {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
func TestManager_List(t *testing.T) {
|
func TestManager_List(t *testing.T) {
|
||||||
store := setupStore(t)
|
store := setupStore(t)
|
||||||
manager := snapshots.NewManager(store, nil)
|
manager := snapshots.NewManager(store, nil, nil)
|
||||||
|
|
||||||
mgrList, err := manager.List()
|
mgrList, err := manager.List()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -32,7 +32,7 @@ func TestManager_List(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_LoadChunk(t *testing.T) {
|
func TestManager_LoadChunk(t *testing.T) {
|
||||||
store := setupStore(t)
|
store := setupStore(t)
|
||||||
manager := snapshots.NewManager(store, nil)
|
manager := snapshots.NewManager(store, nil, nil)
|
||||||
|
|
||||||
// Existing chunk should return body
|
// Existing chunk should return body
|
||||||
chunk, err := manager.LoadChunk(2, 1, 1)
|
chunk, err := manager.LoadChunk(2, 1, 1)
|
||||||
|
@ -53,14 +53,16 @@ func TestManager_LoadChunk(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Take(t *testing.T) {
|
func TestManager_Take(t *testing.T) {
|
||||||
store := setupStore(t)
|
store := setupStore(t)
|
||||||
snapshotter := &mockSnapshotter{
|
items := [][]byte{
|
||||||
chunks: [][]byte{
|
{1, 2, 3},
|
||||||
{1, 2, 3},
|
{4, 5, 6},
|
||||||
{4, 5, 6},
|
{7, 8, 9},
|
||||||
{7, 8, 9},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
manager := snapshots.NewManager(store, snapshotter)
|
snapshotter := &mockSnapshotter{
|
||||||
|
items: items,
|
||||||
|
}
|
||||||
|
expectChunks := snapshotItems(items)
|
||||||
|
manager := snapshots.NewManager(store, snapshotter, nil)
|
||||||
|
|
||||||
// nil manager should return error
|
// nil manager should return error
|
||||||
_, err := (*snapshots.Manager)(nil).Create(1)
|
_, err := (*snapshots.Manager)(nil).Create(1)
|
||||||
|
@ -75,19 +77,18 @@ func TestManager_Take(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, &types.Snapshot{
|
assert.Equal(t, &types.Snapshot{
|
||||||
Height: 5,
|
Height: 5,
|
||||||
Format: types.CurrentFormat,
|
Format: snapshotter.SnapshotFormat(),
|
||||||
Chunks: 3,
|
Chunks: 1,
|
||||||
Hash: []uint8{0x47, 0xe4, 0xee, 0x7f, 0x21, 0x1f, 0x73, 0x26, 0x5d, 0xd1, 0x76, 0x58, 0xf6, 0xe2, 0x1c, 0x13, 0x18, 0xbd, 0x6c, 0x81, 0xf3, 0x75, 0x98, 0xe2, 0xa, 0x27, 0x56, 0x29, 0x95, 0x42, 0xef, 0xcf},
|
Hash: []uint8{0xcd, 0x17, 0x9e, 0x7f, 0x28, 0xb6, 0x82, 0x90, 0xc7, 0x25, 0xf3, 0x42, 0xac, 0x65, 0x73, 0x50, 0xaa, 0xa0, 0x10, 0x5c, 0x40, 0x8c, 0xd5, 0x1, 0xed, 0x82, 0xb5, 0xca, 0x8b, 0xe0, 0x83, 0xa2},
|
||||||
Metadata: types.Metadata{
|
Metadata: types.Metadata{
|
||||||
ChunkHashes: checksums([][]byte{
|
ChunkHashes: checksums(expectChunks),
|
||||||
{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}),
|
|
||||||
},
|
},
|
||||||
}, snapshot)
|
}, snapshot)
|
||||||
|
|
||||||
storeSnapshot, chunks, err := store.Load(snapshot.Height, snapshot.Format)
|
storeSnapshot, chunks, err := store.Load(snapshot.Height, snapshot.Format)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, snapshot, storeSnapshot)
|
assert.Equal(t, snapshot, storeSnapshot)
|
||||||
assert.Equal(t, [][]byte{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, readChunks(chunks))
|
assert.Equal(t, expectChunks, readChunks(chunks))
|
||||||
|
|
||||||
// creating a snapshot while a different snapshot is being created should error
|
// creating a snapshot while a different snapshot is being created should error
|
||||||
manager = setupBusyManager(t)
|
manager = setupBusyManager(t)
|
||||||
|
@ -97,7 +98,7 @@ func TestManager_Take(t *testing.T) {
|
||||||
|
|
||||||
func TestManager_Prune(t *testing.T) {
|
func TestManager_Prune(t *testing.T) {
|
||||||
store := setupStore(t)
|
store := setupStore(t)
|
||||||
manager := snapshots.NewManager(store, nil)
|
manager := snapshots.NewManager(store, nil, nil)
|
||||||
|
|
||||||
pruned, err := manager.Prune(2)
|
pruned, err := manager.Prune(2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -116,14 +117,16 @@ func TestManager_Prune(t *testing.T) {
|
||||||
func TestManager_Restore(t *testing.T) {
|
func TestManager_Restore(t *testing.T) {
|
||||||
store := setupStore(t)
|
store := setupStore(t)
|
||||||
target := &mockSnapshotter{}
|
target := &mockSnapshotter{}
|
||||||
manager := snapshots.NewManager(store, target)
|
manager := snapshots.NewManager(store, target, nil)
|
||||||
|
|
||||||
chunks := [][]byte{
|
expectItems := [][]byte{
|
||||||
{1, 2, 3},
|
{1, 2, 3},
|
||||||
{4, 5, 6},
|
{4, 5, 6},
|
||||||
{7, 8, 9},
|
{7, 8, 9},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chunks := snapshotItems(expectItems)
|
||||||
|
|
||||||
// Restore errors on invalid format
|
// Restore errors on invalid format
|
||||||
err := manager.Restore(types.Snapshot{
|
err := manager.Restore(types.Snapshot{
|
||||||
Height: 3,
|
Height: 3,
|
||||||
|
@ -133,7 +136,7 @@ func TestManager_Restore(t *testing.T) {
|
||||||
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
||||||
})
|
})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, types.ErrUnknownFormat, err)
|
require.ErrorIs(t, err, types.ErrUnknownFormat)
|
||||||
|
|
||||||
// Restore errors on no chunks
|
// Restore errors on no chunks
|
||||||
err = manager.Restore(types.Snapshot{Height: 3, Format: 1, Hash: []byte{1, 2, 3}})
|
err = manager.Restore(types.Snapshot{Height: 3, Format: 1, Hash: []byte{1, 2, 3}})
|
||||||
|
@ -154,7 +157,7 @@ func TestManager_Restore(t *testing.T) {
|
||||||
Height: 3,
|
Height: 3,
|
||||||
Format: 1,
|
Format: 1,
|
||||||
Hash: []byte{1, 2, 3},
|
Hash: []byte{1, 2, 3},
|
||||||
Chunks: 3,
|
Chunks: 1,
|
||||||
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -182,7 +185,7 @@ func TestManager_Restore(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, chunks, target.chunks)
|
assert.Equal(t, expectItems, target.items)
|
||||||
|
|
||||||
// Starting a new restore should fail now, because the target already has contents.
|
// Starting a new restore should fail now, because the target already has contents.
|
||||||
err = manager.Restore(types.Snapshot{
|
err = manager.Restore(types.Snapshot{
|
||||||
|
@ -197,12 +200,12 @@ func TestManager_Restore(t *testing.T) {
|
||||||
// But if we clear out the target we should be able to start a new restore. This time we'll
|
// But if we clear out the target we should be able to start a new restore. This time we'll
|
||||||
// fail it with a checksum error. That error should stop the operation, so that we can do
|
// fail it with a checksum error. That error should stop the operation, so that we can do
|
||||||
// a prune operation right after.
|
// a prune operation right after.
|
||||||
target.chunks = nil
|
target.items = nil
|
||||||
err = manager.Restore(types.Snapshot{
|
err = manager.Restore(types.Snapshot{
|
||||||
Height: 3,
|
Height: 3,
|
||||||
Format: 1,
|
Format: 1,
|
||||||
Hash: []byte{1, 2, 3},
|
Hash: []byte{1, 2, 3},
|
||||||
Chunks: 3,
|
Chunks: 1,
|
||||||
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
Metadata: types.Metadata{ChunkHashes: checksums(chunks)},
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
package snapshots
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"compress/zlib"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
|
"github.com/gogo/protobuf/proto"
|
||||||
|
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Do not change chunk size without new snapshot format (must be uniform across nodes)
|
||||||
|
snapshotChunkSize = uint64(10e6)
|
||||||
|
snapshotBufferSize = int(snapshotChunkSize)
|
||||||
|
// Do not change compression level without new snapshot format (must be uniform across nodes)
|
||||||
|
snapshotCompressionLevel = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamWriter set up a stream pipeline to serialize snapshot nodes:
|
||||||
|
// Exported Items -> delimited Protobuf -> zlib -> buffer -> chunkWriter -> chan io.ReadCloser
|
||||||
|
type StreamWriter struct {
|
||||||
|
chunkWriter *ChunkWriter
|
||||||
|
bufWriter *bufio.Writer
|
||||||
|
zWriter *zlib.Writer
|
||||||
|
protoWriter protoio.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamWriter set up a stream pipeline to serialize snapshot DB records.
|
||||||
|
func NewStreamWriter(ch chan<- io.ReadCloser) *StreamWriter {
|
||||||
|
chunkWriter := NewChunkWriter(ch, snapshotChunkSize)
|
||||||
|
bufWriter := bufio.NewWriterSize(chunkWriter, snapshotBufferSize)
|
||||||
|
zWriter, err := zlib.NewWriterLevel(bufWriter, snapshotCompressionLevel)
|
||||||
|
if err != nil {
|
||||||
|
chunkWriter.CloseWithError(sdkerrors.Wrap(err, "zlib failure"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
protoWriter := protoio.NewDelimitedWriter(zWriter)
|
||||||
|
return &StreamWriter{
|
||||||
|
chunkWriter: chunkWriter,
|
||||||
|
bufWriter: bufWriter,
|
||||||
|
zWriter: zWriter,
|
||||||
|
protoWriter: protoWriter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsg implements protoio.Write interface
|
||||||
|
func (sw *StreamWriter) WriteMsg(msg proto.Message) error {
|
||||||
|
return sw.protoWriter.WriteMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements io.Closer interface
|
||||||
|
func (sw *StreamWriter) Close() error {
|
||||||
|
if err := sw.protoWriter.Close(); err != nil {
|
||||||
|
sw.chunkWriter.CloseWithError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sw.zWriter.Close(); err != nil {
|
||||||
|
sw.chunkWriter.CloseWithError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sw.bufWriter.Flush(); err != nil {
|
||||||
|
sw.chunkWriter.CloseWithError(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return sw.chunkWriter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWithError pass error to chunkWriter
|
||||||
|
func (sw *StreamWriter) CloseWithError(err error) {
|
||||||
|
sw.chunkWriter.CloseWithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamReader set up a restore stream pipeline
|
||||||
|
// chan io.ReadCloser -> chunkReader -> zlib -> delimited Protobuf -> ExportNode
|
||||||
|
type StreamReader struct {
|
||||||
|
chunkReader *ChunkReader
|
||||||
|
zReader io.ReadCloser
|
||||||
|
protoReader protoio.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamReader set up a restore stream pipeline.
|
||||||
|
func NewStreamReader(chunks <-chan io.ReadCloser) (*StreamReader, error) {
|
||||||
|
chunkReader := NewChunkReader(chunks)
|
||||||
|
zReader, err := zlib.NewReader(chunkReader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdkerrors.Wrap(err, "zlib failure")
|
||||||
|
}
|
||||||
|
protoReader := protoio.NewDelimitedReader(zReader, snapshotMaxItemSize)
|
||||||
|
return &StreamReader{
|
||||||
|
chunkReader: chunkReader,
|
||||||
|
zReader: zReader,
|
||||||
|
protoReader: protoReader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMsg implements protoio.Reader interface
|
||||||
|
func (sr *StreamReader) ReadMsg(msg proto.Message) error {
|
||||||
|
return sr.protoReader.ReadMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements io.Closer interface
|
||||||
|
func (sr *StreamReader) Close() error {
|
||||||
|
sr.protoReader.Close()
|
||||||
|
sr.zReader.Close()
|
||||||
|
return sr.chunkReader.Close()
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,35 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
|
)
|
||||||
|
|
||||||
// Snapshotter is something that can create and restore snapshots, consisting of streamed binary
|
// Snapshotter is something that can create and restore snapshots, consisting of streamed binary
|
||||||
// chunks - all of which must be read from the channel and closed. If an unsupported format is
|
// chunks - all of which must be read from the channel and closed. If an unsupported format is
|
||||||
// given, it must return ErrUnknownFormat (possibly wrapped with fmt.Errorf).
|
// given, it must return ErrUnknownFormat (possibly wrapped with fmt.Errorf).
|
||||||
type Snapshotter interface {
|
type Snapshotter interface {
|
||||||
// Snapshot creates a state snapshot, returning a channel of snapshot chunk readers.
|
// Snapshot writes snapshot items into the protobuf writer.
|
||||||
Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error)
|
Snapshot(height uint64, protoWriter protoio.Writer) error
|
||||||
|
|
||||||
// Restore restores a state snapshot, taking snapshot chunk readers as input.
|
// Restore restores a state snapshot from the protobuf items read from the reader.
|
||||||
// If the ready channel is non-nil, it returns a ready signal (by being closed) once the
|
// If the ready channel is non-nil, it returns a ready signal (by being closed) once the
|
||||||
// restorer is ready to accept chunks.
|
// restorer is ready to accept chunks.
|
||||||
Restore(height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{}) error
|
Restore(height uint64, format uint32, protoReader protoio.Reader) (SnapshotItem, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionSnapshotter is an extension Snapshotter that is appended to the snapshot stream.
|
||||||
|
// ExtensionSnapshotter has an unique name and manages it's own internal formats.
|
||||||
|
type ExtensionSnapshotter interface {
|
||||||
|
Snapshotter
|
||||||
|
|
||||||
|
// SnapshotName returns the name of snapshotter, it should be unique in the manager.
|
||||||
|
SnapshotName() string
|
||||||
|
|
||||||
|
// SnapshotFormat returns the default format the extension snapshotter use to encode the
|
||||||
|
// payloads when taking a snapshot.
|
||||||
|
// It's defined within the extension, different from the global format for the whole state-sync snapshot.
|
||||||
|
SnapshotFormat() uint32
|
||||||
|
|
||||||
|
// SupportedFormats returns a list of formats it can restore from.
|
||||||
|
SupportedFormats() []uint32
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteExtensionItem writes an item payload for current extention snapshotter.
|
||||||
|
func WriteExtensionItem(protoWriter protoio.Writer, item []byte) error {
|
||||||
|
return protoWriter.WriteMsg(&SnapshotItem{
|
||||||
|
Item: &SnapshotItem_ExtensionPayload{
|
||||||
|
ExtensionPayload: &SnapshotExtensionPayload{
|
||||||
|
Payload: item,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,296 @@
|
||||||
|
package rootmulti_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/snapshots"
|
||||||
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/iavl"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/rootmulti"
|
||||||
|
"github.com/cosmos/cosmos-sdk/store/types"
|
||||||
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMultiStoreWithGeneratedData(db dbm.DB, stores uint8, storeKeys uint64) *rootmulti.Store {
|
||||||
|
multiStore := rootmulti.NewStore(db)
|
||||||
|
r := rand.New(rand.NewSource(49872768940)) // Fixed seed for deterministic tests
|
||||||
|
|
||||||
|
keys := []*types.KVStoreKey{}
|
||||||
|
for i := uint8(0); i < stores; i++ {
|
||||||
|
key := types.NewKVStoreKey(fmt.Sprintf("store%v", i))
|
||||||
|
multiStore.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
multiStore.LoadLatestVersion()
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
store := multiStore.GetCommitKVStore(key).(*iavl.Store)
|
||||||
|
for i := uint64(0); i < storeKeys; i++ {
|
||||||
|
k := make([]byte, 8)
|
||||||
|
v := make([]byte, 1024)
|
||||||
|
binary.BigEndian.PutUint64(k, i)
|
||||||
|
_, err := r.Read(v)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
store.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multiStore.Commit()
|
||||||
|
multiStore.LoadLatestVersion()
|
||||||
|
|
||||||
|
return multiStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultiStoreWithMixedMounts(db dbm.DB) *rootmulti.Store {
|
||||||
|
store := rootmulti.NewStore(db)
|
||||||
|
store.MountStoreWithDB(types.NewKVStoreKey("iavl1"), types.StoreTypeIAVL, nil)
|
||||||
|
store.MountStoreWithDB(types.NewKVStoreKey("iavl2"), types.StoreTypeIAVL, nil)
|
||||||
|
store.MountStoreWithDB(types.NewKVStoreKey("iavl3"), types.StoreTypeIAVL, nil)
|
||||||
|
store.MountStoreWithDB(types.NewTransientStoreKey("trans1"), types.StoreTypeTransient, nil)
|
||||||
|
store.LoadLatestVersion()
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultiStoreWithMixedMountsAndBasicData(db dbm.DB) *rootmulti.Store {
|
||||||
|
store := newMultiStoreWithMixedMounts(db)
|
||||||
|
store1 := store.GetStoreByName("iavl1").(types.CommitKVStore)
|
||||||
|
store2 := store.GetStoreByName("iavl2").(types.CommitKVStore)
|
||||||
|
trans1 := store.GetStoreByName("trans1").(types.KVStore)
|
||||||
|
|
||||||
|
store1.Set([]byte("a"), []byte{1})
|
||||||
|
store1.Set([]byte("b"), []byte{1})
|
||||||
|
store2.Set([]byte("X"), []byte{255})
|
||||||
|
store2.Set([]byte("A"), []byte{101})
|
||||||
|
trans1.Set([]byte("x1"), []byte{91})
|
||||||
|
store.Commit()
|
||||||
|
|
||||||
|
store1.Set([]byte("b"), []byte{2})
|
||||||
|
store1.Set([]byte("c"), []byte{3})
|
||||||
|
store2.Set([]byte("B"), []byte{102})
|
||||||
|
store.Commit()
|
||||||
|
|
||||||
|
store2.Set([]byte("C"), []byte{103})
|
||||||
|
store2.Delete([]byte("X"))
|
||||||
|
trans1.Set([]byte("x2"), []byte{92})
|
||||||
|
store.Commit()
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertStoresEqual(t *testing.T, expect, actual types.CommitKVStore, msgAndArgs ...interface{}) {
|
||||||
|
assert.Equal(t, expect.LastCommitID(), actual.LastCommitID())
|
||||||
|
expectIter := expect.Iterator(nil, nil)
|
||||||
|
expectMap := map[string][]byte{}
|
||||||
|
for ; expectIter.Valid(); expectIter.Next() {
|
||||||
|
expectMap[string(expectIter.Key())] = expectIter.Value()
|
||||||
|
}
|
||||||
|
require.NoError(t, expectIter.Error())
|
||||||
|
|
||||||
|
actualIter := expect.Iterator(nil, nil)
|
||||||
|
actualMap := map[string][]byte{}
|
||||||
|
for ; actualIter.Valid(); actualIter.Next() {
|
||||||
|
actualMap[string(actualIter.Key())] = actualIter.Value()
|
||||||
|
}
|
||||||
|
require.NoError(t, actualIter.Error())
|
||||||
|
|
||||||
|
assert.Equal(t, expectMap, actualMap, msgAndArgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultistoreSnapshot_Checksum(t *testing.T) {
|
||||||
|
// Chunks from different nodes must fit together, so all nodes must produce identical chunks.
|
||||||
|
// This checksum test makes sure that the byte stream remains identical. If the test fails
|
||||||
|
// without having changed the data (e.g. because the Protobuf or zlib encoding changes),
|
||||||
|
// snapshottypes.CurrentFormat must be bumped.
|
||||||
|
store := newMultiStoreWithGeneratedData(dbm.NewMemDB(), 5, 10000)
|
||||||
|
version := uint64(store.LastCommitID().Version)
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
format uint32
|
||||||
|
chunkHashes []string
|
||||||
|
}{
|
||||||
|
{1, []string{
|
||||||
|
"503e5b51b657055b77e88169fadae543619368744ad15f1de0736c0a20482f24",
|
||||||
|
"e1a0daaa738eeb43e778aefd2805e3dd720798288a410b06da4b8459c4d8f72e",
|
||||||
|
"aa048b4ee0f484965d7b3b06822cf0772cdcaad02f3b1b9055e69f2cb365ef3c",
|
||||||
|
"7921eaa3ed4921341e504d9308a9877986a879fe216a099c86e8db66fcba4c63",
|
||||||
|
"a4a864e6c02c9fca5837ec80dc84f650b25276ed7e4820cf7516ced9f9901b86",
|
||||||
|
"ca2879ac6e7205d257440131ba7e72bef784cd61642e32b847729e543c1928b9",
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(fmt.Sprintf("Format %v", tc.format), func(t *testing.T) {
|
||||||
|
ch := make(chan io.ReadCloser)
|
||||||
|
go func() {
|
||||||
|
streamWriter := snapshots.NewStreamWriter(ch)
|
||||||
|
defer streamWriter.Close()
|
||||||
|
require.NotNil(t, streamWriter)
|
||||||
|
err := store.Snapshot(version, streamWriter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
hashes := []string{}
|
||||||
|
hasher := sha256.New()
|
||||||
|
for chunk := range ch {
|
||||||
|
hasher.Reset()
|
||||||
|
_, err := io.Copy(hasher, chunk)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hashes = append(hashes, hex.EncodeToString(hasher.Sum(nil)))
|
||||||
|
}
|
||||||
|
assert.Equal(t, tc.chunkHashes, hashes,
|
||||||
|
"Snapshot output for format %v has changed", tc.format)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultistoreSnapshot_Errors(t *testing.T) {
|
||||||
|
store := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB())
|
||||||
|
|
||||||
|
testcases := map[string]struct {
|
||||||
|
height uint64
|
||||||
|
expectType error
|
||||||
|
}{
|
||||||
|
"0 height": {0, nil},
|
||||||
|
"unknown height": {9, nil},
|
||||||
|
}
|
||||||
|
for name, tc := range testcases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
err := store.Snapshot(tc.height, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
if tc.expectType != nil {
|
||||||
|
assert.True(t, errors.Is(err, tc.expectType))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultistoreSnapshotRestore(t *testing.T) {
|
||||||
|
source := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB())
|
||||||
|
target := newMultiStoreWithMixedMounts(dbm.NewMemDB())
|
||||||
|
version := uint64(source.LastCommitID().Version)
|
||||||
|
require.EqualValues(t, 3, version)
|
||||||
|
|
||||||
|
chunks := make(chan io.ReadCloser, 100)
|
||||||
|
go func() {
|
||||||
|
streamWriter := snapshots.NewStreamWriter(chunks)
|
||||||
|
require.NotNil(t, streamWriter)
|
||||||
|
defer streamWriter.Close()
|
||||||
|
err := source.Snapshot(version, streamWriter)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
streamReader, err := snapshots.NewStreamReader(chunks)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = target.Restore(version, snapshottypes.CurrentFormat, streamReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, source.LastCommitID(), target.LastCommitID())
|
||||||
|
for key, sourceStore := range source.GetStores() {
|
||||||
|
targetStore := target.GetStoreByName(key.Name()).(types.CommitKVStore)
|
||||||
|
switch sourceStore.GetStoreType() {
|
||||||
|
case types.StoreTypeTransient:
|
||||||
|
assert.False(t, targetStore.Iterator(nil, nil).Valid(),
|
||||||
|
"transient store %v not empty", key.Name())
|
||||||
|
default:
|
||||||
|
assertStoresEqual(t, sourceStore, targetStore, "store %q not equal", key.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkMultistoreSnapshot(b *testing.B, stores uint8, storeKeys uint64) {
|
||||||
|
b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.")
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.StopTimer()
|
||||||
|
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
|
||||||
|
version := source.LastCommitID().Version
|
||||||
|
require.EqualValues(b, 1, version)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := rootmulti.NewStore(dbm.NewMemDB())
|
||||||
|
for key := range source.GetStores() {
|
||||||
|
target.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
||||||
|
}
|
||||||
|
err := target.LoadLatestVersion()
|
||||||
|
require.NoError(b, err)
|
||||||
|
require.EqualValues(b, 0, target.LastCommitID().Version)
|
||||||
|
|
||||||
|
chunks := make(chan io.ReadCloser)
|
||||||
|
go func() {
|
||||||
|
streamWriter := snapshots.NewStreamWriter(chunks)
|
||||||
|
require.NotNil(b, streamWriter)
|
||||||
|
err := source.Snapshot(uint64(version), streamWriter)
|
||||||
|
require.NoError(b, err)
|
||||||
|
}()
|
||||||
|
for reader := range chunks {
|
||||||
|
_, err := io.Copy(io.Discard, reader)
|
||||||
|
require.NoError(b, err)
|
||||||
|
err = reader.Close()
|
||||||
|
require.NoError(b, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkMultistoreSnapshotRestore(b *testing.B, stores uint8, storeKeys uint64) {
|
||||||
|
b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.")
|
||||||
|
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.StopTimer()
|
||||||
|
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
|
||||||
|
version := uint64(source.LastCommitID().Version)
|
||||||
|
require.EqualValues(b, 1, version)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
target := rootmulti.NewStore(dbm.NewMemDB())
|
||||||
|
for key := range source.GetStores() {
|
||||||
|
target.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
||||||
|
}
|
||||||
|
err := target.LoadLatestVersion()
|
||||||
|
require.NoError(b, err)
|
||||||
|
require.EqualValues(b, 0, target.LastCommitID().Version)
|
||||||
|
|
||||||
|
chunks := make(chan io.ReadCloser)
|
||||||
|
go func() {
|
||||||
|
writer := snapshots.NewStreamWriter(chunks)
|
||||||
|
require.NotNil(b, writer)
|
||||||
|
err := source.Snapshot(version, writer)
|
||||||
|
require.NoError(b, err)
|
||||||
|
}()
|
||||||
|
reader, err := snapshots.NewStreamReader(chunks)
|
||||||
|
require.NoError(b, err)
|
||||||
|
_, err = target.Restore(version, snapshottypes.CurrentFormat, reader)
|
||||||
|
require.NoError(b, err)
|
||||||
|
require.Equal(b, source.LastCommitID(), target.LastCommitID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMultistoreSnapshot100K(b *testing.B) {
|
||||||
|
benchmarkMultistoreSnapshot(b, 10, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMultistoreSnapshot1M(b *testing.B) {
|
||||||
|
benchmarkMultistoreSnapshot(b, 10, 100000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMultistoreSnapshotRestore100K(b *testing.B) {
|
||||||
|
benchmarkMultistoreSnapshotRestore(b, 10, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMultistoreSnapshotRestore1M(b *testing.B) {
|
||||||
|
benchmarkMultistoreSnapshotRestore(b, 10, 100000)
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
package rootmulti
|
package rootmulti
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"compress/zlib"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -18,7 +16,6 @@ import (
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
dbm "github.com/tendermint/tm-db"
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/snapshots"
|
|
||||||
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
"github.com/cosmos/cosmos-sdk/store/cachemulti"
|
"github.com/cosmos/cosmos-sdk/store/cachemulti"
|
||||||
"github.com/cosmos/cosmos-sdk/store/dbadapter"
|
"github.com/cosmos/cosmos-sdk/store/dbadapter"
|
||||||
|
@ -35,11 +32,6 @@ const (
|
||||||
latestVersionKey = "s/latest"
|
latestVersionKey = "s/latest"
|
||||||
pruneHeightsKey = "s/pruneheights"
|
pruneHeightsKey = "s/pruneheights"
|
||||||
commitInfoKeyFmt = "s/%d" // s/<version>
|
commitInfoKeyFmt = "s/%d" // s/<version>
|
||||||
|
|
||||||
// Do not change chunk size without new snapshot format (must be uniform across nodes)
|
|
||||||
snapshotChunkSize = uint64(10e6)
|
|
||||||
snapshotBufferSize = int(snapshotChunkSize)
|
|
||||||
snapshotMaxItemSize = int(64e6) // SDK has no key/value size limit, so we set an arbitrary limit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store is composed of many CommitStores. Name contrasts with
|
// Store is composed of many CommitStores. Name contrasts with
|
||||||
|
@ -156,6 +148,11 @@ func (rs *Store) GetCommitKVStore(key types.StoreKey) types.CommitKVStore {
|
||||||
return rs.stores[key]
|
return rs.stores[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStores returns mounted stores
|
||||||
|
func (rs *Store) GetStores() map[types.StoreKey]types.CommitKVStore {
|
||||||
|
return rs.stores
|
||||||
|
}
|
||||||
|
|
||||||
// LoadLatestVersionAndUpgrade implements CommitMultiStore
|
// LoadLatestVersionAndUpgrade implements CommitMultiStore
|
||||||
func (rs *Store) LoadLatestVersionAndUpgrade(upgrades *types.StoreUpgrades) error {
|
func (rs *Store) LoadLatestVersionAndUpgrade(upgrades *types.StoreUpgrades) error {
|
||||||
ver := getLatestVersion(rs.db)
|
ver := getLatestVersion(rs.db)
|
||||||
|
@ -562,11 +559,11 @@ func (rs *Store) GetKVStore(key types.StoreKey) types.KVStore {
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStoreByName performs a lookup of a StoreKey given a store name typically
|
// GetStoreByName performs a lookup of a StoreKey given a store name typically
|
||||||
// provided in a path. The StoreKey is then used to perform a lookup and return
|
// provided in a path. The StoreKey is then used to perform a lookup and return
|
||||||
// a Store. If the Store is wrapped in an inter-block cache, it will be unwrapped
|
// a Store. If the Store is wrapped in an inter-block cache, it will be unwrapped
|
||||||
// prior to being returned. If the StoreKey does not exist, nil is returned.
|
// prior to being returned. If the StoreKey does not exist, nil is returned.
|
||||||
func (rs *Store) getStoreByName(name string) types.Store {
|
func (rs *Store) GetStoreByName(name string) types.Store {
|
||||||
key := rs.keysByName[name]
|
key := rs.keysByName[name]
|
||||||
if key == nil {
|
if key == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -586,7 +583,7 @@ func (rs *Store) Query(req abci.RequestQuery) abci.ResponseQuery {
|
||||||
return sdkerrors.QueryResult(err, false)
|
return sdkerrors.QueryResult(err, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
store := rs.getStoreByName(storeName)
|
store := rs.GetStoreByName(storeName)
|
||||||
if store == nil {
|
if store == nil {
|
||||||
return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no such store: %s", storeName), false)
|
return sdkerrors.QueryResult(sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "no such store: %s", storeName), false)
|
||||||
}
|
}
|
||||||
|
@ -671,15 +668,12 @@ func parsePath(path string) (storeName string, subpath string, err error) {
|
||||||
// identical across nodes such that chunks from different sources fit together. If the output for a
|
// identical across nodes such that chunks from different sources fit together. If the output for a
|
||||||
// given format changes (at the byte level), the snapshot format must be bumped - see
|
// given format changes (at the byte level), the snapshot format must be bumped - see
|
||||||
// TestMultistoreSnapshot_Checksum test.
|
// TestMultistoreSnapshot_Checksum test.
|
||||||
func (rs *Store) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error) {
|
func (rs *Store) Snapshot(height uint64, protoWriter protoio.Writer) error {
|
||||||
if format != snapshottypes.CurrentFormat {
|
|
||||||
return nil, sdkerrors.Wrapf(snapshottypes.ErrUnknownFormat, "format %v", format)
|
|
||||||
}
|
|
||||||
if height == 0 {
|
if height == 0 {
|
||||||
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot snapshot height 0")
|
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot snapshot height 0")
|
||||||
}
|
}
|
||||||
if height > uint64(rs.LastCommitID().Version) {
|
if height > uint64(rs.LastCommitID().Version) {
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot snapshot future height %v", height)
|
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot snapshot future height %v", height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect stores to snapshot (only IAVL stores are supported)
|
// Collect stores to snapshot (only IAVL stores are supported)
|
||||||
|
@ -696,7 +690,7 @@ func (rs *Store) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, e
|
||||||
// Non-persisted stores shouldn't be snapshotted
|
// Non-persisted stores shouldn't be snapshotted
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrLogic,
|
return sdkerrors.Wrapf(sdkerrors.ErrLogic,
|
||||||
"don't know how to snapshot store %q of type %T", key.Name(), store)
|
"don't know how to snapshot store %q of type %T", key.Name(), store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -704,160 +698,97 @@ func (rs *Store) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, e
|
||||||
return strings.Compare(stores[i].name, stores[j].name) == -1
|
return strings.Compare(stores[i].name, stores[j].name) == -1
|
||||||
})
|
})
|
||||||
|
|
||||||
// Spawn goroutine to generate snapshot chunks and pass their io.ReadClosers through a channel
|
// Export each IAVL store. Stores are serialized as a stream of SnapshotItem Protobuf
|
||||||
ch := make(chan io.ReadCloser)
|
// messages. The first item contains a SnapshotStore with store metadata (i.e. name),
|
||||||
go func() {
|
// and the following messages contain a SnapshotNode (i.e. an ExportNode). Store changes
|
||||||
// Set up a stream pipeline to serialize snapshot nodes:
|
// are demarcated by new SnapshotStore items.
|
||||||
// ExportNode -> delimited Protobuf -> zlib -> buffer -> chunkWriter -> chan io.ReadCloser
|
for _, store := range stores {
|
||||||
chunkWriter := snapshots.NewChunkWriter(ch, snapshotChunkSize)
|
exporter, err := store.Export(int64(height))
|
||||||
defer chunkWriter.Close()
|
|
||||||
bufWriter := bufio.NewWriterSize(chunkWriter, snapshotBufferSize)
|
|
||||||
defer func() {
|
|
||||||
if err := bufWriter.Flush(); err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
zWriter, err := zlib.NewWriterLevel(bufWriter, 7)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chunkWriter.CloseWithError(sdkerrors.Wrap(err, "zlib failure"))
|
return err
|
||||||
return
|
}
|
||||||
|
defer exporter.Close()
|
||||||
|
err = protoWriter.WriteMsg(&snapshottypes.SnapshotItem{
|
||||||
|
Item: &snapshottypes.SnapshotItem_Store{
|
||||||
|
Store: &snapshottypes.SnapshotStoreItem{
|
||||||
|
Name: store.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if err := zWriter.Close(); err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
protoWriter := protoio.NewDelimitedWriter(zWriter)
|
|
||||||
defer func() {
|
|
||||||
if err := protoWriter.Close(); err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Export each IAVL store. Stores are serialized as a stream of SnapshotItem Protobuf
|
for {
|
||||||
// messages. The first item contains a SnapshotStore with store metadata (i.e. name),
|
node, err := exporter.Next()
|
||||||
// and the following messages contain a SnapshotNode (i.e. an ExportNode). Store changes
|
if err == iavltree.ExportDone {
|
||||||
// are demarcated by new SnapshotStore items.
|
break
|
||||||
for _, store := range stores {
|
} else if err != nil {
|
||||||
exporter, err := store.Export(int64(height))
|
return err
|
||||||
if err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer exporter.Close()
|
err = protoWriter.WriteMsg(&snapshottypes.SnapshotItem{
|
||||||
err = protoWriter.WriteMsg(&types.SnapshotItem{
|
Item: &snapshottypes.SnapshotItem_IAVL{
|
||||||
Item: &types.SnapshotItem_Store{
|
IAVL: &snapshottypes.SnapshotIAVLItem{
|
||||||
Store: &types.SnapshotStoreItem{
|
Key: node.Key,
|
||||||
Name: store.name,
|
Value: node.Value,
|
||||||
|
Height: int32(node.Height),
|
||||||
|
Version: node.Version,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chunkWriter.CloseWithError(err)
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
|
||||||
node, err := exporter.Next()
|
|
||||||
if err == iavltree.ExportDone {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = protoWriter.WriteMsg(&types.SnapshotItem{
|
|
||||||
Item: &types.SnapshotItem_IAVL{
|
|
||||||
IAVL: &types.SnapshotIAVLItem{
|
|
||||||
Key: node.Key,
|
|
||||||
Value: node.Value,
|
|
||||||
Height: int32(node.Height),
|
|
||||||
Version: node.Version,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
chunkWriter.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exporter.Close()
|
|
||||||
}
|
}
|
||||||
}()
|
exporter.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return ch, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore implements snapshottypes.Snapshotter.
|
// Restore implements snapshottypes.Snapshotter.
|
||||||
|
// returns next snapshot item and error.
|
||||||
func (rs *Store) Restore(
|
func (rs *Store) Restore(
|
||||||
height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{},
|
height uint64, format uint32, protoReader protoio.Reader,
|
||||||
) error {
|
) (snapshottypes.SnapshotItem, error) {
|
||||||
if format != snapshottypes.CurrentFormat {
|
|
||||||
return sdkerrors.Wrapf(snapshottypes.ErrUnknownFormat, "format %v", format)
|
|
||||||
}
|
|
||||||
if height == 0 {
|
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at height 0")
|
|
||||||
}
|
|
||||||
if height > uint64(math.MaxInt64) {
|
|
||||||
return sdkerrors.Wrapf(snapshottypes.ErrInvalidMetadata,
|
|
||||||
"snapshot height %v cannot exceed %v", height, int64(math.MaxInt64))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal readiness. Must be done before the readers below are set up, since the zlib
|
|
||||||
// reader reads from the stream on initialization, potentially causing deadlocks.
|
|
||||||
if ready != nil {
|
|
||||||
close(ready)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a restore stream pipeline
|
|
||||||
// chan io.ReadCloser -> chunkReader -> zlib -> delimited Protobuf -> ExportNode
|
|
||||||
chunkReader := snapshots.NewChunkReader(chunks)
|
|
||||||
defer chunkReader.Close()
|
|
||||||
zReader, err := zlib.NewReader(chunkReader)
|
|
||||||
if err != nil {
|
|
||||||
return sdkerrors.Wrap(err, "zlib failure")
|
|
||||||
}
|
|
||||||
defer zReader.Close()
|
|
||||||
protoReader := protoio.NewDelimitedReader(zReader, snapshotMaxItemSize)
|
|
||||||
defer protoReader.Close()
|
|
||||||
|
|
||||||
// Import nodes into stores. The first item is expected to be a SnapshotItem containing
|
// Import nodes into stores. The first item is expected to be a SnapshotItem containing
|
||||||
// a SnapshotStoreItem, telling us which store to import into. The following items will contain
|
// a SnapshotStoreItem, telling us which store to import into. The following items will contain
|
||||||
// SnapshotNodeItem (i.e. ExportNode) until we reach the next SnapshotStoreItem or EOF.
|
// SnapshotNodeItem (i.e. ExportNode) until we reach the next SnapshotStoreItem or EOF.
|
||||||
var importer *iavltree.Importer
|
var importer *iavltree.Importer
|
||||||
for {
|
for {
|
||||||
item := &types.SnapshotItem{}
|
snapshotItem := &snapshottypes.SnapshotItem{}
|
||||||
err := protoReader.ReadMsg(item)
|
err := protoReader.ReadMsg(snapshotItem)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return sdkerrors.Wrap(err, "invalid protobuf message")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch item := item.Item.(type) {
|
switch item := snapshotItem.Item.(type) {
|
||||||
case *types.SnapshotItem_Store:
|
case *snapshottypes.SnapshotItem_Store:
|
||||||
if importer != nil {
|
if importer != nil {
|
||||||
err = importer.Commit()
|
err = importer.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdkerrors.Wrap(err, "IAVL commit failed")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL commit failed")
|
||||||
}
|
}
|
||||||
importer.Close()
|
importer.Close()
|
||||||
}
|
}
|
||||||
store, ok := rs.getStoreByName(item.Store.Name).(*iavl.Store)
|
store, ok := rs.GetStoreByName(item.Store.Name).(*iavl.Store)
|
||||||
if !ok || store == nil {
|
if !ok || store == nil {
|
||||||
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot import into non-IAVL store %q", item.Store.Name)
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrapf(sdkerrors.ErrLogic, "cannot import into non-IAVL store %q", item.Store.Name)
|
||||||
}
|
}
|
||||||
importer, err = store.Import(int64(height))
|
importer, err = store.Import(int64(height))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdkerrors.Wrap(err, "import failed")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "import failed")
|
||||||
}
|
}
|
||||||
defer importer.Close()
|
defer importer.Close()
|
||||||
|
|
||||||
case *types.SnapshotItem_IAVL:
|
case *snapshottypes.SnapshotItem_IAVL:
|
||||||
if importer == nil {
|
if importer == nil {
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "received IAVL node item before store item")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(sdkerrors.ErrLogic, "received IAVL node item before store item")
|
||||||
}
|
}
|
||||||
if item.IAVL.Height > math.MaxInt8 {
|
if item.IAVL.Height > math.MaxInt8 {
|
||||||
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "node height %v cannot exceed %v",
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrapf(sdkerrors.ErrLogic, "node height %v cannot exceed %v",
|
||||||
item.IAVL.Height, math.MaxInt8)
|
item.IAVL.Height, math.MaxInt8)
|
||||||
}
|
}
|
||||||
node := &iavltree.ExportNode{
|
node := &iavltree.ExportNode{
|
||||||
|
@ -876,24 +807,25 @@ func (rs *Store) Restore(
|
||||||
}
|
}
|
||||||
err := importer.Add(node)
|
err := importer.Add(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdkerrors.Wrap(err, "IAVL node import failed")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL node import failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return sdkerrors.Wrapf(sdkerrors.ErrLogic, "unknown snapshot item %T", item)
|
// pass back the unrecognized item.
|
||||||
|
return *snapshotItem, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if importer != nil {
|
if importer != nil {
|
||||||
err := importer.Commit()
|
err := importer.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sdkerrors.Wrap(err, "IAVL commit failed")
|
return snapshottypes.SnapshotItem{}, sdkerrors.Wrap(err, "IAVL commit failed")
|
||||||
}
|
}
|
||||||
importer.Close()
|
importer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
flushMetadata(rs.db, int64(height), rs.buildCommitInfo(int64(height)), []int64{})
|
flushMetadata(rs.db, int64(height), rs.buildCommitInfo(int64(height)), []int64{})
|
||||||
return rs.LoadLatestVersion()
|
return snapshottypes.SnapshotItem{}, rs.LoadLatestVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) {
|
func (rs *Store) loadCommitStoreFromParams(key types.StoreKey, id types.CommitID, params storeParams) (types.CommitKVStore, error) {
|
||||||
|
|
|
@ -2,24 +2,16 @@ package rootmulti
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
dbm "github.com/tendermint/tm-db"
|
dbm "github.com/tendermint/tm-db"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
codecTypes "github.com/cosmos/cosmos-sdk/codec/types"
|
codecTypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/store/cachemulti"
|
"github.com/cosmos/cosmos-sdk/store/cachemulti"
|
||||||
"github.com/cosmos/cosmos-sdk/store/iavl"
|
"github.com/cosmos/cosmos-sdk/store/iavl"
|
||||||
sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
|
sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
|
||||||
|
@ -86,7 +78,7 @@ func TestCacheMultiStoreWithVersion(t *testing.T) {
|
||||||
|
|
||||||
k, v := []byte("wind"), []byte("blows")
|
k, v := []byte("wind"), []byte("blows")
|
||||||
|
|
||||||
store1 := ms.getStoreByName("store1").(types.KVStore)
|
store1 := ms.GetStoreByName("store1").(types.KVStore)
|
||||||
store1.Set(k, v)
|
store1.Set(k, v)
|
||||||
|
|
||||||
cID := ms.Commit()
|
cID := ms.Commit()
|
||||||
|
@ -123,7 +115,7 @@ func TestHashStableWithEmptyCommit(t *testing.T) {
|
||||||
|
|
||||||
k, v := []byte("wind"), []byte("blows")
|
k, v := []byte("wind"), []byte("blows")
|
||||||
|
|
||||||
store1 := ms.getStoreByName("store1").(types.KVStore)
|
store1 := ms.GetStoreByName("store1").(types.KVStore)
|
||||||
store1.Set(k, v)
|
store1.Set(k, v)
|
||||||
|
|
||||||
cID := ms.Commit()
|
cID := ms.Commit()
|
||||||
|
@ -147,11 +139,11 @@ func TestMultistoreCommitLoad(t *testing.T) {
|
||||||
checkStore(t, store, commitID, commitID)
|
checkStore(t, store, commitID, commitID)
|
||||||
|
|
||||||
// Make sure we can get stores by name.
|
// Make sure we can get stores by name.
|
||||||
s1 := store.getStoreByName("store1")
|
s1 := store.GetStoreByName("store1")
|
||||||
require.NotNil(t, s1)
|
require.NotNil(t, s1)
|
||||||
s3 := store.getStoreByName("store3")
|
s3 := store.GetStoreByName("store3")
|
||||||
require.NotNil(t, s3)
|
require.NotNil(t, s3)
|
||||||
s77 := store.getStoreByName("store77")
|
s77 := store.GetStoreByName("store77")
|
||||||
require.Nil(t, s77)
|
require.Nil(t, s77)
|
||||||
|
|
||||||
// Make a few commits and check them.
|
// Make a few commits and check them.
|
||||||
|
@ -191,21 +183,21 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
|
||||||
|
|
||||||
// write some data in all stores
|
// write some data in all stores
|
||||||
k1, v1 := []byte("first"), []byte("store")
|
k1, v1 := []byte("first"), []byte("store")
|
||||||
s1, _ := store.getStoreByName("store1").(types.KVStore)
|
s1, _ := store.GetStoreByName("store1").(types.KVStore)
|
||||||
require.NotNil(t, s1)
|
require.NotNil(t, s1)
|
||||||
s1.Set(k1, v1)
|
s1.Set(k1, v1)
|
||||||
|
|
||||||
k2, v2 := []byte("second"), []byte("restore")
|
k2, v2 := []byte("second"), []byte("restore")
|
||||||
s2, _ := store.getStoreByName("store2").(types.KVStore)
|
s2, _ := store.GetStoreByName("store2").(types.KVStore)
|
||||||
require.NotNil(t, s2)
|
require.NotNil(t, s2)
|
||||||
s2.Set(k2, v2)
|
s2.Set(k2, v2)
|
||||||
|
|
||||||
k3, v3 := []byte("third"), []byte("dropped")
|
k3, v3 := []byte("third"), []byte("dropped")
|
||||||
s3, _ := store.getStoreByName("store3").(types.KVStore)
|
s3, _ := store.GetStoreByName("store3").(types.KVStore)
|
||||||
require.NotNil(t, s3)
|
require.NotNil(t, s3)
|
||||||
s3.Set(k3, v3)
|
s3.Set(k3, v3)
|
||||||
|
|
||||||
s4, _ := store.getStoreByName("store4").(types.KVStore)
|
s4, _ := store.GetStoreByName("store4").(types.KVStore)
|
||||||
require.Nil(t, s4)
|
require.Nil(t, s4)
|
||||||
|
|
||||||
// do one commit
|
// do one commit
|
||||||
|
@ -228,7 +220,7 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
|
||||||
checkStore(t, store, commitID, commitID)
|
checkStore(t, store, commitID, commitID)
|
||||||
|
|
||||||
// let's query data to see it was saved properly
|
// let's query data to see it was saved properly
|
||||||
s2, _ = store.getStoreByName("store2").(types.KVStore)
|
s2, _ = store.GetStoreByName("store2").(types.KVStore)
|
||||||
require.NotNil(t, s2)
|
require.NotNil(t, s2)
|
||||||
require.Equal(t, v2, s2.Get(k2))
|
require.Equal(t, v2, s2.Get(k2))
|
||||||
|
|
||||||
|
@ -238,17 +230,17 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
// s1 was not changed
|
// s1 was not changed
|
||||||
s1, _ = restore.getStoreByName("store1").(types.KVStore)
|
s1, _ = restore.GetStoreByName("store1").(types.KVStore)
|
||||||
require.NotNil(t, s1)
|
require.NotNil(t, s1)
|
||||||
require.Equal(t, v1, s1.Get(k1))
|
require.Equal(t, v1, s1.Get(k1))
|
||||||
|
|
||||||
// store3 is mounted, but data deleted are gone
|
// store3 is mounted, but data deleted are gone
|
||||||
s3, _ = restore.getStoreByName("store3").(types.KVStore)
|
s3, _ = restore.GetStoreByName("store3").(types.KVStore)
|
||||||
require.NotNil(t, s3)
|
require.NotNil(t, s3)
|
||||||
require.Nil(t, s3.Get(k3)) // data was deleted
|
require.Nil(t, s3.Get(k3)) // data was deleted
|
||||||
|
|
||||||
// store4 is mounted, with empty data
|
// store4 is mounted, with empty data
|
||||||
s4, _ = restore.getStoreByName("store4").(types.KVStore)
|
s4, _ = restore.GetStoreByName("store4").(types.KVStore)
|
||||||
require.NotNil(t, s4)
|
require.NotNil(t, s4)
|
||||||
|
|
||||||
iterator := s4.Iterator(nil, nil)
|
iterator := s4.Iterator(nil, nil)
|
||||||
|
@ -266,11 +258,11 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
|
||||||
s4.Set(k4, v4)
|
s4.Set(k4, v4)
|
||||||
|
|
||||||
// store2 is no longer mounted
|
// store2 is no longer mounted
|
||||||
st2 := restore.getStoreByName("store2")
|
st2 := restore.GetStoreByName("store2")
|
||||||
require.Nil(t, st2)
|
require.Nil(t, st2)
|
||||||
|
|
||||||
// restore2 has the old data
|
// restore2 has the old data
|
||||||
rs2, _ := restore.getStoreByName("restore2").(types.KVStore)
|
rs2, _ := restore.GetStoreByName("restore2").(types.KVStore)
|
||||||
require.NotNil(t, rs2)
|
require.NotNil(t, rs2)
|
||||||
require.Equal(t, v2, rs2.Get(k2))
|
require.Equal(t, v2, rs2.Get(k2))
|
||||||
|
|
||||||
|
@ -284,15 +276,15 @@ func TestMultistoreLoadWithUpgrade(t *testing.T) {
|
||||||
require.Equal(t, migratedID, reload.LastCommitID())
|
require.Equal(t, migratedID, reload.LastCommitID())
|
||||||
|
|
||||||
// query this new store
|
// query this new store
|
||||||
rl1, _ := reload.getStoreByName("store1").(types.KVStore)
|
rl1, _ := reload.GetStoreByName("store1").(types.KVStore)
|
||||||
require.NotNil(t, rl1)
|
require.NotNil(t, rl1)
|
||||||
require.Equal(t, v1, rl1.Get(k1))
|
require.Equal(t, v1, rl1.Get(k1))
|
||||||
|
|
||||||
rl2, _ := reload.getStoreByName("restore2").(types.KVStore)
|
rl2, _ := reload.GetStoreByName("restore2").(types.KVStore)
|
||||||
require.NotNil(t, rl2)
|
require.NotNil(t, rl2)
|
||||||
require.Equal(t, v2, rl2.Get(k2))
|
require.Equal(t, v2, rl2.Get(k2))
|
||||||
|
|
||||||
rl4, _ := reload.getStoreByName("store4").(types.KVStore)
|
rl4, _ := reload.GetStoreByName("store4").(types.KVStore)
|
||||||
require.NotNil(t, rl4)
|
require.NotNil(t, rl4)
|
||||||
require.Equal(t, v4, rl4.Get(k4))
|
require.Equal(t, v4, rl4.Get(k4))
|
||||||
|
|
||||||
|
@ -343,15 +335,15 @@ func TestMultiStoreRestart(t *testing.T) {
|
||||||
|
|
||||||
for i := 1; i < 3; i++ {
|
for i := 1; i < 3; i++ {
|
||||||
// Set and commit data in one store.
|
// Set and commit data in one store.
|
||||||
store1 := multi.getStoreByName("store1").(types.KVStore)
|
store1 := multi.GetStoreByName("store1").(types.KVStore)
|
||||||
store1.Set([]byte(k), []byte(fmt.Sprintf("%s:%d", v, i)))
|
store1.Set([]byte(k), []byte(fmt.Sprintf("%s:%d", v, i)))
|
||||||
|
|
||||||
// ... and another.
|
// ... and another.
|
||||||
store2 := multi.getStoreByName("store2").(types.KVStore)
|
store2 := multi.GetStoreByName("store2").(types.KVStore)
|
||||||
store2.Set([]byte(k2), []byte(fmt.Sprintf("%s:%d", v2, i)))
|
store2.Set([]byte(k2), []byte(fmt.Sprintf("%s:%d", v2, i)))
|
||||||
|
|
||||||
// ... and another.
|
// ... and another.
|
||||||
store3 := multi.getStoreByName("store3").(types.KVStore)
|
store3 := multi.GetStoreByName("store3").(types.KVStore)
|
||||||
store3.Set([]byte(k3), []byte(fmt.Sprintf("%s:%d", v3, i)))
|
store3.Set([]byte(k3), []byte(fmt.Sprintf("%s:%d", v3, i)))
|
||||||
|
|
||||||
multi.Commit()
|
multi.Commit()
|
||||||
|
@ -362,11 +354,11 @@ func TestMultiStoreRestart(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set and commit data in one store.
|
// Set and commit data in one store.
|
||||||
store1 := multi.getStoreByName("store1").(types.KVStore)
|
store1 := multi.GetStoreByName("store1").(types.KVStore)
|
||||||
store1.Set([]byte(k), []byte(fmt.Sprintf("%s:%d", v, 3)))
|
store1.Set([]byte(k), []byte(fmt.Sprintf("%s:%d", v, 3)))
|
||||||
|
|
||||||
// ... and another.
|
// ... and another.
|
||||||
store2 := multi.getStoreByName("store2").(types.KVStore)
|
store2 := multi.GetStoreByName("store2").(types.KVStore)
|
||||||
store2.Set([]byte(k2), []byte(fmt.Sprintf("%s:%d", v2, 3)))
|
store2.Set([]byte(k2), []byte(fmt.Sprintf("%s:%d", v2, 3)))
|
||||||
|
|
||||||
multi.Commit()
|
multi.Commit()
|
||||||
|
@ -376,7 +368,7 @@ func TestMultiStoreRestart(t *testing.T) {
|
||||||
require.NotEqual(t, initCid, flushedCinfo, "CID is different after flush to disk")
|
require.NotEqual(t, initCid, flushedCinfo, "CID is different after flush to disk")
|
||||||
|
|
||||||
// ... and another.
|
// ... and another.
|
||||||
store3 := multi.getStoreByName("store3").(types.KVStore)
|
store3 := multi.GetStoreByName("store3").(types.KVStore)
|
||||||
store3.Set([]byte(k3), []byte(fmt.Sprintf("%s:%d", v3, 3)))
|
store3.Set([]byte(k3), []byte(fmt.Sprintf("%s:%d", v3, 3)))
|
||||||
|
|
||||||
multi.Commit()
|
multi.Commit()
|
||||||
|
@ -393,16 +385,16 @@ func TestMultiStoreRestart(t *testing.T) {
|
||||||
require.Equal(t, int64(4), reloadedCid.Version, "Reloaded CID is not the same as last flushed CID")
|
require.Equal(t, int64(4), reloadedCid.Version, "Reloaded CID is not the same as last flushed CID")
|
||||||
|
|
||||||
// Check that store1 and store2 retained date from 3rd commit
|
// Check that store1 and store2 retained date from 3rd commit
|
||||||
store1 = multi.getStoreByName("store1").(types.KVStore)
|
store1 = multi.GetStoreByName("store1").(types.KVStore)
|
||||||
val := store1.Get([]byte(k))
|
val := store1.Get([]byte(k))
|
||||||
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v, 3)), val, "Reloaded value not the same as last flushed value")
|
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v, 3)), val, "Reloaded value not the same as last flushed value")
|
||||||
|
|
||||||
store2 = multi.getStoreByName("store2").(types.KVStore)
|
store2 = multi.GetStoreByName("store2").(types.KVStore)
|
||||||
val2 := store2.Get([]byte(k2))
|
val2 := store2.Get([]byte(k2))
|
||||||
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v2, 3)), val2, "Reloaded value not the same as last flushed value")
|
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v2, 3)), val2, "Reloaded value not the same as last flushed value")
|
||||||
|
|
||||||
// Check that store3 still has data from last commit even though update happened on 2nd commit
|
// Check that store3 still has data from last commit even though update happened on 2nd commit
|
||||||
store3 = multi.getStoreByName("store3").(types.KVStore)
|
store3 = multi.GetStoreByName("store3").(types.KVStore)
|
||||||
val3 := store3.Get([]byte(k3))
|
val3 := store3.Get([]byte(k3))
|
||||||
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v3, 3)), val3, "Reloaded value not the same as last flushed value")
|
require.Equal(t, []byte(fmt.Sprintf("%s:%d", v3, 3)), val3, "Reloaded value not the same as last flushed value")
|
||||||
}
|
}
|
||||||
|
@ -420,15 +412,15 @@ func TestMultiStoreQuery(t *testing.T) {
|
||||||
cid := multi.Commit()
|
cid := multi.Commit()
|
||||||
|
|
||||||
// Make sure we can get by name.
|
// Make sure we can get by name.
|
||||||
garbage := multi.getStoreByName("bad-name")
|
garbage := multi.GetStoreByName("bad-name")
|
||||||
require.Nil(t, garbage)
|
require.Nil(t, garbage)
|
||||||
|
|
||||||
// Set and commit data in one store.
|
// Set and commit data in one store.
|
||||||
store1 := multi.getStoreByName("store1").(types.KVStore)
|
store1 := multi.GetStoreByName("store1").(types.KVStore)
|
||||||
store1.Set(k, v)
|
store1.Set(k, v)
|
||||||
|
|
||||||
// ... and another.
|
// ... and another.
|
||||||
store2 := multi.getStoreByName("store2").(types.KVStore)
|
store2 := multi.GetStoreByName("store2").(types.KVStore)
|
||||||
store2.Set(k2, v2)
|
store2.Set(k2, v2)
|
||||||
|
|
||||||
// Commit the multistore.
|
// Commit the multistore.
|
||||||
|
@ -551,121 +543,6 @@ func TestMultiStore_PruningRestart(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultistoreSnapshot_Checksum(t *testing.T) {
|
|
||||||
// Chunks from different nodes must fit together, so all nodes must produce identical chunks.
|
|
||||||
// This checksum test makes sure that the byte stream remains identical. If the test fails
|
|
||||||
// without having changed the data (e.g. because the Protobuf or zlib encoding changes),
|
|
||||||
// snapshottypes.CurrentFormat must be bumped.
|
|
||||||
store := newMultiStoreWithGeneratedData(dbm.NewMemDB(), 5, 10000)
|
|
||||||
version := uint64(store.LastCommitID().Version)
|
|
||||||
|
|
||||||
testcases := []struct {
|
|
||||||
format uint32
|
|
||||||
chunkHashes []string
|
|
||||||
}{
|
|
||||||
{1, []string{
|
|
||||||
"503e5b51b657055b77e88169fadae543619368744ad15f1de0736c0a20482f24",
|
|
||||||
"e1a0daaa738eeb43e778aefd2805e3dd720798288a410b06da4b8459c4d8f72e",
|
|
||||||
"aa048b4ee0f484965d7b3b06822cf0772cdcaad02f3b1b9055e69f2cb365ef3c",
|
|
||||||
"7921eaa3ed4921341e504d9308a9877986a879fe216a099c86e8db66fcba4c63",
|
|
||||||
"a4a864e6c02c9fca5837ec80dc84f650b25276ed7e4820cf7516ced9f9901b86",
|
|
||||||
"ca2879ac6e7205d257440131ba7e72bef784cd61642e32b847729e543c1928b9",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, tc := range testcases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(fmt.Sprintf("Format %v", tc.format), func(t *testing.T) {
|
|
||||||
chunks, err := store.Snapshot(version, tc.format)
|
|
||||||
require.NoError(t, err)
|
|
||||||
hashes := []string{}
|
|
||||||
hasher := sha256.New()
|
|
||||||
for chunk := range chunks {
|
|
||||||
hasher.Reset()
|
|
||||||
_, err := io.Copy(hasher, chunk)
|
|
||||||
require.NoError(t, err)
|
|
||||||
hashes = append(hashes, hex.EncodeToString(hasher.Sum(nil)))
|
|
||||||
}
|
|
||||||
assert.Equal(t, tc.chunkHashes, hashes,
|
|
||||||
"Snapshot output for format %v has changed", tc.format)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultistoreSnapshot_Errors(t *testing.T) {
|
|
||||||
store := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB())
|
|
||||||
|
|
||||||
testcases := map[string]struct {
|
|
||||||
height uint64
|
|
||||||
format uint32
|
|
||||||
expectType error
|
|
||||||
}{
|
|
||||||
"0 height": {0, snapshottypes.CurrentFormat, nil},
|
|
||||||
"0 format": {1, 0, snapshottypes.ErrUnknownFormat},
|
|
||||||
"unknown height": {9, snapshottypes.CurrentFormat, nil},
|
|
||||||
"unknown format": {1, 9, snapshottypes.ErrUnknownFormat},
|
|
||||||
}
|
|
||||||
for name, tc := range testcases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
_, err := store.Snapshot(tc.height, tc.format)
|
|
||||||
require.Error(t, err)
|
|
||||||
if tc.expectType != nil {
|
|
||||||
assert.True(t, errors.Is(err, tc.expectType))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultistoreRestore_Errors(t *testing.T) {
|
|
||||||
store := newMultiStoreWithMixedMounts(dbm.NewMemDB())
|
|
||||||
|
|
||||||
testcases := map[string]struct {
|
|
||||||
height uint64
|
|
||||||
format uint32
|
|
||||||
expectType error
|
|
||||||
}{
|
|
||||||
"0 height": {0, snapshottypes.CurrentFormat, nil},
|
|
||||||
"0 format": {1, 0, snapshottypes.ErrUnknownFormat},
|
|
||||||
"unknown format": {1, 9, snapshottypes.ErrUnknownFormat},
|
|
||||||
}
|
|
||||||
for name, tc := range testcases {
|
|
||||||
tc := tc
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
err := store.Restore(tc.height, tc.format, nil, nil)
|
|
||||||
require.Error(t, err)
|
|
||||||
if tc.expectType != nil {
|
|
||||||
assert.True(t, errors.Is(err, tc.expectType))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultistoreSnapshotRestore(t *testing.T) {
|
|
||||||
source := newMultiStoreWithMixedMountsAndBasicData(dbm.NewMemDB())
|
|
||||||
target := newMultiStoreWithMixedMounts(dbm.NewMemDB())
|
|
||||||
version := uint64(source.LastCommitID().Version)
|
|
||||||
require.EqualValues(t, 3, version)
|
|
||||||
|
|
||||||
chunks, err := source.Snapshot(version, snapshottypes.CurrentFormat)
|
|
||||||
require.NoError(t, err)
|
|
||||||
ready := make(chan struct{})
|
|
||||||
err = target.Restore(version, snapshottypes.CurrentFormat, chunks, ready)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.EqualValues(t, struct{}{}, <-ready)
|
|
||||||
|
|
||||||
assert.Equal(t, source.LastCommitID(), target.LastCommitID())
|
|
||||||
for key, sourceStore := range source.stores {
|
|
||||||
targetStore := target.getStoreByName(key.Name()).(types.CommitKVStore)
|
|
||||||
switch sourceStore.GetStoreType() {
|
|
||||||
case types.StoreTypeTransient:
|
|
||||||
assert.False(t, targetStore.Iterator(nil, nil).Valid(),
|
|
||||||
"transient store %v not empty", key.Name())
|
|
||||||
default:
|
|
||||||
assertStoresEqual(t, sourceStore, targetStore, "store %q not equal", key.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetInitialVersion(t *testing.T) {
|
func TestSetInitialVersion(t *testing.T) {
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
multi := newMultiStoreWithMounts(db, types.PruneNothing)
|
multi := newMultiStoreWithMounts(db, types.PruneNothing)
|
||||||
|
@ -853,79 +730,6 @@ func TestTraceConcurrency(t *testing.T) {
|
||||||
stopW <- struct{}{}
|
stopW <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkMultistoreSnapshot100K(b *testing.B) {
|
|
||||||
benchmarkMultistoreSnapshot(b, 10, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMultistoreSnapshot1M(b *testing.B) {
|
|
||||||
benchmarkMultistoreSnapshot(b, 10, 100000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMultistoreSnapshotRestore100K(b *testing.B) {
|
|
||||||
benchmarkMultistoreSnapshotRestore(b, 10, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkMultistoreSnapshotRestore1M(b *testing.B) {
|
|
||||||
benchmarkMultistoreSnapshotRestore(b, 10, 100000)
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkMultistoreSnapshot(b *testing.B, stores uint8, storeKeys uint64) {
|
|
||||||
b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.")
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.StopTimer()
|
|
||||||
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
|
|
||||||
version := source.LastCommitID().Version
|
|
||||||
require.EqualValues(b, 1, version)
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
target := NewStore(dbm.NewMemDB())
|
|
||||||
for key := range source.stores {
|
|
||||||
target.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
|
||||||
}
|
|
||||||
err := target.LoadLatestVersion()
|
|
||||||
require.NoError(b, err)
|
|
||||||
require.EqualValues(b, 0, target.LastCommitID().Version)
|
|
||||||
|
|
||||||
chunks, err := source.Snapshot(uint64(version), snapshottypes.CurrentFormat)
|
|
||||||
require.NoError(b, err)
|
|
||||||
for reader := range chunks {
|
|
||||||
_, err := io.Copy(io.Discard, reader)
|
|
||||||
require.NoError(b, err)
|
|
||||||
err = reader.Close()
|
|
||||||
require.NoError(b, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkMultistoreSnapshotRestore(b *testing.B, stores uint8, storeKeys uint64) {
|
|
||||||
b.Skip("Noisy with slow setup time, please see https://github.com/cosmos/cosmos-sdk/issues/8855.")
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.StopTimer()
|
|
||||||
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
|
|
||||||
version := uint64(source.LastCommitID().Version)
|
|
||||||
require.EqualValues(b, 1, version)
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
target := NewStore(dbm.NewMemDB())
|
|
||||||
for key := range source.stores {
|
|
||||||
target.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
|
||||||
}
|
|
||||||
err := target.LoadLatestVersion()
|
|
||||||
require.NoError(b, err)
|
|
||||||
require.EqualValues(b, 0, target.LastCommitID().Version)
|
|
||||||
|
|
||||||
chunks, err := source.Snapshot(version, snapshottypes.CurrentFormat)
|
|
||||||
require.NoError(b, err)
|
|
||||||
err = target.Restore(version, snapshottypes.CurrentFormat, chunks, nil)
|
|
||||||
require.NoError(b, err)
|
|
||||||
require.Equal(b, source.LastCommitID(), target.LastCommitID())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------
|
//-----------------------------------------------------------------------
|
||||||
// utils
|
// utils
|
||||||
|
|
||||||
|
@ -946,75 +750,6 @@ func newMultiStoreWithMounts(db dbm.DB, pruningOpts types.PruningOptions) *Store
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMultiStoreWithMixedMounts(db dbm.DB) *Store {
|
|
||||||
store := NewStore(db)
|
|
||||||
store.MountStoreWithDB(types.NewKVStoreKey("iavl1"), types.StoreTypeIAVL, nil)
|
|
||||||
store.MountStoreWithDB(types.NewKVStoreKey("iavl2"), types.StoreTypeIAVL, nil)
|
|
||||||
store.MountStoreWithDB(types.NewKVStoreKey("iavl3"), types.StoreTypeIAVL, nil)
|
|
||||||
store.MountStoreWithDB(types.NewTransientStoreKey("trans1"), types.StoreTypeTransient, nil)
|
|
||||||
store.LoadLatestVersion()
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMultiStoreWithMixedMountsAndBasicData(db dbm.DB) *Store {
|
|
||||||
store := newMultiStoreWithMixedMounts(db)
|
|
||||||
store1 := store.getStoreByName("iavl1").(types.CommitKVStore)
|
|
||||||
store2 := store.getStoreByName("iavl2").(types.CommitKVStore)
|
|
||||||
trans1 := store.getStoreByName("trans1").(types.KVStore)
|
|
||||||
|
|
||||||
store1.Set([]byte("a"), []byte{1})
|
|
||||||
store1.Set([]byte("b"), []byte{1})
|
|
||||||
store2.Set([]byte("X"), []byte{255})
|
|
||||||
store2.Set([]byte("A"), []byte{101})
|
|
||||||
trans1.Set([]byte("x1"), []byte{91})
|
|
||||||
store.Commit()
|
|
||||||
|
|
||||||
store1.Set([]byte("b"), []byte{2})
|
|
||||||
store1.Set([]byte("c"), []byte{3})
|
|
||||||
store2.Set([]byte("B"), []byte{102})
|
|
||||||
store.Commit()
|
|
||||||
|
|
||||||
store2.Set([]byte("C"), []byte{103})
|
|
||||||
store2.Delete([]byte("X"))
|
|
||||||
trans1.Set([]byte("x2"), []byte{92})
|
|
||||||
store.Commit()
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMultiStoreWithGeneratedData(db dbm.DB, stores uint8, storeKeys uint64) *Store {
|
|
||||||
multiStore := NewStore(db)
|
|
||||||
r := rand.New(rand.NewSource(49872768940)) // Fixed seed for deterministic tests
|
|
||||||
|
|
||||||
keys := []*types.KVStoreKey{}
|
|
||||||
for i := uint8(0); i < stores; i++ {
|
|
||||||
key := types.NewKVStoreKey(fmt.Sprintf("store%v", i))
|
|
||||||
multiStore.MountStoreWithDB(key, types.StoreTypeIAVL, nil)
|
|
||||||
keys = append(keys, key)
|
|
||||||
}
|
|
||||||
multiStore.LoadLatestVersion()
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
store := multiStore.stores[key].(*iavl.Store)
|
|
||||||
for i := uint64(0); i < storeKeys; i++ {
|
|
||||||
k := make([]byte, 8)
|
|
||||||
v := make([]byte, 1024)
|
|
||||||
binary.BigEndian.PutUint64(k, i)
|
|
||||||
_, err := r.Read(v)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
store.Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiStore.Commit()
|
|
||||||
multiStore.LoadLatestVersion()
|
|
||||||
|
|
||||||
return multiStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMultiStoreWithModifiedMounts(db dbm.DB, pruningOpts types.PruningOptions) (*Store, *types.StoreUpgrades) {
|
func newMultiStoreWithModifiedMounts(db dbm.DB, pruningOpts types.PruningOptions) (*Store, *types.StoreUpgrades) {
|
||||||
store := NewStore(db)
|
store := NewStore(db)
|
||||||
store.pruningOpts = pruningOpts
|
store.pruningOpts = pruningOpts
|
||||||
|
@ -1036,25 +771,6 @@ func newMultiStoreWithModifiedMounts(db dbm.DB, pruningOpts types.PruningOptions
|
||||||
return store, upgrades
|
return store, upgrades
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertStoresEqual(t *testing.T, expect, actual types.CommitKVStore, msgAndArgs ...interface{}) {
|
|
||||||
assert.Equal(t, expect.LastCommitID(), actual.LastCommitID())
|
|
||||||
expectIter := expect.Iterator(nil, nil)
|
|
||||||
expectMap := map[string][]byte{}
|
|
||||||
for ; expectIter.Valid(); expectIter.Next() {
|
|
||||||
expectMap[string(expectIter.Key())] = expectIter.Value()
|
|
||||||
}
|
|
||||||
require.NoError(t, expectIter.Error())
|
|
||||||
|
|
||||||
actualIter := expect.Iterator(nil, nil)
|
|
||||||
actualMap := map[string][]byte{}
|
|
||||||
for ; actualIter.Valid(); actualIter.Next() {
|
|
||||||
actualMap[string(actualIter.Key())] = actualIter.Value()
|
|
||||||
}
|
|
||||||
require.NoError(t, actualIter.Error())
|
|
||||||
|
|
||||||
assert.Equal(t, expectMap, actualMap, msgAndArgs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkStore(t *testing.T, store *Store, expect, got types.CommitID) {
|
func checkStore(t *testing.T, store *Store, expect, got types.CommitID) {
|
||||||
require.Equal(t, expect, got)
|
require.Equal(t, expect, got)
|
||||||
require.Equal(t, expect, store.LastCommitID())
|
require.Equal(t, expect, store.LastCommitID())
|
||||||
|
|
|
@ -30,6 +30,8 @@ type SnapshotItem struct {
|
||||||
// Types that are valid to be assigned to Item:
|
// Types that are valid to be assigned to Item:
|
||||||
// *SnapshotItem_Store
|
// *SnapshotItem_Store
|
||||||
// *SnapshotItem_IAVL
|
// *SnapshotItem_IAVL
|
||||||
|
// *SnapshotItem_Extension
|
||||||
|
// *SnapshotItem_ExtensionPayload
|
||||||
Item isSnapshotItem_Item `protobuf_oneof:"item"`
|
Item isSnapshotItem_Item `protobuf_oneof:"item"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +80,17 @@ type SnapshotItem_Store struct {
|
||||||
type SnapshotItem_IAVL struct {
|
type SnapshotItem_IAVL struct {
|
||||||
IAVL *SnapshotIAVLItem `protobuf:"bytes,2,opt,name=iavl,proto3,oneof" json:"iavl,omitempty"`
|
IAVL *SnapshotIAVLItem `protobuf:"bytes,2,opt,name=iavl,proto3,oneof" json:"iavl,omitempty"`
|
||||||
}
|
}
|
||||||
|
type SnapshotItem_Extension struct {
|
||||||
|
Extension *SnapshotExtensionMeta `protobuf:"bytes,3,opt,name=extension,proto3,oneof" json:"extension,omitempty"`
|
||||||
|
}
|
||||||
|
type SnapshotItem_ExtensionPayload struct {
|
||||||
|
ExtensionPayload *SnapshotExtensionPayload `protobuf:"bytes,4,opt,name=extension_payload,json=extensionPayload,proto3,oneof" json:"extension_payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (*SnapshotItem_Store) isSnapshotItem_Item() {}
|
func (*SnapshotItem_Store) isSnapshotItem_Item() {}
|
||||||
func (*SnapshotItem_IAVL) isSnapshotItem_Item() {}
|
func (*SnapshotItem_IAVL) isSnapshotItem_Item() {}
|
||||||
|
func (*SnapshotItem_Extension) isSnapshotItem_Item() {}
|
||||||
|
func (*SnapshotItem_ExtensionPayload) isSnapshotItem_Item() {}
|
||||||
|
|
||||||
func (m *SnapshotItem) GetItem() isSnapshotItem_Item {
|
func (m *SnapshotItem) GetItem() isSnapshotItem_Item {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
|
@ -103,11 +113,27 @@ func (m *SnapshotItem) GetIAVL() *SnapshotIAVLItem {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotItem) GetExtension() *SnapshotExtensionMeta {
|
||||||
|
if x, ok := m.GetItem().(*SnapshotItem_Extension); ok {
|
||||||
|
return x.Extension
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotItem) GetExtensionPayload() *SnapshotExtensionPayload {
|
||||||
|
if x, ok := m.GetItem().(*SnapshotItem_ExtensionPayload); ok {
|
||||||
|
return x.ExtensionPayload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// XXX_OneofWrappers is for the internal use of the proto package.
|
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||||
func (*SnapshotItem) XXX_OneofWrappers() []interface{} {
|
func (*SnapshotItem) XXX_OneofWrappers() []interface{} {
|
||||||
return []interface{}{
|
return []interface{}{
|
||||||
(*SnapshotItem_Store)(nil),
|
(*SnapshotItem_Store)(nil),
|
||||||
(*SnapshotItem_IAVL)(nil),
|
(*SnapshotItem_IAVL)(nil),
|
||||||
|
(*SnapshotItem_Extension)(nil),
|
||||||
|
(*SnapshotItem_ExtensionPayload)(nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,10 +251,110 @@ func (m *SnapshotIAVLItem) GetHeight() int32 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SnapshotExtensionMeta contains metadata about an external snapshotter.
|
||||||
|
type SnapshotExtensionMeta struct {
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Format int32 `protobuf:"varint,2,opt,name=format,proto3" json:"format,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) Reset() { *m = SnapshotExtensionMeta{} }
|
||||||
|
func (m *SnapshotExtensionMeta) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SnapshotExtensionMeta) ProtoMessage() {}
|
||||||
|
func (*SnapshotExtensionMeta) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_9c55879db4cc4502, []int{3}
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) XXX_Unmarshal(b []byte) error {
|
||||||
|
return m.Unmarshal(b)
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
if deterministic {
|
||||||
|
return xxx_messageInfo_SnapshotExtensionMeta.Marshal(b, m, deterministic)
|
||||||
|
} else {
|
||||||
|
b = b[:cap(b)]
|
||||||
|
n, err := m.MarshalToSizedBuffer(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_SnapshotExtensionMeta.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) XXX_Size() int {
|
||||||
|
return m.Size()
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_SnapshotExtensionMeta.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_SnapshotExtensionMeta proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) GetName() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) GetFormat() int32 {
|
||||||
|
if m != nil {
|
||||||
|
return m.Format
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SnapshotExtensionPayload contains payloads of an external snapshotter.
|
||||||
|
type SnapshotExtensionPayload struct {
|
||||||
|
Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) Reset() { *m = SnapshotExtensionPayload{} }
|
||||||
|
func (m *SnapshotExtensionPayload) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*SnapshotExtensionPayload) ProtoMessage() {}
|
||||||
|
func (*SnapshotExtensionPayload) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_9c55879db4cc4502, []int{4}
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) XXX_Unmarshal(b []byte) error {
|
||||||
|
return m.Unmarshal(b)
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
if deterministic {
|
||||||
|
return xxx_messageInfo_SnapshotExtensionPayload.Marshal(b, m, deterministic)
|
||||||
|
} else {
|
||||||
|
b = b[:cap(b)]
|
||||||
|
n, err := m.MarshalToSizedBuffer(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_SnapshotExtensionPayload.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) XXX_Size() int {
|
||||||
|
return m.Size()
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_SnapshotExtensionPayload.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_SnapshotExtensionPayload proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) GetPayload() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Payload
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterType((*SnapshotItem)(nil), "cosmos.base.store.v1beta1.SnapshotItem")
|
proto.RegisterType((*SnapshotItem)(nil), "cosmos.base.store.v1beta1.SnapshotItem")
|
||||||
proto.RegisterType((*SnapshotStoreItem)(nil), "cosmos.base.store.v1beta1.SnapshotStoreItem")
|
proto.RegisterType((*SnapshotStoreItem)(nil), "cosmos.base.store.v1beta1.SnapshotStoreItem")
|
||||||
proto.RegisterType((*SnapshotIAVLItem)(nil), "cosmos.base.store.v1beta1.SnapshotIAVLItem")
|
proto.RegisterType((*SnapshotIAVLItem)(nil), "cosmos.base.store.v1beta1.SnapshotIAVLItem")
|
||||||
|
proto.RegisterType((*SnapshotExtensionMeta)(nil), "cosmos.base.store.v1beta1.SnapshotExtensionMeta")
|
||||||
|
proto.RegisterType((*SnapshotExtensionPayload)(nil), "cosmos.base.store.v1beta1.SnapshotExtensionPayload")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -236,28 +362,33 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptor_9c55879db4cc4502 = []byte{
|
var fileDescriptor_9c55879db4cc4502 = []byte{
|
||||||
// 324 bytes of a gzipped FileDescriptorProto
|
// 409 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0xc1, 0x4a, 0xc3, 0x30,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xc1, 0xae, 0x93, 0x40,
|
||||||
0x18, 0xc7, 0x1b, 0xd7, 0x4d, 0xfd, 0xdc, 0x61, 0x86, 0x21, 0xd5, 0x43, 0x1d, 0xbb, 0x58, 0x50,
|
0x14, 0x86, 0xe1, 0x16, 0xaa, 0xf7, 0xd8, 0x45, 0xef, 0xe4, 0x6a, 0xd0, 0x05, 0x1a, 0x36, 0x36,
|
||||||
0x13, 0xa6, 0x4f, 0x60, 0xf1, 0xb0, 0xa1, 0xa7, 0x0c, 0x3c, 0x78, 0x4b, 0x67, 0x68, 0xcb, 0xd6,
|
0x51, 0xc1, 0xeb, 0xf5, 0x05, 0x44, 0x4d, 0xb8, 0x51, 0x93, 0x66, 0x9a, 0xb8, 0x70, 0x63, 0x86,
|
||||||
0x65, 0x2c, 0x59, 0x61, 0x6f, 0xe1, 0x6b, 0xf8, 0x26, 0x1e, 0x77, 0xf4, 0x24, 0xd2, 0xbd, 0x88,
|
0x76, 0x04, 0x52, 0x60, 0x08, 0x33, 0x25, 0xf6, 0x2d, 0x7c, 0x2c, 0x97, 0x5d, 0xba, 0x52, 0x43,
|
||||||
0x24, 0xe9, 0x2e, 0x8a, 0xe0, 0xa9, 0xdf, 0xbf, 0xfc, 0xfe, 0xbf, 0x7c, 0xf0, 0x41, 0x34, 0x91,
|
0x5f, 0xc4, 0xcc, 0x0c, 0xd4, 0xa4, 0xb6, 0x49, 0x5d, 0xf5, 0xfc, 0x93, 0xff, 0xff, 0x7a, 0xf8,
|
||||||
0xaa, 0x90, 0x8a, 0x26, 0x5c, 0x09, 0xaa, 0xb4, 0x5c, 0x0a, 0x5a, 0x0e, 0x12, 0xa1, 0xf9, 0x80,
|
0x73, 0x60, 0x32, 0x67, 0xbc, 0x60, 0x3c, 0x88, 0x09, 0xa7, 0x01, 0x17, 0xac, 0xa6, 0x41, 0x73,
|
||||||
0xaa, 0x39, 0x5f, 0xa8, 0x4c, 0x6a, 0xb2, 0x58, 0x4a, 0x2d, 0xf1, 0xa9, 0x23, 0x89, 0x21, 0x89,
|
0x15, 0x53, 0x41, 0xae, 0x02, 0x5e, 0x92, 0x8a, 0xa7, 0x4c, 0xf8, 0x55, 0xcd, 0x04, 0x43, 0xf7,
|
||||||
0x25, 0x49, 0x4d, 0x9e, 0x75, 0x53, 0x99, 0x4a, 0x4b, 0x51, 0x33, 0xb9, 0x42, 0xff, 0x0d, 0x41,
|
0xb5, 0xd3, 0x97, 0x4e, 0x5f, 0x39, 0xfd, 0xce, 0xf9, 0xe0, 0x32, 0x61, 0x09, 0x53, 0xae, 0x40,
|
||||||
0x7b, 0x5c, 0x3b, 0x46, 0x5a, 0x14, 0xf8, 0x1e, 0x9a, 0xb6, 0x17, 0xa0, 0x1e, 0x8a, 0x8e, 0x6e,
|
0x4e, 0x3a, 0xe0, 0xfd, 0x3a, 0x83, 0xd1, 0xac, 0x63, 0xdc, 0x08, 0x5a, 0xa0, 0x37, 0x60, 0xab,
|
||||||
0xae, 0xc8, 0x9f, 0x46, 0xb2, 0xeb, 0x8d, 0xcd, 0x5f, 0x53, 0x1e, 0x7a, 0xcc, 0x95, 0xf1, 0x03,
|
0x9c, 0x63, 0x3e, 0x32, 0x27, 0x77, 0x5e, 0x3c, 0xf5, 0x8f, 0x12, 0xfd, 0x3e, 0x37, 0x93, 0xaf,
|
||||||
0xf8, 0x39, 0x2f, 0x67, 0xc1, 0x9e, 0x95, 0x5c, 0xfe, 0x43, 0x32, 0xba, 0x7b, 0x7a, 0x34, 0x8e,
|
0x32, 0x1c, 0x19, 0x58, 0x87, 0xd1, 0x3b, 0xb0, 0x32, 0xd2, 0xe4, 0xce, 0x99, 0x82, 0x3c, 0x39,
|
||||||
0xf8, 0xa0, 0xfa, 0x3c, 0xf7, 0x4d, 0x1a, 0x7a, 0xcc, 0x4a, 0xe2, 0x16, 0xf8, 0xb9, 0x16, 0x45,
|
0x01, 0x72, 0xf3, 0xea, 0xe3, 0x7b, 0xc9, 0x08, 0x6f, 0xb7, 0x3f, 0x1f, 0x5a, 0x52, 0x45, 0x06,
|
||||||
0xff, 0x02, 0x8e, 0x7f, 0x3d, 0x89, 0x31, 0xf8, 0x73, 0x5e, 0xb8, 0x75, 0x0f, 0x99, 0x9d, 0xfb,
|
0x56, 0x10, 0x34, 0x85, 0x73, 0xfa, 0x55, 0xd0, 0x92, 0x67, 0xac, 0x74, 0x06, 0x8a, 0xf8, 0xfc,
|
||||||
0x33, 0xe8, 0xfc, 0xd4, 0xe2, 0x0e, 0x34, 0xa6, 0x62, 0x6d, 0xb1, 0x36, 0x33, 0x23, 0xee, 0x42,
|
0x04, 0xe2, 0xdb, 0x3e, 0xf3, 0x81, 0x0a, 0x12, 0x19, 0xf8, 0x2f, 0x04, 0xc5, 0x70, 0xb1, 0x13,
|
||||||
0xb3, 0xe4, 0xb3, 0x95, 0xb0, 0x4b, 0xb6, 0x99, 0x0b, 0x38, 0x80, 0xfd, 0x52, 0x2c, 0x55, 0x2e,
|
0x9f, 0x2b, 0xb2, 0xce, 0x19, 0x59, 0x38, 0x96, 0x22, 0x5f, 0xff, 0x0f, 0x79, 0xaa, 0xa3, 0x91,
|
||||||
0xe7, 0x41, 0xa3, 0x87, 0xa2, 0x06, 0xdb, 0x45, 0x7c, 0x02, 0xad, 0x4c, 0xe4, 0x69, 0xa6, 0x03,
|
0x81, 0xc7, 0x74, 0xef, 0x2d, 0x1c, 0x82, 0x95, 0x09, 0x5a, 0x78, 0x8f, 0xe1, 0xe2, 0x9f, 0xa2,
|
||||||
0xbf, 0x87, 0xa2, 0x26, 0xab, 0x53, 0x1c, 0xbf, 0x57, 0x21, 0xda, 0x54, 0x21, 0xfa, 0xaa, 0x42,
|
0x10, 0x02, 0xab, 0x24, 0x85, 0x2e, 0xf9, 0x1c, 0xab, 0xd9, 0xcb, 0x61, 0xbc, 0x5f, 0x06, 0x1a,
|
||||||
0xf4, 0xba, 0x0d, 0xbd, 0xcd, 0x36, 0xf4, 0x3e, 0xb6, 0xa1, 0xf7, 0x1c, 0xa5, 0xb9, 0xce, 0x56,
|
0xc3, 0x60, 0x49, 0xd7, 0xca, 0x36, 0xc2, 0x72, 0x44, 0x97, 0x60, 0x37, 0x24, 0x5f, 0x51, 0x55,
|
||||||
0x09, 0x99, 0xc8, 0x82, 0xd6, 0x27, 0x74, 0x9f, 0x6b, 0xf5, 0x32, 0xad, 0x0f, 0xa9, 0xd7, 0x0b,
|
0xed, 0x08, 0x6b, 0x81, 0x1c, 0xb8, 0xd5, 0xd0, 0x7a, 0x57, 0xd0, 0x00, 0xf7, 0x12, 0xdd, 0x83,
|
||||||
0xa1, 0x92, 0x96, 0xbd, 0xc6, 0xed, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x75, 0x87, 0x24, 0x7b,
|
0x61, 0x4a, 0xb3, 0x24, 0x15, 0xea, 0xfb, 0x6c, 0xdc, 0x29, 0xef, 0x35, 0xdc, 0x3d, 0x58, 0xd4,
|
||||||
0xea, 0x01, 0x00, 0x00,
|
0xa1, 0xd5, 0x24, 0xe4, 0x0b, 0xab, 0x0b, 0x22, 0xd4, 0xbf, 0xda, 0xb8, 0x53, 0xde, 0x4b, 0x70,
|
||||||
|
0x8e, 0x75, 0x22, 0x57, 0xea, 0x9b, 0xd5, 0xeb, 0xf7, 0x32, 0x0c, 0xbf, 0xb7, 0xae, 0xb9, 0x69,
|
||||||
|
0x5d, 0xf3, 0x77, 0xeb, 0x9a, 0xdf, 0xb6, 0xae, 0xb1, 0xd9, 0xba, 0xc6, 0x8f, 0xad, 0x6b, 0x7c,
|
||||||
|
0x9a, 0x24, 0x99, 0x48, 0x57, 0xb1, 0x3f, 0x67, 0x45, 0xd0, 0xdd, 0xbc, 0xfe, 0x79, 0xc6, 0x17,
|
||||||
|
0xcb, 0xee, 0xf2, 0xc5, 0xba, 0xa2, 0x3c, 0x1e, 0xaa, 0xf3, 0xbd, 0xfe, 0x13, 0x00, 0x00, 0xff,
|
||||||
|
0xff, 0x21, 0xfd, 0xa5, 0x3f, 0x1b, 0x03, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *SnapshotItem) Marshal() (dAtA []byte, err error) {
|
func (m *SnapshotItem) Marshal() (dAtA []byte, err error) {
|
||||||
|
@ -334,6 +465,48 @@ func (m *SnapshotItem_IAVL) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
}
|
}
|
||||||
return len(dAtA) - i, nil
|
return len(dAtA) - i, nil
|
||||||
}
|
}
|
||||||
|
func (m *SnapshotItem_Extension) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotItem_Extension) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
if m.Extension != nil {
|
||||||
|
{
|
||||||
|
size, err := m.Extension.MarshalToSizedBuffer(dAtA[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i -= size
|
||||||
|
i = encodeVarintSnapshot(dAtA, i, uint64(size))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x1a
|
||||||
|
}
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
func (m *SnapshotItem_ExtensionPayload) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotItem_ExtensionPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
if m.ExtensionPayload != nil {
|
||||||
|
{
|
||||||
|
size, err := m.ExtensionPayload.MarshalToSizedBuffer(dAtA[:i])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
i -= size
|
||||||
|
i = encodeVarintSnapshot(dAtA, i, uint64(size))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x22
|
||||||
|
}
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
func (m *SnapshotStoreItem) Marshal() (dAtA []byte, err error) {
|
func (m *SnapshotStoreItem) Marshal() (dAtA []byte, err error) {
|
||||||
size := m.Size()
|
size := m.Size()
|
||||||
dAtA = make([]byte, size)
|
dAtA = make([]byte, size)
|
||||||
|
@ -411,6 +584,71 @@ func (m *SnapshotIAVLItem) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
return len(dAtA) - i, nil
|
return len(dAtA) - i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if m.Format != 0 {
|
||||||
|
i = encodeVarintSnapshot(dAtA, i, uint64(m.Format))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0x10
|
||||||
|
}
|
||||||
|
if len(m.Name) > 0 {
|
||||||
|
i -= len(m.Name)
|
||||||
|
copy(dAtA[i:], m.Name)
|
||||||
|
i = encodeVarintSnapshot(dAtA, i, uint64(len(m.Name)))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0xa
|
||||||
|
}
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
size := m.Size()
|
||||||
|
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||||
|
i := len(dAtA)
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if len(m.Payload) > 0 {
|
||||||
|
i -= len(m.Payload)
|
||||||
|
copy(dAtA[i:], m.Payload)
|
||||||
|
i = encodeVarintSnapshot(dAtA, i, uint64(len(m.Payload)))
|
||||||
|
i--
|
||||||
|
dAtA[i] = 0xa
|
||||||
|
}
|
||||||
|
return len(dAtA) - i, nil
|
||||||
|
}
|
||||||
|
|
||||||
func encodeVarintSnapshot(dAtA []byte, offset int, v uint64) int {
|
func encodeVarintSnapshot(dAtA []byte, offset int, v uint64) int {
|
||||||
offset -= sovSnapshot(v)
|
offset -= sovSnapshot(v)
|
||||||
base := offset
|
base := offset
|
||||||
|
@ -458,6 +696,30 @@ func (m *SnapshotItem_IAVL) Size() (n int) {
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
func (m *SnapshotItem_Extension) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if m.Extension != nil {
|
||||||
|
l = m.Extension.Size()
|
||||||
|
n += 1 + l + sovSnapshot(uint64(l))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
func (m *SnapshotItem_ExtensionPayload) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if m.ExtensionPayload != nil {
|
||||||
|
l = m.ExtensionPayload.Size()
|
||||||
|
n += 1 + l + sovSnapshot(uint64(l))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
func (m *SnapshotStoreItem) Size() (n int) {
|
func (m *SnapshotStoreItem) Size() (n int) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return 0
|
return 0
|
||||||
|
@ -494,6 +756,35 @@ func (m *SnapshotIAVLItem) Size() (n int) {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionMeta) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
l = len(m.Name)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovSnapshot(uint64(l))
|
||||||
|
}
|
||||||
|
if m.Format != 0 {
|
||||||
|
n += 1 + sovSnapshot(uint64(m.Format))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *SnapshotExtensionPayload) Size() (n int) {
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
l = len(m.Payload)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovSnapshot(uint64(l))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
func sovSnapshot(x uint64) (n int) {
|
func sovSnapshot(x uint64) (n int) {
|
||||||
return (math_bits.Len64(x|1) + 6) / 7
|
return (math_bits.Len64(x|1) + 6) / 7
|
||||||
}
|
}
|
||||||
|
@ -599,6 +890,76 @@ func (m *SnapshotItem) Unmarshal(dAtA []byte) error {
|
||||||
}
|
}
|
||||||
m.Item = &SnapshotItem_IAVL{v}
|
m.Item = &SnapshotItem_IAVL{v}
|
||||||
iNdEx = postIndex
|
iNdEx = postIndex
|
||||||
|
case 3:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Extension", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
v := &SnapshotExtensionMeta{}
|
||||||
|
if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Item = &SnapshotItem_Extension{v}
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 4:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field ExtensionPayload", wireType)
|
||||||
|
}
|
||||||
|
var msglen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
msglen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if msglen < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + msglen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
v := &SnapshotExtensionPayload{}
|
||||||
|
if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.Item = &SnapshotItem_ExtensionPayload{v}
|
||||||
|
iNdEx = postIndex
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipSnapshot(dAtA[iNdEx:])
|
skippy, err := skipSnapshot(dAtA[iNdEx:])
|
||||||
|
@ -858,6 +1219,191 @@ func (m *SnapshotIAVLItem) Unmarshal(dAtA []byte) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (m *SnapshotExtensionMeta) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: SnapshotExtensionMeta: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: SnapshotExtensionMeta: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
case 1:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
|
||||||
|
}
|
||||||
|
var stringLen uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
stringLen |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
intStringLen := int(stringLen)
|
||||||
|
if intStringLen < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + intStringLen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.Name = string(dAtA[iNdEx:postIndex])
|
||||||
|
iNdEx = postIndex
|
||||||
|
case 2:
|
||||||
|
if wireType != 0 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Format", wireType)
|
||||||
|
}
|
||||||
|
m.Format = 0
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
m.Format |= int32(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipSnapshot(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *SnapshotExtensionPayload) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= uint64(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: SnapshotExtensionPayload: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: SnapshotExtensionPayload: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
case 1:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType)
|
||||||
|
}
|
||||||
|
var byteLen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowSnapshot
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
byteLen |= int(b&0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if byteLen < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + byteLen
|
||||||
|
if postIndex < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...)
|
||||||
|
if m.Payload == nil {
|
||||||
|
m.Payload = []byte{}
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipSnapshot(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||||
|
return ErrInvalidLengthSnapshot
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func skipSnapshot(dAtA []byte) (n int, err error) {
|
func skipSnapshot(dAtA []byte) (n int, err error) {
|
||||||
l := len(dAtA)
|
l := len(dAtA)
|
||||||
iNdEx := 0
|
iNdEx := 0
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
dbm "github.com/cosmos/cosmos-sdk/db"
|
dbm "github.com/cosmos/cosmos-sdk/db"
|
||||||
prefixdb "github.com/cosmos/cosmos-sdk/db/prefix"
|
prefixdb "github.com/cosmos/cosmos-sdk/db/prefix"
|
||||||
util "github.com/cosmos/cosmos-sdk/internal"
|
util "github.com/cosmos/cosmos-sdk/internal"
|
||||||
|
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
|
||||||
sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
|
sdkmaps "github.com/cosmos/cosmos-sdk/store/internal/maps"
|
||||||
"github.com/cosmos/cosmos-sdk/store/listenkv"
|
"github.com/cosmos/cosmos-sdk/store/listenkv"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
|
@ -23,6 +24,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/store/v2/transient"
|
"github.com/cosmos/cosmos-sdk/store/v2/transient"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
"github.com/cosmos/cosmos-sdk/types/kv"
|
"github.com/cosmos/cosmos-sdk/types/kv"
|
||||||
|
protoio "github.com/gogo/protobuf/io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -901,9 +903,11 @@ func (tlm *traceListenMixin) wrapTraceListen(store types.KVStore, skey types.Sto
|
||||||
func (s *Store) GetPruning() types.PruningOptions { return s.Pruning }
|
func (s *Store) GetPruning() types.PruningOptions { return s.Pruning }
|
||||||
func (s *Store) SetPruning(po types.PruningOptions) { s.Pruning = po }
|
func (s *Store) SetPruning(po types.PruningOptions) { s.Pruning = po }
|
||||||
|
|
||||||
func (rs *Store) Restore(height uint64, format uint32, chunks <-chan io.ReadCloser, ready chan<- struct{}) error {
|
func (rs *Store) Restore(
|
||||||
|
height uint64, format uint32, protoReader protoio.Reader,
|
||||||
|
) (snapshottypes.SnapshotItem, error) {
|
||||||
|
return snapshottypes.SnapshotItem{}, nil
|
||||||
|
}
|
||||||
|
func (rs *Store) Snapshot(height uint64, protoWriter protoio.Writer) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (rs *Store) Snapshot(height uint64, format uint32) (<-chan io.ReadCloser, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue