Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add migration script to prune wasm codes #1542

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions x/wasm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ func (k Keeper) storeCodeInfo(ctx sdk.Context, codeID uint64, codeInfo types.Cod
store.Set(types.GetCodeKey(codeID), k.cdc.MustMarshal(&codeInfo))
}

func (k Keeper) deleteCodeInfo(ctx sdk.Context, codeID uint64) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetCodeKey(codeID))
}

func (k Keeper) importCode(ctx sdk.Context, codeID uint64, codeInfo types.CodeInfo, wasmCode []byte) error {
if ioutils.IsGzip(wasmCode) {
var err error
Expand Down Expand Up @@ -1226,3 +1231,80 @@ func (h DefaultWasmVMContractResponseHandler) Handle(ctx sdk.Context, contractAd
}
return result, nil
}

// GetByteCodeByChecksum queries the wasm code by checksum
func (k Keeper) GetByteCodeByChecksum(ctx sdk.Context, checksum wasmvm.Checksum) ([]byte, error) {
return k.wasmVM.GetCode(checksum)
}

// iteratePinnedCodes iterates over all pinned code ids in ascending order
func (k Keeper) iteratePinnedCodes(ctx sdk.Context, cb func(codeID uint64) bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PinnedCodeIndexPrefix)
iter := store.Iterator(nil, nil)
defer iter.Close()

for ; iter.Valid(); iter.Next() {
codeID := sdk.BigEndianToUint64(iter.Key())
if cb(codeID) {
return
}
}
}

// PruneWasmCodes deletes code info for unpinned codes <= than maxCodeID
func (k Keeper) PruneWasmCodes(ctx sdk.Context, maxCodeID uint64) error {
usedCodeIDs := make(map[uint64]struct{})
usedChecksums := make(map[string]struct{})

// collect code ids used by contracts
k.IterateContractInfo(ctx, func(_ sdk.AccAddress, info types.ContractInfo) bool {
usedCodeIDs[info.CodeID] = struct{}{}
return false
})

// collect pinned code ids
k.iteratePinnedCodes(ctx, func(codeID uint64) bool {
usedCodeIDs[codeID] = struct{}{}
return false
})

// check if instances are used only by unpinned code ids <= maxCodeID
k.IterateCodeInfos(ctx, func(codeID uint64, info types.CodeInfo) bool {
if codeID > maxCodeID { // keep all
usedChecksums[string(info.CodeHash)] = struct{}{}
return false
}
if _, ok := usedCodeIDs[codeID]; ok {
usedChecksums[string(info.CodeHash)] = struct{}{}
}
return false
})

var (
deletedCodeInfoCounter int
deletedWasmFileCounter int
)
// delete all unpinned code ids <= maxCodeID and all the
// instances used only by unpinned code ids <= maxCodeID
k.IterateCodeInfos(ctx, func(codeID uint64, info types.CodeInfo) bool {
if codeID > maxCodeID {
return true
}
if _, ok := usedCodeIDs[codeID]; !ok {
k.deleteCodeInfo(ctx, codeID)
deletedCodeInfoCounter++
if _, ok := usedChecksums[string(info.CodeHash)]; !ok {
if err := k.wasmVM.RemoveCode(info.CodeHash); err != nil {
k.Logger(ctx).Error("failed to delete wasm file on disk", "checksum", info.CodeHash)
} else {
deletedWasmFileCounter++
}
}
}
return false
})
k.Logger(ctx).Info("executed prune wasm code",
"total wasm files", deletedWasmFileCounter, "total code infos", deletedCodeInfoCounter)

return nil
}
7 changes: 7 additions & 0 deletions x/wasm/keeper/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
v1 "github.com/CosmWasm/wasmd/x/wasm/migrations/v1"
v2 "github.com/CosmWasm/wasmd/x/wasm/migrations/v2"
v3 "github.com/CosmWasm/wasmd/x/wasm/migrations/v3"
v4 "github.com/CosmWasm/wasmd/x/wasm/migrations/v4"
)

// Migrator is a struct for handling in-place store migrations.
Expand Down Expand Up @@ -36,3 +37,9 @@ func (m Migrator) Migrate2to3(ctx sdk.Context) error {
func (m Migrator) Migrate3to4(ctx sdk.Context) error {
return v3.NewMigrator(m.keeper, m.keeper.storeCodeInfo).Migrate3to4(ctx, m.keeper.storeKey, m.keeper.cdc)
}

// Migrate4to5 migrates the x/wasm module state from the consensus
// version 4 to version 5.
func (m Migrator) Migrate4to5(ctx sdk.Context) error {
return v4.NewMigrator(m.keeper).Migrate4to5(ctx)
}
8 changes: 8 additions & 0 deletions x/wasm/keeper/wasmtesting/mock_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type MockWasmer struct {
IBCPacketTimeoutFn func(codeID wasmvm.Checksum, env wasmvmtypes.Env, msg wasmvmtypes.IBCPacketTimeoutMsg, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) (*wasmvmtypes.IBCBasicResponse, uint64, error)
PinFn func(checksum wasmvm.Checksum) error
UnpinFn func(checksum wasmvm.Checksum) error
RemoveCodeFn func(checksum wasmvm.Checksum) error
GetMetricsFn func() (*wasmvmtypes.Metrics, error)
}

Expand Down Expand Up @@ -177,6 +178,13 @@ func (m *MockWasmer) Unpin(checksum wasmvm.Checksum) error {
return m.UnpinFn(checksum)
}

func (m *MockWasmer) RemoveCode(checksum wasmvm.Checksum) error {
if m.RemoveCodeFn == nil {
panic("not supposed to be called!")
}
return m.RemoveCodeFn(checksum)
}

func (m *MockWasmer) GetMetrics() (*wasmvmtypes.Metrics, error) {
if m.GetMetricsFn == nil {
panic("not expected to be called")
Expand Down
27 changes: 27 additions & 0 deletions x/wasm/migrations/v4/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package v4

import (
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Keeper abstract keeper
type wasmKeeper interface {
PruneWasmCodes(ctx sdk.Context, maxCodeID uint64) error
}

// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper wasmKeeper
}

// NewMigrator returns a new Migrator.
func NewMigrator(k wasmKeeper) Migrator {
return Migrator{keeper: k}
}

// Migrate4to5 migrates from version 4 to 5.
func (m Migrator) Migrate4to5(ctx sdk.Context) error {
return m.keeper.PruneWasmCodes(ctx, math.MaxUint64)
}
9 changes: 9 additions & 0 deletions x/wasm/migrations/v4/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package v4_test

import (
"testing"
)

func TestMigrate4To5(t *testing.T) {
t.Skip("TODO")
}
3 changes: 3 additions & 0 deletions x/wasm/types/wasmer_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ type WasmerEngine interface {
// Unpin is idempotent.
Unpin(checksum wasmvm.Checksum) error

// RemoveCode removes the wasm code referenced by checksum.
RemoveCode(checksum wasmvm.Checksum) error

// GetMetrics some internal metrics for monitoring purposes.
GetMetrics() (*wasmvmtypes.Metrics, error)
}
Expand Down