Skip to content

Commit e2dd822

Browse files
committed
fix(upgrade): v1.3.0 restore validator delegate grant only (release/1.3.x)
Mirror of the #263 review fix on release/1.3.x: drop the SP deposit grant restoration (re-granting it generically broadened authority vs the scoped DepositAuthorization, and was a no-op for the real incident) and narrow the handler to the one grant the state machine requires — validator SelfDelAddress -> gov : MsgDelegate (Generic), enforced by CheckStakeAuthorization on MsgCreateValidator. Renamed V1_3_0RestoreGovGrants -> V1_3_0RestoreValidatorDelegateGrant; SpKeeper dependency removed. Module path evmos/evmos/v12 (release/1.3.x).
1 parent 0f5f886 commit e2dd822

4 files changed

Lines changed: 45 additions & 67 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
4141

4242
- (proto) [#67](https://github.com/mocachain/moca/pull/67) Publish protos to BSR under moca org
4343
- (cli) [#243](https://github.com/mocachain/moca/pull/243) Add `mocad snapshots` command tree (list/delete/dump/export/load/restore) for managing local state-sync snapshots
44-
- (upgrade) [#266](https://github.com/mocachain/moca/pull/266) `v1.3.0` upgrade re-grants the validatorgov (`MsgDelegate`) and SP→gov (`MsgDeposit`) authz grants the moca-iavl commit-time bug dropped from the merkle tree, keyed off the canonical staking/sp stores so it is deterministic on every node
44+
- (upgrade) [#266](https://github.com/mocachain/moca/pull/266) `v1.3.0` upgrade re-grants the validator self-del → gov (`MsgDelegate`, Generic) authz grant required by `MsgCreateValidator` that the moca-iavl commit-time bug dropped from the merkle tree, keyed off the canonical staking store so it is deterministic on every node. Scope is intentionally minimal (only this required grant); other dropped grants are not reconstructable from on-chain state and owners re-create them
4545

4646
### Improvements
4747

app/app.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,17 +1559,18 @@ func (app *Evmos) setupUpgradeHandlers() {
15591559
return app.mm.RunMigrations(ctx, app.configurator, fromVM)
15601560
})
15611561

1562-
// v1.3.0: re-grant the authz grants moca's custom handlers require but that
1563-
// the moca-iavl commit-time bug dropped from the merkle tree — each
1564-
// validator's SelfDelAddress -> gov (MsgDelegate) and each SP's funding
1565-
// address -> gov (MsgDeposit) — keyed off the canonical staking/sp stores so
1566-
// the result is deterministic on every node regardless of per-node fastnode
1567-
// drift. (The fastnode drift itself is fixed by an IAVL rebuild — state-sync
1568-
// or a fastStorageVersionValue bump — not from a consensus handler.) The
1569-
// v1.3.0 binary also carries the cosmos/iavl#1009 GetNode reformatted-root fix.
1562+
// v1.3.0: re-grant the validator SelfDelAddress -> gov (MsgDelegate) authz
1563+
// grant that moca's MsgCreateValidator handler requires but that the
1564+
// moca-iavl commit-time bug dropped from the merkle tree. Keyed off the
1565+
// canonical staking store so it is deterministic on every node regardless of
1566+
// per-node fastnode drift; restored as Generic to match the standard
1567+
// create-validator flow. Other dropped grants are not reconstructable from
1568+
// on-chain state and owners re-create them; the residual fastnode drift is
1569+
// fixed by an IAVL rebuild (state-sync / fastStorageVersionValue bump), not
1570+
// from a consensus handler. The v1.3.0 binary also carries cosmos/iavl#1009.
15701571
app.UpgradeKeeper.SetUpgradeHandler(
15711572
upgrades.V1_3_0UpgradeName,
1572-
upgrades.V1_3_0RestoreGovGrants(app.AuthzKeeper, app.StakingKeeper, app.SpKeeper, app.mm, app.configurator),
1573+
upgrades.V1_3_0RestoreValidatorDelegateGrant(app.AuthzKeeper, app.StakingKeeper, app.mm, app.configurator),
15731574
)
15741575

15751576
// testnet only upgrade Handlers

app/upgrades/v1_3_0_authz_recovery.go

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,66 +13,59 @@ import (
1313
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
1414
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
1515
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
16-
17-
spkeeper "github.com/evmos/evmos/v12/x/sp/keeper"
18-
sptypes "github.com/evmos/evmos/v12/x/sp/types"
1916
)
2017

2118
// V1_3_0UpgradeName is the on-chain name of the v1.3.0 software upgrade.
2219
const V1_3_0UpgradeName = "v1.3.0"
2320

24-
// V1_3_0RestoreGovGrants re-grants the authz grants that moca's custom handlers
25-
// require but that the moca-iavl commit-time bug dropped from the merkle tree:
21+
// V1_3_0RestoreValidatorDelegateGrant re-grants the one authz grant that moca's
22+
// state machine requires for an existing validator to keep functioning but that
23+
// the moca-iavl commit-time bug dropped from the merkle tree:
24+
//
25+
// each validator's SelfDelAddress -> gov : MsgDelegate (GenericAuthorization)
2626
//
27-
// - each validator's SelfDelAddress -> gov : MsgDelegate (Generic), required
28-
// by CheckStakeAuthorization on MsgCreateValidator
29-
// - each storage provider's FundingAddress -> gov : MsgDeposit (Generic),
30-
// required by CheckDepositAuthorization on MsgDeposit
27+
// moca's custom MsgCreateValidator handler enforces this grant via
28+
// CheckStakeAuthorization, and the standard create-validator flow grants it as a
29+
// GenericAuthorization (deployment/dockerup/create_validator_proposal.sh), so
30+
// restoring it as Generic matches the original — no behavior change.
3131
//
32-
// It is keyed off the canonical staking and sp stores (identical on every node
33-
// by consensus), so the set of SaveGrant writes is the SAME on every node
34-
// regardless of any per-node authz fastnode drift. SaveGrant's Set re-adds the
35-
// grant to the merkle tree (so a node that lost it from the tree gets it back)
36-
// and overwrites any stale fastnode entry for that exact key — healing the
37-
// drift for the keys that matter, deterministically.
32+
// Scope (deliberately minimal — this is NOT a full recovery of every dropped
33+
// authz entry): the handler reconstructs only what is derivable from the
34+
// canonical staking store, so the SaveGrant writes are identical on every node
35+
// regardless of per-node authz fastnode drift. SaveGrant's Set re-adds the
36+
// grant to the merkle tree and overwrites any stale fastnode entry for that key.
37+
// Grants whose granter is not a current validator self-delegation address — SP
38+
// deposit grants, user-to-user grants, grants from accounts that have since
39+
// unbonded — cannot be reconstructed from on-chain state and are NOT restored;
40+
// their owners re-create them with the correct authorization (e.g. SP operators
41+
// re-grant a scoped DepositAuthorization, not an unbounded generic one).
3842
//
39-
// Why this does NOT purge the authz store: a 2-node test proved that a handler
40-
// CANNOT safely clear the store. The only enumeration reachable from a handler
41-
// is the store iterator, which reads the IAVL fastnode index — not the tree. A
42-
// node whose fastnode is *missing* a tree-backed key (fastnode < tree) never
43-
// yields that key, so a purge keyed off the iterator skips it; the key survives
44-
// in that node's tree while other nodes delete it -> divergent apphash -> fork.
45-
// The fastnode drift itself is a node-local concern and is fixed the right way
46-
// by an IAVL fastnode rebuild (state-sync, or bumping fastStorageVersionValue),
47-
// which rebuilds the fastnode from the canonical tree and so covers both drift
48-
// directions without any consensus risk.
43+
// The residual fastnode drift on those non-restored keys is node-local and is
44+
// fixed by an IAVL fastnode rebuild (state-sync, or bumping fastStorageVersionValue),
45+
// which rebuilds the fastnode from the canonical tree and covers both drift
46+
// directions without consensus risk.
4947
//
50-
// Determinism notes: the restored grants are Generic with no expiration (the
51-
// standard moca setup grant — see deployment/dockerup/create_validator_proposal.sh),
52-
// so SaveGrant touches no grant-queue state and its result does not depend on
53-
// reading the drifted store. The handler runs at block level on the infinite
54-
// gas meter. MUST run on the v1.3.0 binary (carries cosmos/iavl#1009).
55-
func V1_3_0RestoreGovGrants(
48+
// Determinism notes: the restored grant is Generic with no expiration, so
49+
// SaveGrant touches no grant-queue state and does not depend on reading the
50+
// drifted store. The handler runs at block level on the infinite gas meter.
51+
// MUST run on the v1.3.0 binary (carries cosmos/iavl#1009).
52+
func V1_3_0RestoreValidatorDelegateGrant(
5653
authzKeeper authzkeeper.Keeper,
5754
stakingKeeper *stakingkeeper.Keeper,
58-
spKeeper spkeeper.Keeper,
5955
mm *module.Manager,
6056
configurator module.Configurator,
6157
) upgradetypes.UpgradeHandler {
6258
return func(ctx context.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
6359
sdkCtx := sdk.UnwrapSDKContext(ctx)
6460
logger := sdkCtx.Logger().With("upgrade", V1_3_0UpgradeName)
6561
govAddr := authtypes.NewModuleAddress(govtypes.ModuleName)
66-
6762
delegateMsg := sdk.MsgTypeURL(&stakingtypes.MsgDelegate{})
68-
depositMsg := sdk.MsgTypeURL(&sptypes.MsgDeposit{})
6963

70-
// validator self-delegation account -> gov : MsgDelegate
7164
vals, err := stakingKeeper.GetAllValidators(ctx)
7265
if err != nil {
7366
return nil, fmt.Errorf("authz restore: list validators: %w", err)
7467
}
75-
valGrants := 0
68+
granted := 0
7669
for _, v := range vals {
7770
granter, err := sdk.AccAddressFromHexUnsafe(v.SelfDelAddress)
7871
if err != nil || granter.Empty() {
@@ -83,26 +76,10 @@ func V1_3_0RestoreGovGrants(
8376
if err := authzKeeper.SaveGrant(ctx, govAddr, granter, authz.NewGenericAuthorization(delegateMsg), nil); err != nil {
8477
return nil, fmt.Errorf("authz restore: grant MsgDelegate for %s: %w", v.SelfDelAddress, err)
8578
}
86-
valGrants++
87-
}
88-
89-
// storage provider funding account -> gov : MsgDeposit
90-
spGrants := 0
91-
for _, sp := range spKeeper.GetAllStorageProviders(sdkCtx) {
92-
granter, err := sdk.AccAddressFromHexUnsafe(sp.FundingAddress)
93-
if err != nil || granter.Empty() {
94-
logger.Error("authz restore: skip sp with bad funding addr",
95-
"sp", sp.Id, "funding", sp.FundingAddress, "err", err)
96-
continue
97-
}
98-
if err := authzKeeper.SaveGrant(ctx, govAddr, granter, authz.NewGenericAuthorization(depositMsg), nil); err != nil {
99-
return nil, fmt.Errorf("authz restore: grant MsgDeposit for %s: %w", sp.FundingAddress, err)
100-
}
101-
spGrants++
79+
granted++
10280
}
10381

104-
logger.Info("authz restore: re-granted required gov grants",
105-
"validator_delegate", valGrants, "sp_deposit", spGrants)
82+
logger.Info("authz restore: re-granted validator delegate grants", "count", granted)
10683
return mm.RunMigrations(ctx, configurator, fromVM)
10784
}
10885
}

app/upgrades/v1_3_0_authz_recovery_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func mustAcc(t *testing.T, hexAddr string) sdk.AccAddress {
2727

2828
// The handler must re-grant each validator's SelfDelAddress -> gov : MsgDelegate
2929
// (Generic), and leave unrelated grants untouched.
30-
func TestV1_3_0RestoreGovGrants(t *testing.T) {
30+
func TestV1_3_0RestoreValidatorDelegateGrant(t *testing.T) {
3131
const chainID = "moca_5151-1"
3232
mocaApp := app.Setup(false, feemarkettypes.DefaultGenesisState(), chainID)
3333
sdkCtx := mocaApp.BaseApp.NewContext(false).WithChainID(chainID)
@@ -57,8 +57,8 @@ func TestV1_3_0RestoreGovGrants(t *testing.T) {
5757

5858
mm := module.NewManager()
5959
configurator := module.NewConfigurator(mocaApp.AppCodec(), mocaApp.MsgServiceRouter(), mocaApp.GRPCQueryRouter())
60-
handler := upgrades.V1_3_0RestoreGovGrants(
61-
mocaApp.AuthzKeeper, mocaApp.StakingKeeper, mocaApp.SpKeeper, mm, configurator)
60+
handler := upgrades.V1_3_0RestoreValidatorDelegateGrant(
61+
mocaApp.AuthzKeeper, mocaApp.StakingKeeper, mm, configurator)
6262
_, err = handler(ctx, upgradetypes.Plan{Name: upgrades.V1_3_0UpgradeName}, module.VersionMap{})
6363
require.NoError(t, err)
6464

0 commit comments

Comments
 (0)