Skip to content

Commit

Permalink
feat!: add a signalling mechanism for coordinated upgrades (#2832)
Browse files Browse the repository at this point in the history
As per ADR-018, this PR extends the existing minimal upgrade module with
a signalling mechanism.

Validators are expected to submit an on-chain message to signal that
they wish to change version of the network. When a quorum has signalled
the next version the upgrade module signals to the app that it is ready
to switch to the next state machine. If the app version is not supported
the node will panic. Note that this feature does not currently support
downgrading. The only permissible app version change is the very next
increment. To cancel the upgrade, the same validators need only to
submit an on-chain message with the current version they are on.

Co-authored-by: Evan Forbes <42654277+evan-forbes@users.noreply.github.com>
Co-authored-by: Rootul P <rootulp@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Nov 30, 2023
1 parent 54e8936 commit c1754b0
Show file tree
Hide file tree
Showing 19 changed files with 2,299 additions and 449 deletions.
31 changes: 21 additions & 10 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
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"
upgradetypes "github.com/celestiaorg/celestia-app/x/upgrade/types"
"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 @@ -85,6 +86,7 @@ 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"
Expand Down Expand Up @@ -155,11 +157,12 @@ var (
vesting.AppModuleBasic{},
blobmodule.AppModuleBasic{},
bsmodule.AppModuleBasic{},
upgrade.AppModuleBasic{},
)

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

// module account permissions
maccPerms = map[string][]string{
Expand Down Expand Up @@ -267,7 +270,7 @@ func New(
keys := sdk.NewKVStoreKeys(
authtypes.StoreKey, authzkeeper.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, upgrade.StoreKey, feegrant.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey,
evidencetypes.StoreKey, capabilitytypes.StoreKey,
blobmoduletypes.StoreKey,
bsmoduletypes.StoreKey,
Expand Down Expand Up @@ -333,7 +336,7 @@ func New(
)

app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper)
app.UpgradeKeeper = upgrade.NewKeeper(keys[upgrade.StoreKey], upgradeHeight)
app.UpgradeKeeper = upgrade.NewKeeper(keys[upgradetypes.StoreKey], upgradeHeight, app.StakingKeeper)

app.BlobstreamKeeper = *bsmodulekeeper.NewKeeper(
appCodec,
Expand Down Expand Up @@ -442,6 +445,7 @@ func New(
transferModule,
blobmod,
bsmod,
upgrade.NewAppModule(app.UpgradeKeeper),
)

// During begin block slashing happens after distr.BeginBlocker so that
Expand All @@ -468,7 +472,7 @@ func New(
paramstypes.ModuleName,
authz.ModuleName,
vestingtypes.ModuleName,
upgrade.ModuleName,
upgradetypes.ModuleName,
)

app.mm.SetOrderEndBlockers(
Expand All @@ -491,7 +495,7 @@ func New(
paramstypes.ModuleName,
authz.ModuleName,
vestingtypes.ModuleName,
upgrade.ModuleName,
upgradetypes.ModuleName,
)

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

app.QueryRouter().AddRoute(proof.TxInclusionQueryPath, proof.QueryTxInclusionProof)
Expand Down Expand Up @@ -573,10 +577,17 @@ func (app *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.R
// EndBlocker application updates every end block
func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
res := app.mm.EndBlock(ctx, req)
// NOTE: this is a specific feature for upgrading to v2 as v3 and onward is expected
// to be coordinated through the signalling protocol
if app.UpgradeKeeper.ShouldUpgrade(req.Height) {
app.SetAppVersion(ctx, v2.Version)
// NOTE: this is a specific feature for upgrading from v1 to v2. It will be deprecated in v3
if app.UpgradeKeeper.ShouldUpgradeToV2(req.Height) {
if app.AppVersion(ctx) == v1.Version {
app.SetAppVersion(ctx, v2.Version)
}
// from v2 to v3 and onwards we use a signalling mechanism
} else if shouldUpgrade, version := app.UpgradeKeeper.ShouldUpgrade(); shouldUpgrade {
// Version changes must be increasing. Downgrades are not permitted
if version > app.AppVersion(ctx) {
app.SetAppVersion(ctx, version)
}
}
return res
}
Expand Down
35 changes: 24 additions & 11 deletions app/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/celestiaorg/celestia-app/x/blob"
"github.com/celestiaorg/celestia-app/x/blobstream"
"github.com/celestiaorg/celestia-app/x/mint"
"github.com/celestiaorg/celestia-app/x/upgrade"
upgradetypes "github.com/celestiaorg/celestia-app/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
Expand All @@ -31,6 +33,15 @@ var (
// versions that the current state machine supports
supportedVersions = []uint64{v1.Version, v2.Version}

v1moduleVersionMap = make(module.VersionMap)
v2moduleVersionMap = make(module.VersionMap)
)

const DefaultInitialVersion = v1.Version

// this is used as a compile time consistency check across different module
// based maps
func init() {
v1moduleVersionMap = module.VersionMap{
"bank": bank.AppModule{}.ConsensusVersion(),
"auth": auth.AppModule{}.ConsensusVersion(),
Expand All @@ -53,23 +64,25 @@ var (
"transfer": transfer.AppModule{}.ConsensusVersion(),
}

// There is currently complete parity between v1 and v2 modules, but this
// will likely change
v2moduleVersionMap = v1moduleVersionMap
)

const DefaultInitialVersion = v1.Version
// v2 has all the same modules as v1 with the addition of an upgrade module
v2moduleVersionMap = make(module.VersionMap)
for k, v := range v1moduleVersionMap {
v2moduleVersionMap[k] = v
}
v2moduleVersionMap[upgradetypes.ModuleName] = upgrade.AppModule{}.ConsensusVersion()

// this is used as a compile time consistency check across different module
// based maps
func init() {
for moduleName := range ModuleBasics {
isSupported := false
for _, v := range supportedVersions {
versionMap := GetModuleVersion(v)
if _, ok := versionMap[moduleName]; !ok {
panic(fmt.Sprintf("inconsistency: module %s not found in module version map for version %d", moduleName, v))
if _, ok := versionMap[moduleName]; ok {
isSupported = true
break
}
}
if !isSupported {
panic(fmt.Sprintf("inconsistency: module %s not found in any version", moduleName))
}
}
}

Expand Down
28 changes: 28 additions & 0 deletions proto/celestia/upgrade/v1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
syntax = "proto3";
package celestia.upgrade.v1;

import "google/api/annotations.proto";

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

// Query defines the upgrade Query service.
service Query {
// VersionTally allows the querying of the tally of voting power by all
// validators that have signalled for each version
rpc VersionTally(QueryVersionTallyRequest)
returns (QueryVersionTallyResponse) {
option (google.api.http).get = "/upgrade/v1/tally/{version}";
}
}

// QueryVersionTallyRequest is the request type for the UpgradeStatus RPC
// method.
message QueryVersionTallyRequest { uint64 version = 1; }

// QueryVersionTallyResponse is the response type for the UpgradeStatus RPC
// method.
message QueryVersionTallyResponse {
uint64 voting_power = 1;
uint64 threshold_power = 2;
uint64 total_voting_power = 3;
}
24 changes: 24 additions & 0 deletions proto/celestia/upgrade/v1/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";
package celestia.upgrade.v1;

import "google/api/annotations.proto";

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

// Msg defines the upgrade Msg service.
service Msg {
// SignalVersion allows the validator to signal for an upgrade
rpc SignalVersion(MsgSignalVersion) returns (MsgSignalVersionResponse) {
option (google.api.http).post = "/upgrade/v1/signal";
}
}

// MsgSignalVersion signals for an upgrade
message MsgSignalVersion {
string validator_address = 1;
uint64 version = 2;
}

// MsgSignalVersionResponse describes the response returned after the submission
// of a SignalVersion
message MsgSignalVersionResponse {}
10 changes: 0 additions & 10 deletions proto/celestia/upgrade/v1/types.proto

This file was deleted.

72 changes: 72 additions & 0 deletions x/upgrade/ibc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package upgrade

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
ibctypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types"
)

// We need compatibility with the way that IBC uses the upgrade module. This file
// ensures that we comply to the interface that IBC expects
var _ ibctypes.UpgradeKeeper = (*Keeper)(nil)

// ScheduleUpgrade implements the ibc upgrade keeper interface. This is a noop as
// no other process is allowed to schedule an upgrade but the upgrade keeper itself.
// This is kept around to support the interface.
func (k Keeper) ScheduleUpgrade(_ sdk.Context, _ types.Plan) error {
return nil
}

// GetUpgradePlan implements the ibc upgrade keeper interface. This is used in BeginBlock
// to know when to write the upgraded consensus state. The IBC module needs to sign over
// the next consensus state to ensure a smooth transition for counterparty chains. This
// is implemented as a noop. Any IBC breaking change would be invoked by this upgrade module
// in end blocker.
func (k Keeper) GetUpgradePlan(_ sdk.Context) (plan types.Plan, havePlan bool) {
return types.Plan{}, false
}

// SetUpgradedClient sets the expected upgraded client for the next version of this chain at the last height the current chain will commit.
func (k Keeper) SetUpgradedClient(ctx sdk.Context, planHeight int64, bz []byte) error {
store := ctx.KVStore(k.storeKey)
store.Set(types.UpgradedClientKey(planHeight), bz)
return nil
}

// GetUpgradedClient gets the expected upgraded client for the next version of this chain
func (k Keeper) GetUpgradedClient(ctx sdk.Context, height int64) ([]byte, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UpgradedClientKey(height))
if len(bz) == 0 {
return nil, false
}

return bz, true
}

// SetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain
// using the last height committed on this chain.
func (k Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error {
store := ctx.KVStore(k.storeKey)
store.Set(types.UpgradedConsStateKey(planHeight), bz)
return nil
}

// GetUpgradedConsensusState get the expected upgraded consensus state for the next version of this chain
func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]byte, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UpgradedConsStateKey(lastHeight))
if len(bz) == 0 {
return nil, false
}

return bz, true
}

// ClearIBCState clears any planned IBC state
func (k Keeper) ClearIBCState(ctx sdk.Context, lastHeight int64) {
// delete IBC client and consensus state from store if this is IBC plan
store := ctx.KVStore(k.storeKey)
store.Delete(types.UpgradedClientKey(lastHeight))
store.Delete(types.UpgradedConsStateKey(lastHeight))
}
13 changes: 13 additions & 0 deletions x/upgrade/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package upgrade

import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

type StakingKeeper interface {
GetLastValidatorPower(ctx sdk.Context, addr sdk.ValAddress) int64
GetLastTotalPower(ctx sdk.Context) math.Int
GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator stakingtypes.Validator, found bool)
}
Loading

0 comments on commit c1754b0

Please sign in to comment.