Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8fd01da
fix(evmigration): enforce strict supernode ownership lookup
mateeullahmalik Jul 28, 2026
831adfe
fix(evmigration): preflight strict ownership before execution
mateeullahmalik Jul 28, 2026
744cfcb
fix(evmigration): preserve dual supernode relationships
mateeullahmalik Jul 28, 2026
ab5abbf
fix(evmigration): compare canonical supernode identities
mateeullahmalik Jul 28, 2026
e56dbf7
app: register v1.20.2 migration-only upgrade handler
mateeullahmalik Jul 28, 2026
022ac6f
fix(evmigration): reject destination supernode ownership collisions
mateeullahmalik Jul 28, 2026
4987917
test(devnet): isolate recursive make dry runs
mateeullahmalik Jul 28, 2026
a79d362
Revert "app: register v1.20.2 migration-only upgrade handler"
mateeullahmalik Jul 28, 2026
2132a0d
fix(evmigration): preserve Everlight distribution state across valida…
mateeullahmalik Aug 1, 2026
198a3ee
test(evmigration): cover continuity plan in validator migration mocks
mateeullahmalik Aug 1, 2026
b1db325
app: register v1.20.2 as the evmigration consensus activation boundary
mateeullahmalik Aug 1, 2026
1c08b89
test(upgrades): prove v1.20.2 on both live arrival shapes
mateeullahmalik Aug 1, 2026
d5a748b
test(upgrades): pin v1.20.2 module versions against live chain state
mateeullahmalik Aug 1, 2026
2acc17f
devnet+docs: add v1.20.2 upgrade target and fold operator runbook fin…
mateeullahmalik Aug 4, 2026
e357c6e
devnet: fix two bugs that prevented lumera-uploader from ever starting
mateeullahmalik Aug 4, 2026
94ce613
devnet: fix upgrade halt detection reporting a false alarm on a healt…
mateeullahmalik Aug 4, 2026
97f696d
feat(feemarket): raise base fee fivefold
akobrin1 Aug 4, 2026
37a4721
devnet: add mainnet-shaped pre-EVM config + genesis for one-hop rehea…
mateeullahmalik Aug 4, 2026
7709d58
devnet: add v1.20.1-shaped EVM genesis for a non-vacuous feemarket gate
mateeullahmalik Aug 4, 2026
d3f97e6
fix(tests): derive EVM fee expectations from config after 5x base-fee…
mateeullahmalik Aug 5, 2026
a95ed37
fix(tests): address Copilot review — real upgrade wiring, realistic f…
mateeullahmalik Aug 5, 2026
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
14 changes: 13 additions & 1 deletion .github/workflows/consensus-determinism.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ jobs:
OUT="$WORK/testnet"
CHAIN_ID="testing"

# Derive the gas price from the chain's own feemarket default rather
# than hardcoding a fee. The global minimum fee scales with the
# feemarket base fee, so a flat `--fees 500ulume` (0.0025ulume/gas at
# 200k gas) silently drops below the floor whenever the default base
# fee is retuned, and CheckTx rejects with code 13.
GAS_PRICES="$(grep -oP 'FeeMarketDefaultBaseFee\s*=\s*"\K[0-9.]+' config/evm.go)ulume"
if [ -z "${GAS_PRICES%ulume}" ]; then
echo "could not derive FeeMarketDefaultBaseFee from config/evm.go" >&2
exit 1
fi
echo "Using GAS_PRICES=${GAS_PRICES}"

mkdir -p "$WORK"
rm -rf "$OUT"

Expand Down Expand Up @@ -161,7 +173,7 @@ jobs:
--keyring-backend test \
--chain-id "$CHAIN_ID" \
--node tcp://127.0.0.1:26657 \
--fees 500ulume \
--gas-prices "${GAS_PRICES}" \
--broadcast-mode sync \
--yes -o json > "$WORK/${tag}.json"

Expand Down
15 changes: 15 additions & 0 deletions Makefile.devnet
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,21 @@ devnet-upgrade-1201:
@$(MAKE) devnet-refresh-bin
@cd devnet/scripts && ./upgrade.sh v1.20.1 auto-height ../bin

# v1.20.2 — same locally-built-binary pattern as devnet-upgrade-1201, because
# v1.20.2 has no published release to pre-download. Drives the coordinated
# governance halt + binary swap, which is MANDATORY for this upgrade: v1.20.2
# changes evmigration DeliverTx outcomes (PrevSupernodeAccounts append vs
# rewrite, canonical ownership resolution, Everlight SNDistState move), so a
# rolling node-by-node restart would fork the network.
#
# Serves BOTH rehearsal shapes from one target, since the handler is
# state-driven rather than chain-id-driven:
# testnet-shaped 1.20.1 -> 1.20.2 migrations only (EVM already present)
# mainnet-shaped 1.12.0 -> 1.20.2 full EVM bring-up + add-only store mount
devnet-upgrade-1202:
@$(MAKE) devnet-refresh-bin
@cd devnet/scripts && ./upgrade.sh v1.20.2 auto-height ../bin

devnet-new-1120:
@$(MAKE) devnet-new-version VERSION=v1.12.0

Expand Down
18 changes: 12 additions & 6 deletions app/upgrades/store_loader_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
upgrade_v1_10_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_10_1"
upgrade_v1_11_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_11_1"
upgrade_v1_20_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_1"
upgrade_v1_20_2 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_2"
)

type StoreLoaderSelection struct {
Expand All @@ -29,12 +30,17 @@ func StoreLoaderForUpgrade(
logger log.Logger,
adaptive bool,
) StoreLoaderSelection {
// v1.20.1 always uses the add-only store loader, on every network and
// regardless of the adaptive-store-manager env flag. It mounts the declared
// EVM store keys that are absent from committed state and never deletes a
// store, so it is safe on mainnet and a no-op on chains that already ran
// v1.20.0. See the v1.20.1 case in SetupUpgrades.
if upgradeName == upgrade_v1_20_1.UpgradeName {
// v1.20.1 and v1.20.2 always use the add-only store loader, on every network
// and regardless of the adaptive-store-manager env flag. It mounts the
// declared EVM store keys that are absent from committed state and never
// deletes a store, so it is safe on mainnet and a no-op on chains that
// already ran v1.20.0. See the matching cases in SetupUpgrades.
//
// v1.20.2 must be listed here explicitly: it is the arrival point for a
// direct 1.12.0 -> 1.20.2 one-hop, and without the add-only loader that
// upgrade panics at load with "version of store evmigration mismatch root
// store's version; expected N got 0".
if upgradeName == upgrade_v1_20_1.UpgradeName || upgradeName == upgrade_v1_20_2.UpgradeName {
return StoreLoaderSelection{
Loader: AddOnlyStoreLoader(upgradeHeight, baseUpgrades, logger),
LogLabel: "add-only EVM bring-up",
Expand Down
18 changes: 18 additions & 0 deletions app/upgrades/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
upgrade_v1_12_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_12_0"
upgrade_v1_20_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_0"
upgrade_v1_20_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_1"
upgrade_v1_20_2 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_2"
upgrade_v1_6_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_6_1"
upgrade_v1_8_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_8_0"
upgrade_v1_8_4 "github.com/LumeraProtocol/lumera/app/upgrades/v1_8_4"
Expand All @@ -43,6 +44,7 @@ import (
// | v1.12.0 | custom | none (Everlight in supernode) | Runs migrations; Everlight logic embedded in x/supernode
// | v1.20.0 | custom | non-mainnet: add feemarket, precisebank, vm, erc20 | EVM bring-up; gated to non-mainnet (mainnet runs it via v1.20.1)
// | v1.20.1 | custom | state-driven add-only: feemarket, precisebank, vm, erc20 | EVM bring-up when EVM absent (any network, incl. direct 1.12.0->1.20.1); migrations-only hotfix when EVM already present. Add-only store loader mounts only missing keys.
// | v1.20.2 | custom | state-driven add-only: same EVM set as v1.20.1 | Consensus activation boundary for the evmigration ownership/continuity fixes. Brings up EVM when absent; otherwise runs migrations; both paths apply the configured feemarket base fee. No store migration or consensus-version bump.
// =================================================================================================================================

type UpgradeConfig struct {
Expand Down Expand Up @@ -75,6 +77,7 @@ var upgradeNames = []string{
upgrade_v1_12_0.UpgradeName,
upgrade_v1_20_0.UpgradeName,
upgrade_v1_20_1.UpgradeName,
upgrade_v1_20_2.UpgradeName,
}

var NoUpgradeConfig = UpgradeConfig{
Expand Down Expand Up @@ -165,6 +168,21 @@ func SetupUpgrades(upgradeName string, params appParams.AppUpgradeParams) (Upgra
StoreUpgrade: &upgrade_v1_20_0.StoreUpgrades,
Handler: upgrade_v1_20_0.CreateUpgradeHandler(params),
}, true
case upgrade_v1_20_2.UpgradeName:
// v1.20.2 is the coordinated halt that activates the evmigration
// SuperNode-ownership and Everlight-continuity fixes. Those change
// DeliverTx results for the same migration tx, so the binary must not
// be rolled out node-by-node while migrations can execute.
//
// It declares the same EVM store additions as v1.20.1 for the same
// reason: the add-only store loader mounts only the keys missing from
// committed state, making this a no-op on testnet (already on 1.20.1)
// and the full EVM mount on a direct 1.12.0 one-hop from mainnet.
return UpgradeConfig{
StoreUpgrade: &upgrade_v1_20_0.StoreUpgrades,
Handler: upgrade_v1_20_2.CreateUpgradeHandler(params),
}, true

case upgrade_v1_20_1.UpgradeName:
// v1.20.1 carries the EVM bring-up based on chain STATE, not chain-id.
// It declares the same EVM store additions as v1.20.0 on every network;
Expand Down
69 changes: 61 additions & 8 deletions app/upgrades/upgrades_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import (
upgrade_v1_12_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_12_0"
upgrade_v1_20_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_0"
upgrade_v1_20_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_1"
upgrade_v1_20_2 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_2"
upgrade_v1_6_1 "github.com/LumeraProtocol/lumera/app/upgrades/v1_6_1"
upgrade_v1_8_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_8_0"
upgrade_v1_8_4 "github.com/LumeraProtocol/lumera/app/upgrades/v1_8_4"
upgrade_v1_9_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_9_0"
actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types"
evmigrationtypes "github.com/LumeraProtocol/lumera/x/evmigration/types"
crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types"
erc20types "github.com/cosmos/evm/x/erc20/types"
feemarkettypes "github.com/cosmos/evm/x/feemarket/types"
Expand All @@ -47,6 +49,7 @@ func TestUpgradeNamesOrder(t *testing.T) {
upgrade_v1_12_0.UpgradeName,
upgrade_v1_20_0.UpgradeName,
upgrade_v1_20_1.UpgradeName,
upgrade_v1_20_2.UpgradeName,
}
require.Equal(t, expected, upgradeNames, "upgradeNames should stay in ascending order")
}
Expand Down Expand Up @@ -103,18 +106,21 @@ func TestSetupUpgradesAndHandlers(t *testing.T) {
}

// Custom upgrades that need keepers are skipped in this lightweight harness.
// v1.20.1 is state-driven: with an empty fromVM (no EVM module) it runs
// the full v1.20.0 EVM bring-up on ANY network, which needs keepers, so
// it is skipped here on all networks (the bring-up path is exercised by
// v1_20_0/upgrade_test.go and the migration-only path by a dedicated test).
// v1.20.1 and v1.20.2 are state-driven: with an empty fromVM (no EVM
// module) they run the full v1.20.0 EVM bring-up on ANY network, which
// needs keepers, so they are skipped here on all networks (the bring-up
// path is exercised by v1_20_0/upgrade_test.go, and the v1.20.2 arrival
// shapes and store wiring by TestV1202RegisteredOnAllNetworks and
// app/upgrades/v1_20_2/upgrade_test.go).
if upgradeName == upgrade_v1_9_0.UpgradeName ||
upgradeName == upgrade_v1_10_0.UpgradeName ||
upgradeName == upgrade_v1_10_1.UpgradeName ||
upgradeName == upgrade_v1_11_0.UpgradeName ||
upgradeName == upgrade_v1_11_1.UpgradeName ||
upgradeName == upgrade_v1_12_0.UpgradeName ||
upgradeName == upgrade_v1_20_0.UpgradeName ||
upgradeName == upgrade_v1_20_1.UpgradeName {
upgradeName == upgrade_v1_20_1.UpgradeName ||
upgradeName == upgrade_v1_20_2.UpgradeName {
continue
}

Expand Down Expand Up @@ -225,6 +231,52 @@ func TestV1201CarriesEVMBringupOnAllNetworks(t *testing.T) {
}
}

// TestV1202RegisteredOnAllNetworks pins v1.20.2 as the coordinated consensus
// activation boundary for the evmigration ownership/continuity fixes. Those
// fixes change DeliverTx results for the same migration transaction, so every
// network must halt and switch binaries together -- there is no network where
// this upgrade may be skipped or rolled out node-by-node.
func TestV1202RegisteredOnAllNetworks(t *testing.T) {
for _, chainID := range []string{"lumera-mainnet-1", "lumera-testnet-2", "lumera-devnet-1"} {
params := newTestUpgradeParams(chainID)
config, found := SetupUpgrades(upgrade_v1_20_2.UpgradeName, params)
require.True(t, found, "v1.20.2 must be a known upgrade on %s", chainID)
require.NotNil(t, config.Handler, "v1.20.2 must register a handler on %s", chainID)

// Unlike the reverted migration-only draft, v1.20.2 MUST declare store
// upgrades: mainnet arrives from 1.12.0 with no EVM stores mounted, and
// a nil StoreUpgrade there panics at load.
require.NotNil(t, config.StoreUpgrade, "v1.20.2 must declare store upgrades on %s", chainID)
require.Contains(t, config.StoreUpgrade.Added, evmigrationtypes.StoreKey,
"v1.20.2 must mount the evmigration store on %s", chainID)
require.Contains(t, config.StoreUpgrade.Added, feemarkettypes.StoreKey)
require.Contains(t, config.StoreUpgrade.Added, precisebanktypes.StoreKey)
require.Contains(t, config.StoreUpgrade.Added, evmtypes.StoreKey)
require.Contains(t, config.StoreUpgrade.Added, erc20types.StoreKey)
require.Empty(t, config.StoreUpgrade.Deleted, "v1.20.2 must not delete any store on %s", chainID)
}
}

// TestV1202UsesAddOnlyStoreLoader guards the routing that makes the mainnet
// one-hop survivable. Without the add-only loader a direct 1.12.0 -> 1.20.2
// upgrade panics with "version of store evmigration mismatch root store's
// version; expected N got 0", which was observed on a mainnet-shaped devnet.
func TestV1202UsesAddOnlyStoreLoader(t *testing.T) {
for _, adaptive := range []bool{false, true} {
selection := StoreLoaderForUpgrade(
upgrade_v1_20_2.UpgradeName,
100,
&upgrade_v1_20_0.StoreUpgrades,
nil,
log.NewNopLogger(),
adaptive,
)
require.NotNil(t, selection.Loader)
require.Equal(t, "add-only EVM bring-up", selection.LogLabel,
"v1.20.2 must use the add-only store loader regardless of adaptive=%v", adaptive)
}
}

func newTestUpgradeParams(chainID string) appParams.AppUpgradeParams {
return appParams.AppUpgradeParams{
ChainID: chainID,
Expand Down Expand Up @@ -263,9 +315,10 @@ func expectStoreUpgrade(upgradeName, chainID string) bool {
case upgrade_v1_20_0.UpgradeName:
// EVM stores are added by v1.20.0 only on the networks that run it.
return !IsMainnet(chainID)
case upgrade_v1_20_1.UpgradeName:
// v1.20.1 declares the EVM store additions on every network; the add-only
// store loader mounts only the keys missing from committed state.
case upgrade_v1_20_1.UpgradeName, upgrade_v1_20_2.UpgradeName:
// v1.20.1 and v1.20.2 declare the EVM store additions on every network;
// the add-only store loader mounts only the keys missing from committed
// state, so this is a no-op where the stores already exist.
return true
default:
return false
Expand Down
Loading
Loading