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
112 changes: 108 additions & 4 deletions app/upgrades/v1_12_0/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,48 @@ import (

consensusparams "github.com/LumeraProtocol/lumera/app/upgrades/internal/consensusparams"
appParams "github.com/LumeraProtocol/lumera/app/upgrades/params"
audittypes "github.com/LumeraProtocol/lumera/x/audit/v1/types"
)

// UpgradeName is the on-chain name used for this upgrade.
const UpgradeName = "v1.12.0"

// CreateUpgradeHandler runs the migration that initializes the supernode
// module's embedded Everlight configuration.
// Activation-time policy constants.
//
// These are written exactly once during the upgrade; thereafter, params are
// governed by MsgUpdateParams.
const (
// everlightEnabled is informational: Everlight payout logic activates as
// soon as supernode params carry a non-zero RewardDistribution. We persist
// the default RewardDistribution explicitly in this handler so the very
// first post-upgrade query returns the canonical values.
everlightEnabled = true

// storageTruthEnforcementMode is the LEP-6 enforcement mode burned in at
// activation. SHADOW means evidence is collected but no enforcement actions
// (postpone, slash) are taken. Future modes (SOFT, FULL) require an
// explicit governance MsgUpdateParams.
storageTruthEnforcementMode = audittypes.StorageTruthEnforcementMode_STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW
)

// CreateUpgradeHandler runs migrations and persists default values for params
// and per-chain anchors introduced after v1.11.1-hotfix:
// - LEP-5 (action): SvcChallengeCount, SvcMinChunksForChallenge.
// - Everlight + LEP-4 (supernode): RewardDistribution, MetricsUpdateIntervalBlocks,
// MetricsGracePeriodBlocks, MetricsFreshnessMaxBlocks, MinSupernodeVersion,
// MinCpu/Mem/Storage, MaxCpu/Mem/StorageUsagePercent, RequiredOpenPorts.
// - LEP-6 (audit): StorageTruth* params and explicit enforcement mode (SHADOW).
//
// Module ConsensusVersion is unchanged for action and supernode (purely
// additive params with safe defaults). x/audit's v1→v2 migration is invoked by
// RunMigrations and bumps KeepLastEpochEntries to cover new windows.
//
// Per-chain anchors:
// - Anchors LastDistributionHeight to the upgrade height so the first
// Everlight payout fires one PaymentPeriodBlocks after activation, not on
// the very next block.
// - Calls SupernodeKeeper.EnsureModuleAccount so the supernode ModuleAccount
// is materialised with its updated permissions (Minter+Burner+Staking).
func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHandler {
return func(goCtx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
p.Logger.Info(fmt.Sprintf("Starting upgrade %s...", UpgradeName))
Expand All @@ -28,8 +63,8 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand
return nil, err
}

// Run all module migrations after consensus params have been verified.
// This triggers Everlight's InitGenesis which sets default params.
// RunMigrations triggers x/audit v1→v2 migration (KeepLastEpochEntries
// floor) and any additive InitGenesis for unseen modules.
p.Logger.Info("Running module migrations...")
newVM, err := p.ModuleManager.RunMigrations(ctx, p.Configurator, fromVM)
if err != nil {
Expand All @@ -38,6 +73,75 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand
}
p.Logger.Info("Module migrations completed.")

// The action, supernode, and audit keepers are required by this handler.
// Fail loudly on any wiring regression rather than nil-deref mid-upgrade.
if p.ActionKeeper == nil {
return nil, fmt.Errorf("%s upgrade requires action keeper to be wired", UpgradeName)
}
if p.SupernodeKeeper == nil {
return nil, fmt.Errorf("%s upgrade requires supernode keeper to be wired", UpgradeName)
}
if p.AuditKeeper == nil {
return nil, fmt.Errorf("%s upgrade requires audit keeper to be wired", UpgradeName)
}

// 1) Persist LEP-5 action params defaults.
// keeper.GetParams() now applies WithDefaults() on read and SetParams
// applies it on write — calling SetParams here burns the canonical
// values into the on-disk blob so external param queries return them
// without waiting for the next governance write.
actionParams := p.ActionKeeper.GetParams(ctx)
if err := p.ActionKeeper.SetParams(ctx, actionParams); err != nil {
return nil, fmt.Errorf("persist action params with defaults: %w", err)
}
p.Logger.Info("Action params persisted with LEP-5 defaults",
"svc_challenge_count", actionParams.SvcChallengeCount,
"svc_min_chunks_for_challenge", actionParams.SvcMinChunksForChallenge,
)

// 2) Persist Everlight + LEP-4 supernode params defaults.
snParams := p.SupernodeKeeper.GetParams(ctx).WithDefaults()
if err := p.SupernodeKeeper.SetParams(ctx, snParams); err != nil {
return nil, fmt.Errorf("persist supernode params with defaults: %w", err)
}
if snParams.RewardDistribution != nil {
p.Logger.Info("Supernode reward distribution params persisted",
"payment_period_blocks", snParams.RewardDistribution.PaymentPeriodBlocks,
"registration_fee_share_bps", snParams.RewardDistribution.RegistrationFeeShareBps,
"min_cascade_bytes_for_payment", snParams.RewardDistribution.MinCascadeBytesForPayment,
"everlight_enabled", everlightEnabled,
)
}

// 3) Anchor Everlight distribution clock at upgrade height so the
// first payout fires one PaymentPeriodBlocks AFTER the upgrade, not on
// the next block. Without this, currentHeight - lastDistHeight (=0)
// >= PaymentPeriodBlocks on every chain past PaymentPeriod blocks tall,
// triggering an immediate post-upgrade distribution.
p.SupernodeKeeper.SetLastDistributionHeight(ctx, ctx.BlockHeight())
p.Logger.Info("Anchored Everlight last distribution height",
"height", ctx.BlockHeight(),
)

// 4) Materialise the supernode ModuleAccount so it carries the
// updated permissions (Minter+Burner+Staking) declared in app_config
// instead of a stale BaseAccount or pre-Everlight ModuleAccount entry.
p.SupernodeKeeper.EnsureModuleAccount(ctx)
p.Logger.Info("Ensured supernode ModuleAccount with updated permissions")

// 5) LEP-6 (audit) — burn explicit StorageTruth enforcement mode at
// activation. WithDefaults intentionally does NOT promote UNSPECIFIED
// to SHADOW, so we set it here explicitly. The audit v1→v2 migration
// already ran via RunMigrations and applied StorageTruth* defaults.
auditParams := p.AuditKeeper.GetParams(ctx)
auditParams.StorageTruthEnforcementMode = storageTruthEnforcementMode
if err := p.AuditKeeper.SetParams(ctx, auditParams); err != nil {
return nil, fmt.Errorf("set audit storage_truth_enforcement_mode: %w", err)
}
p.Logger.Info("Audit storage-truth enforcement mode set",
"mode", storageTruthEnforcementMode.String(),
)

p.Logger.Info(fmt.Sprintf("Successfully completed upgrade %s", UpgradeName))
return newVM, nil
}
Expand Down
15 changes: 15 additions & 0 deletions app/upgrades/v1_12_0/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

appParams "github.com/LumeraProtocol/lumera/app/upgrades/params"
audittypes "github.com/LumeraProtocol/lumera/x/audit/v1/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -73,3 +74,17 @@ func TestCreateUpgradeHandlerReturnsNonNil(t *testing.T) {
require.NotNil(t, handler,
"CreateUpgradeHandler should return a non-nil upgrade handler function")
}

// ---------------------------------------------------------------------------
// Activation policy constants — pin so future changes are intentional and
// surface in code review.
// ---------------------------------------------------------------------------

func TestActivationPolicyConstants(t *testing.T) {
require.True(t, everlightEnabled,
"everlightEnabled must be true at activation; Everlight defaults are persisted")
require.Equal(t,
audittypes.StorageTruthEnforcementMode_STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW,
storageTruthEnforcementMode,
"LEP-6 enforcement mode at activation must be SHADOW")
}
3 changes: 2 additions & 1 deletion x/action/v1/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ func (k *Keeper) GetParams(ctx context.Context) (params types.Params) {
}

k.cdc.MustUnmarshal(bz, &params)
return params
return params.WithDefaults()
}

// SetParams set the params
func (k *Keeper) SetParams(ctx context.Context, params types.Params) error {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
params = params.WithDefaults()
bz, err := k.cdc.Marshal(&params)
if err != nil {
return err
Expand Down
19 changes: 19 additions & 0 deletions x/action/v1/types/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ func DefaultParams() Params {
)
}

// WithDefaults returns a copy of the params with any zero-value fields populated
// from module defaults. Older genesis blobs and on-chain params written before
// new fields existed (e.g. LEP-5 SVC params) read back as zero; reading via
// WithDefaults keeps behaviour consistent without requiring a ConsensusVersion
// bump for purely additive params with safe defaults.
//
// Note: WithDefaults does NOT call Validate. Callers that want strict
// 0-as-unset semantics (genesis init, MsgUpdateParams) should apply
// WithDefaults() before Validate().
func (p Params) WithDefaults() Params {
if p.SvcChallengeCount == 0 {
p.SvcChallengeCount = DefaultSVCChallengeCount
}
if p.SvcMinChunksForChallenge == 0 {
p.SvcMinChunksForChallenge = DefaultSVCMinChunksForChallenge
}
Comment thread
a-ok123 marked this conversation as resolved.
return p
Comment thread
a-ok123 marked this conversation as resolved.
}

// ParamSetPairs get the params.ParamSet
func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs {
return paramtypes.ParamSetPairs{
Expand Down
34 changes: 34 additions & 0 deletions x/action/v1/types/params_with_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package types_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/LumeraProtocol/lumera/x/action/v1/types"
)

// TestParamsWithDefaults_FillsZeroSVCFields ensures that an older params blob
// (read back as zero-valued SVC fields) is normalised by WithDefaults to the
// LEP-5 defaults. This is the soft-compat path used by keeper.GetParams and
// SetParams to avoid a ConsensusVersion bump for purely additive params.
func TestParamsWithDefaults_FillsZeroSVCFields(t *testing.T) {
in := types.Params{} // zero values everywhere
out := in.WithDefaults()
require.Equal(t, types.DefaultSVCChallengeCount, out.SvcChallengeCount,
"WithDefaults must populate SvcChallengeCount when zero")
require.Equal(t, types.DefaultSVCMinChunksForChallenge, out.SvcMinChunksForChallenge,
"WithDefaults must populate SvcMinChunksForChallenge when zero")
}

// TestParamsWithDefaults_PreservesNonZero ensures explicit non-zero values are
// not overwritten by WithDefaults.
func TestParamsWithDefaults_PreservesNonZero(t *testing.T) {
in := types.Params{
SvcChallengeCount: 16,
SvcMinChunksForChallenge: 8,
}
out := in.WithDefaults()
require.Equal(t, uint32(16), out.SvcChallengeCount)
require.Equal(t, uint32(8), out.SvcMinChunksForChallenge)
}
Loading
Loading