Skip to content
Merged
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
79 changes: 36 additions & 43 deletions CHANGELOG.md

Large diffs are not rendered by default.

65 changes: 37 additions & 28 deletions app/upgrades/v1_20_0/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,30 @@ import (
// UpgradeName is the on-chain name used for this upgrade.
const UpgradeName = "v1.20.0"

// Devnet and testnet derive a finite migration_end_time automatically from the
// upgrade block time so rehearsals and public testnet exercise a real deadline.
// Mainnet instead receives a specific absolute migration_end_time chosen near
// its own upgrade (the handler leaves it at the default 0).
const (
devnetMigrationWindow = 2 * 24 * time.Hour
testnetMigrationWindow = 7 * 24 * time.Hour
)

// autoMigrationWindow returns the migration window to auto-apply for the given
// chain ID, or 0 if the deadline should be left to a manually chosen timestamp
// (mainnet and any unrecognized chain ID).
func autoMigrationWindow(chainID string) time.Duration {
// Recognized Lumera networks (devnet/testnet/mainnet) derive a finite
// migration_end_time automatically from the upgrade block time so they run
// against a real deadline without hardcoding an absolute timestamp. Devnet uses
// a short fixed window for rehearsals; testnet and mainnet both run against a
// 3-calendar-month window measured from the upgrade block. Unrecognized chain
// IDs (custom networks/forks) are left with no deadline.
const devnetMigrationWindow = 2 * 24 * time.Hour

// autoMigrationEndTime returns the migration_end_time to auto-apply for the
// given chain ID and upgrade block time, plus whether a deadline should be set
// at all. Unrecognized chain IDs leave the deadline unset (ok == false).
//
// A calendar-month offset (AddDate) is used for testnet/mainnet rather than a
// fixed time.Duration because "3 months" is not a constant number of hours; it
// must be applied to the block time directly so month lengths and leap years
// are handled correctly.
func autoMigrationEndTime(chainID string, blockTime time.Time) (time.Time, bool) {
switch {
case lcfg.IsDevnetChainID(chainID):
return devnetMigrationWindow
case lcfg.IsTestnetChainID(chainID):
return testnetMigrationWindow
return blockTime.Add(devnetMigrationWindow), true
case lcfg.IsTestnetChainID(chainID), lcfg.IsMainnetChainID(chainID):
return blockTime.AddDate(0, 3, 0), true
default:
return 0
return time.Time{}, false
}
}

Expand Down Expand Up @@ -153,29 +157,34 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand
}
}

// On devnet and testnet, derive a finite migration_end_time from the
// upgrade block time so those networks run against a real deadline without
// hardcoding an absolute timestamp. RunMigrations already seeded the
// evmigration module with default params (enable_migration=true,
// migration_end_time=0); here we only override the deadline. Mainnet keeps
// the default 0 at upgrade and receives a specific migration_end_time
// chosen separately near launch.
if window := autoMigrationWindow(p.ChainID); window > 0 {
// Derive a finite migration_end_time from the upgrade block time so the
// network runs against a real deadline without hardcoding an absolute
// timestamp. RunMigrations already seeded the evmigration module with
// default params (enable_migration=true, migration_end_time=0); here we
// only override the deadline. Devnet gets a short rehearsal window;
// testnet and mainnet both get a 3-calendar-month window.
//
// The network is identified from the SDK context (ctx.ChainID()), which
// carries the genesis-derived chain ID from the block header. We must not
// use the app-level ChainID captured during setupUpgrades: that value
// comes from the --chain-id flag, which defaults to the non-empty
// "lumera" and so never falls back to genesis, leaving mainnet's deadline
// silently unset on the common `lumerad start` path.
if endTime, ok := autoMigrationEndTime(ctx.ChainID(), ctx.BlockTime()); ok {
if p.EvmigrationKeeper == nil {
return nil, fmt.Errorf("%s upgrade requires evmigration keeper to be wired", UpgradeName)
}
emParams, err := p.EvmigrationKeeper.Params.Get(ctx)
if err != nil {
return nil, fmt.Errorf("get evmigration params: %w", err)
}
emParams.MigrationEndTime = ctx.BlockTime().Add(window).Unix()
emParams.MigrationEndTime = endTime.Unix()
if err := p.EvmigrationKeeper.Params.Set(ctx, emParams); err != nil {
return nil, fmt.Errorf("set evmigration migration_end_time: %w", err)
}
p.Logger.Info("Set migration_end_time from upgrade block time",
"chain_id", p.ChainID,
"chain_id", ctx.ChainID(),
"migration_end_time", emParams.MigrationEndTime,
"window", window.String(),
)
}

Expand Down
60 changes: 46 additions & 14 deletions app/upgrades/v1_20_0/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ func upgradeParamsForChain(app *lumeraapp.App, chainID string) appParams.AppUpgr
// block time (block time + 2 days) so rehearsals run against a real deadline.
func TestV1200SetsDevnetMigrationEndTime(t *testing.T) {
app := lumeraapp.Setup(t)
ctx := app.BaseApp.NewContext(false)
// The handler identifies the network from the SDK context (genesis-derived
// chain ID), not the app-level ChainID captured during setup — which on a
// default `lumerad start` is the non-empty "lumera" flag default. Seed the
// context with the real chain ID and pass the misleading default in params
// to pin that ctx.ChainID() is what's used.
ctx := app.BaseApp.NewContext(false).WithChainID("lumera-devnet-1")

// Default genesis params seed migration with no deadline.
before, err := app.EvmigrationKeeper.Params.Get(ctx)
Expand All @@ -49,7 +54,7 @@ func TestV1200SetsDevnetMigrationEndTime(t *testing.T) {

want := ctx.BlockTime().Add(2 * 24 * time.Hour).Unix()

handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-devnet-1"))
handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera"))
_, err = handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{})
require.NoError(t, err)

Expand All @@ -62,38 +67,65 @@ func TestV1200SetsDevnetMigrationEndTime(t *testing.T) {
"max_validator_delegations default should be 2500")
}

// On testnet the handler derives a 7-day migration window from the upgrade
// block time.
// On testnet the handler derives a 3-calendar-month migration window from the
// upgrade block time.
func TestV1200SetsTestnetMigrationEndTime(t *testing.T) {
app := lumeraapp.Setup(t)
ctx := app.BaseApp.NewContext(false)
// See TestV1200SetsDevnetMigrationEndTime: network is taken from ctx.ChainID().
ctx := app.BaseApp.NewContext(false).WithChainID("lumera-testnet-2")

want := ctx.BlockTime().Add(7 * 24 * time.Hour).Unix()
want := ctx.BlockTime().AddDate(0, 3, 0).Unix()

handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-testnet-1"))
handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera"))
_, err := handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{})
require.NoError(t, err)

after, err := app.EvmigrationKeeper.Params.Get(ctx)
require.NoError(t, err)
require.Equal(t, want, after.MigrationEndTime,
"testnet upgrade should set migration_end_time to upgrade block time + 7 days")
"testnet upgrade should set migration_end_time to upgrade block time + 3 months")
}

// Mainnet keeps migration_end_time at the default 0 at upgrade; a specific
// absolute timestamp is chosen and applied separately near launch.
func TestV1200LeavesMigrationEndTimeZeroOnMainnet(t *testing.T) {
// On mainnet the handler derives a 3-calendar-month migration window from the
// upgrade block time, the same window as testnet.
func TestV1200SetsMainnetMigrationEndTime(t *testing.T) {
app := lumeraapp.Setup(t)
ctx := app.BaseApp.NewContext(false)
// See TestV1200SetsDevnetMigrationEndTime: network is taken from ctx.ChainID().
// This is the case the review flagged — the app-level ChainID would be the
// "lumera" default, leaving the mainnet deadline silently unset if used.
ctx := app.BaseApp.NewContext(false).WithChainID("lumera-mainnet-1")

// Default genesis params seed migration with no deadline.
before, err := app.EvmigrationKeeper.Params.Get(ctx)
require.NoError(t, err)
require.Equal(t, int64(0), before.MigrationEndTime)

want := ctx.BlockTime().AddDate(0, 3, 0).Unix()

handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera"))
_, err = handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{})
require.NoError(t, err)

after, err := app.EvmigrationKeeper.Params.Get(ctx)
require.NoError(t, err)
require.Equal(t, want, after.MigrationEndTime,
"mainnet upgrade should set migration_end_time to upgrade block time + 3 months")
}

// The bare "lumera" CLI default is not a real network ID; the handler must not
// treat it as one and must leave migration_end_time unset (0).
func TestV1200LeavesMigrationEndTimeUnsetForDefaultChainID(t *testing.T) {
app := lumeraapp.Setup(t)
ctx := app.BaseApp.NewContext(false).WithChainID("lumera")

handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera-mainnet-1"))
handler := upgradev1200.CreateUpgradeHandler(upgradeParamsForChain(app, "lumera"))
_, err := handler(sdk.WrapSDKContext(ctx), upgradetypes.Plan{}, module.VersionMap{})
require.NoError(t, err)

after, err := app.EvmigrationKeeper.Params.Get(ctx)
require.NoError(t, err)
require.Equal(t, int64(0), after.MigrationEndTime,
"mainnet upgrade must leave migration_end_time at the default 0")
"unrecognized chain ID must leave migration_end_time at the default 0")
}

func TestV1200InitializesERC20ParamsWhenInitGenesisIsSkipped(t *testing.T) {
Expand Down
6 changes: 3 additions & 3 deletions docs/evm-integration/architecture/rollout.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ The short list that genuinely needs a value chosen before tagging is the `x/evmi
| Parameter | Code default | Live mainnet signal | Decision |
| --- | --- | --- | --- |
| `enable_migration` | `true` (immediate-open) | — | **Keep `true` — immediate-open.** Migration opens as soon as the upgraded chain produces blocks; the upgrade handler leaves `enable_migration=true` with no controlled-open gating. Public messaging, support, and RPC capacity must therefore be live before validators restart. |
| `migration_end_time` | `0` (no deadline) | 160,021 accounts and 83 validators to migrate | **Auto-set by the `v1.20.0` upgrade handler from the upgrade-block time:** devnet = + **2 days**, testnet = + **7 days** (no hardcoded timestamp). **Mainnet:** the handler leaves it `0`; set a specific absolute Unix timestamp (seconds) — a chosen wall-clock end time, ~**+120 days** past the planned upgrade — closer to launch. Must be non-zero before mainnet while the proof format has no expiry. |
| `migration_end_time` | `0` (no deadline) | 160,021 accounts and 83 validators to migrate | **Auto-set by the `v1.20.0` upgrade handler from the upgrade-block time (no hardcoded timestamp):** devnet = + **2 days**, testnet and mainnet = + **3 calendar months** (`AddDate(0, 3, 0)`). The handler derives the deadline on every recognized network, so mainnet is non-zero from the first post-upgrade block while the proof format has no expiry. |
| `max_validator_delegations` | `2500` (raised from 2000) | worst-case validator migration-object count is **1,632** (`chain-helper.sh` reports `max_observed: 1632`, `suggested_cap: 2122` at the default 30% buffer) | **Shipped default is now `2500`** — clears the worst case (1,632) with ~1.53× headroom; `3000` judged over-provisioned given the short runway. Re-check with `chain-helper.sh` near tag time. |
| `max_migrations_per_block` | `50` | at 50/block (~5s blocks) the full 160k account base clears in ~11 days of continuous saturation | **Keep `50`.** Adequate for a weeks-long window; only revisit if load tests show RPC or block-production stress. |
| `max_multisig_sub_keys` | `20` | not enumerable from public state — multisig pubkeys are only revealed on-chain after the group's first tx | **Keep `20`** unless the internal key inventory knows of a larger foundation/treasury multisig; confirm against that inventory rather than chain state. |
Expand Down Expand Up @@ -156,7 +156,7 @@ The short list that genuinely needs a value chosen before tagging is the `x/evmi
| Parameter / decision | Current code default | Required decision before tag | Why it matters | Verification |
| --- | --- | --- | --- | --- |
| `enable_migration` | `true` | **Decided: immediate-open.** Keep `enable_migration=true`; the upgrade handler leaves it enabled so migration opens at the first post-upgrade block | if `true`, migration can open as soon as the upgraded chain produces blocks | `lumerad q evmigration params` immediately after upgrade |
| `migration_end_time` | `0` meaning no deadline | **Decided:** auto-set by the upgrade handler from upgrade-block time — devnet + 2 days, testnet + 7 days; mainnet left `0` by the handler and given a specific absolute Unix timestamp (~+120 days) chosen near launch | an unlimited window extends support and proof-replay risk indefinitely | query params and compare the Unix timestamp to the public migration-window announcement |
| `migration_end_time` | `0` meaning no deadline | **Decided:** auto-set by the upgrade handler from upgrade-block time — devnet + 2 days, testnet and mainnet + 3 calendar months; no network is left at `0` | an unlimited window extends support and proof-replay risk indefinitely | query params and compare the Unix timestamp to the public migration-window announcement |
| `max_migrations_per_block` | `50` | **Decided: keep `50`** — adequate per-block claim throttle; revisit only if RC/devnet load tests show stress | too high can stress blocks/RPC; too low can create user backlog | run migration traffic at the proposed limit alongside normal Cosmos/EVM traffic |
| `max_validator_delegations` | `2500` | **Decided & shipped: `2500`** (raised from 2000) — clears the live worst case of 1,632 migration objects (`chain-helper.sh` `max_observed: 1632`) with ~1.53× headroom; `3000` judged over-provisioned given the short runway | validator migrations iterate delegations, unbondings, and redelegations; under-sizing blocks large validators, over-sizing increases per-tx work | run `scripts/chain-helper.sh max-validator-delegations --json` (works today in `staking-pre-evm` mode) and confirm the chosen cap exceeds the observed maximum plus buffer |
| `max_multisig_sub_keys` | `20` | **Decided: keep `20`** — confirm against the internal key inventory rather than chain state | larger values increase proof size, gas, and coordinator complexity | rehearse multisig migrations at or near the proposed ceiling and verify ante / keeper rejection above it |
Expand All @@ -172,7 +172,7 @@ Before testnet and mainnet, choose one of these activation policies and test tha
- **Immediate-open policy**: migration is available immediately after the upgrade. This is operationally simpler, but public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart.
- **Controlled-open policy**: migration remains disabled until post-upgrade smoke tests pass. This requires an implementation or governance path that is already effective at upgrade time; a normal post-upgrade parameter proposal is too slow to prevent an initial open window.

**Decision:** Lumera uses the **immediate-open policy** — `enable_migration=true` at the first post-upgrade block — paired with a finite `migration_end_time` (a specific absolute Unix timestamp in seconds, compared against block time). On **devnet and testnet** the `v1.20.0` upgrade handler sets this automatically from the upgrade-block time (devnet + **2 days**, testnet + **7 days**). On **mainnet** the handler leaves it `0`; a specific absolute timestamp (~**120 days** past the upgrade) is chosen and applied near launch. Because migration opens immediately, public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart.
**Decision:** Lumera uses the **immediate-open policy** — `enable_migration=true` at the first post-upgrade block — paired with a finite `migration_end_time` (a specific absolute Unix timestamp in seconds, compared against block time). The `v1.20.0` upgrade handler sets this automatically from the upgrade-block time on every recognized network: devnet + **2 days**, and **testnet and mainnet + 3 calendar months** (`AddDate(0, 3, 0)`). Because migration opens immediately, public messaging, support staffing, RPC capacity, and migration monitoring must be live before validators restart.

Mainnet should not rely on an assumed manual "open the window" step; the release candidate must prove that `enable_migration=true` and the intended `migration_end_time` are already in state at the first post-upgrade block.

Expand Down
Binary file modified docs/evm-integration/assets/evmigration-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-13.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-15.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-17.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-18.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-19.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-21.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-22.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-23.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/evm-integration/assets/evmigration-25.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/evm-integration/assets/evmigration-4ex.png
Binary file not shown.
Binary file modified docs/evm-integration/assets/evmigration-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/evm-integration/assets/evmigration-9.png
Loading
Loading