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

Upgrade Module Spec #5294

Merged
merged 11 commits into from
Nov 29, 2019
5 changes: 4 additions & 1 deletion x/upgrade/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,27 @@ import (
// If it is ready, it will execute it if the handler is installed, and panic/abort otherwise.
// If the plan is not ready, it will ensure the handler is not registered too early (and abort otherwise).
//
// The prupose is to ensure the binary is switch EXACTLY at the desired block, and to allow
// The purpose is to ensure the binary is switch EXACTLY at the desired block, and to allow
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// a migration to be executed if needed upon this switch (migration defined in the new binary)
func BeginBlocker(k Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) {
plan, found := k.GetUpgradePlan(ctx)
if !found {
return
}

if plan.ShouldExecute(ctx) {
if !k.HasHandler(plan.Name) {
upgradeMsg := fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
// We don't have an upgrade handler for this upgrade name, meaning this software is out of date so shutdown
ctx.Logger().Error(upgradeMsg)
panic(upgradeMsg)
}

// We have an upgrade handler for this upgrade name, so apply the upgrade
ctx.Logger().Info(fmt.Sprintf("applying upgrade \"%s\" at %s", plan.Name, plan.DueAt()))
ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
k.ApplyUpgrade(ctx, plan)

return
}

Expand Down
1 change: 1 addition & 0 deletions x/upgrade/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func NewSoftwareUpgradeProposalHandler(k Keeper) govtypes.Handler {
switch c := content.(type) {
case SoftwareUpgradeProposal:
return handleSoftwareUpgradeProposal(ctx, k, c)

case CancelSoftwareUpgradeProposal:
return handleCancelSoftwareUpgradeProposal(ctx, k, c)

Expand Down
8 changes: 6 additions & 2 deletions x/upgrade/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ func (k Keeper) SetUpgradeHandler(name string, upgradeHandler types.UpgradeHandl
// If there is another Plan already scheduled, it will overwrite it
// (implicitly cancelling the current plan)
func (k Keeper) ScheduleUpgrade(ctx sdk.Context, plan types.Plan) sdk.Error {
err := plan.ValidateBasic()
if err != nil {
if err := plan.ValidateBasic(); err != nil {
return err
}

if !plan.Time.IsZero() {
if !plan.Time.After(ctx.BlockHeader().Time) {
return sdk.ErrUnknownRequest("upgrade cannot be scheduled in the past")
Expand All @@ -65,6 +65,7 @@ func (k Keeper) getDoneHeight(ctx sdk.Context, name string) int64 {
if len(bz) == 0 {
return 0
}

return int64(binary.BigEndian.Uint64(bz))
}

Expand All @@ -87,6 +88,7 @@ func (k Keeper) GetUpgradePlan(ctx sdk.Context) (plan types.Plan, havePlan bool)
if bz == nil {
return plan, false
}

k.cdc.MustUnmarshalBinaryBare(bz, &plan)
return plan, true
}
Expand All @@ -111,7 +113,9 @@ func (k Keeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) {
if handler == nil {
panic("ApplyUpgrade should never be called without first checking HasHandler")
}

handler(ctx, plan)

k.ClearUpgradePlan(ctx)
k.setDone(ctx, plan.Name)
}
84 changes: 84 additions & 0 deletions x/upgrade/spec/01_concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Concepts

## Plan

The `x/upgrade` module defines a `Plan` type in which a live upgrade is scheduled
to occur. A `Plan` can be scheduled at a specific block height or time, but not both.
A `Plan` is created once a (frozen) release candidate along with an appropriate upgrade
`Handler` (see below) is agreed upon, where the `Name` of a `Plan` corresponds to a
specific `Handler`. Typically, a `Plan` is created through a governance proposal
process, where if voted upon and passed, will be scheduled. The `Info` of a `Plan`
may contain various metadata about the upgrade, typically application specific
upgrade info to be included on-chain such as a git commit that validators could
automatically upgrade to.
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved

### Sidecar Process

If an operator running the application binary also runs a sidecar process to assist
in the automatic download and upgrade of a binary, the `Info` allows this process to
be seamless. Namely, the `x/upgrade` module fulfills the
[cosmosd Upgradeable Binary Specification](https://github.com/regen-network/cosmosd#upgradeable-binary-specification)
specification and `cosmosd` can optionally be used to fully automate the upgrade
process for node operators. By populating the `Info` field with the necessary information,
binaries can automatically be downloaded. See [here](https://github.com/regen-network/cosmosd#auto-download)
for more info.

```go
type Plan struct {
Name string
Time Time
Height int64
Info string
}
```

## Handler

The `x/upgrade` module facilitates upgrading from major version X to major version Y. To
accomplish this, node operators must first upgrade their current binary to a new
binary that has a corresponding `Handler` for the new version Y. It is assumed that
this version has fully been tested and approved by the community at large. This
`Handler` defines what state migrations need to occur before the new binary Y
can successfully run the chain. Naturally, this `Handler` is application specific
and not defined on a per-module basis. Registering a `Handler` is done via
`Keeper#SetUpgradeHandler` in the application.

```go
type UpgradeHandler func(Context, Plan)
```

During each `EndBlock` execution, the `x/upgrade` module checks if there exists a
`Plan` that should execute (is scheduled at that time or height). If so, the corresponding
`Handler` is executed. If the `Plan` is expected to execute but no `Handler` is registered
or if the binary was upgraded too early, the node will gracefully panic and exit.

## Proposal

Typically, a `Plan` is proposed and submitted through governance via a `SoftwareUpgradeProposal`.
This proposal prescribes to the standard governance process. If the proposal passes,
the `Plan`, which targets a specific `Handler`, is persisted and scheduled. The
upgrade can be delayed or hastened by updating the `Plan.Time` in a new proposal.

```go
type SoftwareUpgradeProposal struct {
Title string
Description string
Plan Plan
}
```

### Cancelling Upgrade Proposals

Upgrade proposals can be cancelled. There exists a `CancelSoftwareUpgrade` proposal
type, which can be voted on and passed and will remove the scheduled upgrade `Plan`.
Of course this requires that the upgrade was known to be a bad idea well before the
upgrade itself, to allow time for a vote.

If such a possibility is desired, the upgrade height is to be
`2 * (VotingPeriod + DepositPeriod) + (SafetyDelta)` from the beginning of the
upgrade proposal. The `SafetyDelta` is the time available from the success of an
upgrade proposal and the realization it was a bad idea (due to external social consensus).

A `CancelSoftwareUpgrade` proposal can also be made while the original
`SoftwareUpgradeProposal` is still being voted upon, as long as the `VotingPeriod`
ends after the `SoftwareUpgradeProposal`.
7 changes: 7 additions & 0 deletions x/upgrade/spec/02_state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# State

The internal state of the `x/upgrade` module is relatively minimal and simple. The
state only contains the currently active upgrade `Plan` (if one exists) by key
`0x0` and if a `Plan` is marked as "done" by key `0x1`.

The `x/upgrade` module contains no genesis state.
4 changes: 4 additions & 0 deletions x/upgrade/spec/03_events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Events

The `x/upgrade` does not emit any events by itself. Any and all proposal related
events are emitted through the `x/gov` module.
20 changes: 20 additions & 0 deletions x/upgrade/spec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Upgrade Module Specification

## Abstract

`x/upgrade` is an implementation of a Cosmos SDK module that facilitates smoothly
upgrading a live Cosmos chain to a new (breaking) software version. It accomplishes this by
providing a `BeginBlocker` hook that prevents the blockchain state machine from
proceeding once a pre-defined upgrade block time or height has been reached.

The module does not prescribe anything regarding how governance decides to do an
upgrade, but just the mechanism for coordinating the upgrade safely. Without software
support for upgrades, upgrading a live chain is risky because all of the validators
need to pause their state machines at exactly the same point in the process. If
this is not done correctly, there can be state inconsistencies which are hard to
recover from.
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved

<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Events](03_events.md)**