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

feat!: allow for hardcoded upgrade schedules #2583

Merged
merged 16 commits into from
Oct 10, 2023
Merged
37 changes: 26 additions & 11 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/celestiaorg/celestia-app/x/mint"
mintkeeper "github.com/celestiaorg/celestia-app/x/mint/keeper"
minttypes "github.com/celestiaorg/celestia-app/x/mint/types"
"github.com/celestiaorg/celestia-app/x/upgrade"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
nodeservice "github.com/cosmos/cosmos-sdk/client/grpc/node"
Expand Down Expand Up @@ -66,8 +67,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
sdkupgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
sdkupgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
"github.com/cosmos/ibc-go/v6/modules/apps/transfer"
ibctransferkeeper "github.com/cosmos/ibc-go/v6/modules/apps/transfer/keeper"
ibctransfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
Expand All @@ -86,13 +85,14 @@ import (
"github.com/celestiaorg/celestia-app/app/ante"
"github.com/celestiaorg/celestia-app/app/encoding"
"github.com/celestiaorg/celestia-app/pkg/appconsts"
v1 "github.com/celestiaorg/celestia-app/pkg/appconsts/v1"
v2 "github.com/celestiaorg/celestia-app/pkg/appconsts/v2"
"github.com/celestiaorg/celestia-app/pkg/proof"
blobmodule "github.com/celestiaorg/celestia-app/x/blob"
blobmodulekeeper "github.com/celestiaorg/celestia-app/x/blob/keeper"
blobmoduletypes "github.com/celestiaorg/celestia-app/x/blob/types"
"github.com/celestiaorg/celestia-app/x/paramfilter"
"github.com/celestiaorg/celestia-app/x/tokenfilter"
appupgrade "github.com/celestiaorg/celestia-app/x/upgrade"

qgbmodule "github.com/celestiaorg/celestia-app/x/qgb"
qgbmodulekeeper "github.com/celestiaorg/celestia-app/x/qgb/keeper"
Expand Down Expand Up @@ -160,7 +160,7 @@ var (

// ModuleEncodingRegisters keeps track of all the module methods needed to
// register interfaces and specific type to encoding config
ModuleEncodingRegisters = extractRegisters(ModuleBasics, appupgrade.TypeRegister{})
ModuleEncodingRegisters = extractRegisters(ModuleBasics, upgrade.TypeRegister{})

// module account permissions
maccPerms = map[string][]string{
Expand All @@ -172,8 +172,22 @@ var (
stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking},
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
}

// versions that the current state machine supports
supportedVersions = []uint64{v1.Version, v2.Version}

DefaultInitialVersion = v1.Version
)

func IsSupported(version uint64) bool {
for _, v := range supportedVersions {
if v == version {
return true
}
}
return false
}

var _ servertypes.Application = (*App)(nil)

func init() {
Expand Down Expand Up @@ -219,7 +233,7 @@ type App struct {
DistrKeeper distrkeeper.Keeper
GovKeeper govkeeper.Keeper
CrisisKeeper crisiskeeper.Keeper
UpgradeKeeper sdkupgradekeeper.Keeper
UpgradeKeeper upgrade.Keeper
ParamsKeeper paramskeeper.Keeper
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
EvidenceKeeper evidencekeeper.Keeper
Expand All @@ -243,10 +257,9 @@ func New(
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
skipUpgradeHeights map[int64]bool,
homePath string,
invCheckPeriod uint,
encodingConfig encoding.Config,
upgradeSchedule map[string]upgrade.Schedule,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *App {
Expand All @@ -262,7 +275,7 @@ func New(
keys := sdk.NewKVStoreKeys(
authtypes.StoreKey, authzkeeper.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, sdkupgradetypes.StoreKey, feegrant.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, upgrade.StoreKey, feegrant.StoreKey,
evidencetypes.StoreKey, capabilitytypes.StoreKey,
blobmoduletypes.StoreKey,
qgbmoduletypes.StoreKey,
Expand Down Expand Up @@ -328,7 +341,7 @@ func New(
)

app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper)
app.UpgradeKeeper = sdkupgradekeeper.NewKeeper(skipUpgradeHeights, keys[sdkupgradetypes.StoreKey], appCodec, homePath, app.BaseApp, authtypes.NewModuleAddress(govtypes.ModuleName).String())
app.UpgradeKeeper = upgrade.NewKeeper(keys[upgrade.StoreKey], upgradeSchedule)

app.QgbKeeper = *qgbmodulekeeper.NewKeeper(
appCodec,
Expand Down Expand Up @@ -463,6 +476,7 @@ func New(
paramstypes.ModuleName,
authz.ModuleName,
vestingtypes.ModuleName,
upgrade.ModuleName,
)

app.mm.SetOrderEndBlockers(
Expand All @@ -485,6 +499,7 @@ func New(
paramstypes.ModuleName,
authz.ModuleName,
vestingtypes.ModuleName,
upgrade.ModuleName,
)

// NOTE: The genutils module must occur after staking so that pools are
Expand Down Expand Up @@ -512,7 +527,7 @@ func New(
feegrant.ModuleName,
paramstypes.ModuleName,
authz.ModuleName,
sdkupgradetypes.ModuleName,
upgrade.ModuleName,
)

app.QueryRouter().AddRoute(proof.TxInclusionQueryPath, proof.QueryTxInclusionProof)
Expand Down Expand Up @@ -574,7 +589,7 @@ func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.Res
if err := tmjson.Unmarshal(req.AppStateBytes, &genesisState); err != nil {
panic(err)
}
app.UpgradeKeeper.SetModuleVersionMap(ctx, app.mm.GetVersionMap())
app.SetProtocolVersion(req.ConsensusParams.Version.AppVersion)
return app.mm.InitGenesis(ctx, app.appCodec, genesisState)
}

Expand Down
2 changes: 1 addition & 1 deletion app/default_overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func DefaultConsensusParams() *tmproto.ConsensusParams {
Evidence: DefaultEvidenceParams(),
Validator: coretypes.DefaultValidatorParams(),
Version: tmproto.VersionParams{
AppVersion: appconsts.LatestVersion,
AppVersion: DefaultInitialVersion,
},
}
}
Expand Down
23 changes: 23 additions & 0 deletions app/deliver_tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app

import (
"fmt"

"github.com/celestiaorg/celestia-app/x/upgrade"
abci "github.com/tendermint/tendermint/abci/types"
)

func (app *App) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
sdkTx, err := app.txConfig.TxDecoder()(req.Tx)
if err == nil {
if appVersion, ok := upgrade.IsUpgradeMsg(sdkTx.GetMsgs()); ok {
if !IsSupported(appVersion) {
panic(fmt.Sprintf("network has upgraded to version %d which is not supported by this node. Please upgrade and restart", appVersion))
}
app.SetProtocolVersion(appVersion)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is meant to be done as the first transaction, should we just do this in BeginBlock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be more complex as we would need to track all the different proposed blocks and match them with the one that eventually gets committed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be easier in finalize block because it can reside as part of the preBlock call back function

// TODO: we may want to emit an event for this
evan-forbes marked this conversation as resolved.
Show resolved Hide resolved
return abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
}
}
return app.BaseApp.DeliverTx(req)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since v1 nodes won't be running this, what will they do with this tx?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're setting the protocol version here, I think we have to be extra careful to never use the app.Version method before this in process or prepare. we might even want to remove it. The reason being that we don't want different versions used for different abci methods during the same height.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since v1 nodes won't be running this, what will they do with this tx?

They will just error and skip over it. In the following block they will panic (not sure if in cometbft or in app) because of the app version difference

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[non-blocking] I think de-indenting and following an exit early strategy makes the code easier to follow and easier to test. Additionally I think it reduces the chances of future changes causing bugs.

Suggested change
func (app *App) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
sdkTx, err := app.txConfig.TxDecoder()(req.Tx)
if err == nil {
if appVersion, ok := upgrade.IsUpgradeMsg(sdkTx.GetMsgs()); ok {
if !IsSupported(appVersion) {
panic(fmt.Sprintf("network has upgraded to version %d which is not supported by this node. Please upgrade and restart", appVersion))
}
app.SetProtocolVersion(appVersion)
// TODO: we may want to emit an event for this
return abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
}
}
return app.BaseApp.DeliverTx(req)
}
func (app *App) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
sdkTx, err := app.txConfig.TxDecoder()(req.Tx)
if err != nil {
return app.BaseApp.DeliverTx(req)
}
if appVersion, ok := upgrade.IsUpgradeMsg(sdkTx.GetMsgs()); !ok {
return app.BaseApp.DeliverTx(req)
}
if !IsSupported(appVersion) {
panic(fmt.Sprintf("network has upgraded to version %d which is not supported by this node. Please upgrade and restart", appVersion))
}
app.SetProtocolVersion(appVersion)
// TODO: we may want to emit an event for this
return abci.ResponseDeliverTx{Code: abci.CodeTypeOK}
}

Now it's super clear that there are 4 test cases that need to be created.

  1. err != nil from app.txConfig.TxDecoder()(req.Tx)
  2. !ok from upgrade.IsUpgradeMsg(sdkTx.GetMsgs())
  3. !IsSupported(appVersion)
  4. happy case

30 changes: 30 additions & 0 deletions app/prepare_proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/celestiaorg/celestia-app/pkg/da"
"github.com/celestiaorg/celestia-app/pkg/shares"
"github.com/celestiaorg/celestia-app/pkg/square"
"github.com/celestiaorg/celestia-app/x/upgrade"
"github.com/cosmos/cosmos-sdk/telemetry"
abci "github.com/tendermint/tendermint/abci/types"
core "github.com/tendermint/tendermint/proto/tendermint/types"
Expand Down Expand Up @@ -58,6 +59,27 @@ func (app *App) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePr
txs = make([][]byte, 0)
} else {
txs = FilterTxs(app.Logger(), sdkCtx, handler, app.txConfig, req.BlockData.Txs)

// TODO: this would be improved if we only attempted the upgrade in the first round of the
// height to still allow transactions to pass through without being delayed from trying
// to coordinate the upgrade height
if newVersion, ok := app.UpgradeKeeper.ShouldProposeUpgrade(req.ChainId, req.Height); ok && newVersion > app.GetBaseApp().AppVersion() {
upgradeTx, err := upgrade.NewMsgVersionChange(app.txConfig, newVersion)
if err != nil {
panic(err)
}
// the upgrade transaction must be the first transaction in the block
txs = append([][]byte{upgradeTx}, txs...)
cmwaters marked this conversation as resolved.
Show resolved Hide resolved

// because we are adding bytes, we need to check that we are not going over the limit
// if we are, we continually prune the last tx (the lowest paying blobTx).
size := sizeOf(txs)
for size > int(req.BlockDataSize) {
lastTx := txs[len(txs)-1]
txs = txs[:len(txs)-1]
size -= len(lastTx)
}
cmwaters marked this conversation as resolved.
Show resolved Hide resolved
}
}

// build the square from the set of valid and prioritised transactions.
Expand Down Expand Up @@ -102,3 +124,11 @@ func (app *App) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePr
},
}
}

func sizeOf(txs [][]byte) int {
size := 0
for _, tx := range txs {
size += len(tx)
}
return size
}
28 changes: 26 additions & 2 deletions app/process_proposal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/celestiaorg/celestia-app/pkg/shares"
"github.com/celestiaorg/celestia-app/pkg/square"
blobtypes "github.com/celestiaorg/celestia-app/x/blob/types"
"github.com/celestiaorg/celestia-app/x/upgrade"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
Expand Down Expand Up @@ -66,19 +67,42 @@ func (app *App) ProcessProposal(req abci.RequestProcessProposal) (resp abci.Resp

// handle non-blob transactions first
if !isBlobTx {
_, has := hasPFB(sdkTx.GetMsgs())
msgs := sdkTx.GetMsgs()

_, has := hasPFB(msgs)
if has {
// A non blob tx has a PFB, which is invalid
logInvalidPropBlock(app.Logger(), req.Header, fmt.Sprintf("tx %d has PFB but is not a blob tx", idx))
return reject()
}

if appVersion, ok := upgrade.IsUpgradeMsg(msgs); ok {
if idx != 0 {
logInvalidPropBlock(app.Logger(), req.Header, fmt.Sprintf("upgrade message %d is not the first transaction", idx))
return reject()
}

if !IsSupported(appVersion) {
logInvalidPropBlock(app.Logger(), req.Header, fmt.Sprintf("block proposes an unsupported app version %d", appVersion))
return reject()
}
Comment on lines +79 to +88
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a scenario where nodes running v1 that don't know how to parse this msg or have this logic vote for the proposal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I thought about this. This was why I chose to wrap it as a tx rather than having it unparseable which would then be accepted. As a tx, it would fail the ante handler because there is no signature attached to it. Ideally I can write a test to check it out.


// app version must always increase
if appVersion <= app.GetBaseApp().AppVersion() {
logInvalidPropBlock(app.Logger(), req.Header, fmt.Sprintf("block proposes an app version %d that is not greater than the current app version %d", appVersion, app.GetBaseApp().AppVersion()))
return reject()
}
evan-forbes marked this conversation as resolved.
Show resolved Hide resolved

// we don't need to pass this message through the ante handler
continue
}

// we need to increment the sequence for every transaction so that
// the signature check below is accurate. this error only gets hit
// if the account in question doens't exist.
sdkCtx, err = handler(sdkCtx, sdkTx, false)
if err != nil {
logInvalidPropBlockError(app.Logger(), req.Header, "failure to incrememnt sequence", err)
logInvalidPropBlockError(app.Logger(), req.Header, "failure to increment sequence", err)
return reject()
}

Expand Down
17 changes: 4 additions & 13 deletions cmd/celestia-appd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"
"path/filepath"

"github.com/celestiaorg/celestia-app/pkg/appconsts"
qgbcmd "github.com/celestiaorg/celestia-app/x/qgb/client"

"github.com/celestiaorg/celestia-app/app"
Expand Down Expand Up @@ -211,11 +210,6 @@ func NewAppServer(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts se
cache = store.NewCommitKVStoreCacheManager()
}

skipUpgradeHeights := make(map[int64]bool)
for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) {
skipUpgradeHeights[int64(h)] = true
}

pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts)
if err != nil {
panic(err)
Expand All @@ -234,10 +228,10 @@ func NewAppServer(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts se
}

return app.New(
logger, db, traceStore, true, skipUpgradeHeights,
cast.ToString(appOpts.Get(flags.FlagHome)),
logger, db, traceStore, true,
cast.ToUint(appOpts.Get(server.FlagInvCheckPeriod)),
encoding.MakeConfig(app.ModuleEncodingRegisters...), // Ideally, we would reuse the one created by NewRootCmd.
nil,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's your current preferred way to pass this in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the main question I have from your question is whether we want to allow node operators to be able to modify this in their app.toml config. Either way we will have some hardcoded value that gets ported in the binary. I'm just not sure yet whether that value is a default that can be overrided or a built-in value that can only be changed by switching to a different binary

appOpts,
baseapp.SetPruning(pruningOpts),
baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))),
Expand All @@ -249,9 +243,6 @@ func NewAppServer(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts se
baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))),
baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))),
baseapp.SetSnapshot(snapshotStore, snapshottypes.NewSnapshotOptions(cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval)), cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent)))),
func(b *baseapp.BaseApp) {
b.SetProtocolVersion(appconsts.LatestVersion)
},
Comment on lines -252 to -254
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that this is in init, will it still get called after rebooting? in the past we've had bugs where the chain-id was not set after rebooting because its supposed to be set during init

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[proposal for a future PR, no change needed]

perhaps we should just make this a stateful value? its a one off benefit as it would only apply to v1, however it would guarantee nodes running v1 will hit an apphash error instead of ignoring txs that they don't know about. We'll probably hit one of those anyway, but this just guarantees it asap. The other benefit being that we never have to think about loading a consensus critical value into the application.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should just make this a stateful value?

I agree. This is what later versions of the SDK have. Maybe we can add it in v2 as a migration

)
}

Expand All @@ -263,13 +254,13 @@ func createAppAndExport(
encCfg.Codec = codec.NewProtoCodec(encCfg.InterfaceRegistry)
var capp *app.App
if height != -1 {
capp = app.New(logger, db, traceStore, false, map[int64]bool{}, "", uint(1), encCfg, appOpts)
capp = app.New(logger, db, traceStore, false, uint(1), encCfg, nil, appOpts)

if err := capp.LoadHeight(height); err != nil {
return servertypes.ExportedApp{}, err
}
} else {
capp = app.New(logger, db, traceStore, true, map[int64]bool{}, "", uint(1), encCfg, appOpts)
capp = app.New(logger, db, traceStore, true, uint(1), encCfg, nil, appOpts)
}

return capp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
Expand Down
10 changes: 10 additions & 0 deletions proto/celestia/upgrade/v1/types.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";
package celestia.upgrade.v1;

option go_package = "github.com/celestiaorg/celestia-app/x/upgrade";

// MsgPayForBlobs pays for the inclusion of a blob in the block.
cmwaters marked this conversation as resolved.
Show resolved Hide resolved
message MsgVersionChange {
// the new version to propose the change to
cmwaters marked this conversation as resolved.
Show resolved Hide resolved
uint64 version = 1;
}
2 changes: 1 addition & 1 deletion test/tokenfilter/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func SetupWithGenesisValSet(t testing.TB, valSet *tmtypes.ValidatorSet, genAccs
encCdc := encoding.MakeConfig(app.ModuleEncodingRegisters...)
genesisState := app.NewDefaultGenesisState(encCdc.Codec)
app := app.New(
log.NewNopLogger(), db, nil, true, map[int64]bool{}, app.DefaultNodeHome, 5, encCdc, simapp.EmptyAppOptions{},
log.NewNopLogger(), db, nil, true, 5, encCdc, nil, simapp.EmptyAppOptions{},
)

// set genesis accounts
Expand Down
4 changes: 1 addition & 3 deletions test/util/malicious/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,12 @@ func New(
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
skipUpgradeHeights map[int64]bool,
homePath string,
invCheckPeriod uint,
encodingConfig encoding.Config,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *App {
goodApp := app.New(logger, db, traceStore, loadLatest, skipUpgradeHeights, homePath, invCheckPeriod, encodingConfig, appOpts, baseAppOptions...)
goodApp := app.New(logger, db, traceStore, loadLatest, invCheckPeriod, encodingConfig, nil, appOpts, baseAppOptions...)
badApp := &App{App: goodApp}

// set the malicious prepare proposal handler if it is set in the app options
Expand Down
Loading