Files
wasmd/x/wasm/keeper/snapshotter.go

173 lines
4.9 KiB
Go

package keeper
import (
"encoding/hex"
"io"
snapshot "github.com/cosmos/cosmos-sdk/snapshots/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
protoio "github.com/gogo/protobuf/io"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/CosmWasm/wasmd/x/wasm/ioutils"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
/*
API to implement:
// 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
// given, it must return ErrUnknownFormat (possibly wrapped with fmt.Errorf).
type Snapshotter interface {
// Snapshot writes snapshot items into the protobuf writer.
Snapshot(height uint64, protoWriter protoio.Writer) error
// 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
// restorer is ready to accept chunks.
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
}
*/
type WasmSnapshotter struct {
wasm Keeper
cms sdk.CommitMultiStore
}
func NewWasmSnapshotter(cms sdk.CommitMultiStore, wasm Keeper) *WasmSnapshotter {
return &WasmSnapshotter{
wasm: wasm,
cms: cms,
}
}
func (ws *WasmSnapshotter) SnapshotName() string {
return "WASM Code Snapshot"
}
func (ws *WasmSnapshotter) SnapshotFormat() uint32 {
return 1
}
func (ws *WasmSnapshotter) SupportedFormats() []uint32 {
return []uint32{1}
}
func (ws *WasmSnapshotter) Snapshot(height uint64, protoWriter protoio.Writer) error {
var rerr error
ctx := sdk.NewContext(ws.cms, tmproto.Header{}, false, log.NewNopLogger())
seen := make(map[string]bool)
ws.wasm.IterateCodeInfos(ctx, func(id uint64, info types.CodeInfo) bool {
// Many code ids may point to the same code hash... only sync it once
hexHash := hex.EncodeToString(info.CodeHash)
// if seen, just skip this one and move to the next
if seen[hexHash] {
return false
}
seen[hexHash] = true
// load code and abort on error
wasmBytes, err := ws.wasm.GetByteCode(ctx, id)
if err != nil {
rerr = err
return true
}
compressedWasm, err := ioutils.GzipIt(wasmBytes)
if err != nil {
rerr = err
return true
}
err = snapshot.WriteExtensionItem(protoWriter, compressedWasm)
if err != nil {
rerr = err
return true
}
return false
})
return rerr
}
func (ws *WasmSnapshotter) Restore(
height uint64, format uint32, protoReader protoio.Reader,
) (snapshot.SnapshotItem, error) {
if format == 1 {
return ws.processAllItems(height, protoReader, restoreV1, finalizeV1)
}
return snapshot.SnapshotItem{}, snapshot.ErrUnknownFormat
}
func restoreV1(ctx sdk.Context, k Keeper, compressedCode []byte) error {
wasmCode, err := ioutils.Uncompress(compressedCode, k.GetMaxWasmCodeSize(ctx))
if err != nil {
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
}
// FIXME: check which codeIDs the checksum matches??
_, err = k.wasmVM.Create(wasmCode)
if err != nil {
return sdkerrors.Wrap(types.ErrCreateFailed, err.Error())
}
return nil
}
func finalizeV1(ctx sdk.Context, k Keeper) error {
// FIXME: ensure all codes have been uploaded?
return k.InitializePinnedCodes(ctx)
}
func (ws *WasmSnapshotter) processAllItems(
height uint64,
protoReader protoio.Reader,
cb func(sdk.Context, Keeper, []byte) error,
finalize func(sdk.Context, Keeper) error,
) (snapshot.SnapshotItem, error) {
ctx := sdk.NewContext(ws.cms, tmproto.Header{}, false, log.NewNopLogger())
for {
item := snapshot.SnapshotItem{}
err := protoReader.ReadMsg(&item)
if err == io.EOF {
return snapshot.SnapshotItem{}, finalize(ctx, ws.wasm)
} else if err != nil {
return snapshot.SnapshotItem{}, sdkerrors.Wrap(err, "invalid protobuf message")
}
// if it is not another ExtensionPayload message, then it is not for us.
// we should return it an let the manager handle this one
payload := item.GetExtensionPayload()
if payload == nil {
return item, nil
}
if err := cb(ctx, ws.wasm, payload.Payload); err != nil {
return snapshot.SnapshotItem{}, sdkerrors.Wrap(err, "processing snapshot item")
}
}
}