Skip to content

Commit

Permalink
feat!: add migrator to the commit abci call (#387)
Browse files Browse the repository at this point in the history
It introduces a migrator struct to BaseApp which allows the application to handle adding and removing stores as well as performing module migrations to any store.

This needs to be done in Commit as we need to first write the changes on the current deliver tx branch of state, add and remove the stores from the MultiCommitStore and then create a new branch of state to perform the migrations on. Only once they are completed and written can we calculate the new app hash
  • Loading branch information
cmwaters authored Apr 16, 2024
1 parent 02de314 commit 97a9059
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 0 deletions.
30 changes: 30 additions & 0 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,36 @@ func (app *BaseApp) Commit() abci.ResponseCommit {
// The write to the DeliverTx state writes all state transitions to the root
// MultiStore (app.cms) so when Commit() is called is persists those values.
app.deliverState.ms.Write()

// Check if there has been an app version change. If so and there is a migrator
// set, begin to run migrations. This needs to be done before the commit so
// that the migrations are part of the app hash
if header.Version.App < app.appVersion &&
app.migrator.storeMigrator != nil &&
app.migrator.moduleMigrator != nil {

// first update the stores themselves by adding and removing them as necessary
storeMigrations, err := app.migrator.storeMigrator(header.Version.App, app.appVersion)
if err != nil {
panic(fmt.Sprintf("failed to get store migrations: %v", err))
}
app.MountKVStores(storeMigrations.Added)
err = app.cms.LoadLatestVersionAndUpgrade(storeMigrations.ToStoreUpgrades())
if err != nil {
panic(fmt.Sprintf("failed to upgrade stores: %v", err))
}

// create a new cached branch of the store to apply migrations to
app.setDeliverState(header)
err = app.migrator.moduleMigrator(app.deliverState.ctx, header.Version.App, app.appVersion)
if err != nil {
panic(fmt.Sprintf("failed to migrate modules: %v", err))
}

// write the new state to the branch
app.deliverState.ms.Write()
}

commitID := app.cms.Commit()

res := abci.ResponseCommit{
Expand Down
21 changes: 21 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ type (
// an older version of the software. In particular, if a module changed the substore key name
// (or removed a substore) between two versions of the software.
StoreLoader func(ms sdk.CommitMultiStore) error

// MigrateModuleFn gets called when there is a change in the app version. It is
// triggered in the Commit stage after all state transitions have occurred.
MigrateModuleFn func(ctx sdk.Context, fromVersion, toVersion uint64) error

// MigrateStoreFn gets called when there is a change in the app version. It is
// triggered in the Commit stage after all state transitions have occurred.
MigrateStoreFn func(fromVersion, toVersion uint64) (StoreMigrations, error)

// StoreMigrations is a type that contains the added and removed stores for a
// migration.
StoreMigrations struct {
Added map[string]*storetypes.KVStoreKey
Deleted map[string]*storetypes.KVStoreKey
}
)

// BaseApp reflects the ABCI application implementation.
Expand All @@ -57,6 +72,7 @@ type BaseApp struct { // nolint: maligned
snapshotData
abciData
moduleRouter
migrator

// volatile states:
//
Expand Down Expand Up @@ -135,6 +151,11 @@ type appStore struct {
fauxMerkleMode bool // if true, IAVL MountStores uses MountStoresDB for simulation speed.
}

type migrator struct {
moduleMigrator MigrateModuleFn
storeMigrator MigrateStoreFn
}

type moduleRouter struct {
router sdk.Router // handle any kind of message
queryRouter sdk.QueryRouter // router for redirecting query calls
Expand Down
39 changes: 39 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baseapp
import (
"fmt"
"io"
"sort"

tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
Expand All @@ -12,6 +13,7 @@ import (
"github.com/cosmos/cosmos-sdk/snapshots"
snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types"
"github.com/cosmos/cosmos-sdk/store"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -160,6 +162,20 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
app.endBlocker = endBlocker
}

func (app *BaseApp) SetMigrateModuleFn(migrator MigrateModuleFn) {
if app.sealed {
panic("cannot set migrate module fn: baseapp is sealed")
}
app.migrator.moduleMigrator = migrator
}

func (app *BaseApp) SetMigrateStoreFn(migrator MigrateStoreFn) {
if app.sealed {
panic("cannot set migrate store fn: baseapp is sealed")
}
app.migrator.storeMigrator = migrator
}

func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
if app.sealed {
panic("SetAnteHandler() on sealed BaseApp")
Expand Down Expand Up @@ -263,3 +279,26 @@ func (app *BaseApp) SetQueryMultiStore(ms sdk.MultiStore) {
}
app.qms = ms
}

// ToStoreUpgrades converts the StoreMigrations to StoreUpgrades.
func (sm StoreMigrations) ToStoreUpgrades() *storetypes.StoreUpgrades {
added := make([]string, len(sm.Added))
deleted := make([]string, len(sm.Deleted))
i := 0
for name := range sm.Added {
added[i] = name
i++
}
i = 0
for name := range sm.Deleted {
deleted[i] = name
i++
}
// sort them to ensure deterministic order
sort.Strings(added)
sort.Strings(deleted)
return &storetypes.StoreUpgrades{
Added: added,
Deleted: deleted,
}
}

0 comments on commit 97a9059

Please sign in to comment.