From 32f38d3f3213ccb641e6c8336ad92ecb574d8890 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 00:08:50 +0000 Subject: [PATCH 01/18] feat(supernode): plan validator continuity state migration --- .../v1/keeper/validator_state_migration.go | 320 ++++++++++++++++++ .../keeper/validator_state_migration_test.go | 286 ++++++++++++++++ .../v1/types/identity_migration_plan.go | 138 ++++++++ 3 files changed, 744 insertions(+) create mode 100644 x/supernode/v1/keeper/validator_state_migration.go create mode 100644 x/supernode/v1/keeper/validator_state_migration_test.go create mode 100644 x/supernode/v1/types/identity_migration_plan.go diff --git a/x/supernode/v1/keeper/validator_state_migration.go b/x/supernode/v1/keeper/validator_state_migration.go new file mode 100644 index 00000000..36428f5c --- /dev/null +++ b/x/supernode/v1/keeper/validator_state_migration.go @@ -0,0 +1,320 @@ +package keeper + +import ( + "bytes" + "encoding/json" + "fmt" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/LumeraProtocol/lumera/x/supernode/v1/types" +) + +// IdentityMigrationRDistScanLimit bounds the textual rdist namespace scan. +// Build accepts exactly this many rows and rejects the cap+1 row. +const IdentityMigrationRDistScanLimit = 10_000 + +// BuildIdentityMigrationPlan validates SuperNode ownership integrity and +// snapshots the continuity state owned by this module. Primary records and +// account indexes are validation-only: PR196 owns moving those state families. +// This plan writes only latest metrics and Everlight SNDistState. +func (k Keeper) BuildIdentityMigrationPlan( + ctx sdk.Context, + sourceValidator sdk.ValAddress, + destinationValidator sdk.ValAddress, +) (types.IdentityMigrationPlan, error) { + if len(sourceValidator) == 0 || len(destinationValidator) == 0 { + return nil, fmt.Errorf("source and destination validator addresses must be non-empty") + } + if sourceValidator.Equals(destinationValidator) { + return nil, fmt.Errorf("source and destination validator addresses must differ") + } + + // Own the caller's slice-backed addresses before deriving any plan data. + sourceValidator = sdk.ValAddress(bytes.Clone(sourceValidator)) + destinationValidator = sdk.ValAddress(bytes.Clone(destinationValidator)) + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + + sourcePrimaryKey := types.GetSupernodeKey(sourceValidator) + sourceSN, sourceSNFound, err := k.strictReadMigrationSuperNode(store.Get(sourcePrimaryKey), sourceValidator) + if err != nil { + return nil, fmt.Errorf("source supernode primary state: %w", err) + } + destinationPrimaryKey := types.GetSupernodeKey(destinationValidator) + _, destinationSNFound, err := k.strictReadMigrationSuperNode(store.Get(destinationPrimaryKey), destinationValidator) + if err != nil { + return nil, fmt.Errorf("destination supernode primary state: %w", err) + } + if destinationSNFound { + return nil, fmt.Errorf("destination supernode primary state already exists for %s", destinationValidator) + } + + if err := k.validateMigrationAccountIndexes(ctx, sourceValidator, destinationValidator, sourceSN, sourceSNFound); err != nil { + return nil, err + } + + sourceMetricsKey := types.GetMetricsStateKey(sourceValidator) + sourceMetricsRaw := bytes.Clone(store.Get(sourceMetricsKey)) + sourceMetrics, sourceMetricsFound, err := k.strictReadMigrationMetrics(sourceMetricsRaw, sourceValidator) + if err != nil { + return nil, fmt.Errorf("source metrics state: %w", err) + } + destinationMetricsKey := types.GetMetricsStateKey(destinationValidator) + destinationMetricsRaw := bytes.Clone(store.Get(destinationMetricsKey)) + _, destinationMetricsFound, err := k.strictReadMigrationMetrics(destinationMetricsRaw, destinationValidator) + if err != nil { + return nil, fmt.Errorf("destination metrics state: %w", err) + } + if destinationMetricsFound { + return nil, fmt.Errorf("destination metrics state already exists for %s", destinationValidator) + } + + rdistRows, sourceDistRaw, sourceDistFound, destinationDistFound, err := scanMigrationRDistState(store, sourceValidator, destinationValidator) + if err != nil { + return nil, err + } + if destinationDistFound { + return nil, fmt.Errorf("destination distribution state already exists for %s", destinationValidator) + } + + var movedMetricsRaw []byte + if sourceMetricsFound { + sourceMetrics.ValidatorAddress = destinationValidator.String() + movedMetricsRaw, err = k.cdc.Marshal(&sourceMetrics) + if err != nil { + return nil, fmt.Errorf("marshal destination metrics state: %w", err) + } + } + if !sourceDistFound { + sourceDistRaw = nil + } + return types.NewIdentityMigrationPlan( + sourceValidator, destinationValidator, + sourceMetricsRaw, destinationMetricsRaw, movedMetricsRaw, + rdistRows, sourceDistRaw, + ), nil +} + +// ApplyIdentityMigrationPlan first revalidates every frozen source/destination +// and bounded-prefix precondition, then performs the captured writes. Thus a +// stale/reused plan fails before any mutation and cannot overwrite a late +// destination collision. +func (k Keeper) ApplyIdentityMigrationPlan(ctx sdk.Context, plan types.IdentityMigrationPlan) error { + if plan == nil { + return fmt.Errorf("identity migration plan is nil") + } + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + for _, expected := range plan.Preconditions() { + actual := store.Get(expected.Key) + if !sameMigrationValue(actual, expected.Value) { + return fmt.Errorf("identity migration plan is stale at key %X", expected.Key) + } + } + for _, expected := range plan.PrefixPreconditions() { + actual, err := snapshotMigrationPrefix(store, expected.Prefix, IdentityMigrationRDistScanLimit) + if err != nil { + return err + } + if !equalMigrationRows(actual, expected.Rows) { + return fmt.Errorf("identity migration plan is stale under prefix %q", expected.Prefix) + } + } + + // All reads and comparisons complete before the first write. + for _, write := range plan.Writes() { + if write.Value == nil { + store.Delete(write.Key) + } else { + store.Set(write.Key, write.Value) + } + } + return nil +} + +func (k Keeper) validateMigrationAccountIndexes( + ctx sdk.Context, + sourceValidator, destinationValidator sdk.ValAddress, + sourceSN types.SuperNode, + sourceSNFound bool, +) error { + store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) + iterator := store.Iterator(types.SuperNodeByAccountKey, storetypes.PrefixEndBytes(types.SuperNodeByAccountKey)) + defer func() { _ = iterator.Close() }() + + sourceIndexCount := 0 + for ; iterator.Valid(); iterator.Next() { + key := iterator.Key() + if !bytes.HasPrefix(key, types.SuperNodeByAccountKey) { + return fmt.Errorf("supernode account-index iterator returned key outside prefix: %X", key) + } + accountText := key[len(types.SuperNodeByAccountKey):] + if _, err := sdk.AccAddressFromBech32(string(accountText)); err != nil { + return fmt.Errorf("invalid supernode account-index key %q: %w", accountText, err) + } + validator := iterator.Value() + if err := sdk.VerifyAddressFormat(validator); err != nil { + return fmt.Errorf("invalid validator in supernode account index %q: %w", accountText, err) + } + if bytes.Equal(validator, destinationValidator) { + return fmt.Errorf("destination validator has stale supernode account index %q", accountText) + } + if bytes.Equal(validator, sourceValidator) { + sourceIndexCount++ + if !sourceSNFound { + return fmt.Errorf("source validator has stale supernode account index %q", accountText) + } + indexedAccount, err := sdk.AccAddressFromBech32(string(accountText)) + if err != nil { + return err + } + primaryAccount, err := sdk.AccAddressFromBech32(sourceSN.SupernodeAccount) + if err != nil { + return fmt.Errorf("source supernode account %q is invalid: %w", sourceSN.SupernodeAccount, err) + } + if !indexedAccount.Equals(primaryAccount) || string(accountText) != sourceSN.SupernodeAccount { + return fmt.Errorf("source supernode account index does not canonically match primary account") + } + } + } + if err := strictIteratorTerminalError(iterator); err != nil { + return fmt.Errorf("iterate supernode account-index records: %w", err) + } + if sourceSNFound { + if _, err := sdk.AccAddressFromBech32(sourceSN.SupernodeAccount); err != nil { + return fmt.Errorf("source supernode account %q is invalid: %w", sourceSN.SupernodeAccount, err) + } + if sourceIndexCount != 1 { + return fmt.Errorf("source supernode primary requires exactly one canonical account index, got %d", sourceIndexCount) + } + // Also prove no second primary claims the same canonical account. + if _, found, err := k.StrictGetSuperNodeByAccount(ctx, sourceSN.SupernodeAccount); err != nil { + return fmt.Errorf("source supernode account ownership: %w", err) + } else if !found { + return fmt.Errorf("source supernode account index is absent") + } + } + return nil +} + +func scanMigrationRDistState( + store storetypes.KVStore, + sourceValidator, destinationValidator sdk.ValAddress, +) (rows []types.IdentityMigrationRow, sourceRaw []byte, sourceFound, destinationFound bool, err error) { + rows, err = snapshotMigrationPrefix(store, types.SNDistStatePrefix, IdentityMigrationRDistScanLimit) + if err != nil { + return nil, nil, false, false, err + } + for _, row := range rows { + suffix := row.Key[len(types.SNDistStatePrefix):] + validator, parseErr := sdk.ValAddressFromBech32(string(suffix)) + if parseErr != nil { + return nil, nil, false, false, fmt.Errorf("malformed rdist validator suffix %q: %w", suffix, parseErr) + } + isSource := validator.Equals(sourceValidator) + isDestination := validator.Equals(destinationValidator) + if (isSource || isDestination) && string(suffix) != validator.String() { + return nil, nil, false, false, fmt.Errorf("non-canonical rdist validator suffix %q", suffix) + } + if _, _, readErr := strictReadMigrationDistState(row.Value); readErr != nil { + return nil, nil, false, false, fmt.Errorf("distribution state %q: %w", suffix, readErr) + } + if isSource { + if sourceFound { + return nil, nil, false, false, fmt.Errorf("duplicate source distribution state for %s", sourceValidator) + } + sourceFound = true + sourceRaw = bytes.Clone(row.Value) + } + if isDestination { + if destinationFound { + return nil, nil, false, false, fmt.Errorf("duplicate destination distribution state for %s", destinationValidator) + } + destinationFound = true + } + } + return rows, sourceRaw, sourceFound, destinationFound, nil +} + +func snapshotMigrationPrefix(store storetypes.KVStore, prefix []byte, limit int) ([]types.IdentityMigrationRow, error) { + iterator := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + defer func() { _ = iterator.Close() }() + rows := make([]types.IdentityMigrationRow, 0) + for ; iterator.Valid(); iterator.Next() { + if len(rows) == limit { + return nil, fmt.Errorf("identity migration prefix %q exceeds scan limit %d", prefix, limit) + } + rows = append(rows, types.IdentityMigrationRow{Key: bytes.Clone(iterator.Key()), Value: bytes.Clone(iterator.Value())}) + } + if err := strictIteratorTerminalError(iterator); err != nil { + return nil, fmt.Errorf("iterate identity migration prefix %q: %w", prefix, err) + } + return rows, nil +} + +func sameMigrationValue(actual, expected []byte) bool { + return (actual == nil) == (expected == nil) && bytes.Equal(actual, expected) +} + +func equalMigrationRows(actual, expected []types.IdentityMigrationRow) bool { + if len(actual) != len(expected) { + return false + } + for i := range actual { + if !bytes.Equal(actual[i].Key, expected[i].Key) || !sameMigrationValue(actual[i].Value, expected[i].Value) { + return false + } + } + return true +} + +func (k Keeper) strictReadMigrationSuperNode(raw []byte, expectedValidator sdk.ValAddress) (types.SuperNode, bool, error) { + if raw == nil { + return types.SuperNode{}, false, nil + } + var sn types.SuperNode + if err := k.cdc.Unmarshal(raw, &sn); err != nil { + return types.SuperNode{}, false, fmt.Errorf("malformed row: %w", err) + } + embeddedValidator, err := sdk.ValAddressFromBech32(sn.ValidatorAddress) + if err != nil { + return types.SuperNode{}, false, fmt.Errorf("invalid embedded validator %q: %w", sn.ValidatorAddress, err) + } + if !embeddedValidator.Equals(expectedValidator) { + return types.SuperNode{}, false, fmt.Errorf("embedded validator mismatch: got %s, expected %s", sn.ValidatorAddress, expectedValidator) + } + return sn, true, nil +} + +func (k Keeper) strictReadMigrationMetrics(raw []byte, expectedValidator sdk.ValAddress) (types.SupernodeMetricsState, bool, error) { + if raw == nil { + return types.SupernodeMetricsState{}, false, nil + } + var state types.SupernodeMetricsState + if err := k.cdc.Unmarshal(raw, &state); err != nil { + return types.SupernodeMetricsState{}, false, fmt.Errorf("malformed row: %w", err) + } + embeddedValidator, err := sdk.ValAddressFromBech32(state.ValidatorAddress) + if err != nil { + return types.SupernodeMetricsState{}, false, fmt.Errorf("invalid embedded validator %q: %w", state.ValidatorAddress, err) + } + if !embeddedValidator.Equals(expectedValidator) { + return types.SupernodeMetricsState{}, false, fmt.Errorf("embedded validator mismatch: got %s, expected %s", state.ValidatorAddress, expectedValidator) + } + return state, true, nil +} + +func strictReadMigrationDistState(raw []byte) ([]byte, bool, error) { + if raw == nil { + return nil, false, nil + } + var state *types.SNDistState + if err := json.Unmarshal(raw, &state); err != nil { + return nil, false, fmt.Errorf("malformed row: %w", err) + } + if state == nil { + return nil, false, fmt.Errorf("malformed row: null distribution state") + } + return bytes.Clone(raw), true, nil +} diff --git a/x/supernode/v1/keeper/validator_state_migration_test.go b/x/supernode/v1/keeper/validator_state_migration_test.go new file mode 100644 index 00000000..079cd631 --- /dev/null +++ b/x/supernode/v1/keeper/validator_state_migration_test.go @@ -0,0 +1,286 @@ +package keeper + +import ( + "bytes" + "strings" + "testing" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/runtime" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/LumeraProtocol/lumera/x/supernode/v1/types" +) + +func migrationValidators() (sdk.ValAddress, sdk.ValAddress, string) { + source := sdk.ValAddress(bytes.Repeat([]byte{0x31}, 20)) + destination := sdk.ValAddress(bytes.Repeat([]byte{0x32}, 20)) + account := sdk.AccAddress(bytes.Repeat([]byte{0x41}, 20)).String() + return source, destination, account +} + +func seedMigrationSuperNode(t *testing.T, k Keeper, ctx sdk.Context, validator sdk.ValAddress, account string) types.SuperNode { + t.Helper() + sn := rawTestSuperNode(validator, account) + store := migrationRawStore(k, ctx) + store.Set(types.GetSupernodeKey(validator), marshalRawSuperNode(t, k, sn)) + store.Set(append(bytes.Clone(types.SuperNodeByAccountKey), []byte(account)...), validator) + return sn +} + +func migrationRawStore(k Keeper, ctx sdk.Context) storetypes.KVStore { + return runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) +} + +func TestIdentityMigrationPlanMovesOnlyContinuityState(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + sourceSN := seedMigrationSuperNode(t, k, ctx, source, account) + metrics := types.SupernodeMetricsState{ + ValidatorAddress: source.String(), + Metrics: &types.SupernodeMetrics{CascadeKademliaDbBytes: 987654.5, PeersCount: 17}, + ReportCount: 23, + Height: 456, + } + require.NoError(t, k.SetMetricsState(ctx, metrics)) + dist := SNDistState{SmoothedBytes: 123.5, PrevRawBytes: 234.5, EligibilityStartHeight: 42, PeriodsActive: 9} + k.SetSNDistState(ctx, source.String(), dist) + + store := migrationRawStore(k, ctx) + sourcePrimaryRaw := bytes.Clone(store.Get(types.GetSupernodeKey(source))) + accountIndexKey := append(bytes.Clone(types.SuperNodeByAccountKey), []byte(account)...) + accountIndexRaw := bytes.Clone(store.Get(accountIndexKey)) + payoutKey := append(types.PayoutHistoryPrefixForValidator(source.String()), []byte("00000000000000000456")...) + payoutRaw := []byte{0xde, 0xad, 0xbe, 0xef} + store.Set(payoutKey, payoutRaw) + + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + require.NoError(t, k.ApplyIdentityMigrationPlan(ctx, plan)) + + // Primary/account/history are owned by PR196 and are validation-only here. + require.Equal(t, sourcePrimaryRaw, store.Get(types.GetSupernodeKey(source))) + require.Nil(t, store.Get(types.GetSupernodeKey(destination))) + require.Equal(t, accountIndexRaw, store.Get(accountIndexKey)) + require.Equal(t, sourceSN.ValidatorAddress, source.String()) + require.Equal(t, payoutRaw, store.Get(payoutKey)) + require.Nil(t, store.Get(append(types.PayoutHistoryPrefixForValidator(destination.String()), []byte("00000000000000000456")...))) + + require.Nil(t, store.Get(types.GetMetricsStateKey(source))) + movedMetrics, found := k.GetMetricsState(ctx, destination) + require.True(t, found) + metrics.ValidatorAddress = destination.String() + require.Equal(t, metrics, movedMetrics) + require.Nil(t, store.Get(types.SNDistStateKey(source.String()))) + movedDist, found := k.GetSNDistState(ctx, destination.String()) + require.True(t, found) + require.Equal(t, dist, movedDist) + require.Equal(t, applyEMA(dist.SmoothedBytes, applyGrowthCap(300, dist.PrevRawBytes, 1250), 4), + applyEMA(movedDist.SmoothedBytes, applyGrowthCap(300, movedDist.PrevRawBytes, 1250), 4)) + require.Equal(t, computeRampUpWeight(dist.PeriodsActive, 12), computeRampUpWeight(movedDist.PeriodsActive, 12)) +} + +func TestBuildIdentityMigrationPlanValidatesPrimaryAndIndexes(t *testing.T) { + t.Run("destination primary", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + seedMigrationSuperNode(t, k, ctx, source, account) + seedMigrationSuperNode(t, k, ctx, destination, sdk.AccAddress(bytes.Repeat([]byte{0x42}, 20)).String()) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "destination supernode primary") + }) + + t.Run("missing source index", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + sn := rawTestSuperNode(source, account) + migrationRawStore(k, ctx).Set(types.GetSupernodeKey(source), marshalRawSuperNode(t, k, sn)) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "exactly one") + }) + + t.Run("destination index alias", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + seedMigrationSuperNode(t, k, ctx, source, account) + alias := sdk.AccAddress(bytes.Repeat([]byte{0x43}, 20)).String() + migrationRawStore(k, ctx).Set(append(bytes.Clone(types.SuperNodeByAccountKey), []byte(alias)...), destination) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "destination validator has stale") + }) + + t.Run("source index alias", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + seedMigrationSuperNode(t, k, ctx, source, account) + migrationRawStore(k, ctx).Set(append(bytes.Clone(types.SuperNodeByAccountKey), []byte(strings.ToUpper(account))...), source) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.Error(t, err) + }) +} + +func TestApplyIdentityMigrationPlanRejectsStaleRowsBeforeWriting(t *testing.T) { + for _, tc := range []struct { + name string + stale func(store storetypes.KVStore, source, destination sdk.ValAddress) + }{ + { + name: "source metrics", + stale: func(store storetypes.KVStore, source, _ sdk.ValAddress) { + store.Set(types.GetMetricsStateKey(source), []byte{0xff}) + }, + }, + { + name: "destination metrics", + stale: func(store storetypes.KVStore, _, destination sdk.ValAddress) { + store.Set(types.GetMetricsStateKey(destination), []byte("late collision")) + }, + }, + { + name: "source rdist", + stale: func(store storetypes.KVStore, source, _ sdk.ValAddress) { + store.Set(types.SNDistStateKey(source.String()), []byte(`{"periods_active":99}`)) + }, + }, + { + name: "destination rdist", + stale: func(store storetypes.KVStore, _, destination sdk.ValAddress) { + store.Set(types.SNDistStateKey(destination.String()), []byte(`{"periods_active":1}`)) + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + seedMigrationSuperNode(t, k, ctx, source, account) + require.NoError(t, k.SetMetricsState(ctx, types.SupernodeMetricsState{ValidatorAddress: source.String(), ReportCount: 7})) + k.SetSNDistState(ctx, source.String(), SNDistState{PeriodsActive: 3}) + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + store := migrationRawStore(k, ctx) + tc.stale(store, source, destination) + before := snapshotSuperNodeStore(t, k, ctx) + + err = k.ApplyIdentityMigrationPlan(ctx, plan) + require.ErrorContains(t, err, "stale") + require.Equal(t, before, snapshotSuperNodeStore(t, k, ctx), "failed Apply must perform no writes") + }) + } +} + +func TestApplyIdentityMigrationPlanTwiceFailsWithoutMutation(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + seedMigrationSuperNode(t, k, ctx, source, account) + require.NoError(t, k.SetMetricsState(ctx, types.SupernodeMetricsState{ValidatorAddress: source.String(), ReportCount: 4})) + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + require.NoError(t, k.ApplyIdentityMigrationPlan(ctx, plan)) + before := snapshotSuperNodeStore(t, k, ctx) + require.ErrorContains(t, k.ApplyIdentityMigrationPlan(ctx, plan), "stale") + require.Equal(t, before, snapshotSuperNodeStore(t, k, ctx)) +} + +func TestIdentityMigrationPlanIsOpaqueAndOwnsBuffers(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + sourceExpected := sdk.ValAddress(bytes.Clone(source)) + destinationExpected := sdk.ValAddress(bytes.Clone(destination)) + require.NoError(t, k.SetMetricsState(ctx, types.SupernodeMetricsState{ValidatorAddress: source.String(), ReportCount: 5})) + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + + // Every accessor returns deep copies; mutation cannot change shared plan data. + preconditions := plan.Preconditions() + writes := plan.Writes() + prefixes := plan.PrefixPreconditions() + preconditions[0].Key[0] ^= 0xff + writes[0].Key[0] ^= 0xff + prefixes[0].Prefix[0] ^= 0xff + for i := range source { + source[i] = 0x71 + destination[i] = 0x72 + } + + require.NoError(t, k.ApplyIdentityMigrationPlan(ctx, plan)) + require.Nil(t, migrationRawStore(k, ctx).Get(types.GetMetricsStateKey(sourceExpected))) + state, found := k.GetMetricsState(ctx, destinationExpected) + require.True(t, found) + require.Equal(t, uint64(5), state.ReportCount) +} + +func TestBuildIdentityMigrationPlanCanonicalRDistScan(t *testing.T) { + t.Run("alternate source spelling", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + store := migrationRawStore(k, ctx) + store.Set(append(bytes.Clone(types.SNDistStatePrefix), []byte(strings.ToUpper(source.String()))...), []byte(`{}`)) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "non-canonical") + }) + + t.Run("duplicate source alternate", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + store := migrationRawStore(k, ctx) + store.Set(types.SNDistStateKey(source.String()), []byte(`{}`)) + store.Set(append(bytes.Clone(types.SNDistStatePrefix), []byte(strings.ToUpper(source.String()))...), []byte(`{}`)) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.Error(t, err) + }) + + t.Run("malformed valoper", func(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + migrationRawStore(k, ctx).Set(append(bytes.Clone(types.SNDistStatePrefix), []byte("not-a-valoper")...), []byte(`{}`)) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "malformed rdist") + }) +} + +func TestIdentityMigrationRDistScanExactCapAndCapPlusOne(t *testing.T) { + seedRows := func(t *testing.T, count int) (Keeper, sdk.Context, sdk.ValAddress, sdk.ValAddress) { + t.Helper() + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + store := migrationRawStore(k, ctx) + for i := 0; i < count; i++ { + address := make([]byte, 20) + address[0] = byte(i >> 8) + address[1] = byte(i) + address[2] = 0x7f + validator := sdk.ValAddress(address) + store.Set(types.SNDistStateKey(validator.String()), []byte(`{}`)) + } + return k, ctx, source, destination + } + + t.Run("cap", func(t *testing.T) { + k, ctx, source, destination := seedRows(t, IdentityMigrationRDistScanLimit) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + }) + t.Run("cap plus one", func(t *testing.T) { + k, ctx, source, destination := seedRows(t, IdentityMigrationRDistScanLimit+1) + _, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.ErrorContains(t, err, "exceeds scan limit") + }) +} + +func TestIdentityMigrationPlanWithoutPrimaryAndInvalidRequests(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, _ := migrationValidators() + require.NoError(t, k.SetMetricsState(ctx, types.SupernodeMetricsState{ValidatorAddress: source.String(), ReportCount: 1})) + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + require.NoError(t, k.ApplyIdentityMigrationPlan(ctx, plan)) + + _, err = k.BuildIdentityMigrationPlan(ctx, nil, destination) + require.ErrorContains(t, err, "non-empty") + _, err = k.BuildIdentityMigrationPlan(ctx, source, nil) + require.ErrorContains(t, err, "non-empty") + _, err = k.BuildIdentityMigrationPlan(ctx, source, bytes.Clone(source)) + require.ErrorContains(t, err, "must differ") + require.ErrorContains(t, k.ApplyIdentityMigrationPlan(ctx, nil), "nil") +} diff --git a/x/supernode/v1/types/identity_migration_plan.go b/x/supernode/v1/types/identity_migration_plan.go new file mode 100644 index 00000000..3b0cc4f6 --- /dev/null +++ b/x/supernode/v1/types/identity_migration_plan.go @@ -0,0 +1,138 @@ +package types + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// IdentityMigrationPlan is an opaque, immutable snapshot of the SuperNode +// continuity writes validated by the keeper. The unexported method seals the +// interface so callers outside this package cannot forge implementations. +type IdentityMigrationPlan interface { + Preconditions() []IdentityMigrationRow + PrefixPreconditions() []IdentityMigrationPrefix + Writes() []IdentityMigrationWrite + identityMigrationPlan() +} + +// IdentityMigrationRow is a read-only copy of one exact key/value precondition. +// A nil Value means the key must be absent. +type IdentityMigrationRow struct { + Key []byte + Value []byte +} + +// IdentityMigrationPrefix is a read-only copy of a complete bounded prefix +// snapshot. Rows are in store iteration order. +type IdentityMigrationPrefix struct { + Prefix []byte + Rows []IdentityMigrationRow +} + +// IdentityMigrationWrite is one set or delete operation. A nil Value denotes a +// delete; continuity state never stores nil values. +type IdentityMigrationWrite struct { + Key []byte + Value []byte +} + +type identityMigrationPlan struct { + preconditions []IdentityMigrationRow + prefixPreconditions []IdentityMigrationPrefix + writes []IdentityMigrationWrite +} + +// NewIdentityMigrationPlan constructs the only supported continuity operation: +// moving source metrics and/or distribution state to an empty destination. It +// derives every key itself and takes deep copies of all supplied state, so the +// public constructor cannot be used to forge arbitrary module writes. +func NewIdentityMigrationPlan( + sourceValidator, destinationValidator sdk.ValAddress, + sourceMetrics, destinationMetrics, movedMetrics []byte, + rdistRows []IdentityMigrationRow, + sourceDist []byte, +) IdentityMigrationPlan { + preconditions := []IdentityMigrationRow{ + {Key: GetMetricsStateKey(sourceValidator), Value: sourceMetrics}, + {Key: GetMetricsStateKey(destinationValidator), Value: destinationMetrics}, + } + prefixPreconditions := []IdentityMigrationPrefix{{Prefix: SNDistStatePrefix, Rows: rdistRows}} + writes := make([]IdentityMigrationWrite, 0, 4) + if sourceMetrics != nil { + writes = append(writes, + IdentityMigrationWrite{Key: GetMetricsStateKey(sourceValidator)}, + IdentityMigrationWrite{Key: GetMetricsStateKey(destinationValidator), Value: movedMetrics}, + ) + } + if sourceDist != nil { + writes = append(writes, + IdentityMigrationWrite{Key: SNDistStateKey(sourceValidator.String())}, + IdentityMigrationWrite{Key: SNDistStateKey(destinationValidator.String()), Value: sourceDist}, + ) + } + return &identityMigrationPlan{ + preconditions: cloneMigrationRows(preconditions), + prefixPreconditions: cloneMigrationPrefixes(prefixPreconditions), + writes: cloneMigrationWrites(writes), + } +} + +func (*identityMigrationPlan) identityMigrationPlan() {} + +func (p *identityMigrationPlan) Preconditions() []IdentityMigrationRow { + if p == nil { + return nil + } + return cloneMigrationRows(p.preconditions) +} + +func (p *identityMigrationPlan) PrefixPreconditions() []IdentityMigrationPrefix { + if p == nil { + return nil + } + return cloneMigrationPrefixes(p.prefixPreconditions) +} + +func (p *identityMigrationPlan) Writes() []IdentityMigrationWrite { + if p == nil { + return nil + } + return cloneMigrationWrites(p.writes) +} + +func cloneMigrationRows(rows []IdentityMigrationRow) []IdentityMigrationRow { + if rows == nil { + return nil + } + out := make([]IdentityMigrationRow, len(rows)) + for i, row := range rows { + out[i] = IdentityMigrationRow{Key: bytes.Clone(row.Key), Value: bytes.Clone(row.Value)} + } + return out +} + +func cloneMigrationPrefixes(prefixes []IdentityMigrationPrefix) []IdentityMigrationPrefix { + if prefixes == nil { + return nil + } + out := make([]IdentityMigrationPrefix, len(prefixes)) + for i, snapshot := range prefixes { + out[i] = IdentityMigrationPrefix{ + Prefix: bytes.Clone(snapshot.Prefix), + Rows: cloneMigrationRows(snapshot.Rows), + } + } + return out +} + +func cloneMigrationWrites(writes []IdentityMigrationWrite) []IdentityMigrationWrite { + if writes == nil { + return nil + } + out := make([]IdentityMigrationWrite, len(writes)) + for i, write := range writes { + out[i] = IdentityMigrationWrite{Key: bytes.Clone(write.Key), Value: bytes.Clone(write.Value)} + } + return out +} From 55822e71a5096b4b252fc099e1dc04bdbc4fd445 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 01:03:44 +0000 Subject: [PATCH 02/18] feat(audit): preserve identity continuity across migrations --- docs/static/openapi.yml | 2 +- proto/lumera/audit/v1/audit.proto | 13 + proto/lumera/audit/v1/genesis.proto | 2 + x/audit/v1/keeper/audit_peer_assignment.go | 22 +- x/audit/v1/keeper/enforcement.go | 18 +- x/audit/v1/keeper/export_test.go | 8 + x/audit/v1/keeper/fixture_test.go | 2 + x/audit/v1/keeper/genesis.go | 65 ++ x/audit/v1/keeper/identity_continuity.go | 484 ++++++++++++++ .../identity_continuity_additional_test.go | 273 ++++++++ .../identity_continuity_genesis_test.go | 41 ++ .../identity_continuity_regression_test.go | 219 +++++++ .../keeper/identity_continuity_report_test.go | 77 +++ x/audit/v1/keeper/identity_continuity_test.go | 138 ++++ x/audit/v1/keeper/msg_storage_truth.go | 54 +- x/audit/v1/keeper/msg_storage_truth_test.go | 2 +- x/audit/v1/keeper/msg_submit_epoch_report.go | 53 +- x/audit/v1/keeper/query_assigned_targets.go | 11 +- x/audit/v1/keeper/state.go | 31 +- x/audit/v1/keeper/storage_truth_divergence.go | 58 +- .../v1/keeper/storage_truth_fact_indexes.go | 134 ++-- x/audit/v1/keeper/storage_truth_scoring.go | 81 ++- x/audit/v1/module/migrations.go | 6 + x/audit/v1/module/migrations_test.go | 7 +- x/audit/v1/module/module.go | 3 + x/audit/v1/types/audit.pb.go | 613 +++++++++++++----- x/audit/v1/types/genesis.go | 50 +- x/audit/v1/types/genesis.pb.go | 228 ++++--- x/audit/v1/types/keys.go | 24 + 29 files changed, 2375 insertions(+), 344 deletions(-) create mode 100644 x/audit/v1/keeper/identity_continuity.go create mode 100644 x/audit/v1/keeper/identity_continuity_additional_test.go create mode 100644 x/audit/v1/keeper/identity_continuity_genesis_test.go create mode 100644 x/audit/v1/keeper/identity_continuity_regression_test.go create mode 100644 x/audit/v1/keeper/identity_continuity_report_test.go create mode 100644 x/audit/v1/keeper/identity_continuity_test.go diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index e12b9e45..d858dfb0 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -1 +1 @@ -{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: true.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file +{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"current_submitter":{"description":"current_submitter is the live account that authenticated submission. It is\nintentionally distinct from supernode_account, the epoch-logical identity.\nEmpty decodes preserve reports written before identity continuity shipped.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: true.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file diff --git a/proto/lumera/audit/v1/audit.proto b/proto/lumera/audit/v1/audit.proto index 814291e5..c5275e4e 100644 --- a/proto/lumera/audit/v1/audit.proto +++ b/proto/lumera/audit/v1/audit.proto @@ -210,4 +210,17 @@ message EpochReport { HostReport host_report = 4 [(gogoproto.nullable) = false]; repeated StorageChallengeObservation storage_challenge_observations = 5; repeated StorageProofResult storage_proof_results = 6; + + // current_submitter is the live account that authenticated submission. It is + // intentionally distinct from supernode_account, the epoch-logical identity. + // Empty decodes preserve reports written before identity continuity shipped. + string current_submitter = 7 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; +} + +// AccountTransition records a durable account lineage edge. The destination +// becomes the account for the lineage beginning at effective_epoch. +message AccountTransition { + string source_account = 1 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; + string destination_account = 2 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; + uint64 effective_epoch = 3; } diff --git a/proto/lumera/audit/v1/genesis.proto b/proto/lumera/audit/v1/genesis.proto index c3d9f7d9..ec0ff283 100644 --- a/proto/lumera/audit/v1/genesis.proto +++ b/proto/lumera/audit/v1/genesis.proto @@ -59,6 +59,8 @@ message GenesisState { // Per final-gate F-B4 — per-verifier heal-op votes must survive // export/import workflows. repeated GenesisHealOpVerification heal_op_verifications = 22 [(gogoproto.nullable) = false]; + + repeated AccountTransition account_transitions = 23 [(gogoproto.nullable) = false]; } // StorageTruthPostponement records a supernode's storage-truth postponement state diff --git a/x/audit/v1/keeper/audit_peer_assignment.go b/x/audit/v1/keeper/audit_peer_assignment.go index 2504d07b..fc98a2c3 100644 --- a/x/audit/v1/keeper/audit_peer_assignment.go +++ b/x/audit/v1/keeper/audit_peer_assignment.go @@ -263,9 +263,9 @@ func sortedUniqueStrings(in []string) []string { return out } -func (k Keeper) storageTruthEligibleChallengers(ctx sdk.Context, activeSorted []string, epochID uint64, params types.Params) []string { +func (k Keeper) storageTruthEligibleChallengers(ctx sdk.Context, activeSorted []string, epochID uint64, params types.Params) ([]string, error) { if params.StorageTruthEnforcementMode == types.StorageTruthEnforcementMode_STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED { - return append([]string(nil), activeSorted...) + return append([]string(nil), activeSorted...), nil } threshold := params.StorageTruthReporterReliabilityIneligibleThreshold @@ -274,17 +274,25 @@ func (k Keeper) storageTruthEligibleChallengers(ctx sdk.Context, activeSorted [] } eligible := make([]string, 0, len(activeSorted)) - for _, account := range activeSorted { - state, found := k.GetReporterReliabilityState(ctx, account) + for _, epochAccount := range activeSorted { + // Epoch anchors are immutable and keep the account that was logical at + // epoch start. Reliability is mutable current state and moves with an + // identity transition, so read it through the live lineage endpoint while + // retaining the anchored account in the assignment set. + currentAccount, err := k.CurrentAccount(ctx, epochAccount) + if err != nil { + return nil, err + } + state, found := k.GetReporterReliabilityState(ctx, currentAccount) if !found { - eligible = append(eligible, account) + eligible = append(eligible, epochAccount) continue } score := decayTowardZero(state.ReliabilityScore, params.StorageTruthReporterReliabilityDecayPerEpoch, epochDelta(epochID, state.LastUpdatedEpoch)) if score >= threshold || (state.IneligibleUntilEpoch != 0 && state.IneligibleUntilEpoch >= epochID) { continue } - eligible = append(eligible, account) + eligible = append(eligible, epochAccount) } - return eligible + return eligible, nil } diff --git a/x/audit/v1/keeper/enforcement.go b/x/audit/v1/keeper/enforcement.go index 5908a651..a28fdf01 100644 --- a/x/audit/v1/keeper/enforcement.go +++ b/x/audit/v1/keeper/enforcement.go @@ -282,6 +282,10 @@ func (k Keeper) shouldRecoverAtEpochEnd(ctx sdk.Context, supernodeAccount string if len(peers) == 0 { return false, nil } + logicalTarget, err := k.AccountForEpoch(ctx, supernodeAccount, epochID) + if err != nil { + return false, err + } // Recovery requires at least one peer report that shows all required ports OPEN for this supernode in this epoch. for _, reporter := range peers { @@ -292,7 +296,7 @@ func (k Keeper) shouldRecoverAtEpochEnd(ctx sdk.Context, supernodeAccount string var obs *types.StorageChallengeObservation for i := range r.StorageChallengeObservations { - if r.StorageChallengeObservations[i] != nil && r.StorageChallengeObservations[i].TargetSupernodeAccount == supernodeAccount { + if r.StorageChallengeObservations[i] != nil && r.StorageChallengeObservations[i].TargetSupernodeAccount == logicalTarget { obs = r.StorageChallengeObservations[i] break } @@ -486,6 +490,10 @@ func (k Keeper) peersPortStateMeetsThresholdWithPeers(ctx sdk.Context, target st if len(peers) == 0 { return false, nil } + logicalTarget, err := k.AccountForEpoch(ctx, target, epochID) + if err != nil { + return false, err + } matches := uint64(0) for _, reporter := range peers { @@ -496,7 +504,7 @@ func (k Keeper) peersPortStateMeetsThresholdWithPeers(ctx sdk.Context, target st var obs *types.StorageChallengeObservation for i := range r.StorageChallengeObservations { - if r.StorageChallengeObservations[i] != nil && r.StorageChallengeObservations[i].TargetSupernodeAccount == target { + if r.StorageChallengeObservations[i] != nil && r.StorageChallengeObservations[i].TargetSupernodeAccount == logicalTarget { obs = r.StorageChallengeObservations[i] break } @@ -518,8 +526,12 @@ func (k Keeper) peersPortStateMeetsThresholdWithPeers(ctx sdk.Context, target st } func (k Keeper) peerReportersForTargetEpoch(ctx sdk.Context, target string, epochID uint64) ([]string, error) { + logicalTarget, err := k.AccountForEpoch(ctx, target, epochID) + if err != nil { + return nil, err + } store := k.kvStore(ctx) - prefix := types.StorageChallengeReportIndexEpochPrefix(target, epochID) + prefix := types.StorageChallengeReportIndexEpochPrefix(logicalTarget, epochID) it := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) defer func() { _ = it.Close() }() diff --git a/x/audit/v1/keeper/export_test.go b/x/audit/v1/keeper/export_test.go index fdbfd23a..f71d3ac3 100644 --- a/x/audit/v1/keeper/export_test.go +++ b/x/audit/v1/keeper/export_test.go @@ -27,6 +27,14 @@ var ApplyTicketDeteriorationDeltaForTest = func(k Keeper, ctx sdk.Context, epoch return k.applyTicketDeteriorationDelta(ctx, epochID, reporterAccount, result, ticketID, delta, decayPerEpoch, contradictionConfirmed) } +var ApplyStorageTruthScoresForTest = func(k Keeper, ctx sdk.Context, epochID uint64, reporterAccount string, results []*types.StorageProofResult) error { + return k.applyStorageTruthScores(ctx, epochID, reporterAccount, results) +} + +var PeersPortStateMeetsThresholdForTest = func(k Keeper, ctx sdk.Context, target string, epochID uint64, portIndex int, desired types.PortState, thresholdPercent uint32) (bool, error) { + return k.peersPortStateMeetsThreshold(ctx, target, epochID, portIndex, desired, thresholdPercent) +} + // WriteRawNextHealOpIDForTest writes raw bytes to the next-heal-op-id store key, // bypassing the well-formed encoder. Used to test panic-on-malformed (NEW-B-7). var WriteRawNextHealOpIDForTest = func(k Keeper, ctx sdk.Context, raw []byte) { diff --git a/x/audit/v1/keeper/fixture_test.go b/x/audit/v1/keeper/fixture_test.go index e911a763..7d1e2d12 100644 --- a/x/audit/v1/keeper/fixture_test.go +++ b/x/audit/v1/keeper/fixture_test.go @@ -25,6 +25,7 @@ type fixture struct { ctx sdk.Context keeper keeper.Keeper addressCodec address.Codec + storeKey *storetypes.KVStoreKey supernodeKeeper *supernodemocks.MockSupernodeKeeper } @@ -63,6 +64,7 @@ func initFixture(t *testing.T) *fixture { ctx: ctx, keeper: k, addressCodec: addressCodec, + storeKey: storeKey, supernodeKeeper: snKeeper, } } diff --git a/x/audit/v1/keeper/genesis.go b/x/audit/v1/keeper/genesis.go index 1ee888a1..25ed0de2 100644 --- a/x/audit/v1/keeper/genesis.go +++ b/x/audit/v1/keeper/genesis.go @@ -16,6 +16,17 @@ func (k Keeper) InitGenesis(ctx context.Context, genState types.GenesisState) er if err := params.Validate(); err != nil { return err } + if err := types.ValidateAccountTransitions(genState.AccountTransitions); err != nil { + return err + } + for _, transition := range genState.AccountTransitions { + if err := k.validateTransitionEndpoints(transition); err != nil { + return err + } + } + if err := validateGenesisSingletonTransitionEndpoints(genState); err != nil { + return err + } // Genesis is the initial source of truth for module params. After genesis, params can // only be updated via governance (MsgUpdateParams). @@ -36,6 +47,11 @@ func (k Keeper) InitGenesis(ctx context.Context, genState types.GenesisState) er if err := types.ValidateScoreStatesGenesis(genState, currentEpoch); err != nil { return err } + for _, transition := range genState.AccountTransitions { + if err := k.ImportAccountTransition(sdkCtx, transition); err != nil { + return err + } + } var nextEvidenceID uint64 if genState.NextEvidenceId != 0 { @@ -167,6 +183,51 @@ func (k Keeper) InitGenesis(ctx context.Context, genState types.GenesisState) er return nil } +func validateGenesisSingletonTransitionEndpoints(genState types.GenesisState) error { + forward := make(map[string]string, len(genState.AccountTransitions)) + for _, transition := range genState.AccountTransitions { + forward[transition.SourceAccount] = transition.DestinationAccount + } + checkCurrent := func(kind, account string) error { + current := account + for hops := 0; ; hops++ { + next, found := forward[current] + if !found { + break + } + if hops >= types.MaxAccountTransitions { + return fmt.Errorf("audit genesis: account lineage exceeds transition limit") + } + current = next + } + if current != account { + return fmt.Errorf("audit genesis: live %s singleton %q is keyed to non-current transition source; want %q", kind, account, current) + } + return nil + } + for _, state := range genState.NodeSuspicionStates { + if err := checkCurrent("node-suspicion", state.SupernodeAccount); err != nil { + return err + } + } + for _, state := range genState.ReporterReliabilityStates { + if err := checkCurrent("reporter-reliability", state.ReporterSupernodeAccount); err != nil { + return err + } + } + for _, marker := range genState.StorageTruthPostponements { + if err := checkCurrent("storage-truth-postponement", marker.SupernodeAccount); err != nil { + return err + } + } + for _, marker := range genState.ActionFinalizationPostponements { + if err := checkCurrent("action-finalization-postponement", marker.SupernodeAccount); err != nil { + return err + } + } + return nil +} + // ExportGenesis returns the module's exported genesis. func (k Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) { genesis := types.DefaultGenesis() @@ -245,6 +306,10 @@ func (k Keeper) ExportGenesis(ctx context.Context) (*types.GenesisState, error) genesis.ReportIndices = k.GetAllReportIndicesForGenesis(sdkCtx) genesis.HostReportIndices = k.GetAllHostReportIndicesForGenesis(sdkCtx) genesis.StorageChallengeIndices = k.GetAllStorageChallengeIndicesForGenesis(sdkCtx) + genesis.AccountTransitions, err = k.GetAllAccountTransitions(sdkCtx) + if err != nil { + return nil, err + } return genesis, nil } diff --git a/x/audit/v1/keeper/identity_continuity.go b/x/audit/v1/keeper/identity_continuity.go new file mode 100644 index 00000000..cbee48c6 --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity.go @@ -0,0 +1,484 @@ +package keeper + +import ( + "bytes" + "fmt" + + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/LumeraProtocol/lumera/x/audit/v1/types" +) + +type accountSingletonMove struct { + sourceKey []byte + destinationKey []byte + sourceVal []byte + destinationVal []byte +} + +// AccountTransitionPlan is an immutable snapshot of every Audit write needed +// for one identity transition. Its fields are private so callers can pass a +// plan to Apply but cannot alter discovered fragments. +type AccountTransitionPlan struct { + transition types.AccountTransition + transitionBytes []byte + singletons []accountSingletonMove + transitionReads []accountSingletonMove + transitionCount int +} + +// RecordAccountTransition builds and applies one durable lineage edge. It is a +// convenience for callers that do not need to aggregate this plan with plans +// from other modules. +func (k Keeper) RecordAccountTransition(ctx sdk.Context, transition types.AccountTransition) error { + plan, err := k.BuildAccountTransitionPlan(ctx, transition) + if err != nil { + return err + } + return k.ApplyAccountTransitionPlan(ctx, plan) +} + +// BuildAccountTransitionPlan performs all graph, collision, corruption and +// active-workflow checks without writing state. +func (k Keeper) BuildAccountTransitionPlan(ctx sdk.Context, transition types.AccountTransition) (AccountTransitionPlan, error) { + if err := k.validateTransitionEndpoints(transition); err != nil { + return AccountTransitionPlan{}, err + } + params := k.GetParams(ctx).WithDefaults() + currentEpoch, err := deriveEpochAtHeight(ctx.BlockHeight(), params) + if err != nil { + return AccountTransitionPlan{}, err + } + if transition.EffectiveEpoch != currentEpoch.EpochID+1 { + return AccountTransitionPlan{}, fmt.Errorf("account transition effective epoch must be current epoch + 1: got %d, want %d", transition.EffectiveEpoch, currentEpoch.EpochID+1) + } + store := k.kvStore(ctx) + transitionCount, err := k.accountTransitionCount(ctx) + if err != nil { + return AccountTransitionPlan{}, err + } + if transitionCount >= types.MaxAccountTransitions { + return AccountTransitionPlan{}, fmt.Errorf("account transitions exceed limit %d", types.MaxAccountTransitions) + } + if store.Has(types.AccountTransitionForwardKey(transition.SourceAccount)) { + return AccountTransitionPlan{}, fmt.Errorf("account transition fork at %q", transition.SourceAccount) + } + if store.Has(types.AccountTransitionReverseKey(transition.DestinationAccount)) || store.Has(types.AccountTransitionForwardKey(transition.DestinationAccount)) { + return AccountTransitionPlan{}, fmt.Errorf("account transition destination collision at %q", transition.DestinationAccount) + } + if previous, ok, err := k.reverseTransition(ctx, transition.SourceAccount); err != nil { + return AccountTransitionPlan{}, err + } else if ok && previous.EffectiveEpoch >= transition.EffectiveEpoch { + return AccountTransitionPlan{}, fmt.Errorf("account transition epochs must strictly increase") + } + root, err := k.lineageRoot(ctx, transition.SourceAccount) + if err != nil { + return AccountTransitionPlan{}, err + } + if root == transition.DestinationAccount { + return AccountTransitionPlan{}, fmt.Errorf("account transition cycle") + } + if err := k.accountTransitionWorkflowBlocker(ctx, transition.SourceAccount); err != nil { + return AccountTransitionPlan{}, err + } + singletons, err := k.buildSingletonMoves(ctx, transition.SourceAccount, transition.DestinationAccount) + if err != nil { + return AccountTransitionPlan{}, err + } + bz, err := k.cdc.Marshal(&transition) + if err != nil { + return AccountTransitionPlan{}, err + } + transitionReads, err := k.snapshotAccountTransitionIndexes(ctx) + if err != nil { + return AccountTransitionPlan{}, err + } + return AccountTransitionPlan{ + transition: transition, + transitionBytes: append([]byte(nil), bz...), + singletons: singletons, + transitionReads: transitionReads, + transitionCount: transitionCount, + }, nil +} + +// ApplyAccountTransitionPlan consumes only frozen plan fragments and performs +// no discovery decisions after mutation begins. +func (k Keeper) ApplyAccountTransitionPlan(ctx sdk.Context, plan AccountTransitionPlan) error { + if len(plan.transitionBytes) == 0 || plan.transition.SourceAccount == "" { + return fmt.Errorf("invalid account transition plan") + } + store := k.kvStore(ctx) + count, err := k.accountTransitionCount(ctx) + if err != nil { + return err + } + if count != plan.transitionCount || + store.Has(types.AccountTransitionForwardKey(plan.transition.SourceAccount)) || + store.Has(types.AccountTransitionReverseKey(plan.transition.DestinationAccount)) || + store.Has(types.AccountTransitionForwardKey(plan.transition.DestinationAccount)) { + return fmt.Errorf("stale or already applied account transition plan") + } + if !accountTransitionIndexesMatchSnapshot(store, plan.transitionReads) { + return fmt.Errorf("stale account transition index precondition") + } + for _, move := range plan.singletons { + if !bytes.Equal(store.Get(move.sourceKey), move.sourceVal) || store.Has(move.destinationKey) { + return fmt.Errorf("stale account transition singleton precondition") + } + } + for _, move := range plan.singletons { + if move.sourceVal == nil { + continue + } + store.Set(move.destinationKey, move.destinationVal) + store.Delete(move.sourceKey) + } + store.Set(types.AccountTransitionForwardKey(plan.transition.SourceAccount), plan.transitionBytes) + store.Set(types.AccountTransitionReverseKey(plan.transition.DestinationAccount), plan.transitionBytes) + return nil +} + +// snapshotAccountTransitionIndexes freezes and validates both copies of every +// lineage edge. A migration fails closed if either index is orphaned, malformed, +// mismatched, or exceeds the consensus bound. +func (k Keeper) snapshotAccountTransitionIndexes(ctx sdk.Context) ([]accountSingletonMove, error) { + store := k.kvStore(ctx) + reads := make([]accountSingletonMove, 0) + for _, spec := range []struct { + prefix []byte + forward bool + }{ + {types.AccountTransitionForwardPrefix(), true}, + {types.AccountTransitionReversePrefix(), false}, + } { + it := store.Iterator(spec.prefix, storetypes.PrefixEndBytes(spec.prefix)) + count := 0 + for ; it.Valid(); it.Next() { + count++ + if count > types.MaxAccountTransitions { + _ = it.Close() + return nil, fmt.Errorf("account transition indexes exceed limit %d", types.MaxAccountTransitions) + } + var transition types.AccountTransition + if err := k.cdc.Unmarshal(it.Value(), &transition); err != nil { + _ = it.Close() + return nil, fmt.Errorf("malformed account transition index: %w", err) + } + if err := k.validateTransitionEndpoints(transition); err != nil { + _ = it.Close() + return nil, fmt.Errorf("malformed account transition index: %w", err) + } + suffix := string(it.Key()[len(spec.prefix):]) + mirrorKey := types.AccountTransitionReverseKey(transition.DestinationAccount) + if spec.forward { + if suffix != transition.SourceAccount { + _ = it.Close() + return nil, fmt.Errorf("malformed account transition: forward index key does not match source account") + } + } else { + if suffix != transition.DestinationAccount { + _ = it.Close() + return nil, fmt.Errorf("malformed account transition: reverse index key does not match destination account") + } + mirrorKey = types.AccountTransitionForwardKey(transition.SourceAccount) + } + if !bytes.Equal(store.Get(mirrorKey), it.Value()) { + _ = it.Close() + return nil, fmt.Errorf("malformed account transition: forward/reverse indexes disagree") + } + reads = append(reads, accountSingletonMove{ + sourceKey: append([]byte(nil), it.Key()...), + sourceVal: append([]byte(nil), it.Value()...), + }) + } + _ = it.Close() + } + return reads, nil +} + +func accountTransitionIndexesMatchSnapshot(store storetypes.KVStore, reads []accountSingletonMove) bool { + readIndex := 0 + for _, prefix := range [][]byte{types.AccountTransitionForwardPrefix(), types.AccountTransitionReversePrefix()} { + it := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + for ; it.Valid(); it.Next() { + if readIndex >= len(reads) || + !bytes.Equal(it.Key(), reads[readIndex].sourceKey) || + !bytes.Equal(it.Value(), reads[readIndex].sourceVal) { + _ = it.Close() + return false + } + readIndex++ + } + _ = it.Close() + } + return readIndex == len(reads) +} + +func (k Keeper) validateTransitionEndpoints(transition types.AccountTransition) error { + if transition.SourceAccount == "" || transition.DestinationAccount == "" || transition.SourceAccount == transition.DestinationAccount { + return fmt.Errorf("invalid account transition endpoints") + } + if transition.EffectiveEpoch == 0 { + return fmt.Errorf("effective epoch must be non-zero") + } + sourceBytes, err := k.addressCodec.StringToBytes(transition.SourceAccount) + if err != nil { + return fmt.Errorf("malformed source account: %w", err) + } + canonicalSource, err := k.addressCodec.BytesToString(sourceBytes) + if err != nil || canonicalSource != transition.SourceAccount { + return fmt.Errorf("noncanonical source account") + } + destinationBytes, err := k.addressCodec.StringToBytes(transition.DestinationAccount) + if err != nil { + return fmt.Errorf("malformed destination account: %w", err) + } + canonicalDestination, err := k.addressCodec.BytesToString(destinationBytes) + if err != nil || canonicalDestination != transition.DestinationAccount { + return fmt.Errorf("noncanonical destination account") + } + return nil +} + +// AccountForEpoch resolves the member of account's lineage that was logical at +// epoch. Boundary semantics are destination-at-effective_epoch. +func (k Keeper) AccountForEpoch(ctx sdk.Context, account string, epoch uint64) (string, error) { + current, err := k.lineageRoot(ctx, account) + if err != nil { + return "", err + } + for hops := 0; ; hops++ { + next, ok, err := k.forwardTransition(ctx, current) + if err != nil { + return "", err + } + if !ok || next.EffectiveEpoch > epoch { + return current, nil + } + if hops >= types.MaxAccountTransitions { + return "", fmt.Errorf("account lineage exceeds transition limit %d", types.MaxAccountTransitions) + } + current = next.DestinationAccount + } +} + +// CurrentAccount resolves the live endpoint of an account lineage. +func (k Keeper) CurrentAccount(ctx sdk.Context, account string) (string, error) { + current, err := k.lineageRoot(ctx, account) + if err != nil { + return "", err + } + for hops := 0; ; hops++ { + next, ok, err := k.forwardTransition(ctx, current) + if err != nil { + return "", err + } + if !ok { + return current, nil + } + if hops >= types.MaxAccountTransitions { + return "", fmt.Errorf("account lineage exceeds transition limit %d", types.MaxAccountTransitions) + } + current = next.DestinationAccount + } +} + +func (k Keeper) forwardTransition(ctx sdk.Context, source string) (types.AccountTransition, bool, error) { + transition, found, err := k.transitionAtKey(ctx, types.AccountTransitionForwardKey(source)) + if err == nil && found && transition.SourceAccount != source { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: forward index key does not match source account") + } + return transition, found, err +} + +func (k Keeper) reverseTransition(ctx sdk.Context, destination string) (types.AccountTransition, bool, error) { + transition, found, err := k.transitionAtKey(ctx, types.AccountTransitionReverseKey(destination)) + if err == nil && found && transition.DestinationAccount != destination { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: reverse index key does not match destination account") + } + return transition, found, err +} + +func (k Keeper) transitionAtKey(ctx sdk.Context, key []byte) (types.AccountTransition, bool, error) { + bz := k.kvStore(ctx).Get(key) + if bz == nil { + return types.AccountTransition{}, false, nil + } + var transition types.AccountTransition + if err := k.cdc.Unmarshal(bz, &transition); err != nil { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: %w", err) + } + if err := k.validateTransitionEndpoints(transition); err != nil { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: %w", err) + } + return transition, true, nil +} + +// ImportAccountTransition restores already genesis-validated indexes without +// replaying singleton movement. +func (k Keeper) ImportAccountTransition(ctx sdk.Context, transition types.AccountTransition) error { + if err := k.validateTransitionEndpoints(transition); err != nil { + return err + } + bz, err := k.cdc.Marshal(&transition) + if err != nil { + return err + } + store := k.kvStore(ctx) + store.Set(types.AccountTransitionForwardKey(transition.SourceAccount), bz) + store.Set(types.AccountTransitionReverseKey(transition.DestinationAccount), bz) + return nil +} + +func (k Keeper) GetAllAccountTransitions(ctx sdk.Context) ([]types.AccountTransition, error) { + prefix := types.AccountTransitionForwardPrefix() + store := k.kvStore(ctx) + it := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + defer func() { _ = it.Close() }() + out := make([]types.AccountTransition, 0) + for ; it.Valid(); it.Next() { + if len(out) >= types.MaxAccountTransitions { + return nil, fmt.Errorf("account transitions exceed limit %d", types.MaxAccountTransitions) + } + var transition types.AccountTransition + if err := k.cdc.Unmarshal(it.Value(), &transition); err != nil { + return nil, fmt.Errorf("malformed account transition: %w", err) + } + if err := k.validateTransitionEndpoints(transition); err != nil { + return nil, fmt.Errorf("malformed account transition: %w", err) + } + if transition.SourceAccount != string(it.Key()[len(prefix):]) { + return nil, fmt.Errorf("malformed account transition: forward index key does not match source account") + } + out = append(out, transition) + } + return out, nil +} + +func (k Keeper) lineageRoot(ctx sdk.Context, account string) (string, error) { + seen := make(map[string]struct{}, types.MaxAccountTransitions) + for hops := 0; ; hops++ { + if _, duplicate := seen[account]; duplicate { + return "", fmt.Errorf("account transition cycle") + } + seen[account] = struct{}{} + previous, ok, err := k.reverseTransition(ctx, account) + if err != nil { + return "", err + } + if !ok { + return account, nil + } + if hops >= types.MaxAccountTransitions { + return "", fmt.Errorf("account lineage exceeds transition limit %d", types.MaxAccountTransitions) + } + account = previous.SourceAccount + } +} + +func (k Keeper) accountTransitionCount(ctx sdk.Context) (int, error) { + prefix := types.AccountTransitionForwardPrefix() + it := k.kvStore(ctx).Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + defer func() { _ = it.Close() }() + count := 0 + for ; it.Valid(); it.Next() { + count++ + if count > types.MaxAccountTransitions { + return 0, fmt.Errorf("account transitions exceed limit %d", types.MaxAccountTransitions) + } + } + return count, nil +} + +func (k Keeper) buildSingletonMoves(ctx sdk.Context, source, destination string) ([]accountSingletonMove, error) { + store := k.kvStore(ctx) + pairs := [][2][]byte{ + {types.ActionFinalizationPostponementKey(source), types.ActionFinalizationPostponementKey(destination)}, + {types.StorageTruthPostponementKey(source), types.StorageTruthPostponementKey(destination)}, + {types.StorageTruthPostponementStrongKey(source), types.StorageTruthPostponementStrongKey(destination)}, + {types.NodeSuspicionStateKey(source), types.NodeSuspicionStateKey(destination)}, + {types.ReporterReliabilityStateKey(source), types.ReporterReliabilityStateKey(destination)}, + } + moves := make([]accountSingletonMove, 0, len(pairs)) + for i, pair := range pairs { + if store.Has(pair[1]) { + return nil, fmt.Errorf("account transition singleton destination collision") + } + value := store.Get(pair[0]) + moves = append(moves, accountSingletonMove{ + sourceKey: append([]byte(nil), pair[0]...), destinationKey: append([]byte(nil), pair[1]...), sourceVal: append([]byte(nil), value...), + }) + if value == nil { + continue + } + destinationValue := append([]byte(nil), value...) + switch i { + case 0, 1: + if len(value) != 8 { + return nil, fmt.Errorf("malformed account transition epoch marker") + } + case 2: + if !bytes.Equal(value, []byte{1}) { + return nil, fmt.Errorf("malformed account transition strong-postpone marker") + } + case 3: + var state types.NodeSuspicionState + if err := k.cdc.Unmarshal(value, &state); err != nil || state.SupernodeAccount != source { + return nil, fmt.Errorf("malformed account transition node-suspicion state") + } + state.SupernodeAccount = destination + var err error + destinationValue, err = k.cdc.Marshal(&state) + if err != nil { + return nil, err + } + case 4: + var state types.ReporterReliabilityState + if err := k.cdc.Unmarshal(value, &state); err != nil || state.ReporterSupernodeAccount != source { + return nil, fmt.Errorf("malformed account transition reporter-reliability state") + } + state.ReporterSupernodeAccount = destination + var err error + destinationValue, err = k.cdc.Marshal(&state) + if err != nil { + return nil, err + } + } + moves[len(moves)-1].destinationVal = destinationValue + } + return moves, nil +} + +// accountTransitionWorkflowBlocker rejects moving an account participating in +// a non-final heal operation. The prefix scan is deterministic and capped. +func (k Keeper) accountTransitionWorkflowBlocker(ctx sdk.Context, source string) error { + prefix := types.HealOpPrefix() + store := k.kvStore(ctx) + it := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + defer func() { _ = it.Close() }() + count := 0 + for ; it.Valid(); it.Next() { + count++ + if count > types.MaxIdentityTransitionHealOps { + return fmt.Errorf("heal operations exceed identity transition limit %d", types.MaxIdentityTransitionHealOps) + } + var op types.HealOp + if err := k.cdc.Unmarshal(it.Value(), &op); err != nil { + return fmt.Errorf("malformed heal operation: %w", err) + } + if isHealOpFinalStatus(op.Status) { + continue + } + if op.HealerSupernodeAccount == source { + return fmt.Errorf("account transition blocked: source is healer in non-final heal operation %d", op.HealOpId) + } + for _, verifier := range op.VerifierSupernodeAccounts { + if verifier == source { + return fmt.Errorf("account transition blocked: source is verifier in non-final heal operation %d", op.HealOpId) + } + } + } + return nil +} diff --git a/x/audit/v1/keeper/identity_continuity_additional_test.go b/x/audit/v1/keeper/identity_continuity_additional_test.go new file mode 100644 index 00000000..2eb4943e --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_additional_test.go @@ -0,0 +1,273 @@ +package keeper_test + +import ( + "encoding/binary" + "fmt" + "strings" + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func addContinuityResultFacts(t *testing.T, f *fixture, epoch uint64, reporter string, failures, passes int) { + t.Helper() + for i := 0; i < failures+passes; i++ { + class := types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH + if i >= failures { + class = types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS + } + require.NoError(t, keeper.SetStorageTruthReporterResultForTest(f.keeper, f.ctx, epoch, reporter, &types.StorageProofResult{ + TicketId: fmt.Sprintf("continuity-%d-%s-%d", epoch, reporter, i), TargetSupernodeAccount: fmt.Sprintf("target-%d", i), ResultClass: class, + })) + } +} + +func TestAccountTransitionHealOpBlockersAndBound(t *testing.T) { + for _, tc := range []struct { + name string + op func(source, other string) types.HealOp + want string + }{ + {"source healer", func(source, other string) types.HealOp { + return types.HealOp{HealOpId: 1, HealerSupernodeAccount: source, VerifierSupernodeAccounts: []string{other}, Status: types.HealOpStatus_HEAL_OP_STATUS_SCHEDULED} + }, "source is healer"}, + {"source verifier", func(source, other string) types.HealOp { + return types.HealOp{HealOpId: 1, HealerSupernodeAccount: other, VerifierSupernodeAccounts: []string{source}, Status: types.HealOpStatus_HEAL_OP_STATUS_IN_PROGRESS} + }, "source is verifier"}, + } { + t.Run(tc.name, func(t *testing.T) { + f := initFixture(t) + source := testAddress(t, f, []byte{1, 2, 3, 40}) + destination := testAddress(t, f, []byte{5, 6, 7, 80}) + other := testAddress(t, f, []byte{9, 10, 11, 120}) + require.NoError(t, f.keeper.SetHealOp(f.ctx, tc.op(source, other))) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2}) + require.ErrorContains(t, err, tc.want) + }) + } + + t.Run("final operation allowed", func(t *testing.T) { + f := initFixture(t) + source := testAddress(t, f, []byte{1, 3, 5, 7}) + destination := testAddress(t, f, []byte{2, 4, 6, 8}) + require.NoError(t, f.keeper.SetHealOp(f.ctx, types.HealOp{HealOpId: 1, HealerSupernodeAccount: source, Status: types.HealOpStatus_HEAL_OP_STATUS_VERIFIED})) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2}) + require.NoError(t, err) + }) + + for _, extra := range []int{0, 1} { + name := "exact cap" + if extra == 1 { + name = "cap plus one" + } + t.Run(name, func(t *testing.T) { + f := initFixture(t) + for i := 0; i < types.MaxIdentityTransitionHealOps+extra; i++ { + require.NoError(t, f.keeper.SetHealOp(f.ctx, types.HealOp{HealOpId: uint64(i + 1), Status: types.HealOpStatus_HEAL_OP_STATUS_VERIFIED})) + } + source := testAddress(t, f, []byte{13, 14, 15, 16}) + destination := testAddress(t, f, []byte{17, 18, 19, 20}) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2}) + if extra == 0 { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, "heal operations exceed") + } + }) + } +} + +func TestAccountTransitionMovesEverySingletonAndRewritesEmbeddedAccounts(t *testing.T) { + f := initFixture(t) + source := testAddress(t, f, []byte{21, 22, 23, 24}) + destination := testAddress(t, f, []byte{25, 26, 27, 28}) + store := f.ctx.KVStore(f.storeKey) + store.Set(types.ActionFinalizationPostponementKey(source), binary.BigEndian.AppendUint64(nil, 11)) + store.Set(types.StorageTruthPostponementKey(source), binary.BigEndian.AppendUint64(nil, 12)) + store.Set(types.StorageTruthPostponementStrongKey(source), []byte{1}) + f.keeper.SetNodeSuspicionState(f.ctx, types.NodeSuspicionState{SupernodeAccount: source, SuspicionScore: 13}) + f.keeper.SetReporterReliabilityState(f.ctx, types.ReporterReliabilityState{ReporterSupernodeAccount: source, ReliabilityScore: 14}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2})) + for _, pair := range [][2][]byte{ + {types.ActionFinalizationPostponementKey(source), types.ActionFinalizationPostponementKey(destination)}, + {types.StorageTruthPostponementKey(source), types.StorageTruthPostponementKey(destination)}, + {types.StorageTruthPostponementStrongKey(source), types.StorageTruthPostponementStrongKey(destination)}, + {types.NodeSuspicionStateKey(source), types.NodeSuspicionStateKey(destination)}, + {types.ReporterReliabilityStateKey(source), types.ReporterReliabilityStateKey(destination)}, + } { + require.False(t, store.Has(pair[0])) + require.True(t, store.Has(pair[1])) + } + node, found := f.keeper.GetNodeSuspicionState(f.ctx, destination) + require.True(t, found) + require.Equal(t, destination, node.SupernodeAccount) + reporter, found := f.keeper.GetReporterReliabilityState(f.ctx, destination) + require.True(t, found) + require.Equal(t, destination, reporter.ReporterSupernodeAccount) +} + +func TestAccountTransitionCanonicalEndpointsAndGenesisValidation(t *testing.T) { + f := initFixture(t) + source := testAddress(t, f, []byte{31, 32, 33, 34}) + destination := testAddress(t, f, []byte{35, 36, 37, 38}) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: strings.ToUpper(source), DestinationAccount: destination, EffectiveEpoch: 2}) + require.ErrorContains(t, err, "noncanonical") + + genesis := types.DefaultGenesis() + genesis.AccountTransitions = []types.AccountTransition{{SourceAccount: source, DestinationAccount: strings.ToUpper(destination), EffectiveEpoch: 2}} + require.NoError(t, genesis.Validate(), "types validation is structural") + require.ErrorContains(t, f.keeper.InitGenesis(f.ctx, *genesis), "noncanonical") +} + +func TestAccountTransitionExactMaxAndGetAllNoOffByOne(t *testing.T) { + f := initFixture(t) + accounts := make([]string, types.MaxAccountTransitions+2) + for i := range accounts { + bz := make([]byte, 4) + binary.BigEndian.PutUint32(bz, uint32(i+1)) + accounts[i] = testAddress(t, f, bz) + } + for i := 0; i < types.MaxAccountTransitions; i++ { + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: accounts[i], DestinationAccount: accounts[i+1], EffectiveEpoch: uint64(i + 1)})) + } + got, err := f.keeper.CurrentAccount(f.ctx, accounts[0]) + require.NoError(t, err) + require.Equal(t, accounts[types.MaxAccountTransitions], got) + all, err := f.keeper.GetAllAccountTransitions(f.ctx) + require.NoError(t, err) + require.Len(t, all, types.MaxAccountTransitions) + err = recordAccountTransition(t, f, types.AccountTransition{SourceAccount: accounts[types.MaxAccountTransitions], DestinationAccount: accounts[types.MaxAccountTransitions+1], EffectiveEpoch: types.MaxAccountTransitions + 1}) + require.ErrorContains(t, err, "exceed limit") +} + +func TestSetReportLiveSideEffectUsesCurrentAccount(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{41, 42, 43, 44}) + current := testAddress(t, f, []byte{45, 46, 47, 48}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2})) + sn := sntypes.SuperNode{SupernodeAccount: current, States: []*sntypes.SuperNodeStateRecord{{State: sntypes.SuperNodeStateActive}}} + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), current).Return(sn, true, nil).Times(1) + f.supernodeKeeper.EXPECT().GetParams(gomock.Any()).Return(sntypes.DefaultParams()).Times(1) + require.NoError(t, f.keeper.SetReport(f.ctx, types.EpochReport{EpochId: 1, SupernodeAccount: old, HostReport: types.HostReport{DiskUsagePercent: 1}})) +} + +func TestSubmitMalformedLineageFailsInsteadOfPanics(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + creator := testAddress(t, f, []byte{51, 52, 53, 54}) + seedEpochAnchorForReportTest(t, f, 0, []string{creator}, []string{creator}) + f.ctx.KVStore(f.storeKey).Set(types.AccountTransitionReverseKey(creator), []byte{0xff}) + require.NotPanics(t, func() { + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{Creator: creator, EpochId: 0}) + require.ErrorContains(t, err, "malformed account transition") + }) +} + +func TestSubmitSignerContinuityAndLegacyReportDecode(t *testing.T) { + t.Run("old signer rejected", func(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + old := testAddress(t, f, []byte{61, 62, 63, 64}) + current := testAddress(t, f, []byte{65, 66, 67, 68}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 1})) + seedEpochAnchorForReportTest(t, f, 0, []string{old}, []string{old}) + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{Creator: old, EpochId: 0}) + require.ErrorIs(t, err, types.ErrInvalidSigner) + }) + + t.Run("unlinked current account rejected", func(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + old := testAddress(t, f, []byte{71, 72, 73, 74}) + linkedCurrent := testAddress(t, f, []byte{79, 80, 81, 82}) + unlinked := testAddress(t, f, []byte{75, 76, 77, 78}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: linkedCurrent, EffectiveEpoch: 1})) + seedEpochAnchorForReportTest(t, f, 0, []string{old}, []string{old}) + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), unlinked).Return(sntypes.SuperNode{SupernodeAccount: unlinked}, true, nil).Times(1) + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{Creator: unlinked, EpochId: 0}) + require.Error(t, err) + }) + + t.Run("historical empty current submitter decodes", func(t *testing.T) { + f := initFixture(t) + account := testAddress(t, f, []byte{81, 82, 83, 84}) + require.NoError(t, f.keeper.SetReportRaw(f.ctx, types.EpochReport{EpochId: 4, SupernodeAccount: account})) + report, found := f.keeper.GetReport(f.ctx, 4, account) + require.True(t, found) + require.Empty(t, report.CurrentSubmitter) + }) +} + +func TestEnforcementRecognizesHistoricalReportAcrossTransition(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{91, 92, 93, 94}) + current := testAddress(t, f, []byte{95, 96, 97, 98}) + validator := sdk.ValAddress([]byte{99, 100, 101, 102}).String() + require.NoError(t, f.keeper.SetReportRaw(f.ctx, types.EpochReport{EpochId: 1, SupernodeAccount: old})) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2})) + sn := sntypes.SuperNode{SupernodeAccount: current, ValidatorAddress: validator} + params := types.DefaultParams() + params.ConsecutiveEpochsToPostpone = 1 + params.RequiredOpenPorts = nil + f.supernodeKeeper.EXPECT().GetAllSuperNodes(gomock.Any(), sntypes.SuperNodeStateActive).Return([]sntypes.SuperNode{sn}, nil).Times(1) + f.supernodeKeeper.EXPECT().GetAllSuperNodes(gomock.Any(), sntypes.SuperNodeStatePostponed).Return(nil, nil).Times(1) + f.supernodeKeeper.EXPECT().SetSuperNodePostponed(gomock.Any(), gomock.Any(), "audit_missing_reports").Times(0) + require.NoError(t, f.keeper.EnforceEpochEnd(f.ctx, 1, params)) +} + +func TestReporterDivergenceAndCleanRecoverySpanTransition(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{111, 112, 113, 114}) + current := testAddress(t, f, []byte{115, 116, 117, 118}) + baseA := testAddress(t, f, []byte{121, 122, 123, 124}) + baseB := testAddress(t, f, []byte{125, 126, 127, 128}) + for _, account := range []string{old, baseA, baseB} { + require.NoError(t, f.keeper.SetReporterReliabilityState(f.ctx, types.ReporterReliabilityState{ReporterSupernodeAccount: account})) + } + addContinuityResultFacts(t, f, 1, old, 4, 1) + addContinuityResultFacts(t, f, 1, baseA, 1, 4) + addContinuityResultFacts(t, f, 1, baseB, 1, 4) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2})) + addContinuityResultFacts(t, f, 2, current, 5, 0) + addContinuityResultFacts(t, f, 2, baseA, 1, 4) + addContinuityResultFacts(t, f, 2, baseB, 1, 4) + params := types.DefaultParams().WithDefaults() + params.StorageTruthReporterMinReportsForDivergence = 5 + require.NoError(t, f.keeper.ApplyReporterDivergenceAtEpochEnd(f.ctx, 2, params)) + state, found := f.keeper.GetReporterReliabilityState(f.ctx, current) + require.True(t, found) + require.Equal(t, int64(8), state.ReliabilityScore) + + addContinuityResultFacts(t, f, 3, current, 0, 5) + require.NoError(t, f.keeper.ApplyReporterCleanEpochRecoveryAtEpochEnd(f.ctx, 3, params)) + state, found = f.keeper.GetReporterReliabilityState(f.ctx, current) + require.True(t, found) + require.Equal(t, int64(3), state.ReliabilityScore, "one epoch of decay followed by the four-point clean recovery") +} + +func TestReporterCleanRecoveryDoesNotDoubleApplyDuringTransitionEpoch(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{131, 132, 133, 134}) + current := testAddress(t, f, []byte{135, 136, 137, 138}) + require.NoError(t, f.keeper.SetReporterReliabilityState(f.ctx, types.ReporterReliabilityState{ + ReporterSupernodeAccount: old, + ReliabilityScore: 8, + LastUpdatedEpoch: 1, + })) + addContinuityResultFacts(t, f, 1, old, 0, 5) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2, + })) + + params := types.DefaultParams().WithDefaults() + require.NoError(t, f.keeper.ApplyReporterCleanEpochRecoveryAtEpochEnd(f.ctx, 1, params)) + state, found := f.keeper.GetReporterReliabilityState(f.ctx, current) + require.True(t, found) + require.Equal(t, int64(4), state.ReliabilityScore, "old epoch index and current singleton must collapse to one actor") +} diff --git a/x/audit/v1/keeper/identity_continuity_genesis_test.go b/x/audit/v1/keeper/identity_continuity_genesis_test.go new file mode 100644 index 00000000..dbae6184 --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_genesis_test.go @@ -0,0 +1,41 @@ +package keeper_test + +import ( + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + "github.com/stretchr/testify/require" +) + +func TestAccountTransitionsGenesisRoundTrip(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{31, 32, 33, 34}) + b := testAddress(t, f, []byte{41, 42, 43, 44}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 3})) + + exported, err := f.keeper.ExportGenesis(f.ctx) + require.NoError(t, err) + require.Equal(t, []types.AccountTransition{{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 3}}, exported.AccountTransitions) +} + +func TestGenesisRejectsInvalidAccountTransitionGraph(t *testing.T) { + genesis := types.DefaultGenesis() + genesis.AccountTransitions = []types.AccountTransition{ + {SourceAccount: "a", DestinationAccount: "b", EffectiveEpoch: 3}, + {SourceAccount: "b", DestinationAccount: "a", EffectiveEpoch: 4}, + } + require.Error(t, genesis.Validate()) +} + +func TestGenesisRejectsLiveSingletonAtTransitionSource(t *testing.T) { + f := initFixture(t) + source := testAddress(t, f, []byte{51, 52, 53, 54}) + destination := testAddress(t, f, []byte{61, 62, 63, 64}) + genesis := types.DefaultGenesis() + genesis.AccountTransitions = []types.AccountTransition{{SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2}} + genesis.NodeSuspicionStates = []types.NodeSuspicionState{{SupernodeAccount: source, SuspicionScore: 7}} + err := f.keeper.InitGenesis(f.ctx, *genesis) + require.ErrorContains(t, err, "non-current transition source") + require.False(t, f.ctx.KVStore(f.storeKey).Has(types.AccountTransitionForwardKey(source)), "validation fails before importing lineage") + require.False(t, f.ctx.KVStore(f.storeKey).Has(types.NodeSuspicionStateKey(source)), "validation fails before singleton writes") +} diff --git a/x/audit/v1/keeper/identity_continuity_regression_test.go b/x/audit/v1/keeper/identity_continuity_regression_test.go new file mode 100644 index 00000000..42f9c2bd --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_regression_test.go @@ -0,0 +1,219 @@ +package keeper_test + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func auditStoreSnapshot(f *fixture) map[string][]byte { + store := f.ctx.KVStore(f.storeKey) + it := store.Iterator(nil, nil) + defer func() { _ = it.Close() }() + out := make(map[string][]byte) + for ; it.Valid(); it.Next() { + out[string(it.Key())] = append([]byte(nil), it.Value()...) + } + return out +} + +func TestAccountTransitionPlanStaleAndDuplicateApplyHaveZeroWrites(t *testing.T) { + t.Run("same-count raw transition corruption", func(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{1, 1, 1, 10}) + b := testAddress(t, f, []byte{2, 2, 2, 20}) + c := testAddress(t, f, []byte{3, 3, 3, 30}) + d := testAddress(t, f, []byte{4, 4, 4, 40}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 1})) + f.ctx.KVStore(f.storeKey).Set(types.ActionFinalizationPostponementKey(c), []byte{0, 0, 0, 0, 0, 0, 0, 7}) + plan, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: c, DestinationAccount: d, EffectiveEpoch: 2}) + require.NoError(t, err) + + store := f.ctx.KVStore(f.storeKey) + store.Set(types.AccountTransitionForwardKey(a), []byte{0xff}) + before := auditStoreSnapshot(f) + err = f.keeper.ApplyAccountTransitionPlan(f.ctx, plan) + require.ErrorContains(t, err, "index precondition") + require.Equal(t, before, auditStoreSnapshot(f)) + require.True(t, store.Has(types.ActionFinalizationPostponementKey(c))) + require.False(t, store.Has(types.ActionFinalizationPostponementKey(d))) + require.False(t, store.Has(types.AccountTransitionForwardKey(c))) + }) + + t.Run("duplicate", func(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{11, 11, 11, 11}) + b := testAddress(t, f, []byte{12, 12, 12, 12}) + plan, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 1}) + require.NoError(t, err) + require.NoError(t, f.keeper.ApplyAccountTransitionPlan(f.ctx, plan)) + before := auditStoreSnapshot(f) + err = f.keeper.ApplyAccountTransitionPlan(f.ctx, plan) + require.ErrorContains(t, err, "stale or already applied") + require.Equal(t, before, auditStoreSnapshot(f)) + }) +} + +func TestAccountTransitionRejectsForwardAndReverseKeyValueMismatch(t *testing.T) { + for _, reverse := range []bool{false, true} { + f := initFixture(t) + a := testAddress(t, f, []byte{21, 21, 21, 21}) + b := testAddress(t, f, []byte{22, 22, 22, 22}) + other := testAddress(t, f, []byte{23, 23, 23, 23}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 1})) + store := f.ctx.KVStore(f.storeKey) + if reverse { + store.Set(types.AccountTransitionReverseKey(other), store.Get(types.AccountTransitionReverseKey(b))) + _, err := f.keeper.CurrentAccount(f.ctx, other) + require.ErrorContains(t, err, "reverse index key does not match") + } else { + store.Set(types.AccountTransitionForwardKey(other), store.Get(types.AccountTransitionForwardKey(a))) + _, err := f.keeper.CurrentAccount(f.ctx, other) + require.ErrorContains(t, err, "forward index key does not match") + } + } +} + +func TestMigratedTicketTargetAndReporterRemainSameIdentities(t *testing.T) { + f := initFixture(t) + targetOld := testAddress(t, f, []byte{31, 31, 31, 31}) + targetNew := testAddress(t, f, []byte{32, 32, 32, 32}) + reporterOld := testAddress(t, f, []byte{33, 33, 33, 33}) + reporterNew := testAddress(t, f, []byte{34, 34, 34, 34}) + require.NoError(t, f.keeper.SetTicketDeteriorationState(f.ctx, types.TicketDeteriorationState{ + TicketId: "same-lineage", DeteriorationScore: 5, LastUpdatedEpoch: 1, + LastFailureEpoch: 1, RecentFailureEpochCount: 1, DistinctHolderFailureCount: 1, + LastTargetSupernodeAccount: targetOld, LastReporterSupernodeAccount: reporterOld, + LastResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH, LastResultEpoch: 1, + })) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: targetOld, DestinationAccount: targetNew, EffectiveEpoch: 2})) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: reporterOld, DestinationAccount: reporterNew, EffectiveEpoch: 2})) + + state, updated, err := keeper.ApplyTicketDeteriorationDeltaForTest(f.keeper, f.ctx, 3, reporterNew, &types.StorageProofResult{ + TicketId: "same-lineage", TargetSupernodeAccount: targetNew, + ResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH, + }, "same-lineage", 5, 0, false) + require.NoError(t, err) + require.True(t, updated) + require.Equal(t, uint32(1), state.DistinctHolderFailureCount, "migration is not a distinct holder") + require.Equal(t, targetNew, state.LastTargetSupernodeAccount) + require.Equal(t, reporterNew, state.LastReporterSupernodeAccount) +} + +func TestCurrentReporterReliabilityScalesMigratedReporterResult(t *testing.T) { + f := initFixture(t) + reporterOld := testAddress(t, f, []byte{41, 41, 41, 41}) + reporterNew := testAddress(t, f, []byte{42, 42, 42, 42}) + target := testAddress(t, f, []byte{43, 43, 43, 43}) + require.NoError(t, f.keeper.SetReporterReliabilityState(f.ctx, types.ReporterReliabilityState{ + ReporterSupernodeAccount: reporterOld, ReliabilityScore: 50, LastUpdatedEpoch: 2, + })) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: reporterOld, DestinationAccount: reporterNew, EffectiveEpoch: 2})) + require.NoError(t, keeper.ApplyStorageTruthScoresForTest(f.keeper, f.ctx.WithEventManager(sdk.NewEventManager()), 2, reporterNew, []*types.StorageProofResult{{ + TicketId: "scaled", TargetSupernodeAccount: target, + ResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH, + ArtifactClass: types.StorageProofArtifactClass_STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL, + BucketType: types.StorageProofBucketType_STORAGE_PROOF_BUCKET_TYPE_RECENT, + }})) + node, found := f.keeper.GetNodeSuspicionState(f.ctx, target) + require.True(t, found) + require.Equal(t, int64(9), node.SuspicionScore, "current singleton reliability 50 scales +18 to +9") + ticket, found := f.keeper.GetTicketDeteriorationState(f.ctx, "scaled") + require.True(t, found) + require.Equal(t, int64(2), ticket.DeteriorationScore, "current singleton reliability 50 scales +5 toward zero") +} + +func TestHistoricalPeerPortObservationUsesEpochTarget(t *testing.T) { + f := initFixture(t) + targetOld := testAddress(t, f, []byte{51, 51, 51, 51}) + targetNew := testAddress(t, f, []byte{52, 52, 52, 52}) + reporter := testAddress(t, f, []byte{53, 53, 53, 53}) + require.NoError(t, f.keeper.SetReportRaw(f.ctx, types.EpochReport{ + EpochId: 1, SupernodeAccount: reporter, + StorageChallengeObservations: []*types.StorageChallengeObservation{{TargetSupernodeAccount: targetOld, PortStates: []types.PortState{types.PortState_PORT_STATE_OPEN}}}, + })) + f.keeper.SetStorageChallengeReportIndex(f.ctx, targetOld, 1, reporter) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: targetOld, DestinationAccount: targetNew, EffectiveEpoch: 2})) + met, err := keeper.PeersPortStateMeetsThresholdForTest(f.keeper, f.ctx, targetNew, 1, 0, types.PortState_PORT_STATE_OPEN, 100) + require.NoError(t, err) + require.True(t, met) +} + +func TestMigratedRecheckerDedupSelfAttestationAndTranscriptIdentity(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1).WithEventManager(sdk.NewEventManager()) + recheckerOld := testAddress(t, f, []byte{61, 61, 61, 61}) + recheckerNew := testAddress(t, f, []byte{62, 62, 62, 62}) + targetOld := testAddress(t, f, []byte{63, 63, 63, 63}) + targetNew := testAddress(t, f, []byte{64, 64, 64, 64}) + originalReporter := testAddress(t, f, []byte{65, 65, 65, 65}) + seedEpochAnchorForReportTest(t, f, 0, []string{recheckerOld, targetOld}, []string{recheckerOld, targetOld}) + seedIndexedChallengeResult(t, f, originalReporter, targetOld, "migrated-ticket", "challenged-hash") + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: recheckerOld, DestinationAccount: recheckerNew, EffectiveEpoch: 1})) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: targetOld, DestinationAccount: targetNew, EffectiveEpoch: 1})) + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), recheckerNew).Return(sntypes.SuperNode{}, true, nil).AnyTimes() + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), targetNew).Return(sntypes.SuperNode{}, true, nil).AnyTimes() + ms := keeper.NewMsgServerImpl(f.keeper) + + self := &types.MsgSubmitStorageRecheckEvidence{Creator: targetNew, EpochId: 0, ChallengedSupernodeAccount: targetNew, TicketId: "migrated-ticket", ChallengedResultTranscriptHash: "challenged-hash", RecheckTranscriptHash: "self-hash", RecheckResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS} + _, err := ms.SubmitStorageRecheckEvidence(f.ctx, self) + require.ErrorContains(t, err, "target lineage") + + req := &types.MsgSubmitStorageRecheckEvidence{Creator: recheckerNew, EpochId: 0, ChallengedSupernodeAccount: targetNew, TicketId: "migrated-ticket", ChallengedResultTranscriptHash: "challenged-hash", RecheckTranscriptHash: "recheck-hash", RecheckResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS} + _, err = ms.SubmitStorageRecheckEvidence(f.ctx, req) + require.NoError(t, err) + _, err = ms.SubmitStorageRecheckEvidence(f.ctx, req) + require.ErrorContains(t, err, "already submitted") + + exported, err := f.keeper.ExportGenesis(f.ctx) + require.NoError(t, err) + require.Contains(t, exported.RecheckEvidence, types.GenesisRecheckEvidence{EpochId: 0, TicketId: "migrated-ticket", CreatorAccount: recheckerOld}) + var recheckJSON []byte + for _, transcript := range exported.StorageProofTranscripts { + if transcript.TranscriptHash == "recheck-hash" { + recheckJSON = transcript.RecordJson + } + } + require.NotEmpty(t, recheckJSON) + var record map[string]any + require.NoError(t, json.Unmarshal(recheckJSON, &record)) + require.Equal(t, recheckerOld, record["reporter_account"]) + require.Equal(t, "challenged-hash", record["challenged_transcript_hash"]) + require.False(t, bytes.Contains(recheckJSON, []byte(recheckerNew)), "historical transcript preserves epoch identity") +} + +func TestRecheckScoresHistoricalTargetUnderCurrentEpochIdentity(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(401).WithEventManager(sdk.NewEventManager()) + creator := testAddress(t, f, []byte{71, 71, 71, 71}) + targetOld := testAddress(t, f, []byte{72, 72, 72, 72}) + targetNew := testAddress(t, f, []byte{73, 73, 73, 73}) + originalReporter := testAddress(t, f, []byte{74, 74, 74, 74}) + seedEpochAnchorForReportTest(t, f, 0, []string{creator, targetOld}, []string{creator, targetOld}) + seedIndexedChallengeResult(t, f, originalReporter, targetOld, "recheck-current-target", "historical-target-hash") + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: targetOld, DestinationAccount: targetNew, EffectiveEpoch: 1})) + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), creator).Return(sntypes.SuperNode{}, true, nil).AnyTimes() + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), targetNew).Return(sntypes.SuperNode{}, true, nil).AnyTimes() + + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitStorageRecheckEvidence(f.ctx, &types.MsgSubmitStorageRecheckEvidence{ + Creator: creator, + EpochId: 0, + ChallengedSupernodeAccount: targetOld, + TicketId: "recheck-current-target", + ChallengedResultTranscriptHash: "historical-target-hash", + RecheckTranscriptHash: "current-epoch-recheck-hash", + RecheckResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL, + }) + require.NoError(t, err) + _, found := f.keeper.GetNodeSuspicionState(f.ctx, targetNew) + require.True(t, found, "current-epoch scoring must write the current logical target") + _, found = f.keeper.GetNodeSuspicionState(f.ctx, targetOld) + require.False(t, found, "historical alias must not receive current-epoch singleton state") +} diff --git a/x/audit/v1/keeper/identity_continuity_report_test.go b/x/audit/v1/keeper/identity_continuity_report_test.go new file mode 100644 index 00000000..91110e2a --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_report_test.go @@ -0,0 +1,77 @@ +package keeper_test + +import ( + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestSubmitEpochReportUsesEpochLogicalReporterAndCurrentSubmitter(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + old := testAddress(t, f, []byte{11, 12, 13, 14}) + current := testAddress(t, f, []byte{21, 22, 23, 24}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 1, + })) + seedEpochAnchorForReportTest(t, f, 0, []string{old}, []string{old}) + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), current). + Return(sntypes.SuperNode{SupernodeAccount: current}, true, nil).AnyTimes() + + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: current, EpochId: 0, HostReport: types.HostReport{}, + }) + require.NoError(t, err) + report, found := f.keeper.GetReport(f.ctx, 0, old) + require.True(t, found) + require.Equal(t, old, report.SupernodeAccount) + require.Equal(t, current, report.CurrentSubmitter) + require.True(t, f.keeper.HasReport(f.ctx, 0, old)) + require.True(t, f.keeper.HasReport(f.ctx, 0, current), "lineage-aware reads find the epoch-logical row") + require.False(t, f.ctx.KVStore(f.storeKey).Has(types.ReportKey(0, current)), "historical row is not rewritten") + + _, err = keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: current, EpochId: 0, HostReport: types.HostReport{}, + }) + require.ErrorIs(t, err, types.ErrDuplicateReport) +} + +func TestHistoricalReportReadFollowsLineageWithoutRewriting(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{111, 112, 113, 114}) + current := testAddress(t, f, []byte{121, 122, 123, 124}) + require.NoError(t, f.keeper.SetReportRaw(f.ctx, types.EpochReport{EpochId: 2, SupernodeAccount: old})) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 3, + })) + + report, found := f.keeper.GetReport(f.ctx, 2, current) + require.True(t, found) + require.Equal(t, old, report.SupernodeAccount) + _, rawStillPresent := f.keeper.GetReport(f.ctx, 3, old) + require.False(t, rawStillPresent) +} + +func TestReportBeforeTransitionThenCurrentDuplicateDoesNotWrite(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + old := testAddress(t, f, []byte{71, 72, 73, 74}) + current := testAddress(t, f, []byte{75, 76, 77, 78}) + require.NoError(t, f.keeper.SetReportRaw(f.ctx, types.EpochReport{EpochId: 0, SupernodeAccount: old, HostReport: types.HostReport{DiskUsagePercent: 17}})) + seedEpochAnchorForReportTest(t, f, 0, []string{old}, []string{old}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 1})) + f.supernodeKeeper.EXPECT().GetSuperNodeByAccount(gomock.Any(), current).Return(sntypes.SuperNode{SupernodeAccount: current}, true, nil).AnyTimes() + before := auditStoreSnapshot(f) + _, err := keeper.NewMsgServerImpl(f.keeper).SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{Creator: current, EpochId: 0}) + require.ErrorIs(t, err, types.ErrDuplicateReport) + require.Equal(t, before, auditStoreSnapshot(f)) + report, found := f.keeper.GetReport(f.ctx, 0, current) + require.True(t, found) + require.Equal(t, float64(17), report.HostReport.DiskUsagePercent) + require.Empty(t, report.CurrentSubmitter) + require.False(t, f.ctx.KVStore(f.storeKey).Has(types.ReportKey(0, current))) +} diff --git a/x/audit/v1/keeper/identity_continuity_test.go b/x/audit/v1/keeper/identity_continuity_test.go new file mode 100644 index 00000000..9bf3c735 --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_test.go @@ -0,0 +1,138 @@ +package keeper_test + +import ( + "encoding/binary" + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + "github.com/stretchr/testify/require" +) + +func buildAccountTransitionPlan(t *testing.T, f *fixture, transition types.AccountTransition) (keeper.AccountTransitionPlan, error) { + t.Helper() + params := f.keeper.GetParams(f.ctx).WithDefaults() + height := int64(params.EpochZeroHeight) + if transition.EffectiveEpoch > 0 { + height += int64(transition.EffectiveEpoch-1) * int64(params.EpochLengthBlocks) + } + return f.keeper.BuildAccountTransitionPlan(f.ctx.WithBlockHeight(height), transition) +} + +func recordAccountTransition(t *testing.T, f *fixture, transition types.AccountTransition) error { + t.Helper() + params := f.keeper.GetParams(f.ctx).WithDefaults() + height := int64(params.EpochZeroHeight) + if transition.EffectiveEpoch > 0 { + height += int64(transition.EffectiveEpoch-1) * int64(params.EpochLengthBlocks) + } + return f.keeper.RecordAccountTransition(f.ctx.WithBlockHeight(height), transition) +} + +func testAddress(t *testing.T, f *fixture, bz []byte) string { + t.Helper() + address, err := f.addressCodec.BytesToString(bz) + require.NoError(t, err) + return address +} + +func TestAccountTransitionLineageBoundariesAndTwoHop(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{1, 2, 3, 4}) + mid := testAddress(t, f, []byte{5, 6, 7, 8}) + current := testAddress(t, f, []byte{9, 10, 11, 12}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: mid, EffectiveEpoch: 5})) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: mid, DestinationAccount: current, EffectiveEpoch: 9})) + + for _, tc := range []struct { + epoch uint64 + want string + }{{4, old}, {5, mid}, {8, mid}, {9, current}, {100, current}} { + got, err := f.keeper.AccountForEpoch(f.ctx, old, tc.epoch) + require.NoError(t, err) + require.Equal(t, tc.want, got) + } + got, err := f.keeper.CurrentAccount(f.ctx, old) + require.NoError(t, err) + require.Equal(t, current, got) +} + +func TestAccountTransitionRejectsCycleForkAndOutOfOrder(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{1, 1, 1, 1}) + b := testAddress(t, f, []byte{2, 2, 2, 2}) + c := testAddress(t, f, []byte{3, 3, 3, 3}) + d := testAddress(t, f, []byte{4, 4, 4, 4}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 5})) + require.Error(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: a, DestinationAccount: c, EffectiveEpoch: 6}), "fork") + require.Error(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: b, DestinationAccount: a, EffectiveEpoch: 6}), "cycle") + require.Error(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: b, DestinationAccount: d, EffectiveEpoch: 4}), "epochs must increase") +} + +func TestAccountTransitionMovesCurrentSingletonState(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{1, 2, 3, 4}) + current := testAddress(t, f, []byte{5, 6, 7, 8}) + f.keeper.SetNodeSuspicionState(f.ctx, types.NodeSuspicionState{SupernodeAccount: old, SuspicionScore: 7}) + f.keeper.SetReporterReliabilityState(f.ctx, types.ReporterReliabilityState{ReporterSupernodeAccount: old, ReliabilityScore: 8}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2})) + _, found := f.keeper.GetNodeSuspicionState(f.ctx, old) + require.False(t, found) + ns, found := f.keeper.GetNodeSuspicionState(f.ctx, current) + require.True(t, found) + require.Equal(t, int64(7), ns.SuspicionScore) + _, found = f.keeper.GetReporterReliabilityState(f.ctx, old) + require.False(t, found) + rr, found := f.keeper.GetReporterReliabilityState(f.ctx, current) + require.True(t, found) + require.Equal(t, int64(8), rr.ReliabilityScore) +} + +func TestBuildAccountTransitionPlanRejectsEverySingletonCollisionAndMalformedSource(t *testing.T) { + for _, tc := range []struct { + name string + key func(string) []byte + valid []byte + }{ + {"action marker", types.ActionFinalizationPostponementKey, binary.BigEndian.AppendUint64(nil, 4)}, + {"storage marker", types.StorageTruthPostponementKey, binary.BigEndian.AppendUint64(nil, 4)}, + {"strong marker", types.StorageTruthPostponementStrongKey, []byte{1}}, + } { + t.Run(tc.name+" destination collision", func(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{51, 52, 53, 54}) + current := testAddress(t, f, []byte{61, 62, 63, 64}) + store := f.ctx.KVStore(f.storeKey) + store.Set(tc.key(current), tc.valid) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2}) + require.ErrorContains(t, err, "destination collision") + require.False(t, store.Has(types.AccountTransitionForwardKey(old))) + }) + t.Run(tc.name+" malformed source", func(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{71, 72, 73, 74}) + current := testAddress(t, f, []byte{81, 82, 83, 84}) + store := f.ctx.KVStore(f.storeKey) + store.Set(tc.key(old), []byte{9, 9}) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2}) + require.ErrorContains(t, err, "malformed") + require.False(t, store.Has(types.AccountTransitionForwardKey(old))) + }) + } +} + +func TestBuildAccountTransitionPlanRejectsMalformedProtobufSingletonsWithoutWriting(t *testing.T) { + for _, key := range []func(string) []byte{types.NodeSuspicionStateKey, types.ReporterReliabilityStateKey} { + f := initFixture(t) + old := testAddress(t, f, []byte{91, 92, 93, 94}) + current := testAddress(t, f, []byte{101, 102, 103, 104}) + store := f.ctx.KVStore(f.storeKey) + store.Set(key(old), []byte{0xff}) + _, err := buildAccountTransitionPlan(t, f, types.AccountTransition{SourceAccount: old, DestinationAccount: current, EffectiveEpoch: 2}) + require.ErrorContains(t, err, "malformed") + require.False(t, store.Has(types.AccountTransitionForwardKey(old))) + } +} diff --git a/x/audit/v1/keeper/msg_storage_truth.go b/x/audit/v1/keeper/msg_storage_truth.go index fea69568..62dd37f8 100644 --- a/x/audit/v1/keeper/msg_storage_truth.go +++ b/x/audit/v1/keeper/msg_storage_truth.go @@ -22,9 +22,6 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. if req.ChallengedSupernodeAccount == "" { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "challenged_supernode_account is required") } - if req.ChallengedSupernodeAccount == req.Creator { - return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "challenged_supernode_account must not equal creator") - } if req.TicketId == "" { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "ticket_id is required") } @@ -40,12 +37,34 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. return nil, errorsmod.Wrapf(types.ErrInvalidEpochID, "epoch anchor not found for epoch_id %d", req.EpochId) } - if _, found, err := m.supernodeKeeper.GetSuperNodeByAccount(sdkCtx, req.Creator); err != nil { + currentCreator, err := m.CurrentAccount(sdkCtx, req.Creator) + if err != nil { + return nil, err + } + if currentCreator != req.Creator { + return nil, errorsmod.Wrap(types.ErrInvalidSigner, "creator is not the current account for its lineage") + } + logicalCreator, err := m.AccountForEpoch(sdkCtx, currentCreator, req.EpochId) + if err != nil { + return nil, err + } + logicalTarget, err := m.AccountForEpoch(sdkCtx, req.ChallengedSupernodeAccount, req.EpochId) + if err != nil { + return nil, err + } + currentTarget, err := m.CurrentAccount(sdkCtx, req.ChallengedSupernodeAccount) + if err != nil { + return nil, err + } + if logicalCreator == logicalTarget { + return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "creator must not be in the challenged target lineage") + } + if _, found, err := m.supernodeKeeper.GetSuperNodeByAccount(sdkCtx, currentCreator); err != nil { return nil, err } else if !found { return nil, errorsmod.Wrap(types.ErrReporterNotFound, "creator is not a registered supernode") } - if _, found, err := m.supernodeKeeper.GetSuperNodeByAccount(sdkCtx, req.ChallengedSupernodeAccount); err != nil { + if _, found, err := m.supernodeKeeper.GetSuperNodeByAccount(sdkCtx, currentTarget); err != nil { return nil, err } else if !found { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "challenged_supernode_account is not a registered supernode") @@ -75,10 +94,14 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. if challengedRecord.TicketID != req.TicketId { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "challenged result ticket_id does not match request ticket_id") } - if challengedRecord.TargetAccount != req.ChallengedSupernodeAccount { + if challengedRecord.TargetAccount != logicalTarget { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "challenged result target does not match challenged_supernode_account") } - if challengedRecord.ReporterAccount == req.Creator { + originalReporterForEpoch, err := m.AccountForEpoch(sdkCtx, challengedRecord.ReporterAccount, challengedRecord.EpochID) + if err != nil { + return nil, err + } + if originalReporterForEpoch == logicalCreator { return nil, errorsmod.Wrap(types.ErrInvalidRecheckEvidence, "creator must be independent from the challenged result reporter") } if !challengedRecord.RecheckEligible { @@ -86,7 +109,7 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. } // Replay protection: one recheck per (epoch, ticket, creator). - if m.HasRecheckEvidence(sdkCtx, req.EpochId, req.TicketId, req.Creator) { + if m.HasRecheckEvidence(sdkCtx, req.EpochId, req.TicketId, logicalCreator) { return nil, errorsmod.Wrapf(types.ErrInvalidRecheckEvidence, "recheck evidence already submitted for epoch %d ticket %q by %q", req.EpochId, req.TicketId, req.Creator) } // Link transcript BEFORE persisting the dedup key so that a link failure @@ -97,12 +120,12 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. sdkCtx, req.ChallengedResultTranscriptHash, req.RecheckTranscriptHash, - req.Creator, + logicalCreator, req.RecheckResultClass, ); err != nil { return nil, err } - m.SetRecheckEvidence(sdkCtx, req.EpochId, req.TicketId, req.Creator) + m.SetRecheckEvidence(sdkCtx, req.EpochId, req.TicketId, logicalCreator) // Derive current epoch for scoring context. params := m.GetParams(sdkCtx).WithDefaults() @@ -117,7 +140,7 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. var overturnOriginalReporter, confirmOriginalReporter string { origReporter := challengedRecord.ReporterAccount - if origReporter != "" && origReporter != req.Creator { + if origReporter != "" && originalReporterForEpoch != logicalCreator { switch req.RecheckResultClass { case types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS: overturnOriginalReporter = origReporter @@ -127,10 +150,17 @@ func (m msgServer) SubmitStorageRecheckEvidence(ctx context.Context, req *types. } } + scoringTarget, err := m.AccountForEpoch(sdkCtx, currentTarget, currentEpoch.EpochID) + if err != nil { + return nil, err + } + // Synthesise a StorageProofResult carrying the recheck outcome and apply scores. + // The challenged transcript remains historical, while the new failure fact is + // keyed by the target identity logical at the scoring epoch. recheckResult := &types.StorageProofResult{ TicketId: req.TicketId, - TargetSupernodeAccount: req.ChallengedSupernodeAccount, + TargetSupernodeAccount: scoringTarget, ResultClass: req.RecheckResultClass, BucketType: types.StorageProofBucketType_STORAGE_PROOF_BUCKET_TYPE_RECHECK, } diff --git a/x/audit/v1/keeper/msg_storage_truth_test.go b/x/audit/v1/keeper/msg_storage_truth_test.go index c1e8bd15..16f9807f 100644 --- a/x/audit/v1/keeper/msg_storage_truth_test.go +++ b/x/audit/v1/keeper/msg_storage_truth_test.go @@ -80,7 +80,7 @@ func TestMsgSubmitStorageRecheckEvidence(t *testing.T) { RecheckResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS, }) require.Error(t, err) - require.Contains(t, err.Error(), "must not equal creator") + require.Contains(t, err.Error(), "creator must not be in the challenged target lineage") // Valid request: recheck is now implemented and should succeed. _, err = ms.SubmitStorageRecheckEvidence(f.ctx, &types.MsgSubmitStorageRecheckEvidence{ diff --git a/x/audit/v1/keeper/msg_submit_epoch_report.go b/x/audit/v1/keeper/msg_submit_epoch_report.go index 124ff47b..4c3e95ac 100644 --- a/x/audit/v1/keeper/msg_submit_epoch_report.go +++ b/x/audit/v1/keeper/msg_submit_epoch_report.go @@ -39,6 +39,13 @@ func (m msgServer) SubmitEpochReport(ctx context.Context, req *types.MsgSubmitEp if sdkCtx.BlockHeight() < epoch.StartHeight || sdkCtx.BlockHeight() > epoch.EndHeight { return nil, errorsmod.Wrapf(types.ErrInvalidEpochID, "epoch_id not accepted at height %d", sdkCtx.BlockHeight()) } + currentCreator, err := m.CurrentAccount(sdkCtx, req.Creator) + if err != nil { + return nil, err + } + if currentCreator != req.Creator { + return nil, errorsmod.Wrap(types.ErrInvalidSigner, "creator is not the current account for its lineage") + } sn, found, err := m.supernodeKeeper.GetSuperNodeByAccount(sdkCtx, req.Creator) if err != nil { @@ -61,7 +68,39 @@ func (m msgServer) SubmitEpochReport(ctx context.Context, req *types.MsgSubmitEp return nil, errorsmod.Wrapf(types.ErrInvalidEpochID, "epoch anchor not found for epoch_id %d", req.EpochId) } - reporterAccount := req.Creator + reporterAccount, err := m.AccountForEpoch(sdkCtx, req.Creator, req.EpochId) + if err != nil { + return nil, err + } + anchoredReporter := false + for _, account := range anchor.ActiveSupernodeAccounts { + if account == reporterAccount { + anchoredReporter = true + break + } + } + if !anchoredReporter { + for _, account := range anchor.TargetSupernodeAccounts { + if account == reporterAccount { + anchoredReporter = true + break + } + } + } + anchorHasMigratedIdentity := false + for _, account := range append(append([]string(nil), anchor.ActiveSupernodeAccounts...), anchor.TargetSupernodeAccounts...) { + liveAccount, err := m.CurrentAccount(sdkCtx, account) + if err != nil { + return nil, err + } + if liveAccount != account { + anchorHasMigratedIdentity = true + break + } + } + if !anchoredReporter && anchorHasMigratedIdentity { + return nil, errorsmod.Wrap(types.ErrInvalidReporterState, "reporter identity is not linked to a migrated identity in the epoch anchor") + } // Keep assignment/gating stable within the epoch by using the params snapshot captured // at epoch start (when available). Fallback to current params for backward compatibility. @@ -70,7 +109,10 @@ func (m msgServer) SubmitEpochReport(ctx context.Context, req *types.MsgSubmitEp assignParams = snap.WithDefaults() } - eligibleChallengers := m.storageTruthEligibleChallengers(sdkCtx, anchor.ActiveSupernodeAccounts, req.EpochId, assignParams) + eligibleChallengers, err := m.storageTruthEligibleChallengers(sdkCtx, anchor.ActiveSupernodeAccounts, req.EpochId, assignParams) + if err != nil { + return nil, err + } allowedTargetsList, isProber, err := computeAuditPeerTargetsForReporter(&assignParams, eligibleChallengers, anchor.TargetSupernodeAccounts, anchor.Seed, reporterAccount) if err != nil { return nil, err @@ -148,7 +190,11 @@ func (m msgServer) SubmitEpochReport(ctx context.Context, req *types.MsgSubmitEp return nil, err } - if m.HasReport(sdkCtx, req.EpochId, reporterAccount) { + hasReport, err := m.HasReportStrict(sdkCtx, req.EpochId, reporterAccount) + if err != nil { + return nil, err + } + if hasReport { return nil, errorsmod.Wrap(types.ErrDuplicateReport, "report already submitted for this epoch") } @@ -159,6 +205,7 @@ func (m msgServer) SubmitEpochReport(ctx context.Context, req *types.MsgSubmitEp HostReport: req.HostReport, StorageChallengeObservations: req.StorageChallengeObservations, StorageProofResults: req.StorageProofResults, + CurrentSubmitter: req.Creator, } if err := m.SetReport(sdkCtx, report); err != nil { diff --git a/x/audit/v1/keeper/query_assigned_targets.go b/x/audit/v1/keeper/query_assigned_targets.go index 81c2f59d..82c74336 100644 --- a/x/audit/v1/keeper/query_assigned_targets.go +++ b/x/audit/v1/keeper/query_assigned_targets.go @@ -60,8 +60,15 @@ func (q queryServer) AssignedTargets(ctx context.Context, req *types.QueryAssign assignParams = snap.WithDefaults() } - eligibleChallengers := q.k.storageTruthEligibleChallengers(sdkCtx, anchor.ActiveSupernodeAccounts, epochID, assignParams) - targets, _, err := computeAuditPeerTargetsForReporter(&assignParams, eligibleChallengers, anchor.TargetSupernodeAccounts, anchor.Seed, req.SupernodeAccount) + logicalAccount, err := q.k.AccountForEpoch(sdkCtx, req.SupernodeAccount, epochID) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + eligibleChallengers, err := q.k.storageTruthEligibleChallengers(sdkCtx, anchor.ActiveSupernodeAccounts, epochID, assignParams) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + targets, _, err := computeAuditPeerTargetsForReporter(&assignParams, eligibleChallengers, anchor.TargetSupernodeAccounts, anchor.Seed, logicalAccount) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } diff --git a/x/audit/v1/keeper/state.go b/x/audit/v1/keeper/state.go index c86277a6..facd02ef 100644 --- a/x/audit/v1/keeper/state.go +++ b/x/audit/v1/keeper/state.go @@ -11,13 +11,31 @@ import ( ) func (k Keeper) HasReport(ctx sdk.Context, epochID uint64, reporterSupernodeAccount string) bool { + found, err := k.HasReportStrict(ctx, epochID, reporterSupernodeAccount) + if err != nil { + panic(err) + } + return found +} + +// HasReportStrict is used by transaction paths, where malformed lineage state +// must reject the transaction instead of escalating consensus corruption to a panic. +func (k Keeper) HasReportStrict(ctx sdk.Context, epochID uint64, reporterSupernodeAccount string) (bool, error) { + logical, err := k.AccountForEpoch(ctx, reporterSupernodeAccount, epochID) + if err != nil { + return false, err + } store := k.kvStore(ctx) - return store.Has(types.ReportKey(epochID, reporterSupernodeAccount)) + return store.Has(types.ReportKey(epochID, logical)), nil } func (k Keeper) GetReport(ctx sdk.Context, epochID uint64, reporterSupernodeAccount string) (types.EpochReport, bool) { + logical, err := k.AccountForEpoch(ctx, reporterSupernodeAccount, epochID) + if err != nil { + panic(err) + } store := k.kvStore(ctx) - bz := store.Get(types.ReportKey(epochID, reporterSupernodeAccount)) + bz := store.Get(types.ReportKey(epochID, logical)) if bz == nil { return types.EpochReport{}, false } @@ -27,6 +45,13 @@ func (k Keeper) GetReport(ctx sdk.Context, epochID uint64, reporterSupernodeAcco } func (k Keeper) SetReport(ctx sdk.Context, r types.EpochReport) error { + // Resolve every potentially failing live-state lookup before persisting the + // historical report. The report remains keyed by its epoch-logical account; + // only the SuperNode side effect targets the live endpoint. + liveReporter, err := k.CurrentAccount(ctx, r.SupernodeAccount) + if err != nil { + return err + } store := k.kvStore(ctx) bz, err := k.cdc.Marshal(&r) if err != nil { @@ -44,7 +69,7 @@ func (k Keeper) SetReport(ctx sdk.Context, r types.EpochReport) error { ctx.EventManager().EmitEvent(sdk.NewEvent("audit_set_report_transition", sdk.NewAttribute("transition_skipped", "true"), sdk.NewAttribute("reason", "invalid_disk_usage_percent"))) return nil } - reporterSN, found, err := k.supernodeKeeper.GetSuperNodeByAccount(ctx, r.SupernodeAccount) + reporterSN, found, err := k.supernodeKeeper.GetSuperNodeByAccount(ctx, liveReporter) if err != nil { return err } diff --git a/x/audit/v1/keeper/storage_truth_divergence.go b/x/audit/v1/keeper/storage_truth_divergence.go index e53547dd..fe47111c 100644 --- a/x/audit/v1/keeper/storage_truth_divergence.go +++ b/x/audit/v1/keeper/storage_truth_divergence.go @@ -156,16 +156,25 @@ func (k Keeper) ApplyReporterCleanEpochRecoveryAtEpochEnd(ctx sdk.Context, epoch } reporterSet := make(map[string]struct{}, len(states)) for _, state := range states { - if state.ReporterSupernodeAccount != "" { - reporterSet[state.ReporterSupernodeAccount] = struct{}{} + if state.ReporterSupernodeAccount == "" { + continue } + current, err := k.CurrentAccount(ctx, state.ReporterSupernodeAccount) + if err != nil { + return err + } + reporterSet[current] = struct{}{} } epochReporters, err := k.storageTruthReporterAccountsForEpoch(ctx, epochID) if err != nil { return err } for _, reporter := range epochReporters { - reporterSet[reporter] = struct{}{} + current, err := k.CurrentAccount(ctx, reporter) + if err != nil { + return err + } + reporterSet[current] = struct{}{} } if len(reporterSet) == 0 { return nil @@ -230,7 +239,11 @@ func (k Keeper) storageTruthReporterAccountsForEpoch(ctx sdk.Context, epochID ui // storageTruthReporterEpochPassStats counts PASS results for a reporter in a // single epoch and reports whether any of them was overturned by recheck. func (k Keeper) storageTruthReporterEpochPassStats(ctx sdk.Context, reporterAccount string, epochID uint64) (uint64, bool, error) { - start, end := types.ReporterStorageTruthResultEpochScanRange(reporterAccount, epochID, epochID) + logicalReporter, err := k.AccountForEpoch(ctx, reporterAccount, epochID) + if err != nil { + return 0, false, err + } + start, end := types.ReporterStorageTruthResultEpochScanRange(logicalReporter, epochID, epochID) it := k.kvStore(ctx).Iterator(start, end) defer func() { _ = it.Close() }() var passes uint64 @@ -261,22 +274,33 @@ func (k Keeper) storageTruthReporterEpochPassStats(ctx sdk.Context, reporterAcco func (k Keeper) storageTruthReporterDivergenceStats(ctx sdk.Context, reporterAccount string, startEpoch uint64, endEpoch uint64) (storageTruthDivergenceStats, error) { var stats storageTruthDivergenceStats - // Bounded epoch scan per CP-NEW-A-11 residue — key shape unchanged, - // only iterator bounds use [startEpoch, endEpoch+1). - start, end := types.ReporterStorageTruthResultEpochScanRange(reporterAccount, startEpoch, endEpoch) - it := k.kvStore(ctx).Iterator(start, end) - defer func() { _ = it.Close() }() - for ; it.Valid(); it.Next() { - var record storageTruthReporterResultRecord - if err := json.Unmarshal(it.Value(), &record); err != nil { + // The divergence window is bounded by epochs. Resolve the logical account + // independently at each epoch so a transition inside the window is one + // actor without rewriting either side's historical facts. + for epoch := startEpoch; ; epoch++ { + logicalReporter, err := k.AccountForEpoch(ctx, reporterAccount, epoch) + if err != nil { return stats, err } - stats.total++ - if isStorageTruthFailureClass(types.StorageProofResultClass(record.ResultClass)) { - stats.negative++ - if record.ConfirmedByRecheck { - stats.confirmedNegative++ + start, end := types.ReporterStorageTruthResultEpochScanRange(logicalReporter, epoch, epoch) + it := k.kvStore(ctx).Iterator(start, end) + for ; it.Valid(); it.Next() { + var record storageTruthReporterResultRecord + if err := json.Unmarshal(it.Value(), &record); err != nil { + _ = it.Close() + return stats, err } + stats.total++ + if isStorageTruthFailureClass(types.StorageProofResultClass(record.ResultClass)) { + stats.negative++ + if record.ConfirmedByRecheck { + stats.confirmedNegative++ + } + } + } + _ = it.Close() + if epoch == endEpoch { + break } } return stats, nil diff --git a/x/audit/v1/keeper/storage_truth_fact_indexes.go b/x/audit/v1/keeper/storage_truth_fact_indexes.go index c4b0d907..b31167e5 100644 --- a/x/audit/v1/keeper/storage_truth_fact_indexes.go +++ b/x/audit/v1/keeper/storage_truth_fact_indexes.go @@ -266,23 +266,32 @@ func (k Keeper) linkStorageTruthRecheckTranscript( func (k Keeper) distinctNodeFailedTickets(ctx sdk.Context, supernodeAccount string, startEpoch uint64, endEpoch uint64, include func(storageTruthNodeFailureRecord) bool) (map[string]struct{}, uint32, error) { tickets := make(map[string]struct{}) var events uint32 - // Bounded epoch scan per CP-NEW-A-11 residue. - start, end := types.NodeStorageTruthFailureEpochScanRange(supernodeAccount, startEpoch, endEpoch) - it := k.kvStore(ctx).Iterator(start, end) - defer func() { _ = it.Close() }() - for ; it.Valid(); it.Next() { - var record storageTruthNodeFailureRecord - if err := json.Unmarshal(it.Value(), &record); err != nil { + for epoch := startEpoch; ; epoch++ { + logical, err := k.AccountForEpoch(ctx, supernodeAccount, epoch) + if err != nil { return nil, 0, err } - if include != nil && !include(record) { - continue - } - if record.TicketID != "" { - tickets[record.TicketID] = struct{}{} + start, end := types.NodeStorageTruthFailureEpochScanRange(logical, epoch, epoch) + it := k.kvStore(ctx).Iterator(start, end) + for ; it.Valid(); it.Next() { + var record storageTruthNodeFailureRecord + if err := json.Unmarshal(it.Value(), &record); err != nil { + _ = it.Close() + return nil, 0, err + } + if include != nil && !include(record) { + continue + } + if record.TicketID != "" { + tickets[record.TicketID] = struct{}{} + } + if events < ^uint32(0) { + events++ + } } - if events < ^uint32(0) { - events++ + _ = it.Close() + if epoch == endEpoch { + break } } return tickets, events, nil @@ -304,25 +313,38 @@ func (k Keeper) hasIndependentReporterPassInWindow( // Per 122-Copilot-3 + 122-F1 — indexed lookup avoids DeliverTx full-table scan. // Scan secondary index: "st/rrs-tt/" + target + "/" + u64be(epoch) + "/" // for each epoch in [startEpoch, endEpoch]. - startKey, endKey := types.ReporterStorageTruthResultByTargetEpochScanRange(targetAccount, startEpoch, endEpoch) - it := k.kvStore(ctx).Iterator(startKey, endKey) - defer func() { _ = it.Close() }() - - for ; it.Valid(); it.Next() { - var record storageTruthReporterResultRecord - if err := json.Unmarshal(it.Value(), &record); err != nil { + for epoch := startEpoch; ; epoch++ { + logical, err := k.AccountForEpoch(ctx, targetAccount, epoch) + if err != nil { return false, err } - if record.TicketID != ticketID { - continue - } - if types.StorageProofResultClass(record.ResultClass) != types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS { - continue + startKey, endKey := types.ReporterStorageTruthResultByTargetEpochScanRange(logical, epoch, epoch) + it := k.kvStore(ctx).Iterator(startKey, endKey) + for ; it.Valid(); it.Next() { + var record storageTruthReporterResultRecord + if err := json.Unmarshal(it.Value(), &record); err != nil { + _ = it.Close() + return false, err + } + excludedForRecordEpoch, err := k.AccountForEpoch(ctx, excludeReporter, record.EpochID) + if err != nil { + _ = it.Close() + return false, err + } + recordReporterForEpoch, err := k.AccountForEpoch(ctx, record.Reporter, record.EpochID) + if err != nil { + _ = it.Close() + return false, err + } + if record.TicketID == ticketID && types.StorageProofResultClass(record.ResultClass) == types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS && record.Reporter != "" && recordReporterForEpoch != excludedForRecordEpoch { + _ = it.Close() + return true, nil + } } - if record.Reporter == "" || record.Reporter == excludeReporter { - continue + _ = it.Close() + if epoch == endEpoch { + break } - return true, nil } return false, nil } @@ -337,22 +359,28 @@ func (k Keeper) hasCleanRecheckInWindow( // Per 122-Copilot-4 + 122-F1 — indexed lookup avoids DeliverTx full-table scan. // Scan secondary index: "st/spt-tbe/" + target + "/" + u32be(RECHECK) + "/" epoch range. recheckBucket := uint32(types.StorageProofBucketType_STORAGE_PROOF_BUCKET_TYPE_RECHECK) - startKey, endKey := types.TranscriptByTargetBucketEpochScanRange(targetAccount, recheckBucket, startEpoch, endEpoch) - it := k.kvStore(ctx).Iterator(startKey, endKey) - defer func() { _ = it.Close() }() - - for ; it.Valid(); it.Next() { - var record storageProofTranscriptRecord - if err := json.Unmarshal(it.Value(), &record); err != nil { + for epoch := startEpoch; ; epoch++ { + logical, err := k.AccountForEpoch(ctx, targetAccount, epoch) + if err != nil { return false, err } - if record.TicketID != ticketID { - continue + startKey, endKey := types.TranscriptByTargetBucketEpochScanRange(logical, recheckBucket, epoch, epoch) + it := k.kvStore(ctx).Iterator(startKey, endKey) + for ; it.Valid(); it.Next() { + var record storageProofTranscriptRecord + if err := json.Unmarshal(it.Value(), &record); err != nil { + _ = it.Close() + return false, err + } + if record.TicketID == ticketID && types.StorageProofResultClass(record.ResultClass) == types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS { + _ = it.Close() + return true, nil + } } - if types.StorageProofResultClass(record.ResultClass) != types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS { - continue + _ = it.Close() + if epoch == endEpoch { + break } - return true, nil } return false, nil } @@ -369,18 +397,24 @@ func (k Keeper) setStorageTruthFailedHeal(ctx sdk.Context, supernodeAccount stri } func (k Keeper) hasStorageTruthFailedHeal(ctx sdk.Context, supernodeAccount string, startEpoch uint64, endEpoch uint64) bool { - prefix := types.StorageTruthFailedHealPrefix(supernodeAccount) - it := k.kvStore(ctx).Iterator(prefix, storetypes.PrefixEndBytes(prefix)) - defer func() { _ = it.Close() }() - for ; it.Valid(); it.Next() { - key := it.Key() - if len(key) < len(prefix)+8 { - continue + for epoch := startEpoch; ; epoch++ { + logical, err := k.AccountForEpoch(ctx, supernodeAccount, epoch) + if err != nil { + panic(err) } - epochID := binary.BigEndian.Uint64(key[len(prefix) : len(prefix)+8]) - if epochID >= startEpoch && epochID <= endEpoch { + prefix := types.StorageTruthFailedHealPrefix(logical) + start := append(append([]byte(nil), prefix...), make([]byte, 8)...) + binary.BigEndian.PutUint64(start[len(prefix):], epoch) + end := append(append([]byte(nil), start...), 0xff) + it := k.kvStore(ctx).Iterator(start, end) + found := it.Valid() + _ = it.Close() + if found { return true } + if epoch == endEpoch { + break + } } return false } diff --git a/x/audit/v1/keeper/storage_truth_scoring.go b/x/audit/v1/keeper/storage_truth_scoring.go index e2c53eee..411ec601 100644 --- a/x/audit/v1/keeper/storage_truth_scoring.go +++ b/x/audit/v1/keeper/storage_truth_scoring.go @@ -195,7 +195,10 @@ func (k Keeper) applyNodeSuspicionDelta( if result == nil || result.TargetSupernodeAccount == "" { return 0, false, nil } - supernodeAccount := result.TargetSupernodeAccount + supernodeAccount, err := k.CurrentAccount(ctx, result.TargetSupernodeAccount) + if err != nil { + return 0, false, err + } state, found := k.GetNodeSuspicionState(ctx, supernodeAccount) if !found && delta == 0 { return 0, false, nil @@ -301,6 +304,10 @@ func (k Keeper) applyReporterReliabilityDelta( if reporterAccount == "" { return types.ReporterReliabilityState{}, false, nil } + reporterAccount, err := k.CurrentAccount(ctx, reporterAccount) + if err != nil { + return types.ReporterReliabilityState{}, false, err + } state, found := k.GetReporterReliabilityState(ctx, reporterAccount) if !found && delta == 0 && contradictionIncrements == 0 { return types.ReporterReliabilityState{}, false, nil @@ -376,6 +383,29 @@ func (k Keeper) applyTicketDeteriorationDelta( if !found && delta == 0 { return types.TicketDeteriorationState{}, false, nil } + logicalReporter, err := k.AccountForEpoch(ctx, reporterAccount, epochID) + if err != nil { + return types.TicketDeteriorationState{}, false, err + } + logicalTarget := "" + if result != nil && result.TargetSupernodeAccount != "" { + logicalTarget, err = k.AccountForEpoch(ctx, result.TargetSupernodeAccount, epochID) + if err != nil { + return types.TicketDeteriorationState{}, false, err + } + } + sameTargetLineage := false + if found && state.LastTargetSupernodeAccount != "" && logicalTarget != "" { + priorTargetAtStoredEpoch, resolveErr := k.AccountForEpoch(ctx, state.LastTargetSupernodeAccount, state.LastResultEpoch) + if resolveErr != nil { + return types.TicketDeteriorationState{}, false, resolveErr + } + currentTargetAtStoredEpoch, resolveErr := k.AccountForEpoch(ctx, result.TargetSupernodeAccount, state.LastResultEpoch) + if resolveErr != nil { + return types.TicketDeteriorationState{}, false, resolveErr + } + sameTargetLineage = priorTargetAtStoredEpoch == currentTargetAtStoredEpoch + } current := int64(0) if found { @@ -391,7 +421,7 @@ func (k Keeper) applyTicketDeteriorationDelta( result.ResultClass == types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS && found && state.LastTargetSupernodeAccount != "" && - state.LastTargetSupernodeAccount != result.TargetSupernodeAccount && + !sameTargetLineage && isStorageTruthFailureClass(state.LastResultClass) { next = addInt64Saturated(next, -3) } @@ -412,7 +442,7 @@ func (k Keeper) applyTicketDeteriorationDelta( } if result.TicketId != "" { // Track distinct holder failure count. - if isFailure && state.LastTargetSupernodeAccount != "" && state.LastTargetSupernodeAccount != result.TargetSupernodeAccount { + if isFailure && state.LastTargetSupernodeAccount != "" && !sameTargetLineage { if nextState.DistinctHolderFailureCount < math.MaxUint32 { nextState.DistinctHolderFailureCount++ } @@ -433,8 +463,8 @@ func (k Keeper) applyTicketDeteriorationDelta( } } - nextState.LastTargetSupernodeAccount = result.TargetSupernodeAccount - nextState.LastReporterSupernodeAccount = reporterAccount + nextState.LastTargetSupernodeAccount = logicalTarget + nextState.LastReporterSupernodeAccount = logicalReporter nextState.LastResultClass = result.ResultClass nextState.LastResultEpoch = epochID // Per Zee 119-F7 — same-epoch contradictions must be counted; <= not <. @@ -443,7 +473,7 @@ func (k Keeper) applyTicketDeteriorationDelta( // independent reporter PASS in window AND no clean recheck transcript). if contradictionConfirmed && state.LastResultEpoch <= epochID && - state.LastTargetSupernodeAccount == result.TargetSupernodeAccount && + sameTargetLineage && storageTruthResultsContradict(state.LastResultClass, result.ResultClass) { nextState.ContradictionCount = state.ContradictionCount + 1 } @@ -564,7 +594,11 @@ func (k Keeper) storageTruthBookkeepingForResult( } reliabilityScore := int64(0) - if state, found := k.GetReporterReliabilityState(ctx, reporterAccount); found { + currentReporter, err := k.CurrentAccount(ctx, reporterAccount) + if err != nil { + return bookkeeping, err + } + if state, found := k.GetReporterReliabilityState(ctx, currentReporter); found { reliabilityScore = decayTowardZero(state.ReliabilityScore, params.StorageTruthReporterReliabilityDecayPerEpoch, epochDelta(epochID, state.LastUpdatedEpoch)) } bookkeeping.reporterTrustBand = reporterTrustBandForScore(reliabilityScore, params) @@ -586,6 +620,22 @@ func (k Keeper) storageTruthBookkeepingForResult( } ticketState, found := k.GetTicketDeteriorationState(ctx, result.TicketId) + logicalResultTarget, err := k.AccountForEpoch(ctx, result.TargetSupernodeAccount, epochID) + if err != nil { + return bookkeeping, err + } + sameTargetLineage := false + if found && ticketState.LastTargetSupernodeAccount != "" && logicalResultTarget != "" { + previousTargetAtStoredEpoch, resolveErr := k.AccountForEpoch(ctx, ticketState.LastTargetSupernodeAccount, ticketState.LastResultEpoch) + if resolveErr != nil { + return bookkeeping, resolveErr + } + resultTargetAtStoredEpoch, resolveErr := k.AccountForEpoch(ctx, result.TargetSupernodeAccount, ticketState.LastResultEpoch) + if resolveErr != nil { + return bookkeeping, resolveErr + } + sameTargetLineage = previousTargetAtStoredEpoch == resultTargetAtStoredEpoch + } if isStorageTruthFailureClass(result.ResultClass) { patternWindow := uint64(params.StorageTruthPatternEscalationWindow) if patternWindow == 0 { @@ -606,7 +656,7 @@ func (k Keeper) storageTruthBookkeepingForResult( // Different holder failing same ticket in window: +10. // Same holder failing same ticket in a different epoch: +6. if epochID != ticketState.LastFailureEpoch && ticketState.LastTargetSupernodeAccount != "" { - if ticketState.LastTargetSupernodeAccount != result.TargetSupernodeAccount { + if !sameTargetLineage { bookkeeping.ticketBonus = 10 } else { bookkeeping.ticketBonus = 6 @@ -639,7 +689,7 @@ func (k Keeper) storageTruthBookkeepingForResult( } if found && ticketState.LastResultEpoch <= epochID && - ticketState.LastTargetSupernodeAccount == result.TargetSupernodeAccount && + sameTargetLineage && isStorageTruthFailureClass(ticketState.LastResultClass) && result.ResultClass == types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_PASS { contradictionWindow := uint64(params.StorageTruthContradictionWindowEpochs) @@ -659,7 +709,18 @@ func (k Keeper) storageTruthBookkeepingForResult( } if confirmed { bookkeeping.contradictionDetected = true - if ticketState.LastReporterSupernodeAccount != "" && ticketState.LastReporterSupernodeAccount != reporterAccount { + previousReporterAtStoredEpoch := "" + if ticketState.LastReporterSupernodeAccount != "" { + previousReporterAtStoredEpoch, err = k.AccountForEpoch(ctx, ticketState.LastReporterSupernodeAccount, ticketState.LastResultEpoch) + if err != nil { + return bookkeeping, err + } + } + currentReporterAtStoredEpoch, resolveErr := k.AccountForEpoch(ctx, reporterAccount, ticketState.LastResultEpoch) + if resolveErr != nil { + return bookkeeping, resolveErr + } + if ticketState.LastReporterSupernodeAccount != "" && previousReporterAtStoredEpoch != currentReporterAtStoredEpoch { bookkeeping.contradictedReporter = ticketState.LastReporterSupernodeAccount bookkeeping.contradictedReporterDelta = 12 } diff --git a/x/audit/v1/module/migrations.go b/x/audit/v1/module/migrations.go index ed4a52ad..51abdd81 100644 --- a/x/audit/v1/module/migrations.go +++ b/x/audit/v1/module/migrations.go @@ -39,3 +39,9 @@ func NewMigrateV1ToV2(k keeper.Keeper) func(ctx sdk.Context) error { return k.SetParams(ctx, params) } } + +// NewMigrateV2ToV3 is state-no-op. Legacy reports decode with an empty +// current_submitter and identity indexes begin empty. +func NewMigrateV2ToV3() func(ctx sdk.Context) error { + return func(_ sdk.Context) error { return nil } +} diff --git a/x/audit/v1/module/migrations_test.go b/x/audit/v1/module/migrations_test.go index b4feba54..1976d482 100644 --- a/x/audit/v1/module/migrations_test.go +++ b/x/audit/v1/module/migrations_test.go @@ -7,9 +7,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestAuditMigrationStaysV1ToV2UntilV2Ships(t *testing.T) { - // Per R3 C-F9: there is no separate v2→v3 handler because audit consensus - // version v2 has not shipped to mainnet. The C-F1/C-F3 KeepLastEpochEntries - // backfill is intentionally folded into NewMigrateV1ToV2 before v2 release. - require.Equal(t, 2, types.ConsensusVersion) +func TestAuditIdentityContinuityBumpsV2ToV3(t *testing.T) { + require.Equal(t, 3, types.ConsensusVersion) } diff --git a/x/audit/v1/module/module.go b/x/audit/v1/module/module.go index ab0ad689..2aec2dfe 100644 --- a/x/audit/v1/module/module.go +++ b/x/audit/v1/module/module.go @@ -100,6 +100,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { if err := cfg.RegisterMigration(types.ModuleName, 1, NewMigrateV1ToV2(am.keeper)); err != nil { panic(fmt.Sprintf("failed to register audit v1->v2 migration: %v", err)) } + if err := cfg.RegisterMigration(types.ModuleName, 2, NewMigrateV2ToV3()); err != nil { + panic(fmt.Sprintf("failed to register audit v2->v3 migration: %v", err)) + } } func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} diff --git a/x/audit/v1/types/audit.pb.go b/x/audit/v1/types/audit.pb.go index 3e509668..85eed62a 100644 --- a/x/audit/v1/types/audit.pb.go +++ b/x/audit/v1/types/audit.pb.go @@ -1176,6 +1176,10 @@ type EpochReport struct { HostReport HostReport `protobuf:"bytes,4,opt,name=host_report,json=hostReport,proto3" json:"host_report"` StorageChallengeObservations []*StorageChallengeObservation `protobuf:"bytes,5,rep,name=storage_challenge_observations,json=storageChallengeObservations,proto3" json:"storage_challenge_observations,omitempty"` StorageProofResults []*StorageProofResult `protobuf:"bytes,6,rep,name=storage_proof_results,json=storageProofResults,proto3" json:"storage_proof_results,omitempty"` + // current_submitter is the live account that authenticated submission. It is + // intentionally distinct from supernode_account, the epoch-logical identity. + // Empty decodes preserve reports written before identity continuity shipped. + CurrentSubmitter string `protobuf:"bytes,7,opt,name=current_submitter,json=currentSubmitter,proto3" json:"current_submitter,omitempty"` } func (m *EpochReport) Reset() { *m = EpochReport{} } @@ -1253,6 +1257,75 @@ func (m *EpochReport) GetStorageProofResults() []*StorageProofResult { return nil } +func (m *EpochReport) GetCurrentSubmitter() string { + if m != nil { + return m.CurrentSubmitter + } + return "" +} + +// AccountTransition records a durable account lineage edge. The destination +// becomes the account for the lineage beginning at effective_epoch. +type AccountTransition struct { + SourceAccount string `protobuf:"bytes,1,opt,name=source_account,json=sourceAccount,proto3" json:"source_account,omitempty"` + DestinationAccount string `protobuf:"bytes,2,opt,name=destination_account,json=destinationAccount,proto3" json:"destination_account,omitempty"` + EffectiveEpoch uint64 `protobuf:"varint,3,opt,name=effective_epoch,json=effectiveEpoch,proto3" json:"effective_epoch,omitempty"` +} + +func (m *AccountTransition) Reset() { *m = AccountTransition{} } +func (m *AccountTransition) String() string { return proto.CompactTextString(m) } +func (*AccountTransition) ProtoMessage() {} +func (*AccountTransition) Descriptor() ([]byte, []int) { + return fileDescriptor_0613fff850c07858, []int{9} +} +func (m *AccountTransition) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AccountTransition) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AccountTransition.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AccountTransition) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccountTransition.Merge(m, src) +} +func (m *AccountTransition) XXX_Size() int { + return m.Size() +} +func (m *AccountTransition) XXX_DiscardUnknown() { + xxx_messageInfo_AccountTransition.DiscardUnknown(m) +} + +var xxx_messageInfo_AccountTransition proto.InternalMessageInfo + +func (m *AccountTransition) GetSourceAccount() string { + if m != nil { + return m.SourceAccount + } + return "" +} + +func (m *AccountTransition) GetDestinationAccount() string { + if m != nil { + return m.DestinationAccount + } + return "" +} + +func (m *AccountTransition) GetEffectiveEpoch() uint64 { + if m != nil { + return m.EffectiveEpoch + } + return 0 +} + func init() { proto.RegisterEnum("lumera.audit.v1.PortState", PortState_name, PortState_value) proto.RegisterEnum("lumera.audit.v1.StorageProofBucketType", StorageProofBucketType_name, StorageProofBucketType_value) @@ -1269,159 +1342,165 @@ func init() { proto.RegisterType((*TicketArtifactCountState)(nil), "lumera.audit.v1.TicketArtifactCountState") proto.RegisterType((*HealOp)(nil), "lumera.audit.v1.HealOp") proto.RegisterType((*EpochReport)(nil), "lumera.audit.v1.EpochReport") + proto.RegisterType((*AccountTransition)(nil), "lumera.audit.v1.AccountTransition") } func init() { proto.RegisterFile("lumera/audit/v1/audit.proto", fileDescriptor_0613fff850c07858) } var fileDescriptor_0613fff850c07858 = []byte{ - // 2341 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xdb, 0x6e, 0xdb, 0xc8, - 0xf9, 0xb7, 0x0e, 0xf1, 0xe1, 0xb3, 0x2d, 0x53, 0xe3, 0x43, 0xe4, 0x43, 0x1c, 0xaf, 0xf2, 0xdf, - 0x8d, 0xa3, 0x4d, 0xe2, 0xb5, 0xf7, 0xbf, 0x58, 0x14, 0x8b, 0x2d, 0x40, 0x49, 0x74, 0xa4, 0x5a, - 0x16, 0xb5, 0x43, 0x29, 0xd9, 0xb4, 0x68, 0x07, 0x14, 0x39, 0xb1, 0x88, 0xd0, 0xa4, 0x40, 0x52, - 0xde, 0xf5, 0x43, 0x14, 0x58, 0xf4, 0xb2, 0x40, 0x2f, 0x7a, 0xd7, 0xdb, 0x02, 0xbd, 0xe8, 0x23, - 0x2c, 0xd0, 0x5e, 0x2c, 0x7a, 0xd5, 0x8b, 0xa2, 0x28, 0x92, 0xbe, 0x41, 0x5f, 0xa0, 0x98, 0x19, - 0x92, 0xa2, 0x4e, 0x76, 0x1a, 0xb4, 0x37, 0x86, 0xf8, 0xfd, 0x7e, 0xdf, 0x61, 0xe6, 0x3b, 0x70, - 0x86, 0x86, 0x5d, 0x7b, 0x70, 0x49, 0x3d, 0xfd, 0x48, 0x1f, 0x98, 0x56, 0x70, 0x74, 0x75, 0x2c, - 0x7e, 0x3c, 0xed, 0x7b, 0x6e, 0xe0, 0xa2, 0x35, 0x01, 0x3e, 0x15, 0xb2, 0xab, 0xe3, 0x9d, 0xbc, - 0x7e, 0x69, 0x39, 0xee, 0x11, 0xff, 0x2b, 0x38, 0x3b, 0xdb, 0x86, 0xeb, 0x5f, 0xba, 0x3e, 0xe1, - 0x4f, 0x47, 0xe2, 0x21, 0x84, 0x36, 0x2e, 0xdc, 0x0b, 0x57, 0xc8, 0xd9, 0x2f, 0x21, 0x2d, 0xfe, - 0x29, 0x0d, 0x50, 0x73, 0xfd, 0x00, 0xd3, 0xbe, 0xeb, 0x05, 0xa8, 0x04, 0x79, 0xa3, 0x3f, 0x20, - 0x03, 0x5f, 0xbf, 0xa0, 0xa4, 0x4f, 0x3d, 0x83, 0x3a, 0x41, 0x21, 0x75, 0x90, 0x3a, 0x4c, 0xe1, - 0x35, 0xa3, 0x3f, 0xe8, 0x30, 0x79, 0x4b, 0x88, 0x19, 0xf7, 0x92, 0x5e, 0x8e, 0x71, 0xd3, 0x82, - 0x7b, 0x49, 0x2f, 0x47, 0xb8, 0x8f, 0x01, 0x99, 0x96, 0xff, 0x7a, 0x8c, 0x9c, 0xe1, 0x64, 0x89, - 0x21, 0x23, 0xec, 0x9f, 0xc0, 0xba, 0xe5, 0x74, 0xdd, 0x81, 0x63, 0x12, 0x16, 0x15, 0xf1, 0x03, - 0x3d, 0xa0, 0x7e, 0x21, 0x7b, 0x90, 0x39, 0xcc, 0x9d, 0xec, 0x3c, 0x1d, 0xdb, 0x87, 0xa7, 0x2d, - 0xd7, 0x0b, 0x34, 0x46, 0xc1, 0xf9, 0x50, 0x2d, 0x96, 0xf8, 0xe8, 0x13, 0xd8, 0x78, 0xa5, 0x5b, - 0x36, 0x35, 0x89, 0x6e, 0x04, 0x96, 0xeb, 0xf8, 0xc4, 0x70, 0x07, 0x4e, 0x50, 0xb8, 0x73, 0x90, - 0x3a, 0x5c, 0xc5, 0x48, 0x60, 0xb2, 0x80, 0x2a, 0x0c, 0x41, 0x3f, 0x82, 0x6d, 0x43, 0xf7, 0x0d, - 0xdd, 0xa4, 0xe4, 0xb5, 0x6e, 0xd2, 0x4b, 0xdb, 0xd2, 0x89, 0xd9, 0x25, 0xdd, 0x6b, 0x16, 0xc3, - 0x3c, 0x0f, 0x79, 0x2b, 0x24, 0x9c, 0x85, 0x78, 0xb5, 0x5b, 0x66, 0x68, 0xf1, 0xf7, 0x29, 0xd8, - 0xd5, 0x02, 0xd7, 0xd3, 0x2f, 0x68, 0xa5, 0xa7, 0xdb, 0x36, 0x75, 0x2e, 0xa8, 0xda, 0xf5, 0xa9, - 0x77, 0xa5, 0x33, 0x07, 0xa8, 0x03, 0x85, 0x40, 0xf7, 0x2e, 0x68, 0x40, 0xfc, 0x41, 0x9f, 0x7a, - 0x8e, 0x6b, 0x52, 0xa2, 0x1b, 0x22, 0x20, 0xb6, 0xcb, 0x4b, 0xe5, 0xdd, 0xbf, 0xfc, 0xe1, 0xc9, - 0xdd, 0x30, 0x6f, 0xb2, 0x61, 0xc8, 0xa6, 0xe9, 0x51, 0xdf, 0xd7, 0x02, 0xcf, 0x72, 0x2e, 0xf0, - 0x96, 0x50, 0xd6, 0x22, 0x5d, 0x59, 0xa8, 0xa2, 0x2f, 0x60, 0x39, 0xb9, 0x4f, 0xe9, 0x5b, 0xf7, - 0x09, 0xfa, 0xf1, 0x06, 0x15, 0xff, 0x38, 0x0f, 0x28, 0x8c, 0xb9, 0xe5, 0xb9, 0xee, 0x2b, 0x4c, - 0xfd, 0x81, 0x1d, 0xfc, 0xaf, 0x42, 0xfd, 0x39, 0xec, 0x19, 0xd1, 0xce, 0x78, 0x53, 0x4c, 0xa7, - 0x6f, 0x37, 0xbd, 0x33, 0x34, 0x30, 0x61, 0x7e, 0x17, 0x96, 0x02, 0xcb, 0x78, 0x4d, 0x03, 0x62, - 0x99, 0xbc, 0xbc, 0x96, 0xf0, 0xa2, 0x10, 0xd4, 0x4d, 0x54, 0x83, 0xe5, 0xee, 0x80, 0x83, 0xc1, - 0x75, 0x9f, 0x16, 0xb2, 0x07, 0xa9, 0xc3, 0xdc, 0xc9, 0xc3, 0x89, 0x6d, 0x4a, 0x6e, 0x46, 0x99, - 0xf3, 0xdb, 0xd7, 0x7d, 0x8a, 0xa1, 0x1b, 0xff, 0x46, 0x5f, 0x41, 0x4e, 0xf7, 0x02, 0xeb, 0x95, - 0x6e, 0x04, 0xc4, 0xb0, 0x75, 0xdf, 0xe7, 0xe5, 0x94, 0x3b, 0x29, 0xdd, 0x68, 0x4c, 0x0e, 0x55, - 0x2a, 0x4c, 0x03, 0xaf, 0xea, 0xc9, 0x47, 0xf4, 0x08, 0xa4, 0xd8, 0xa4, 0xeb, 0x99, 0x96, 0xa3, - 0xdb, 0xbc, 0xd8, 0x56, 0xf1, 0x5a, 0x24, 0x57, 0x85, 0x18, 0x7d, 0x00, 0x2b, 0x31, 0xf5, 0x35, - 0xbd, 0x2e, 0x2c, 0xf0, 0x75, 0x2e, 0x47, 0xb2, 0x33, 0x7a, 0x8d, 0xce, 0x60, 0xc5, 0xe3, 0x79, - 0x0c, 0xc3, 0x5b, 0xe4, 0xe1, 0x1d, 0xde, 0x18, 0x9e, 0x48, 0xbc, 0x08, 0x6e, 0xd9, 0x1b, 0x3e, - 0xa0, 0x87, 0xb0, 0x16, 0x78, 0xba, 0xe3, 0x1b, 0x9e, 0xd5, 0x0f, 0x48, 0x4f, 0xf7, 0x7b, 0x85, - 0x25, 0xee, 0x32, 0x37, 0x14, 0xd7, 0x74, 0xbf, 0x87, 0x0a, 0xb0, 0x60, 0xd2, 0x40, 0xb7, 0x6c, - 0xbf, 0x00, 0x9c, 0x10, 0x3d, 0xa2, 0x0f, 0x93, 0x1b, 0xc6, 0x13, 0xbd, 0xcc, 0xd7, 0x36, 0xdc, - 0x04, 0x9e, 0xbe, 0x13, 0xd8, 0x34, 0xa9, 0x67, 0x89, 0x6e, 0x21, 0x96, 0xd3, 0x1f, 0x84, 0xfe, - 0x56, 0xb8, 0xb9, 0xf5, 0x21, 0x58, 0x67, 0x18, 0x77, 0x7a, 0x0c, 0x1b, 0xc9, 0x8a, 0xb2, 0x2e, - 0x1c, 0x3d, 0x18, 0x78, 0xb4, 0xb0, 0x2a, 0x54, 0x12, 0xc5, 0x12, 0x41, 0xe8, 0x14, 0xee, 0xbb, - 0xbc, 0x2b, 0xa9, 0x47, 0xf4, 0x20, 0xa0, 0xac, 0x6f, 0x98, 0xc3, 0x58, 0xd9, 0x2f, 0xe4, 0x0e, - 0x32, 0x87, 0x4b, 0xf8, 0x5e, 0x44, 0x93, 0x87, 0xac, 0xd8, 0x8c, 0x5f, 0xfc, 0xd5, 0x3c, 0xa0, - 0xa6, 0x6b, 0x52, 0x6d, 0xe0, 0xf7, 0x2d, 0x83, 0x61, 0xac, 0xa5, 0x50, 0x0d, 0xf2, 0xef, 0xd5, - 0x33, 0x92, 0x3f, 0x5e, 0xce, 0x0f, 0x61, 0xcd, 0x8f, 0x6c, 0x13, 0xdf, 0x70, 0x3d, 0xca, 0x1b, - 0x24, 0x83, 0x73, 0xb1, 0x58, 0x63, 0x52, 0x36, 0x5f, 0x6d, 0xdd, 0x0f, 0xc8, 0xa0, 0x6f, 0xea, - 0x01, 0x35, 0x09, 0xed, 0xbb, 0x46, 0x8f, 0x37, 0x40, 0x16, 0x4b, 0x0c, 0xe9, 0x08, 0x40, 0x61, - 0x72, 0xf4, 0x29, 0x6c, 0x71, 0xb6, 0x47, 0xd9, 0xb8, 0x25, 0x6c, 0x06, 0x86, 0x1a, 0x59, 0xae, - 0xb1, 0xce, 0x50, 0xcc, 0xc1, 0x53, 0xdd, 0xb2, 0x85, 0xd2, 0x13, 0xe0, 0x62, 0xe2, 0xda, 0x66, - 0x52, 0xe3, 0xce, 0xd0, 0x87, 0x6a, 0x9b, 0x43, 0xfa, 0x97, 0xb0, 0x6b, 0x5a, 0x7e, 0x60, 0x39, - 0x46, 0x40, 0xc2, 0x96, 0xe4, 0x5a, 0xdf, 0x58, 0x8e, 0xe9, 0x7e, 0x13, 0x96, 0x76, 0x21, 0xa2, - 0xb4, 0x39, 0x83, 0x69, 0xbf, 0xe0, 0x38, 0x5b, 0x90, 0x60, 0xb2, 0xa1, 0xe6, 0x05, 0xa1, 0xb3, - 0x05, 0xe1, 0x4c, 0x20, 0x1a, 0x03, 0x84, 0xb3, 0x23, 0xd8, 0xe0, 0x75, 0x4e, 0x74, 0x51, 0x5d, - 0x91, 0x97, 0x45, 0xee, 0x25, 0xcf, 0x31, 0x99, 0x97, 0x58, 0x68, 0xfe, 0xe3, 0x70, 0xbf, 0x22, - 0x2d, 0x61, 0x7e, 0x89, 0x9b, 0x5f, 0x63, 0x08, 0xaf, 0x7c, 0x79, 0xcc, 0x7a, 0x77, 0xd4, 0x3a, - 0x24, 0xac, 0x97, 0x67, 0x5b, 0xef, 0x86, 0xd6, 0x97, 0xc7, 0xac, 0x97, 0x85, 0xf5, 0x43, 0x90, - 0x0c, 0x9b, 0xea, 0x0e, 0xe9, 0x33, 0xb2, 0x28, 0x96, 0x15, 0x6e, 0x39, 0xc7, 0xe5, 0x2d, 0xdd, - 0x0f, 0x5f, 0x4c, 0xc7, 0xb0, 0x19, 0x9a, 0x8d, 0xe9, 0xc2, 0xf2, 0x2a, 0xb7, 0x8c, 0x84, 0xe5, - 0x50, 0x45, 0x18, 0x8f, 0x54, 0x2c, 0xc7, 0xa4, 0xdf, 0x26, 0xd3, 0x96, 0x1b, 0xaa, 0xd4, 0x19, - 0x36, 0x4c, 0xdc, 0x8f, 0x61, 0x6f, 0x3c, 0x1e, 0xa2, 0x07, 0xa4, 0xef, 0xfa, 0x41, 0xdf, 0x75, - 0x68, 0x61, 0x4d, 0x64, 0x6e, 0x34, 0x36, 0x39, 0x68, 0x85, 0x78, 0xf1, 0xbb, 0x2c, 0x14, 0xc4, - 0x69, 0x82, 0x7a, 0x98, 0xda, 0x96, 0xde, 0xb5, 0x6c, 0x2b, 0xb8, 0x16, 0xad, 0xf1, 0x12, 0x76, - 0xbc, 0x10, 0x7b, 0xbf, 0xf7, 0x4a, 0x21, 0x52, 0x9f, 0x18, 0xfd, 0x1f, 0x43, 0xde, 0x1b, 0xba, - 0x1b, 0xe9, 0x16, 0x29, 0x01, 0xbc, 0x4f, 0xbf, 0xc8, 0x00, 0x81, 0x37, 0xf0, 0x03, 0xd2, 0xd5, - 0x1d, 0x33, 0x7c, 0x6f, 0x14, 0x27, 0x66, 0x69, 0xb4, 0xe8, 0x36, 0xa3, 0x96, 0x75, 0xc7, 0xc4, - 0x4b, 0x41, 0xf4, 0x13, 0x1d, 0xc1, 0xba, 0xe1, 0x3a, 0x81, 0xa7, 0x9b, 0x16, 0x3f, 0x6b, 0x24, - 0x4e, 0x21, 0x59, 0x8c, 0x46, 0x20, 0x91, 0xec, 0xff, 0x87, 0x2d, 0xcb, 0xa1, 0xb6, 0x75, 0x61, - 0x75, 0x6d, 0x4a, 0x06, 0x4e, 0x10, 0xa7, 0x6e, 0x9e, 0xeb, 0x6c, 0x0c, 0xd1, 0x0e, 0x03, 0x45, - 0xa4, 0x27, 0xb0, 0x19, 0xb6, 0x4d, 0xdf, 0xf5, 0xad, 0xc0, 0xba, 0xa2, 0xa1, 0xa3, 0x05, 0x9e, - 0xb5, 0x75, 0x01, 0xb6, 0x42, 0x2c, 0x1e, 0xba, 0xa1, 0x8e, 0x43, 0x2f, 0xf4, 0x84, 0xce, 0x62, - 0x52, 0xa7, 0x19, 0x62, 0x42, 0x67, 0x7a, 0x7b, 0x2e, 0x4d, 0x6f, 0xcf, 0xe2, 0xbf, 0x16, 0xa0, - 0x20, 0x3a, 0xbc, 0x4a, 0x03, 0xea, 0x59, 0xae, 0x27, 0x26, 0x29, 0x2f, 0x89, 0x91, 0x57, 0x76, - 0x6a, 0xec, 0x95, 0x7d, 0x04, 0xeb, 0x66, 0x52, 0x65, 0x24, 0xad, 0x68, 0x04, 0x7a, 0x9f, 0xc4, - 0x3e, 0x82, 0x3c, 0x3b, 0x15, 0x5e, 0x51, 0xd2, 0xa3, 0xba, 0x4d, 0xdc, 0x3e, 0x8b, 0x41, 0xcc, - 0xc0, 0x9c, 0x00, 0x6a, 0x54, 0xb7, 0xd5, 0x7e, 0xdd, 0x64, 0xbb, 0xd4, 0xf7, 0xdc, 0xae, 0x88, - 0x22, 0x99, 0x0e, 0x91, 0xc2, 0xf5, 0x18, 0x4c, 0x64, 0xe3, 0x23, 0xe0, 0xdd, 0x2e, 0x8c, 0x27, - 0x93, 0xb7, 0xca, 0xc4, 0xcc, 0xb4, 0xe0, 0x45, 0x41, 0xb3, 0xfe, 0x1c, 0x78, 0x74, 0x74, 0xd8, - 0x31, 0xe4, 0x54, 0x00, 0x82, 0xfd, 0x05, 0xeb, 0xa1, 0x78, 0x70, 0xc7, 0xfc, 0x91, 0xa4, 0xdd, - 0xf5, 0xe2, 0xe9, 0x1d, 0xe9, 0x89, 0xc4, 0xcd, 0xa8, 0xc3, 0xa5, 0x99, 0x75, 0xf8, 0x0b, 0xb8, - 0xc7, 0x63, 0x9b, 0x79, 0x18, 0x84, 0x77, 0x38, 0xb1, 0x31, 0x0b, 0xed, 0xe9, 0x07, 0xc2, 0x2e, - 0xdc, 0x0f, 0xdf, 0x45, 0x33, 0xc7, 0xc2, 0xf2, 0xed, 0x1e, 0xf6, 0xc4, 0x1b, 0x6b, 0xc6, 0x68, - 0x68, 0x43, 0x3e, 0xf4, 0x91, 0x38, 0x12, 0xad, 0xfc, 0x87, 0x47, 0xa2, 0x35, 0xe1, 0x62, 0x78, - 0x2c, 0x2a, 0x8d, 0x5a, 0x4d, 0x8e, 0xe2, 0x04, 0x37, 0x9a, 0x20, 0xf7, 0xe2, 0xb7, 0x61, 0xcf, - 0xb5, 0x4d, 0xea, 0xc5, 0xc9, 0x13, 0x6b, 0xcc, 0xf1, 0xb4, 0xed, 0x44, 0xa4, 0x1a, 0xe7, 0x84, - 0xe9, 0x13, 0x89, 0xf8, 0x1c, 0x0a, 0x63, 0xa3, 0x7c, 0x58, 0x2a, 0x6b, 0xdc, 0xeb, 0xe6, 0xc8, - 0x34, 0x8f, 0xeb, 0xe5, 0x4b, 0xd8, 0x0d, 0xeb, 0x25, 0x3c, 0xfd, 0x8e, 0xea, 0x4a, 0x5c, 0xb7, - 0x20, 0x28, 0xe2, 0xbc, 0x3b, 0xa2, 0xfe, 0x39, 0x14, 0xd8, 0x2b, 0x7f, 0xaa, 0x6e, 0x5e, 0xf8, - 0x75, 0x6d, 0x73, 0x52, 0xb1, 0xf8, 0x9b, 0x54, 0xd4, 0xf5, 0x72, 0xf2, 0x90, 0xf7, 0x0e, 0x5d, - 0xff, 0x09, 0x6c, 0x88, 0x55, 0x8e, 0x9d, 0x19, 0xd3, 0xe2, 0xce, 0xc6, 0x31, 0x79, 0xfc, 0xe0, - 0xe8, 0x5f, 0x5f, 0x76, 0x5d, 0x7b, 0x5c, 0x25, 0x23, 0x66, 0x98, 0x00, 0x47, 0x74, 0x8a, 0xbf, - 0xcc, 0xc2, 0xbc, 0x68, 0x6f, 0xb4, 0x07, 0x90, 0x18, 0x00, 0x29, 0xbe, 0xaa, 0xc5, 0x5e, 0xd4, - 0xfa, 0x23, 0xb1, 0xa6, 0xc7, 0x62, 0x7d, 0x0c, 0xc8, 0x37, 0x7a, 0xd4, 0x1c, 0xd8, 0xd1, 0xb4, - 0x89, 0xae, 0x1e, 0x59, 0x2c, 0xc5, 0x08, 0xdf, 0x91, 0xba, 0xc9, 0x6e, 0x55, 0xcc, 0xec, 0xd4, - 0x32, 0xcf, 0xbe, 0xc3, 0xad, 0x4a, 0x28, 0x4f, 0x14, 0xf8, 0xcf, 0x60, 0xf7, 0x8a, 0x7a, 0xd6, - 0x2b, 0x6b, 0x9a, 0x61, 0x76, 0x39, 0xc9, 0xdc, 0x66, 0x79, 0x3b, 0xd2, 0x1f, 0xb7, 0xed, 0xa3, - 0xcf, 0x60, 0x9e, 0x1d, 0x7d, 0x07, 0xe2, 0xf2, 0x9b, 0x3b, 0xb9, 0x37, 0xd1, 0x32, 0x62, 0x17, - 0x35, 0x4e, 0xc2, 0x21, 0x99, 0x1d, 0xf9, 0x0d, 0x8f, 0xf2, 0x21, 0xdc, 0xa3, 0xd6, 0x45, 0x2f, - 0x08, 0x07, 0xda, 0x6a, 0x28, 0xad, 0x71, 0x21, 0xa3, 0x45, 0xb3, 0x3a, 0xa4, 0x2d, 0x0a, 0x5a, - 0x28, 0x0d, 0x69, 0x25, 0xc8, 0x9b, 0x54, 0x37, 0x6d, 0xcb, 0xa1, 0xc3, 0x5d, 0x0e, 0xcf, 0x6b, - 0x11, 0x10, 0x6d, 0xf2, 0x7d, 0x08, 0xaf, 0x2f, 0xe2, 0xee, 0x20, 0xae, 0x22, 0x20, 0x44, 0xfc, - 0xca, 0xb0, 0x01, 0x77, 0x1c, 0x97, 0xdd, 0x94, 0xf9, 0x64, 0xc1, 0xe2, 0xa1, 0xf8, 0xbb, 0x0c, - 0x2c, 0x73, 0x13, 0xe1, 0xb7, 0x90, 0xff, 0xde, 0x31, 0x7e, 0x1b, 0x16, 0xe3, 0x98, 0xd3, 0x3c, - 0xe6, 0x05, 0x1a, 0xc6, 0xfa, 0x00, 0x56, 0xc5, 0xe4, 0x8b, 0x56, 0x9f, 0xe1, 0xaf, 0xb6, 0x15, - 0x21, 0x0c, 0x17, 0x5f, 0x86, 0xe5, 0x9e, 0x1b, 0xcf, 0x48, 0x5e, 0x28, 0xcb, 0x27, 0xbb, 0x93, - 0x69, 0x88, 0xbf, 0xe3, 0x94, 0xb3, 0xdf, 0xff, 0xfd, 0xfe, 0x1c, 0x86, 0xde, 0xf0, 0xcb, 0x8e, - 0x07, 0xfb, 0xbe, 0x98, 0x6c, 0x24, 0xbe, 0x12, 0x11, 0x77, 0xf8, 0x6d, 0x42, 0x54, 0xc9, 0xf2, - 0xc9, 0xe3, 0x59, 0x03, 0x71, 0xda, 0x07, 0x0d, 0xbc, 0xe7, 0xcf, 0x06, 0x7d, 0xf4, 0x02, 0x36, - 0x23, 0x9f, 0x7d, 0x36, 0x4e, 0xc3, 0x51, 0xc9, 0x0a, 0x89, 0xb9, 0x7a, 0xf0, 0x0e, 0xb3, 0x17, - 0xaf, 0xfb, 0x13, 0x32, 0xbf, 0xa4, 0xc2, 0x52, 0xfc, 0x31, 0x03, 0x6d, 0x01, 0x6a, 0xa9, 0xb8, - 0x4d, 0xb4, 0xb6, 0xdc, 0x56, 0x48, 0xa7, 0x79, 0xd6, 0x54, 0x5f, 0x34, 0xa5, 0x39, 0xb4, 0x0e, - 0x6b, 0x09, 0xb9, 0xda, 0x52, 0x9a, 0x52, 0x0a, 0x6d, 0x42, 0x3e, 0x21, 0xac, 0x34, 0x54, 0x4d, - 0xa9, 0x4a, 0xe9, 0xd2, 0xdf, 0x52, 0xb0, 0x35, 0xfd, 0xde, 0x8f, 0x1e, 0xc1, 0x87, 0x5a, 0x5b, - 0xc5, 0xf2, 0x33, 0x85, 0xb4, 0xb0, 0xaa, 0x9e, 0x92, 0x72, 0xa7, 0x72, 0xa6, 0xb4, 0x49, 0xfb, - 0x65, 0x8b, 0x79, 0xd3, 0x5a, 0x4a, 0xa5, 0x7e, 0x5a, 0x57, 0xaa, 0xd2, 0x1c, 0xfa, 0x3f, 0x38, - 0x98, 0x4d, 0xc5, 0x4a, 0x45, 0x69, 0xb6, 0xa5, 0x14, 0xfa, 0x00, 0xee, 0xcd, 0x66, 0xa9, 0x8d, - 0xaa, 0x94, 0x46, 0x0f, 0xe1, 0xc1, 0x6c, 0x4a, 0x0b, 0xab, 0x65, 0xb9, 0x5d, 0x57, 0x9b, 0x52, - 0x06, 0x7d, 0x08, 0x1f, 0xdc, 0xe8, 0xb1, 0xa6, 0x54, 0xce, 0xa4, 0x6c, 0xe9, 0xd7, 0x29, 0xd8, - 0x9e, 0xf9, 0x25, 0x02, 0x3d, 0x86, 0xc3, 0x51, 0x23, 0x32, 0x6e, 0xd7, 0x4f, 0xe5, 0x4a, 0x9b, - 0x54, 0x1a, 0xb2, 0xa6, 0x8d, 0x2d, 0xf2, 0x23, 0x28, 0xde, 0xc8, 0xae, 0x37, 0xab, 0xca, 0xd7, - 0x52, 0x6a, 0x72, 0x0d, 0x63, 0x3c, 0xed, 0xe5, 0x79, 0x59, 0x6d, 0x48, 0xe9, 0xd2, 0x6f, 0x33, - 0x70, 0x77, 0xc6, 0x4b, 0x17, 0x95, 0xe0, 0xa3, 0x51, 0x23, 0x58, 0xd1, 0x3a, 0x8d, 0xe9, 0x81, - 0x3d, 0x80, 0xfb, 0x37, 0x70, 0x5b, 0xb2, 0xa6, 0x49, 0xa9, 0xc9, 0xb5, 0x8e, 0x90, 0x6a, 0xb2, - 0x56, 0x23, 0xe7, 0x75, 0xed, 0x5c, 0x6e, 0x57, 0x6a, 0x52, 0x1a, 0x7d, 0x06, 0xc7, 0x37, 0xb0, - 0xdb, 0xf5, 0x73, 0x45, 0xed, 0xb4, 0x89, 0x8a, 0x49, 0x53, 0x65, 0x50, 0x4b, 0x6d, 0x6a, 0x8a, - 0x94, 0x41, 0x9f, 0xc2, 0xd1, 0x0d, 0x6a, 0x6a, 0x59, 0x53, 0xf0, 0x73, 0x05, 0x93, 0xaf, 0x3a, - 0x2a, 0xee, 0x9c, 0x93, 0x53, 0xb9, 0xde, 0x90, 0xb2, 0xe8, 0x18, 0x9e, 0xdc, 0xa0, 0xd4, 0x54, - 0x89, 0xd2, 0xa8, 0x3f, 0xab, 0x97, 0x1b, 0x0a, 0x69, 0xd7, 0x59, 0x8e, 0xa5, 0x3b, 0xb7, 0xa8, - 0xd4, 0x9b, 0xcf, 0xe5, 0x46, 0xbd, 0x4a, 0xda, 0x58, 0x6e, 0x6a, 0x15, 0x5c, 0x6f, 0xb5, 0xa5, - 0xf9, 0x5b, 0x56, 0x14, 0x56, 0x0c, 0xa9, 0xa8, 0xcd, 0xd3, 0x3a, 0x3e, 0x57, 0xaa, 0x22, 0xb8, - 0x85, 0xd2, 0x9f, 0x53, 0x90, 0x9f, 0xb8, 0xdf, 0xb0, 0x1d, 0xc7, 0x0a, 0x6b, 0x27, 0x05, 0x93, - 0x36, 0xee, 0x68, 0x6d, 0x52, 0x96, 0x9b, 0xd5, 0xb1, 0xb4, 0xec, 0xc3, 0xce, 0x34, 0x52, 0x53, - 0xc5, 0xe7, 0x72, 0x43, 0xb4, 0xc3, 0x34, 0xbc, 0xa1, 0xbe, 0x10, 0x8f, 0x52, 0x1a, 0x3d, 0x81, - 0x47, 0xd3, 0x28, 0x95, 0x9a, 0xdc, 0x68, 0x28, 0xcd, 0x67, 0x0a, 0x26, 0xf5, 0x66, 0xb4, 0x3b, - 0x52, 0x06, 0x1d, 0xc0, 0xde, 0x34, 0x7a, 0x55, 0x79, 0x86, 0xe5, 0xaa, 0x52, 0x95, 0xb2, 0xa5, - 0x7f, 0xa6, 0x60, 0x25, 0xf9, 0xd2, 0x62, 0x41, 0xd6, 0x14, 0xb9, 0x41, 0xd4, 0x16, 0x9f, 0x0c, - 0x9d, 0xf1, 0xda, 0xda, 0x83, 0xc2, 0x18, 0xae, 0x55, 0x6a, 0x4a, 0xb5, 0xd3, 0x50, 0xaa, 0x52, - 0x6a, 0x8a, 0x76, 0xbd, 0xc9, 0xb6, 0xf7, 0x19, 0x56, 0x34, 0x4d, 0x4a, 0xa3, 0x22, 0xec, 0x8f, - 0xe1, 0xec, 0x51, 0xc1, 0x24, 0x0c, 0xb3, 0x2a, 0x65, 0xd0, 0x2e, 0xdc, 0x1d, 0xe3, 0x3c, 0x57, - 0xb0, 0x70, 0x9f, 0x45, 0xdb, 0xb0, 0x39, 0x06, 0xb2, 0xbc, 0x28, 0x55, 0xe9, 0x0e, 0xda, 0x81, - 0xad, 0x31, 0x48, 0xf9, 0xba, 0x55, 0xc7, 0x4a, 0x55, 0x9a, 0x2f, 0x97, 0xbe, 0x7f, 0xb3, 0x9f, - 0xfa, 0xe1, 0xcd, 0x7e, 0xea, 0x1f, 0x6f, 0xf6, 0x53, 0xdf, 0xbd, 0xdd, 0x9f, 0xfb, 0xe1, 0xed, - 0xfe, 0xdc, 0x5f, 0xdf, 0xee, 0xcf, 0xfd, 0x54, 0xfa, 0x76, 0xf8, 0x3f, 0x86, 0xe0, 0xba, 0x4f, - 0xfd, 0xee, 0x3c, 0xff, 0x7f, 0xc0, 0xa7, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xc6, 0x0d, 0x4d, - 0x25, 0x83, 0x18, 0x00, 0x00, + // 2424 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xdb, 0x6e, 0xe3, 0xd6, + 0xd5, 0x1e, 0x1d, 0xc6, 0x87, 0x65, 0x5b, 0xa6, 0xb6, 0x0f, 0xa3, 0xb1, 0x3d, 0x1e, 0x47, 0xf3, + 0x27, 0xe3, 0x51, 0x66, 0xc6, 0x19, 0xe7, 0x0f, 0x82, 0x22, 0x48, 0x01, 0x4a, 0xa2, 0x47, 0xea, + 0xc8, 0xa2, 0x42, 0x52, 0x99, 0xa4, 0x45, 0xbb, 0x41, 0x91, 0xdb, 0x16, 0x31, 0x34, 0x29, 0x90, + 0x94, 0x13, 0x3f, 0x44, 0x81, 0xa0, 0x97, 0x05, 0x7a, 0xd1, 0x47, 0x28, 0xd0, 0x8b, 0x3e, 0x42, + 0x80, 0x16, 0x45, 0xd0, 0xab, 0x5e, 0x14, 0x45, 0x91, 0xf4, 0x09, 0xda, 0x17, 0x28, 0xf6, 0x81, + 0x14, 0x75, 0xb2, 0x67, 0x06, 0xed, 0x8d, 0x21, 0xae, 0xef, 0x5b, 0x6b, 0xaf, 0xbd, 0xd7, 0x81, + 0x6b, 0xd3, 0xb0, 0xeb, 0x0e, 0x2f, 0x48, 0x60, 0x1e, 0x99, 0x43, 0xdb, 0x89, 0x8e, 0x2e, 0x9f, + 0xf1, 0x1f, 0x4f, 0x07, 0x81, 0x1f, 0xf9, 0x68, 0x9d, 0x83, 0x4f, 0xb9, 0xec, 0xf2, 0xd9, 0x4e, + 0xd1, 0xbc, 0x70, 0x3c, 0xff, 0x88, 0xfd, 0xe5, 0x9c, 0x9d, 0xbb, 0x96, 0x1f, 0x5e, 0xf8, 0x21, + 0x66, 0x4f, 0x47, 0xfc, 0x41, 0x40, 0x9b, 0xe7, 0xfe, 0xb9, 0xcf, 0xe5, 0xf4, 0x17, 0x97, 0x96, + 0xff, 0x98, 0x05, 0x68, 0xf8, 0x61, 0xa4, 0x91, 0x81, 0x1f, 0x44, 0xa8, 0x02, 0x45, 0x6b, 0x30, + 0xc4, 0xc3, 0xd0, 0x3c, 0x27, 0x78, 0x40, 0x02, 0x8b, 0x78, 0x51, 0x29, 0x73, 0x90, 0x39, 0xcc, + 0x68, 0xeb, 0xd6, 0x60, 0xd8, 0xa5, 0xf2, 0x0e, 0x17, 0x53, 0xee, 0x05, 0xb9, 0x98, 0xe0, 0x66, + 0x39, 0xf7, 0x82, 0x5c, 0x8c, 0x71, 0x1f, 0x03, 0xb2, 0x9d, 0xf0, 0xd5, 0x04, 0x39, 0xc7, 0xc8, + 0x12, 0x45, 0xc6, 0xd8, 0x3f, 0x81, 0x0d, 0xc7, 0xeb, 0xf9, 0x43, 0xcf, 0xc6, 0xd4, 0x2b, 0x1c, + 0x46, 0x66, 0x44, 0xc2, 0x52, 0xfe, 0x20, 0x77, 0x58, 0x38, 0xde, 0x79, 0x3a, 0x71, 0x0e, 0x4f, + 0x3b, 0x7e, 0x10, 0xe9, 0x94, 0xa2, 0x15, 0x85, 0x5a, 0x22, 0x09, 0xd1, 0x07, 0xb0, 0x79, 0x66, + 0x3a, 0x2e, 0xb1, 0xb1, 0x69, 0x45, 0x8e, 0xef, 0x85, 0xd8, 0xf2, 0x87, 0x5e, 0x54, 0xba, 0x7d, + 0x90, 0x39, 0x5c, 0xd3, 0x10, 0xc7, 0x64, 0x0e, 0xd5, 0x28, 0x82, 0x7e, 0x04, 0x77, 0x2d, 0x33, + 0xb4, 0x4c, 0x9b, 0xe0, 0x57, 0xa6, 0x4d, 0x2e, 0x5c, 0xc7, 0xc4, 0x76, 0x0f, 0xf7, 0xae, 0xa8, + 0x0f, 0x0b, 0xcc, 0xe5, 0x6d, 0x41, 0x78, 0x21, 0xf0, 0x7a, 0xaf, 0x4a, 0xd1, 0xf2, 0xef, 0x32, + 0xb0, 0xab, 0x47, 0x7e, 0x60, 0x9e, 0x93, 0x5a, 0xdf, 0x74, 0x5d, 0xe2, 0x9d, 0x13, 0xb5, 0x17, + 0x92, 0xe0, 0xd2, 0xa4, 0x0b, 0xa0, 0x2e, 0x94, 0x22, 0x33, 0x38, 0x27, 0x11, 0x0e, 0x87, 0x03, + 0x12, 0x78, 0xbe, 0x4d, 0xb0, 0x69, 0x71, 0x87, 0xe8, 0x29, 0x2f, 0x57, 0x77, 0xff, 0xf2, 0xfb, + 0x27, 0x77, 0x44, 0xdc, 0x64, 0xcb, 0x92, 0x6d, 0x3b, 0x20, 0x61, 0xa8, 0x47, 0x81, 0xe3, 0x9d, + 0x6b, 0xdb, 0x5c, 0x59, 0x8f, 0x75, 0x65, 0xae, 0x8a, 0x3e, 0x81, 0x95, 0xf4, 0x39, 0x65, 0x6f, + 0x3c, 0x27, 0x18, 0x24, 0x07, 0x54, 0xfe, 0xc3, 0x02, 0x20, 0xe1, 0x73, 0x27, 0xf0, 0xfd, 0x33, + 0x8d, 0x84, 0x43, 0x37, 0xfa, 0x5f, 0xb9, 0xfa, 0x73, 0xd8, 0xb3, 0xe2, 0x93, 0x09, 0x66, 0x98, + 0xce, 0xde, 0x6c, 0x7a, 0x67, 0x64, 0x60, 0xca, 0xfc, 0x2e, 0x2c, 0x47, 0x8e, 0xf5, 0x8a, 0x44, + 0xd8, 0xb1, 0x59, 0x7a, 0x2d, 0x6b, 0x4b, 0x5c, 0xd0, 0xb4, 0x51, 0x03, 0x56, 0x7a, 0x43, 0x06, + 0x46, 0x57, 0x03, 0x52, 0xca, 0x1f, 0x64, 0x0e, 0x0b, 0xc7, 0x0f, 0xa7, 0x8e, 0x29, 0x7d, 0x18, + 0x55, 0xc6, 0x37, 0xae, 0x06, 0x44, 0x83, 0x5e, 0xf2, 0x1b, 0x7d, 0x06, 0x05, 0x33, 0x88, 0x9c, + 0x33, 0xd3, 0x8a, 0xb0, 0xe5, 0x9a, 0x61, 0xc8, 0xd2, 0xa9, 0x70, 0x5c, 0xb9, 0xd6, 0x98, 0x2c, + 0x54, 0x6a, 0x54, 0x43, 0x5b, 0x33, 0xd3, 0x8f, 0xe8, 0x11, 0x48, 0x89, 0x49, 0x3f, 0xb0, 0x1d, + 0xcf, 0x74, 0x59, 0xb2, 0xad, 0x69, 0xeb, 0xb1, 0x5c, 0xe5, 0x62, 0xf4, 0x0e, 0xac, 0x26, 0xd4, + 0x57, 0xe4, 0xaa, 0xb4, 0xc8, 0xf6, 0xb9, 0x12, 0xcb, 0x5e, 0x90, 0x2b, 0xf4, 0x02, 0x56, 0x03, + 0x16, 0x47, 0xe1, 0xde, 0x12, 0x73, 0xef, 0xf0, 0x5a, 0xf7, 0x78, 0xe0, 0xb9, 0x73, 0x2b, 0xc1, + 0xe8, 0x01, 0x3d, 0x84, 0xf5, 0x28, 0x30, 0xbd, 0xd0, 0x0a, 0x9c, 0x41, 0x84, 0xfb, 0x66, 0xd8, + 0x2f, 0x2d, 0xb3, 0x25, 0x0b, 0x23, 0x71, 0xc3, 0x0c, 0xfb, 0xa8, 0x04, 0x8b, 0x36, 0x89, 0x4c, + 0xc7, 0x0d, 0x4b, 0xc0, 0x08, 0xf1, 0x23, 0x7a, 0x37, 0x7d, 0x60, 0x2c, 0xd0, 0x2b, 0x6c, 0x6f, + 0xa3, 0x43, 0x60, 0xe1, 0x3b, 0x86, 0x2d, 0x9b, 0x04, 0x0e, 0xaf, 0x16, 0xec, 0x78, 0x83, 0xa1, + 0x58, 0x6f, 0x95, 0x99, 0xdb, 0x18, 0x81, 0x4d, 0x8a, 0xb1, 0x45, 0x9f, 0xc1, 0x66, 0x3a, 0xa3, + 0x9c, 0x73, 0xcf, 0x8c, 0x86, 0x01, 0x29, 0xad, 0x71, 0x95, 0x54, 0xb2, 0xc4, 0x10, 0x3a, 0x81, + 0xfb, 0x3e, 0xab, 0x4a, 0x12, 0x60, 0x33, 0x8a, 0x08, 0xad, 0x1b, 0xba, 0x60, 0xa2, 0x1c, 0x96, + 0x0a, 0x07, 0xb9, 0xc3, 0x65, 0xed, 0x5e, 0x4c, 0x93, 0x47, 0xac, 0xc4, 0x4c, 0x58, 0xfe, 0xd5, + 0x02, 0xa0, 0xb6, 0x6f, 0x13, 0x7d, 0x18, 0x0e, 0x1c, 0x8b, 0x62, 0xb4, 0xa4, 0x50, 0x03, 0x8a, + 0x6f, 0x55, 0x33, 0x52, 0x38, 0x99, 0xce, 0x0f, 0x61, 0x3d, 0x8c, 0x6d, 0xe3, 0xd0, 0xf2, 0x03, + 0xc2, 0x0a, 0x24, 0xa7, 0x15, 0x12, 0xb1, 0x4e, 0xa5, 0xb4, 0xbf, 0xba, 0x66, 0x18, 0xe1, 0xe1, + 0xc0, 0x36, 0x23, 0x62, 0x63, 0x32, 0xf0, 0xad, 0x3e, 0x2b, 0x80, 0xbc, 0x26, 0x51, 0xa4, 0xcb, + 0x01, 0x85, 0xca, 0xd1, 0x87, 0xb0, 0xcd, 0xd8, 0x01, 0xa1, 0xed, 0x16, 0xd3, 0x1e, 0x28, 0x34, + 0xf2, 0x4c, 0x63, 0x83, 0xa2, 0x1a, 0x03, 0x4f, 0x4c, 0xc7, 0xe5, 0x4a, 0x4f, 0x80, 0x89, 0xb1, + 0xef, 0xda, 0x69, 0x8d, 0xdb, 0xa3, 0x35, 0x54, 0xd7, 0x1e, 0xd1, 0x3f, 0x85, 0x5d, 0xdb, 0x09, + 0x23, 0xc7, 0xb3, 0x22, 0x2c, 0x4a, 0x92, 0x69, 0x7d, 0xe5, 0x78, 0xb6, 0xff, 0x95, 0x48, 0xed, + 0x52, 0x4c, 0x31, 0x18, 0x83, 0x6a, 0xbf, 0x64, 0x38, 0xdd, 0x10, 0x67, 0xd2, 0xa6, 0x16, 0x44, + 0x62, 0xb1, 0x45, 0xbe, 0x18, 0x47, 0x74, 0x0a, 0xf0, 0xc5, 0x8e, 0x60, 0x93, 0xe5, 0x39, 0x36, + 0x79, 0x76, 0xc5, 0xab, 0x2c, 0xb1, 0x55, 0x8a, 0x0c, 0x93, 0x59, 0x8a, 0x09, 0xf3, 0xef, 0x8b, + 0xf3, 0x8a, 0xb5, 0xb8, 0xf9, 0x65, 0x66, 0x7e, 0x9d, 0x22, 0x2c, 0xf3, 0xe5, 0x09, 0xeb, 0xbd, + 0x71, 0xeb, 0x90, 0xb2, 0x5e, 0x9d, 0x6f, 0xbd, 0x27, 0xac, 0xaf, 0x4c, 0x58, 0xaf, 0x72, 0xeb, + 0x87, 0x20, 0x59, 0x2e, 0x31, 0x3d, 0x3c, 0xa0, 0x64, 0x9e, 0x2c, 0xab, 0xcc, 0x72, 0x81, 0xc9, + 0x3b, 0x66, 0x28, 0x5e, 0x4c, 0xcf, 0x60, 0x4b, 0x98, 0x4d, 0xe8, 0xdc, 0xf2, 0x1a, 0xb3, 0x8c, + 0xb8, 0x65, 0xa1, 0xc2, 0x8d, 0xc7, 0x2a, 0x8e, 0x67, 0x93, 0xaf, 0xd3, 0x61, 0x2b, 0x8c, 0x54, + 0x9a, 0x14, 0x1b, 0x05, 0xee, 0xc7, 0xb0, 0x37, 0xe9, 0x0f, 0x36, 0x23, 0x3c, 0xf0, 0xc3, 0x68, + 0xe0, 0x7b, 0xa4, 0xb4, 0xce, 0x23, 0x37, 0xee, 0x9b, 0x1c, 0x75, 0x04, 0x5e, 0xfe, 0x26, 0x0f, + 0x25, 0x3e, 0x4d, 0x90, 0x40, 0x23, 0xae, 0x63, 0xf6, 0x1c, 0xd7, 0x89, 0xae, 0x78, 0x69, 0x7c, + 0x09, 0x3b, 0x81, 0xc0, 0xde, 0xee, 0xbd, 0x52, 0x8a, 0xd5, 0xa7, 0x5a, 0xff, 0xfb, 0x50, 0x0c, + 0x46, 0xcb, 0x8d, 0x55, 0x8b, 0x94, 0x02, 0xde, 0xa6, 0x5e, 0x64, 0x80, 0x28, 0x18, 0x86, 0x11, + 0xee, 0x99, 0x9e, 0x2d, 0xde, 0x1b, 0xe5, 0xa9, 0x5e, 0x1a, 0x6f, 0xda, 0xa0, 0xd4, 0xaa, 0xe9, + 0xd9, 0xda, 0x72, 0x14, 0xff, 0x44, 0x47, 0xb0, 0x61, 0xf9, 0x5e, 0x14, 0x98, 0xb6, 0xc3, 0x66, + 0x8d, 0xd4, 0x14, 0x92, 0xd7, 0xd0, 0x18, 0xc4, 0x83, 0xfd, 0xff, 0xb0, 0xed, 0x78, 0xc4, 0x75, + 0xce, 0x9d, 0x9e, 0x4b, 0xf0, 0xd0, 0x8b, 0x92, 0xd0, 0x2d, 0x30, 0x9d, 0xcd, 0x11, 0xda, 0xa5, + 0x20, 0xf7, 0xf4, 0x18, 0xb6, 0x44, 0xd9, 0x0c, 0xfc, 0xd0, 0x89, 0x9c, 0x4b, 0x22, 0x16, 0x5a, + 0x64, 0x51, 0xdb, 0xe0, 0x60, 0x47, 0x60, 0x49, 0xd3, 0x15, 0x3a, 0x1e, 0x39, 0x37, 0x53, 0x3a, + 0x4b, 0x69, 0x9d, 0xb6, 0xc0, 0xb8, 0xce, 0xec, 0xf2, 0x5c, 0x9e, 0x5d, 0x9e, 0xe5, 0x7f, 0x2f, + 0x42, 0x89, 0x57, 0x78, 0x9d, 0x44, 0x24, 0x70, 0xfc, 0x80, 0x77, 0x52, 0x96, 0x12, 0x63, 0xaf, + 0xec, 0xcc, 0xc4, 0x2b, 0xfb, 0x08, 0x36, 0xec, 0xb4, 0xca, 0x58, 0x58, 0xd1, 0x18, 0xf4, 0x36, + 0x81, 0x7d, 0x04, 0x45, 0x3a, 0x15, 0x5e, 0x12, 0xdc, 0x27, 0xa6, 0x8b, 0xfd, 0x01, 0xf5, 0x81, + 0xf7, 0xc0, 0x02, 0x07, 0x1a, 0xc4, 0x74, 0xd5, 0x41, 0xd3, 0xa6, 0xa7, 0x34, 0x08, 0xfc, 0x1e, + 0xf7, 0x22, 0x1d, 0x0e, 0x1e, 0xc2, 0x8d, 0x04, 0x4c, 0x45, 0xe3, 0x3d, 0x60, 0xd5, 0xce, 0x8d, + 0xa7, 0x83, 0xb7, 0x46, 0xc5, 0xd4, 0x34, 0xe7, 0xc5, 0x4e, 0xd3, 0xfa, 0x1c, 0x06, 0x64, 0xbc, + 0xd9, 0x51, 0xe4, 0x84, 0x03, 0x9c, 0xfd, 0x09, 0xad, 0xa1, 0xa4, 0x71, 0x27, 0xfc, 0xb1, 0xa0, + 0xdd, 0x09, 0x92, 0xee, 0x1d, 0xeb, 0xf1, 0xc0, 0xcd, 0xc9, 0xc3, 0xe5, 0xb9, 0x79, 0xf8, 0x0b, + 0xb8, 0xc7, 0x7c, 0x9b, 0x3b, 0x0c, 0xc2, 0x6b, 0x4c, 0x6c, 0xd4, 0x82, 0x31, 0x7b, 0x20, 0xec, + 0xc1, 0x7d, 0xf1, 0x2e, 0x9a, 0xdb, 0x16, 0x56, 0x6e, 0x5e, 0x61, 0x8f, 0xbf, 0xb1, 0xe6, 0xb4, + 0x06, 0x03, 0x8a, 0x62, 0x8d, 0xd4, 0x48, 0xb4, 0xfa, 0x86, 0x23, 0xd1, 0x3a, 0x5f, 0x62, 0x34, + 0x16, 0x55, 0xc6, 0xad, 0xa6, 0x5b, 0x71, 0x8a, 0x1b, 0x77, 0x90, 0x7b, 0xc9, 0xdb, 0xb0, 0xef, + 0xbb, 0x36, 0x09, 0x92, 0xe0, 0xf1, 0x3d, 0x16, 0x58, 0xd8, 0x76, 0x62, 0x52, 0x83, 0x71, 0x44, + 0xf8, 0x78, 0x20, 0x3e, 0x86, 0xd2, 0x44, 0x2b, 0x1f, 0xa5, 0xca, 0x3a, 0x5b, 0x75, 0x6b, 0xac, + 0x9b, 0x27, 0xf9, 0xf2, 0x29, 0xec, 0x8a, 0x7c, 0x11, 0xd3, 0xef, 0xb8, 0xae, 0xc4, 0x74, 0x4b, + 0x9c, 0xc2, 0xe7, 0xdd, 0x31, 0xf5, 0x8f, 0xa1, 0x44, 0x5f, 0xf9, 0x33, 0x75, 0x8b, 0x7c, 0x5d, + 0xdf, 0xb5, 0xa7, 0x15, 0xcb, 0xbf, 0xc9, 0xc4, 0x55, 0x2f, 0xa7, 0x87, 0xbc, 0xd7, 0xa8, 0xfa, + 0x0f, 0x60, 0x93, 0xef, 0x72, 0x62, 0x66, 0xcc, 0xf2, 0x3b, 0x1b, 0xc3, 0xe4, 0xc9, 0xc1, 0x31, + 0xbc, 0xba, 0xe8, 0xf9, 0xee, 0xa4, 0x4a, 0x8e, 0xf7, 0x30, 0x0e, 0x8e, 0xe9, 0x94, 0x7f, 0x99, + 0x87, 0x05, 0x5e, 0xde, 0x68, 0x0f, 0x20, 0xd5, 0x00, 0x32, 0x6c, 0x57, 0x4b, 0xfd, 0xb8, 0xf4, + 0xc7, 0x7c, 0xcd, 0x4e, 0xf8, 0xfa, 0x18, 0x50, 0x68, 0xf5, 0x89, 0x3d, 0x74, 0xe3, 0x6e, 0x13, + 0x5f, 0x3d, 0xf2, 0x9a, 0x94, 0x20, 0xec, 0x44, 0x9a, 0x36, 0xbd, 0x55, 0x51, 0xb3, 0x33, 0xd3, + 0x3c, 0xff, 0x1a, 0xb7, 0x2a, 0xae, 0x3c, 0x95, 0xe0, 0x3f, 0x83, 0xdd, 0x4b, 0x12, 0x38, 0x67, + 0xce, 0x2c, 0xc3, 0xf4, 0x72, 0x92, 0xbb, 0xc9, 0xf2, 0xdd, 0x58, 0x7f, 0xd2, 0x76, 0x88, 0x3e, + 0x82, 0x05, 0x3a, 0xfa, 0x0e, 0xf9, 0xe5, 0xb7, 0x70, 0x7c, 0x6f, 0xaa, 0x64, 0xf8, 0x29, 0xea, + 0x8c, 0xa4, 0x09, 0x32, 0x1d, 0xf9, 0xad, 0x80, 0xb0, 0x26, 0xdc, 0x27, 0xce, 0x79, 0x3f, 0x12, + 0x0d, 0x6d, 0x4d, 0x48, 0x1b, 0x4c, 0x48, 0x69, 0x71, 0xaf, 0x16, 0xb4, 0x25, 0x4e, 0x13, 0x52, + 0x41, 0xab, 0x40, 0xd1, 0x26, 0xa6, 0xed, 0x3a, 0x1e, 0x19, 0x9d, 0xb2, 0x98, 0xd7, 0x62, 0x20, + 0x3e, 0xe4, 0xfb, 0x20, 0xae, 0x2f, 0xfc, 0xee, 0xc0, 0xaf, 0x22, 0xc0, 0x45, 0xec, 0xca, 0xb0, + 0x09, 0xb7, 0x3d, 0x9f, 0xde, 0x94, 0x59, 0x67, 0xd1, 0xf8, 0x43, 0xf9, 0x5f, 0x39, 0x58, 0x61, + 0x26, 0xc4, 0xb7, 0x90, 0xff, 0xde, 0x18, 0x7f, 0x17, 0x96, 0x12, 0x9f, 0xb3, 0xcc, 0xe7, 0x45, + 0x22, 0x7c, 0x7d, 0x00, 0x6b, 0xbc, 0xf3, 0xc5, 0xbb, 0xcf, 0xb1, 0x57, 0xdb, 0x2a, 0x17, 0x8a, + 0xcd, 0x57, 0x61, 0xa5, 0xef, 0x27, 0x3d, 0x92, 0x25, 0xca, 0xca, 0xf1, 0xee, 0x74, 0x18, 0x92, + 0xef, 0x38, 0xd5, 0xfc, 0xb7, 0x7f, 0xbf, 0x7f, 0x4b, 0x83, 0xfe, 0xe8, 0xcb, 0x4e, 0x00, 0xfb, + 0x21, 0xef, 0x6c, 0x38, 0xb9, 0x12, 0x61, 0x7f, 0xf4, 0x6d, 0x82, 0x67, 0xc9, 0xca, 0xf1, 0xe3, + 0x79, 0x0d, 0x71, 0xd6, 0x07, 0x0d, 0x6d, 0x2f, 0x9c, 0x0f, 0x86, 0xe8, 0x25, 0x6c, 0xc5, 0x6b, + 0x0e, 0x68, 0x3b, 0x15, 0xad, 0x92, 0x26, 0x12, 0x5d, 0xea, 0xc1, 0x6b, 0xf4, 0x5e, 0x6d, 0x23, + 0x9c, 0x92, 0x85, 0x34, 0x34, 0xd6, 0x30, 0x08, 0x68, 0x4f, 0x0b, 0x87, 0xbd, 0x0b, 0x27, 0x8a, + 0x48, 0xc0, 0xaf, 0xc1, 0x37, 0x84, 0x46, 0x68, 0xe9, 0xb1, 0x52, 0xf9, 0xcf, 0x19, 0x28, 0x8a, + 0x30, 0x19, 0xf4, 0x32, 0xeb, 0xb0, 0xef, 0x34, 0x55, 0x28, 0x84, 0xfe, 0x30, 0xb0, 0xde, 0x28, + 0xee, 0x6b, 0x5c, 0x25, 0x0e, 0x7a, 0x8b, 0x8e, 0x2e, 0xb4, 0x9b, 0xf3, 0x91, 0xe1, 0x0d, 0x3e, + 0x70, 0xa0, 0x94, 0x5e, 0xea, 0x26, 0x48, 0xce, 0xce, 0x08, 0x1f, 0x56, 0xd2, 0x43, 0x4d, 0x21, + 0x11, 0xb3, 0xdc, 0xad, 0xa8, 0xb0, 0x9c, 0x7c, 0xe7, 0x41, 0xdb, 0x80, 0x3a, 0xaa, 0x66, 0x60, + 0xdd, 0x90, 0x0d, 0x05, 0x77, 0xdb, 0x2f, 0xda, 0xea, 0xcb, 0xb6, 0x74, 0x0b, 0x6d, 0xc0, 0x7a, + 0x4a, 0xae, 0x76, 0x94, 0xb6, 0x94, 0x41, 0x5b, 0x50, 0x4c, 0x09, 0x6b, 0x2d, 0x55, 0x57, 0xea, + 0x52, 0xb6, 0xf2, 0xb7, 0x0c, 0x6c, 0xcf, 0xfe, 0x24, 0x82, 0x1e, 0xc1, 0xbb, 0xba, 0xa1, 0x6a, + 0xf2, 0x73, 0x05, 0x77, 0x34, 0x55, 0x3d, 0xc1, 0xd5, 0x6e, 0xed, 0x85, 0x62, 0x60, 0xe3, 0xcb, + 0x0e, 0x5d, 0x4d, 0xef, 0x28, 0xb5, 0xe6, 0x49, 0x53, 0xa9, 0x4b, 0xb7, 0xd0, 0xff, 0xc1, 0xc1, + 0x7c, 0xaa, 0xa6, 0xd4, 0x94, 0xb6, 0x21, 0x65, 0xd0, 0x3b, 0x70, 0x6f, 0x3e, 0x4b, 0x6d, 0xd5, + 0xa5, 0x2c, 0x7a, 0x08, 0x0f, 0xe6, 0x53, 0x3a, 0x9a, 0x5a, 0x95, 0x8d, 0xa6, 0xda, 0x96, 0x72, + 0xe8, 0x5d, 0x78, 0xe7, 0xda, 0x15, 0x1b, 0x4a, 0xed, 0x85, 0x94, 0xaf, 0xfc, 0x3a, 0x03, 0x77, + 0xe7, 0x7e, 0xa4, 0x41, 0x8f, 0xe1, 0x70, 0xdc, 0x88, 0xac, 0x19, 0xcd, 0x13, 0xb9, 0x66, 0xe0, + 0x5a, 0x4b, 0xd6, 0xf5, 0x89, 0x4d, 0xbe, 0x07, 0xe5, 0x6b, 0xd9, 0xcd, 0x76, 0x5d, 0xf9, 0x42, + 0xca, 0x4c, 0xef, 0x61, 0x82, 0xa7, 0x7f, 0x79, 0x5a, 0x55, 0x5b, 0x52, 0xb6, 0xf2, 0xdb, 0x1c, + 0xdc, 0x99, 0x33, 0x8f, 0xa0, 0x0a, 0xbc, 0x37, 0x6e, 0x44, 0x53, 0xf4, 0x6e, 0x6b, 0xb6, 0x63, + 0x0f, 0xe0, 0xfe, 0x35, 0xdc, 0x8e, 0xac, 0xeb, 0x52, 0x66, 0x7a, 0xaf, 0x63, 0xa4, 0x86, 0xac, + 0x37, 0xf0, 0x69, 0x53, 0x3f, 0x95, 0x8d, 0x5a, 0x43, 0xca, 0xa2, 0x8f, 0xe0, 0xd9, 0x35, 0x6c, + 0xa3, 0x79, 0xaa, 0xa8, 0x5d, 0x03, 0xab, 0x1a, 0x6e, 0xab, 0x14, 0xea, 0xa8, 0x6d, 0x5d, 0x91, + 0x72, 0xe8, 0x43, 0x38, 0xba, 0x46, 0x4d, 0xad, 0xea, 0x8a, 0xf6, 0xb9, 0xa2, 0xe1, 0xcf, 0xba, + 0xaa, 0xd6, 0x3d, 0xc5, 0x27, 0x72, 0xb3, 0x25, 0xe5, 0xd1, 0x33, 0x78, 0x72, 0x8d, 0x52, 0x5b, + 0xc5, 0x4a, 0xab, 0xf9, 0xbc, 0x59, 0x6d, 0x29, 0xd8, 0x68, 0xd2, 0x18, 0x4b, 0xb7, 0x6f, 0x50, + 0x69, 0xb6, 0x3f, 0x97, 0x5b, 0xcd, 0x3a, 0x36, 0x34, 0xb9, 0xad, 0xd7, 0xb4, 0x66, 0xc7, 0x90, + 0x16, 0x6e, 0xd8, 0x91, 0xc8, 0x18, 0x5c, 0x53, 0xdb, 0x27, 0x4d, 0xed, 0x54, 0xa9, 0x73, 0xe7, + 0x16, 0x2b, 0x7f, 0xca, 0x40, 0x71, 0xea, 0xea, 0x47, 0x4f, 0x5c, 0x53, 0x68, 0x39, 0x29, 0x1a, + 0x36, 0xb4, 0xae, 0x6e, 0xe0, 0xaa, 0xdc, 0xae, 0x4f, 0x84, 0x65, 0x1f, 0x76, 0x66, 0x91, 0xda, + 0xaa, 0x76, 0x2a, 0xb7, 0x78, 0x39, 0xcc, 0xc2, 0x5b, 0xea, 0x4b, 0xfe, 0x28, 0x65, 0xd1, 0x13, + 0x78, 0x34, 0x8b, 0x52, 0x6b, 0xc8, 0xad, 0x96, 0xd2, 0x7e, 0xae, 0x68, 0xb8, 0xd9, 0x8e, 0x4f, + 0x47, 0xca, 0xa1, 0x03, 0xd8, 0x9b, 0x45, 0xaf, 0x2b, 0xcf, 0x35, 0xb9, 0xae, 0xd4, 0xa5, 0x7c, + 0xe5, 0x9f, 0x19, 0x58, 0x4d, 0xbf, 0xcf, 0xa9, 0x93, 0x0d, 0x45, 0x6e, 0x61, 0xb5, 0xc3, 0x3a, + 0x43, 0x77, 0x32, 0xb7, 0xf6, 0xa0, 0x34, 0x81, 0xeb, 0xb5, 0x86, 0x52, 0xef, 0xb6, 0x94, 0xba, + 0x94, 0x99, 0xa1, 0xdd, 0x6c, 0xd3, 0xe3, 0x7d, 0xae, 0x29, 0xba, 0x2e, 0x65, 0x51, 0x19, 0xf6, + 0x27, 0x70, 0xfa, 0xa8, 0x68, 0x58, 0xb8, 0x59, 0x97, 0x72, 0x68, 0x17, 0xee, 0x4c, 0x70, 0x3e, + 0x57, 0x34, 0xbe, 0x7c, 0x1e, 0xdd, 0x85, 0xad, 0x09, 0x90, 0xc6, 0x45, 0xa9, 0x4b, 0xb7, 0xd1, + 0x0e, 0x6c, 0x4f, 0x40, 0xca, 0x17, 0x9d, 0xa6, 0xa6, 0xd4, 0xa5, 0x85, 0x6a, 0xe5, 0xdb, 0xef, + 0xf7, 0x33, 0xdf, 0x7d, 0xbf, 0x9f, 0xf9, 0xc7, 0xf7, 0xfb, 0x99, 0x6f, 0x7e, 0xd8, 0xbf, 0xf5, + 0xdd, 0x0f, 0xfb, 0xb7, 0xfe, 0xfa, 0xc3, 0xfe, 0xad, 0x9f, 0x4a, 0x5f, 0x8f, 0xfe, 0xfd, 0x12, + 0x5d, 0x0d, 0x48, 0xd8, 0x5b, 0x60, 0xff, 0x2a, 0xf9, 0xf0, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x94, 0xf2, 0x37, 0xc9, 0x9e, 0x19, 0x00, 0x00, } func (m *HostReport) Marshal() (dAtA []byte, err error) { @@ -2093,6 +2172,13 @@ func (m *EpochReport) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.CurrentSubmitter) > 0 { + i -= len(m.CurrentSubmitter) + copy(dAtA[i:], m.CurrentSubmitter) + i = encodeVarintAudit(dAtA, i, uint64(len(m.CurrentSubmitter))) + i-- + dAtA[i] = 0x3a + } if len(m.StorageProofResults) > 0 { for iNdEx := len(m.StorageProofResults) - 1; iNdEx >= 0; iNdEx-- { { @@ -2151,6 +2237,48 @@ func (m *EpochReport) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AccountTransition) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AccountTransition) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AccountTransition) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.EffectiveEpoch != 0 { + i = encodeVarintAudit(dAtA, i, uint64(m.EffectiveEpoch)) + i-- + dAtA[i] = 0x18 + } + if len(m.DestinationAccount) > 0 { + i -= len(m.DestinationAccount) + copy(dAtA[i:], m.DestinationAccount) + i = encodeVarintAudit(dAtA, i, uint64(len(m.DestinationAccount))) + i-- + dAtA[i] = 0x12 + } + if len(m.SourceAccount) > 0 { + i -= len(m.SourceAccount) + copy(dAtA[i:], m.SourceAccount) + i = encodeVarintAudit(dAtA, i, uint64(len(m.SourceAccount))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintAudit(dAtA []byte, offset int, v uint64) int { offset -= sovAudit(v) base := offset @@ -2528,6 +2656,30 @@ func (m *EpochReport) Size() (n int) { n += 1 + l + sovAudit(uint64(l)) } } + l = len(m.CurrentSubmitter) + if l > 0 { + n += 1 + l + sovAudit(uint64(l)) + } + return n +} + +func (m *AccountTransition) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SourceAccount) + if l > 0 { + n += 1 + l + sovAudit(uint64(l)) + } + l = len(m.DestinationAccount) + if l > 0 { + n += 1 + l + sovAudit(uint64(l)) + } + if m.EffectiveEpoch != 0 { + n += 1 + sovAudit(uint64(m.EffectiveEpoch)) + } return n } @@ -4941,6 +5093,171 @@ func (m *EpochReport) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentSubmitter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAudit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAudit + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAudit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CurrentSubmitter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAudit(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAudit + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AccountTransition) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAudit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AccountTransition: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AccountTransition: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAudit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAudit + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAudit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DestinationAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAudit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAudit + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAudit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DestinationAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EffectiveEpoch", wireType) + } + m.EffectiveEpoch = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAudit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EffectiveEpoch |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipAudit(dAtA[iNdEx:]) diff --git a/x/audit/v1/types/genesis.go b/x/audit/v1/types/genesis.go index b17974e8..7d0d4b49 100644 --- a/x/audit/v1/types/genesis.go +++ b/x/audit/v1/types/genesis.go @@ -4,7 +4,7 @@ import "fmt" const ( // Per 122-F4 — bump KeepLastEpochEntries to cover OldClassAFaultWindow for safe pruning. - ConsensusVersion = 2 + ConsensusVersion = 3 ) func DefaultGenesis() *GenesisState { @@ -35,6 +35,54 @@ func (gs GenesisState) Validate() error { } seenHealOpIDs[healOp.HealOpId] = struct{}{} } + if err := ValidateAccountTransitions(gs.AccountTransitions); err != nil { + return err + } + + return nil +} +func ValidateAccountTransitions(transitions []AccountTransition) error { + // This types-layer check is structural because no configured address codec + // is available here. Keeper.InitGenesis additionally requires canonical + // Bech32 endpoint text before importing any transition index. + if len(transitions) > MaxAccountTransitions { + return fmt.Errorf("account transitions exceed limit %d", MaxAccountTransitions) + } + forward := make(map[string]AccountTransition, len(transitions)) + reverse := make(map[string]AccountTransition, len(transitions)) + for _, transition := range transitions { + if transition.SourceAccount == "" || transition.DestinationAccount == "" || transition.SourceAccount == transition.DestinationAccount || transition.EffectiveEpoch == 0 { + return fmt.Errorf("invalid account transition") + } + if _, exists := forward[transition.SourceAccount]; exists { + return fmt.Errorf("account transition fork at %q", transition.SourceAccount) + } + if _, exists := reverse[transition.DestinationAccount]; exists { + return fmt.Errorf("account transition destination collision at %q", transition.DestinationAccount) + } + forward[transition.SourceAccount] = transition + reverse[transition.DestinationAccount] = transition + } + for start := range forward { + seen := map[string]struct{}{} + account := start + var previousEpoch uint64 + for { + if _, exists := seen[account]; exists { + return fmt.Errorf("account transition cycle") + } + seen[account] = struct{}{} + transition, exists := forward[account] + if !exists { + break + } + if previousEpoch != 0 && transition.EffectiveEpoch <= previousEpoch { + return fmt.Errorf("account transition epochs must strictly increase") + } + previousEpoch = transition.EffectiveEpoch + account = transition.DestinationAccount + } + } return nil } diff --git a/x/audit/v1/types/genesis.pb.go b/x/audit/v1/types/genesis.pb.go index 6c292be9..cb8224d0 100644 --- a/x/audit/v1/types/genesis.pb.go +++ b/x/audit/v1/types/genesis.pb.go @@ -60,6 +60,7 @@ type GenesisState struct { // Per final-gate F-B4 — per-verifier heal-op votes must survive // export/import workflows. HealOpVerifications []GenesisHealOpVerification `protobuf:"bytes,22,rep,name=heal_op_verifications,json=healOpVerifications,proto3" json:"heal_op_verifications"` + AccountTransitions []AccountTransition `protobuf:"bytes,23,rep,name=account_transitions,json=accountTransitions,proto3" json:"account_transitions"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -249,6 +250,13 @@ func (m *GenesisState) GetHealOpVerifications() []GenesisHealOpVerification { return nil } +func (m *GenesisState) GetAccountTransitions() []AccountTransition { + if m != nil { + return m.AccountTransitions + } + return nil +} + // StorageTruthPostponement records a supernode's storage-truth postponement state // for genesis export/import. Per 121-F7. type StorageTruthPostponement struct { @@ -1014,87 +1022,89 @@ func init() { func init() { proto.RegisterFile("lumera/audit/v1/genesis.proto", fileDescriptor_a433cb4f206fdbad) } var fileDescriptor_a433cb4f206fdbad = []byte{ - // 1269 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4f, 0x4f, 0x24, 0x45, - 0x14, 0xa7, 0x17, 0x76, 0x99, 0x29, 0x86, 0x99, 0xa1, 0x80, 0xa5, 0xf9, 0x37, 0xe0, 0x18, 0xb2, - 0x2c, 0xc6, 0x21, 0x60, 0x62, 0x8c, 0x7a, 0x81, 0x55, 0x16, 0x4c, 0x54, 0xd2, 0x10, 0x63, 0x34, - 0x6b, 0xa7, 0xe8, 0xae, 0x99, 0xee, 0x65, 0xa6, 0xab, 0xad, 0xaa, 0x21, 0xe0, 0xc1, 0xab, 0x1e, - 0x4d, 0xfc, 0x08, 0x7a, 0xf0, 0xe8, 0xd5, 0x2f, 0x60, 0xd6, 0xdb, 0x1e, 0x3d, 0x19, 0x03, 0x87, - 0xfd, 0x1a, 0xa6, 0xeb, 0x4f, 0x4f, 0x4f, 0xf7, 0x74, 0x8b, 0xc6, 0xf5, 0x02, 0xd3, 0xef, 0xd5, - 0xef, 0xfd, 0xde, 0xbf, 0x7a, 0xaf, 0x1b, 0xac, 0x76, 0xfb, 0x3d, 0x4c, 0xd1, 0x36, 0xea, 0xbb, - 0x3e, 0xdf, 0xbe, 0xd8, 0xd9, 0xee, 0xe0, 0x00, 0x33, 0x9f, 0xb5, 0x42, 0x4a, 0x38, 0x81, 0x35, - 0xa9, 0x6e, 0x09, 0x75, 0xeb, 0x62, 0x67, 0x69, 0x06, 0xf5, 0xfc, 0x80, 0x6c, 0x8b, 0xbf, 0xf2, - 0xcc, 0xd2, 0x5c, 0x87, 0x74, 0x88, 0xf8, 0xb9, 0x1d, 0xfd, 0x52, 0xd2, 0x95, 0xb4, 0xe1, 0x10, - 0x51, 0xd4, 0x53, 0x76, 0x97, 0x1a, 0x69, 0x2d, 0xbe, 0xf0, 0x5d, 0x1c, 0x38, 0x58, 0xe9, 0x97, - 0xd3, 0x7a, 0xe9, 0x80, 0x50, 0x36, 0x5f, 0xd4, 0x40, 0xe5, 0xb1, 0x74, 0xf3, 0x84, 0x23, 0x8e, - 0xe1, 0xdb, 0xe0, 0x9e, 0xb4, 0x6e, 0x1a, 0xeb, 0xc6, 0xe6, 0xd4, 0xee, 0x42, 0x2b, 0xe5, 0x76, - 0xeb, 0x58, 0xa8, 0xf7, 0xcb, 0xcf, 0xfe, 0x58, 0x1b, 0xfb, 0xe9, 0xc5, 0xcf, 0x5b, 0x86, 0xa5, - 0x10, 0xf0, 0x1d, 0x50, 0xd2, 0xdc, 0xe6, 0x9d, 0xf5, 0xf1, 0xcd, 0xa9, 0xdd, 0xc5, 0x0c, 0xfa, - 0x7d, 0x75, 0x60, 0x7f, 0x22, 0xc2, 0x5b, 0x31, 0x00, 0x6e, 0x82, 0x7a, 0x80, 0x2f, 0xb9, 0xad, - 0x05, 0xb6, 0xef, 0x9a, 0xe3, 0xeb, 0xc6, 0xe6, 0x84, 0x55, 0x8d, 0xe4, 0x1a, 0x77, 0xe4, 0xc2, - 0x27, 0x60, 0x3e, 0x20, 0x2e, 0xb6, 0x59, 0x9f, 0x85, 0xbe, 0xe3, 0x93, 0xc0, 0x66, 0x91, 0xeb, - 0xcc, 0x9c, 0x10, 0x9c, 0xaf, 0x66, 0x38, 0x3f, 0x22, 0x2e, 0x3e, 0xd1, 0x87, 0x45, 0x98, 0x8a, - 0x7d, 0x36, 0xc8, 0x68, 0x18, 0x24, 0x60, 0x99, 0xe2, 0x90, 0x50, 0x8e, 0xa9, 0x4d, 0x71, 0xd7, - 0x47, 0x67, 0x7e, 0xd7, 0xe7, 0x57, 0x9a, 0xe4, 0xae, 0x20, 0x79, 0x98, 0x21, 0xb1, 0x14, 0xc6, - 0x1a, 0x40, 0x92, 0x54, 0x8b, 0x34, 0x47, 0x2f, 0x08, 0xb9, 0xef, 0x9c, 0x63, 0x6e, 0xbb, 0x98, - 0x63, 0xea, 0x13, 0x8a, 0x78, 0x22, 0xaa, 0x7b, 0x39, 0x84, 0xa7, 0x02, 0xf3, 0x5e, 0x12, 0x32, - 0x44, 0xc8, 0x73, 0xf4, 0x0c, 0xbe, 0x05, 0x4a, 0x1e, 0x46, 0x5d, 0x9b, 0x84, 0xcc, 0x9c, 0x14, - 0xd6, 0xb3, 0x55, 0x3e, 0xc4, 0xa8, 0xfb, 0x71, 0xa8, 0x6c, 0x4d, 0x7a, 0xe2, 0x89, 0xc1, 0x0d, - 0x50, 0x13, 0x45, 0x52, 0xf0, 0xa8, 0x46, 0x25, 0x51, 0xa3, 0x4a, 0x24, 0x96, 0x98, 0x23, 0x17, - 0x86, 0x60, 0x45, 0x45, 0x84, 0x28, 0xf7, 0xdb, 0xc8, 0xe1, 0xb6, 0x43, 0xfa, 0x01, 0xd7, 0x21, - 0x95, 0x0b, 0x43, 0xda, 0x53, 0x98, 0x47, 0x11, 0x64, 0x44, 0x48, 0x59, 0xbd, 0xc8, 0x21, 0xe3, - 0x84, 0xa2, 0x0e, 0xb6, 0x39, 0xed, 0x73, 0xcf, 0x0e, 0x09, 0xe3, 0x21, 0x09, 0x70, 0x0f, 0x07, - 0x9c, 0x99, 0x20, 0x87, 0xf0, 0x44, 0x62, 0x4e, 0x23, 0xc8, 0x71, 0x02, 0xa1, 0x09, 0x59, 0x8e, - 0x9e, 0xc1, 0x4f, 0x41, 0x9d, 0x62, 0xc7, 0xc3, 0xce, 0x79, 0xdc, 0xb1, 0xe6, 0x94, 0x60, 0x79, - 0x90, 0x61, 0x51, 0x17, 0xcc, 0x92, 0xe7, 0x53, 0x37, 0xa0, 0x46, 0x87, 0xc5, 0x30, 0x04, 0x9a, - 0xd6, 0x0e, 0x29, 0x21, 0x6d, 0x9b, 0x53, 0x14, 0x30, 0x87, 0xfa, 0x21, 0x67, 0x66, 0x45, 0x50, - 0xb4, 0xf2, 0x28, 0x54, 0x3c, 0xc7, 0x11, 0xee, 0x34, 0x86, 0x29, 0xa6, 0x05, 0x36, 0x52, 0xcb, - 0xe0, 0xe7, 0x00, 0x8a, 0x0b, 0xd5, 0x46, 0x7e, 0xb7, 0x4f, 0xa3, 0xff, 0x0e, 0x67, 0xe6, 0x74, - 0x71, 0x34, 0xd1, 0xa5, 0x3a, 0x90, 0x80, 0x03, 0xe4, 0x68, 0x8e, 0x7a, 0x30, 0x2c, 0x66, 0xd0, - 0x05, 0xf3, 0x89, 0xeb, 0xc4, 0xfa, 0x5d, 0xae, 0xec, 0x57, 0x85, 0xfd, 0xad, 0xfc, 0x6c, 0xe9, - 0xfb, 0x12, 0x61, 0x12, 0x14, 0xb3, 0x34, 0xa3, 0x61, 0xf0, 0x0b, 0x30, 0x1b, 0x79, 0x8f, 0x5d, - 0xd9, 0x9a, 0x3d, 0x44, 0xcf, 0x31, 0x65, 0x66, 0x4d, 0x70, 0x6c, 0xe6, 0x71, 0x1c, 0x08, 0x48, - 0xd4, 0xb6, 0x1f, 0x0a, 0x80, 0x62, 0x98, 0x69, 0xa7, 0xe4, 0x0c, 0x3e, 0x06, 0xd3, 0x38, 0x24, - 0x8e, 0x67, 0x4b, 0x72, 0x66, 0xd6, 0x85, 0xe5, 0x95, 0xec, 0x7c, 0x8b, 0x4e, 0x49, 0xdf, 0x95, - 0xb5, 0x0a, 0x1e, 0x88, 0x18, 0x3c, 0x06, 0x55, 0x69, 0xc2, 0xf6, 0x03, 0xd7, 0x77, 0x30, 0x33, - 0x67, 0x72, 0xa6, 0xd6, 0x50, 0x1e, 0x8e, 0x02, 0x17, 0x5f, 0x2a, 0x83, 0xd3, 0x54, 0x8b, 0x22, - 0x3c, 0x7c, 0x02, 0x66, 0x3d, 0xc2, 0xb8, 0x9d, 0x32, 0x0b, 0x8b, 0xcb, 0x77, 0x48, 0x18, 0xcf, - 0x9a, 0x9e, 0xf1, 0x92, 0x62, 0x61, 0x9e, 0x0c, 0xda, 0xd1, 0xf1, 0x50, 0xb7, 0x8b, 0x83, 0x0e, - 0x8e, 0x49, 0x66, 0x05, 0xc9, 0xeb, 0x7f, 0xd3, 0x8e, 0x8f, 0x34, 0x2e, 0x49, 0xa5, 0xbb, 0x31, - 0xa9, 0x14, 0x84, 0xdf, 0x1a, 0xe0, 0x15, 0xe4, 0x88, 0x09, 0xd8, 0xf6, 0x03, 0xd4, 0xf5, 0xbf, - 0x92, 0xe3, 0x70, 0xf8, 0x46, 0xcf, 0x09, 0xe6, 0x37, 0xf3, 0x98, 0xf7, 0x84, 0x81, 0x83, 0x04, - 0x7e, 0xc4, 0xf5, 0x5e, 0x43, 0x85, 0xa7, 0x44, 0xef, 0xc6, 0xeb, 0x48, 0x96, 0x5f, 0x8c, 0x31, - 0x66, 0xce, 0x17, 0xf7, 0xae, 0xbe, 0xcb, 0xa2, 0x19, 0xc4, 0x98, 0xd2, 0xbd, 0x8b, 0x33, 0x1a, - 0xc1, 0xa2, 0xe7, 0xe9, 0x05, 0xa6, 0x7e, 0xdb, 0x77, 0x84, 0x2b, 0xcc, 0xbc, 0x5f, 0xcc, 0x22, - 0xc7, 0xed, 0x27, 0x09, 0x88, 0x66, 0xf1, 0x32, 0x1a, 0xd6, 0xfc, 0xc1, 0x00, 0x66, 0xde, 0xb8, - 0x83, 0xaf, 0x81, 0x19, 0xd6, 0x0f, 0x31, 0x15, 0x63, 0x00, 0x39, 0x22, 0x4a, 0xf1, 0x02, 0x50, - 0xb6, 0xea, 0xb1, 0x62, 0x4f, 0xca, 0xe1, 0x0e, 0x98, 0xd7, 0xb5, 0x70, 0x6d, 0xc4, 0x55, 0x66, - 0x7c, 0xd7, 0xbc, 0x23, 0x56, 0x01, 0x8c, 0x95, 0x7b, 0x5c, 0x84, 0x79, 0xe4, 0xc2, 0x07, 0xa0, - 0xc6, 0x38, 0x25, 0x41, 0x27, 0xae, 0xa2, 0xd8, 0xed, 0x25, 0xab, 0x2a, 0xc5, 0xda, 0x99, 0xe6, - 0x37, 0x06, 0xd8, 0xb8, 0x55, 0x09, 0x5f, 0xb6, 0xcb, 0xcd, 0x5f, 0x0c, 0xb0, 0x98, 0x5b, 0x4e, - 0xb8, 0x08, 0x4a, 0xb1, 0x0d, 0x43, 0xd8, 0x98, 0xc4, 0x89, 0x58, 0xfb, 0x67, 0x4f, 0xb1, 0xc3, - 0x6d, 0xe4, 0xba, 0x14, 0x33, 0x26, 0x58, 0xca, 0x56, 0x55, 0x89, 0xf7, 0xa4, 0x14, 0xee, 0x83, - 0xe9, 0xb8, 0xbb, 0xf8, 0x55, 0x28, 0x53, 0x52, 0xdd, 0x5d, 0xcd, 0x7d, 0x67, 0x3a, 0xbd, 0x0a, - 0xb1, 0x55, 0xc1, 0x89, 0x27, 0x38, 0x07, 0xee, 0xca, 0xc8, 0x27, 0x84, 0x13, 0xf2, 0xa1, 0xf9, - 0xfd, 0xc0, 0xf7, 0x6c, 0x93, 0xc0, 0x15, 0x00, 0x12, 0xfb, 0x5b, 0x7a, 0x5f, 0xf2, 0xf4, 0xee, - 0x7e, 0x17, 0x2c, 0xc9, 0x2e, 0xc4, 0xd4, 0xce, 0x26, 0x58, 0x46, 0x62, 0xea, 0x13, 0x27, 0xe9, - 0x44, 0x2f, 0x81, 0x92, 0xd2, 0xb9, 0xaa, 0xc2, 0xf1, 0x73, 0xf3, 0x0a, 0xdc, 0x1f, 0xbd, 0x09, - 0x8b, 0xb2, 0xb9, 0x0c, 0xca, 0xea, 0x55, 0x42, 0x55, 0xab, 0x6c, 0x95, 0xa4, 0x40, 0xa6, 0xda, - 0xa1, 0x18, 0x71, 0x42, 0x63, 0x07, 0xc7, 0x65, 0xaa, 0x95, 0x58, 0xb9, 0xd5, 0xf4, 0xc1, 0x6a, - 0xe1, 0x86, 0x8c, 0x2c, 0x0d, 0xd6, 0xac, 0xed, 0x21, 0xe6, 0xa9, 0x5e, 0xaa, 0x0e, 0xc4, 0x87, - 0x88, 0x79, 0x70, 0x0d, 0x4c, 0x51, 0xec, 0x10, 0xea, 0xda, 0x4f, 0x19, 0x09, 0x84, 0x47, 0x15, - 0x0b, 0x48, 0xd1, 0x07, 0x8c, 0x04, 0xcd, 0xdf, 0x8c, 0x38, 0xcc, 0xd4, 0x8a, 0xfc, 0x67, 0x2d, - 0x9b, 0xcc, 0xc9, 0x9d, 0x82, 0x9c, 0x8c, 0xa7, 0x72, 0xf2, 0x30, 0x7a, 0x31, 0x51, 0xfb, 0x56, - 0x73, 0x4c, 0x88, 0x33, 0x35, 0x2d, 0xd7, 0x14, 0xa9, 0x58, 0xee, 0x66, 0x62, 0xf9, 0x75, 0xd0, - 0x47, 0xd9, 0x75, 0x3c, 0x92, 0xc9, 0x18, 0xcd, 0xf4, 0x6f, 0x83, 0xd9, 0x00, 0x55, 0x8e, 0x68, - 0x27, 0x7a, 0x91, 0x1c, 0x0a, 0x65, 0x5a, 0x4a, 0x6f, 0x1d, 0xc8, 0xd7, 0x60, 0x21, 0x67, 0xe5, - 0xff, 0x2f, 0x45, 0x69, 0xf6, 0x00, 0xcc, 0xae, 0xf3, 0xe8, 0xaa, 0xc5, 0x09, 0xcc, 0xf3, 0xc1, - 0xd4, 0x27, 0x4e, 0x6e, 0xef, 0x4b, 0xf3, 0xcb, 0xb8, 0x05, 0x53, 0x6b, 0xfe, 0xe5, 0x51, 0xfe, - 0x68, 0x80, 0x95, 0xa2, 0xad, 0xff, 0x9f, 0xe5, 0xb9, 0x38, 0x82, 0xf1, 0xe2, 0x08, 0xf6, 0xb7, - 0x9e, 0x5d, 0x37, 0x8c, 0xe7, 0xd7, 0x0d, 0xe3, 0xcf, 0xeb, 0x86, 0xf1, 0xdd, 0x4d, 0x63, 0xec, - 0xf9, 0x4d, 0x63, 0xec, 0xf7, 0x9b, 0xc6, 0xd8, 0x67, 0xf5, 0xcb, 0xc1, 0x17, 0x72, 0x34, 0x8d, - 0xd9, 0xd9, 0x3d, 0xf1, 0x89, 0xfc, 0xc6, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x77, 0x2c, 0x92, - 0x60, 0xd8, 0x0f, 0x00, 0x00, + // 1301 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4d, 0x6f, 0x1c, 0x45, + 0x13, 0xf6, 0xc4, 0x4e, 0xb2, 0x6e, 0xdb, 0xeb, 0x75, 0x3b, 0x8e, 0xc7, 0x8e, 0xb3, 0xc9, 0xbb, + 0xaf, 0xa2, 0x38, 0x41, 0xd8, 0x4a, 0x90, 0x10, 0x02, 0x2e, 0x76, 0xc0, 0x89, 0x91, 0x00, 0x6b, + 0x6c, 0x21, 0x3e, 0x14, 0x46, 0x9d, 0x99, 0xde, 0x9d, 0x4e, 0x76, 0xa7, 0x87, 0xee, 0x5e, 0x2b, + 0xe6, 0xc0, 0x15, 0x8e, 0x48, 0xfc, 0x04, 0x38, 0x70, 0xe4, 0xca, 0x95, 0x03, 0x0a, 0xb7, 0x1c, + 0x39, 0x21, 0x94, 0x1c, 0xf8, 0x1b, 0x68, 0xaa, 0xbb, 0x67, 0x67, 0x67, 0x76, 0x06, 0x83, 0x08, + 0x97, 0xc4, 0x53, 0x55, 0x4f, 0x3d, 0x55, 0xd5, 0xd5, 0xd5, 0xb5, 0xe8, 0x72, 0x7f, 0x38, 0xa0, + 0x82, 0x6c, 0x93, 0x61, 0xc8, 0xd4, 0xf6, 0xf1, 0xad, 0xed, 0x1e, 0x8d, 0xa9, 0x64, 0x72, 0x2b, + 0x11, 0x5c, 0x71, 0xbc, 0xa8, 0xd5, 0x5b, 0xa0, 0xde, 0x3a, 0xbe, 0xb5, 0xbe, 0x44, 0x06, 0x2c, + 0xe6, 0xdb, 0xf0, 0xaf, 0xb6, 0x59, 0xbf, 0xd0, 0xe3, 0x3d, 0x0e, 0x7f, 0x6e, 0xa7, 0x7f, 0x19, + 0xe9, 0x46, 0xd1, 0x71, 0x42, 0x04, 0x19, 0x18, 0xbf, 0xeb, 0xed, 0xa2, 0x96, 0x1e, 0xb3, 0x90, + 0xc6, 0x01, 0x35, 0xfa, 0x4b, 0x45, 0xbd, 0x0e, 0x00, 0x94, 0x9d, 0x9f, 0x5a, 0x68, 0xfe, 0xae, + 0x0e, 0xf3, 0x50, 0x11, 0x45, 0xf1, 0xeb, 0xe8, 0x9c, 0xf6, 0xee, 0x3a, 0x57, 0x9d, 0xcd, 0xb9, + 0xdb, 0xab, 0x5b, 0x85, 0xb0, 0xb7, 0x0e, 0x40, 0xbd, 0x3b, 0xfb, 0xe4, 0xb7, 0x2b, 0x53, 0xdf, + 0xff, 0xf1, 0xc3, 0x4d, 0xc7, 0x33, 0x08, 0xfc, 0x06, 0x6a, 0x58, 0x6e, 0xf7, 0xcc, 0xd5, 0xe9, + 0xcd, 0xb9, 0xdb, 0x6b, 0x25, 0xf4, 0xdb, 0xc6, 0x60, 0x77, 0x26, 0xc5, 0x7b, 0x19, 0x00, 0x6f, + 0xa2, 0x56, 0x4c, 0x1f, 0x2b, 0xdf, 0x0a, 0x7c, 0x16, 0xba, 0xd3, 0x57, 0x9d, 0xcd, 0x19, 0xaf, + 0x99, 0xca, 0x2d, 0x6e, 0x3f, 0xc4, 0xf7, 0xd1, 0x4a, 0xcc, 0x43, 0xea, 0xcb, 0xa1, 0x4c, 0x58, + 0xc0, 0x78, 0xec, 0xcb, 0x34, 0x74, 0xe9, 0xce, 0x00, 0xe7, 0xff, 0x4b, 0x9c, 0xef, 0xf1, 0x90, + 0x1e, 0x5a, 0x63, 0x48, 0xd3, 0xb0, 0x2f, 0xc7, 0x25, 0x8d, 0xc4, 0x1c, 0x5d, 0x12, 0x34, 0xe1, + 0x42, 0x51, 0xe1, 0x0b, 0xda, 0x67, 0xe4, 0x01, 0xeb, 0x33, 0x75, 0x62, 0x49, 0xce, 0x02, 0xc9, + 0x8d, 0x12, 0x89, 0x67, 0x30, 0xde, 0x08, 0x92, 0xa7, 0x5a, 0x13, 0x15, 0x7a, 0x20, 0x54, 0x2c, + 0x78, 0x44, 0x95, 0x1f, 0x52, 0x45, 0x05, 0xe3, 0x82, 0xa8, 0x5c, 0x56, 0xe7, 0x2a, 0x08, 0x8f, + 0x00, 0xf3, 0x56, 0x1e, 0x32, 0x46, 0xa8, 0x2a, 0xf4, 0x12, 0xbf, 0x86, 0x1a, 0x11, 0x25, 0x7d, + 0x9f, 0x27, 0xd2, 0x3d, 0x0f, 0xde, 0xcb, 0xa7, 0x7c, 0x8f, 0x92, 0xfe, 0xfb, 0x89, 0xf1, 0x75, + 0x3e, 0x82, 0x2f, 0x89, 0xaf, 0xa1, 0x45, 0x38, 0x24, 0x03, 0x4f, 0xcf, 0xa8, 0x01, 0x67, 0x34, + 0x9f, 0x8a, 0x35, 0x66, 0x3f, 0xc4, 0x09, 0xda, 0x30, 0x19, 0x11, 0xa1, 0x58, 0x97, 0x04, 0xca, + 0x0f, 0xf8, 0x30, 0x56, 0x36, 0xa5, 0xd9, 0xda, 0x94, 0x76, 0x0c, 0xe6, 0x4e, 0x0a, 0x99, 0x90, + 0x52, 0x59, 0x0f, 0x35, 0x94, 0x8a, 0x0b, 0xd2, 0xa3, 0xbe, 0x12, 0x43, 0x15, 0xf9, 0x09, 0x97, + 0x2a, 0xe1, 0x31, 0x1d, 0xd0, 0x58, 0x49, 0x17, 0x55, 0x10, 0x1e, 0x6a, 0xcc, 0x51, 0x0a, 0x39, + 0xc8, 0x21, 0x2c, 0xa1, 0xac, 0xd0, 0x4b, 0xfc, 0x21, 0x6a, 0x09, 0x1a, 0x44, 0x34, 0x78, 0x94, + 0x75, 0xac, 0x3b, 0x07, 0x2c, 0xd7, 0x4b, 0x2c, 0xe6, 0x82, 0x79, 0xda, 0xbe, 0x70, 0x03, 0x16, + 0xc5, 0xb8, 0x18, 0x27, 0xc8, 0xd2, 0xfa, 0x89, 0xe0, 0xbc, 0xeb, 0x2b, 0x41, 0x62, 0x19, 0x08, + 0x96, 0x28, 0xe9, 0xce, 0x03, 0xc5, 0x56, 0x15, 0x85, 0xc9, 0xe7, 0x20, 0xc5, 0x1d, 0x65, 0x30, + 0xc3, 0xb4, 0x2a, 0x27, 0x6a, 0x25, 0xfe, 0x04, 0x61, 0xb8, 0x50, 0x5d, 0xc2, 0xfa, 0x43, 0x91, + 0xfe, 0x1f, 0x28, 0xe9, 0x2e, 0xd4, 0x67, 0x93, 0x5e, 0xaa, 0x3d, 0x0d, 0xd8, 0x23, 0x81, 0xe5, + 0x68, 0xc5, 0xe3, 0x62, 0x89, 0x43, 0xb4, 0x92, 0xbb, 0x4e, 0x72, 0xd8, 0x57, 0xc6, 0x7f, 0x13, + 0xfc, 0xdf, 0xac, 0xae, 0x96, 0xbd, 0x2f, 0x29, 0x26, 0x47, 0xb1, 0x2c, 0x4a, 0x1a, 0x89, 0x3f, + 0x45, 0xcb, 0x69, 0xf4, 0x34, 0xd4, 0xad, 0x39, 0x20, 0xe2, 0x11, 0x15, 0xd2, 0x5d, 0x04, 0x8e, + 0xcd, 0x2a, 0x8e, 0x3d, 0x80, 0xa4, 0x6d, 0xfb, 0x2e, 0x00, 0x0c, 0xc3, 0x52, 0xb7, 0x20, 0x97, + 0xf8, 0x2e, 0x5a, 0xa0, 0x09, 0x0f, 0x22, 0x5f, 0x93, 0x4b, 0xb7, 0x05, 0x9e, 0x37, 0xca, 0xf3, + 0x2d, 0xb5, 0xd2, 0xb1, 0x1b, 0x6f, 0xf3, 0x74, 0x24, 0x92, 0xf8, 0x00, 0x35, 0xb5, 0x0b, 0x9f, + 0xc5, 0x21, 0x0b, 0xa8, 0x74, 0x97, 0x2a, 0xa6, 0xd6, 0x58, 0x1d, 0xf6, 0xe3, 0x90, 0x3e, 0x36, + 0x0e, 0x17, 0x84, 0x15, 0xa5, 0x78, 0x7c, 0x1f, 0x2d, 0x47, 0x5c, 0x2a, 0xbf, 0xe0, 0x16, 0xd7, + 0x1f, 0xdf, 0x3d, 0x2e, 0x55, 0xd9, 0xf5, 0x52, 0x94, 0x17, 0x83, 0x7b, 0x3e, 0x6a, 0xc7, 0x20, + 0x22, 0xfd, 0x3e, 0x8d, 0x7b, 0x34, 0x23, 0x59, 0x06, 0x92, 0x97, 0xff, 0xa2, 0x1d, 0xef, 0x58, + 0x5c, 0x9e, 0xca, 0x76, 0x63, 0x5e, 0x09, 0x84, 0x5f, 0x39, 0xe8, 0x7f, 0x24, 0x80, 0x09, 0xd8, + 0x65, 0x31, 0xe9, 0xb3, 0xcf, 0xf5, 0x38, 0x1c, 0xbf, 0xd1, 0x17, 0x80, 0xf9, 0xd5, 0x2a, 0xe6, + 0x1d, 0x70, 0xb0, 0x97, 0xc3, 0x4f, 0xb8, 0xde, 0x57, 0x48, 0xad, 0x15, 0xf4, 0x6e, 0xf6, 0x1c, + 0xe9, 0xe3, 0x87, 0x31, 0x26, 0xdd, 0x95, 0xfa, 0xde, 0xb5, 0x77, 0x19, 0x9a, 0x01, 0xc6, 0x94, + 0xed, 0x5d, 0x5a, 0xd2, 0x00, 0x8b, 0x9d, 0xa7, 0xc7, 0x54, 0xb0, 0x2e, 0x0b, 0x20, 0x14, 0xe9, + 0x5e, 0xac, 0x67, 0xd1, 0xe3, 0xf6, 0x83, 0x1c, 0xc4, 0xb2, 0x44, 0x25, 0x8d, 0xc4, 0x1f, 0xa1, + 0x65, 0x12, 0xe8, 0x29, 0x0c, 0x03, 0x85, 0x69, 0x8e, 0x55, 0xe0, 0xe8, 0x94, 0x38, 0x76, 0xb4, + 0xed, 0x51, 0x66, 0x6a, 0x7c, 0x63, 0x52, 0x54, 0xc8, 0xce, 0xb7, 0x0e, 0x72, 0xab, 0x26, 0x29, + 0x7e, 0x09, 0x2d, 0xc9, 0x61, 0x42, 0x05, 0x4c, 0x18, 0x03, 0x86, 0xdd, 0x62, 0xd6, 0x6b, 0x65, + 0x0a, 0xc3, 0x86, 0x6f, 0xa1, 0x15, 0x7b, 0xcc, 0xa1, 0x4f, 0x94, 0x29, 0x3a, 0x0b, 0xdd, 0x33, + 0xf0, 0xca, 0xe0, 0x4c, 0xb9, 0xa3, 0xa0, 0x82, 0xfb, 0x21, 0xbe, 0x8e, 0x16, 0xa5, 0x12, 0x3c, + 0xee, 0x65, 0x0d, 0x02, 0x6b, 0x43, 0xc3, 0x6b, 0x6a, 0xb1, 0x0d, 0xa6, 0xf3, 0xa5, 0x83, 0xae, + 0x9d, 0xaa, 0x3b, 0x5e, 0x74, 0xc8, 0x9d, 0x1f, 0x1d, 0xb4, 0x56, 0xd9, 0x29, 0x78, 0x0d, 0x35, + 0x32, 0x1f, 0x0e, 0xf8, 0x38, 0x4f, 0x73, 0xb9, 0x0e, 0x1f, 0x3c, 0xa4, 0x81, 0xf2, 0x49, 0x18, + 0x0a, 0x2a, 0x25, 0xb0, 0xcc, 0x7a, 0x4d, 0x23, 0xde, 0xd1, 0x52, 0xbc, 0x8b, 0x16, 0xb2, 0xc6, + 0x55, 0x27, 0x89, 0x2e, 0x49, 0xf3, 0xf6, 0xe5, 0xca, 0x75, 0xec, 0xe8, 0x24, 0xa1, 0xde, 0x3c, + 0xcd, 0x7d, 0xe1, 0x0b, 0xe8, 0xac, 0xce, 0x7c, 0x06, 0x82, 0xd0, 0x1f, 0x9d, 0x6f, 0x46, 0xb1, + 0x97, 0xfb, 0x0f, 0x6f, 0x20, 0x94, 0x5b, 0x0d, 0x74, 0xf4, 0x8d, 0xc8, 0xae, 0x05, 0x6f, 0xa2, + 0x75, 0xdd, 0xe0, 0x54, 0xf8, 0xe5, 0x02, 0xeb, 0x4c, 0x5c, 0x6b, 0x71, 0x58, 0x2c, 0xf4, 0x3a, + 0x6a, 0x18, 0x5d, 0x68, 0x4e, 0x38, 0xfb, 0xee, 0x9c, 0xa0, 0x8b, 0x93, 0x1f, 0xd9, 0xba, 0x6a, + 0x5e, 0x42, 0xb3, 0x66, 0x4b, 0x31, 0xa7, 0x35, 0xeb, 0x35, 0xb4, 0x40, 0x97, 0x3a, 0x10, 0x94, + 0x28, 0x2e, 0xb2, 0x00, 0xa7, 0x75, 0xa9, 0x8d, 0xd8, 0x84, 0xd5, 0x61, 0xe8, 0x72, 0xed, 0xe3, + 0x9b, 0x7a, 0x1a, 0xbd, 0xe0, 0x7e, 0x44, 0x64, 0x64, 0x7a, 0xa9, 0x39, 0x12, 0xdf, 0x23, 0x32, + 0xc2, 0x57, 0xd0, 0x9c, 0xa0, 0x01, 0x17, 0xa1, 0xff, 0x50, 0xf2, 0x18, 0x22, 0x9a, 0xf7, 0x90, + 0x16, 0xbd, 0x23, 0x79, 0xdc, 0xf9, 0xc5, 0xc9, 0xd2, 0x2c, 0xbc, 0xbe, 0x7f, 0xaf, 0x65, 0xf3, + 0x35, 0x39, 0x53, 0x53, 0x93, 0xe9, 0x42, 0x4d, 0x6e, 0xa4, 0x3b, 0x8f, 0x79, 0xca, 0x2d, 0xc7, + 0x0c, 0xd8, 0x2c, 0x5a, 0xb9, 0xa5, 0x28, 0xe4, 0x72, 0xb6, 0x94, 0xcb, 0xcf, 0xa3, 0x3e, 0x2a, + 0xbf, 0xf4, 0x13, 0x99, 0x9c, 0xc9, 0x4c, 0xff, 0x34, 0x99, 0x6b, 0xa8, 0xa9, 0x88, 0xe8, 0xa5, + 0x3b, 0xea, 0x58, 0x2a, 0x0b, 0x5a, 0x7a, 0xea, 0x44, 0xbe, 0x40, 0xab, 0x15, 0xdb, 0xc4, 0x7f, + 0x72, 0x28, 0x9d, 0x01, 0xc2, 0xe5, 0x4d, 0x21, 0xbd, 0x6a, 0x59, 0x01, 0xab, 0x62, 0x70, 0xad, + 0xc5, 0xe1, 0xe9, 0x63, 0xe9, 0x7c, 0x96, 0xb5, 0x60, 0x61, 0x83, 0x78, 0x71, 0x94, 0xdf, 0x39, + 0x68, 0xa3, 0x6e, 0xa1, 0xf8, 0xd7, 0xea, 0x5c, 0x9f, 0xc1, 0x74, 0x7d, 0x06, 0xbb, 0x37, 0x9f, + 0x3c, 0x6b, 0x3b, 0x4f, 0x9f, 0xb5, 0x9d, 0xdf, 0x9f, 0xb5, 0x9d, 0xaf, 0x9f, 0xb7, 0xa7, 0x9e, + 0x3e, 0x6f, 0x4f, 0xfd, 0xfa, 0xbc, 0x3d, 0xf5, 0x71, 0xeb, 0xf1, 0xe8, 0xc7, 0x77, 0x3a, 0x8d, + 0xe5, 0x83, 0x73, 0xf0, 0xeb, 0xfb, 0x95, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xc1, 0x5c, 0x66, + 0xc5, 0x33, 0x10, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -1117,6 +1127,22 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.AccountTransitions) > 0 { + for iNdEx := len(m.AccountTransitions) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AccountTransitions[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xba + } + } if len(m.HealOpVerifications) > 0 { for iNdEx := len(m.HealOpVerifications) - 1; iNdEx >= 0; iNdEx-- { { @@ -2074,6 +2100,12 @@ func (m *GenesisState) Size() (n int) { n += 2 + l + sovGenesis(uint64(l)) } } + if len(m.AccountTransitions) > 0 { + for _, e := range m.AccountTransitions { + l = e.Size() + n += 2 + l + sovGenesis(uint64(l)) + } + } return n } @@ -3070,6 +3102,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AccountTransitions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AccountTransitions = append(m.AccountTransitions, AccountTransition{}) + if err := m.AccountTransitions[len(m.AccountTransitions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/audit/v1/types/keys.go b/x/audit/v1/types/keys.go index 12f7f942..11e62f85 100644 --- a/x/audit/v1/types/keys.go +++ b/x/audit/v1/types/keys.go @@ -11,6 +11,12 @@ const ( // MaxStorageProofResultsPerReport caps the number of storage proof results // a reporter may submit in a single epoch report. Per PR #118 / Zee F2. MaxStorageProofResultsPerReport = 16 + + // Identity-continuity bounds are consensus constants. Exceeding either cap + // fails closed instead of turning migration or lineage reads into an + // unbounded store scan. + MaxAccountTransitions = 256 + MaxIdentityTransitionHealOps = 10_000 ) var ( @@ -46,6 +52,10 @@ var ( epochParamsSnapshotPrefix = []byte("eps/") reportPrefix = []byte("r/") + // Durable deterministic account-lineage indexes. + accountTransitionForwardPrefix = []byte("id/f/") + accountTransitionReversePrefix = []byte("id/r/") + reportIndexPrefix = []byte("ri/") // storageChallengeReportIndexPrefix indexes reports that include a storage-challenge observation for a given supernode. @@ -131,6 +141,20 @@ var ( transcriptByTargetBucketEpochPrefix = []byte("st/spt-tbe/") ) +func AccountTransitionForwardKey(source string) []byte { + key := append([]byte{}, accountTransitionForwardPrefix...) + return append(key, source...) +} + +func AccountTransitionReverseKey(destination string) []byte { + key := append([]byte{}, accountTransitionReversePrefix...) + return append(key, destination...) +} + +func AccountTransitionForwardPrefix() []byte { return accountTransitionForwardPrefix } + +func AccountTransitionReversePrefix() []byte { return accountTransitionReversePrefix } + // EpochAnchorKey returns the store key for the EpochAnchor identified by epochID. func EpochAnchorKey(epochID uint64) []byte { key := make([]byte, 0, len(epochAnchorPrefix)+8) // "ea/" + u64be(epoch_id) From 0d3685bc48ee9efde706faba4e8afa2e487837f6 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 02:00:50 +0000 Subject: [PATCH 03/18] feat(evmigration): apply cross-module continuity plans --- app/upgrades/v1_20_0/upgrade.go | 5 +- app/upgrades/v1_20_0/upgrade_test.go | 2 +- docs/static/openapi.yml | 2 +- proto/lumera/evmigration/params.proto | 8 +- x/audit/v1/keeper/identity_continuity.go | 16 ++ x/audit/v1/keeper/identity_continuity_test.go | 19 ++ x/evmigration/keeper/keeper.go | 3 + x/evmigration/keeper/keeper_test.go | 1 + x/evmigration/keeper/migrate_supernode.go | 28 ++- x/evmigration/keeper/migrate_test.go | 91 ++------- x/evmigration/keeper/migrate_validator.go | 48 +++-- .../keeper/msg_server_claim_legacy.go | 32 +++- .../keeper/msg_server_claim_legacy_test.go | 179 ++++++++++++++++-- .../keeper/msg_server_migrate_validator.go | 23 ++- .../msg_server_migrate_validator_test.go | 3 + .../keeper/msg_update_params_test.go | 22 +++ x/evmigration/mocks/expected_keepers_mock.go | 83 ++++++++ x/evmigration/module/depinject.go | 3 + x/evmigration/types/errors.go | 1 + x/evmigration/types/expected_keepers.go | 9 + x/evmigration/types/genesis_test.go | 17 ++ x/evmigration/types/params.go | 37 +++- x/evmigration/types/params.pb.go | 116 +++++++++--- x/evmigration/types/params_test.go | 55 ++++++ 24 files changed, 669 insertions(+), 134 deletions(-) create mode 100644 x/evmigration/types/params_test.go diff --git a/app/upgrades/v1_20_0/upgrade.go b/app/upgrades/v1_20_0/upgrade.go index 552caa69..cfa2177a 100644 --- a/app/upgrades/v1_20_0/upgrade.go +++ b/app/upgrades/v1_20_0/upgrade.go @@ -160,8 +160,9 @@ func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHand // 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; + // default params (enable_migration=false, migration_end_time=0); here we + // only set the deadline. Governance must explicitly choose canary or open + // mode after the continuity release gates pass. Devnet gets a short // testnet and mainnet both get a 3-calendar-month window. // // The network is identified from the SDK context (ctx.ChainID()), which diff --git a/app/upgrades/v1_20_0/upgrade_test.go b/app/upgrades/v1_20_0/upgrade_test.go index a42c1ab8..e3331baa 100644 --- a/app/upgrades/v1_20_0/upgrade_test.go +++ b/app/upgrades/v1_20_0/upgrade_test.go @@ -62,7 +62,7 @@ func TestV1200SetsDevnetMigrationEndTime(t *testing.T) { require.NoError(t, err) require.Equal(t, want, after.MigrationEndTime, "devnet upgrade should set migration_end_time to upgrade block time + 2 days") - require.True(t, after.EnableMigration, "enable_migration should remain true (immediate-open)") + require.False(t, after.EnableMigration, "fresh upgrade must remain disabled until reviewed governance enablement") require.Equal(t, uint64(2500), after.MaxValidatorDelegations, "max_validator_delegations default should be 2500") } diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index d858dfb0..6488f5c1 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -1 +1 @@ -{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"current_submitter":{"description":"current_submitter is the live account that authenticated submission. It is\nintentionally distinct from supernode_account, the epoch-logical identity.\nEmpty decodes preserve reports written before identity continuity shipped.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: true.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file +{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"current_submitter":{"description":"current_submitter is the live account that authenticated submission. It is\nintentionally distinct from supernode_account, the epoch-logical identity.\nEmpty decodes preserve reports written before identity continuity shipped.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"canary_legacy_addresses":{"description":"canary_legacy_addresses optionally restricts migration to the exact,\ncanonical legacy source addresses listed here. An empty list leaves\nmigration open when enable_migration is true. Entries must be unique and\nsorted lexicographically; at most 64 entries are permitted.","items":{"type":"string"},"type":"array"},"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: true.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file diff --git a/proto/lumera/evmigration/params.proto b/proto/lumera/evmigration/params.proto index b50e4c61..459b6aa0 100644 --- a/proto/lumera/evmigration/params.proto +++ b/proto/lumera/evmigration/params.proto @@ -17,7 +17,7 @@ message Params { // When false, all MsgClaimLegacyAccount and MsgMigrateValidator messages // are rejected regardless of other parameter values. // Governance should set this to false once the migration window closes. - // Default: true. + // Default: false; governance must enable canary or open mode explicitly. bool enable_migration = 1; // migration_end_time is an optional hard deadline expressed as a unix @@ -46,4 +46,10 @@ message Params { // account's MultisigProof. Bounds per-tx verification cost. // Default: 20. uint32 max_multisig_sub_keys = 5; + + // canary_legacy_addresses optionally restricts migration to the exact, + // canonical legacy source addresses listed here. An empty list leaves + // migration open when enable_migration is true. Entries must be unique and + // sorted lexicographically; at most 64 entries are permitted. + repeated string canary_legacy_addresses = 6; } diff --git a/x/audit/v1/keeper/identity_continuity.go b/x/audit/v1/keeper/identity_continuity.go index cbee48c6..ea3dac5a 100644 --- a/x/audit/v1/keeper/identity_continuity.go +++ b/x/audit/v1/keeper/identity_continuity.go @@ -28,6 +28,22 @@ type AccountTransitionPlan struct { transitionCount int } +// BuildCurrentAccountTransitionPlan derives the transition boundary from +// Audit's own epoch configuration. Cross-module migration callers must not +// duplicate or expose Audit's height-to-epoch rules. +func (k Keeper) BuildCurrentAccountTransitionPlan(ctx sdk.Context, source, destination string) (AccountTransitionPlan, error) { + params := k.GetParams(ctx).WithDefaults() + currentEpoch, err := deriveEpochAtHeight(ctx.BlockHeight(), params) + if err != nil { + return AccountTransitionPlan{}, err + } + return k.BuildAccountTransitionPlan(ctx, types.AccountTransition{ + SourceAccount: source, + DestinationAccount: destination, + EffectiveEpoch: currentEpoch.EpochID + 1, + }) +} + // RecordAccountTransition builds and applies one durable lineage edge. It is a // convenience for callers that do not need to aggregate this plan with plans // from other modules. diff --git a/x/audit/v1/keeper/identity_continuity_test.go b/x/audit/v1/keeper/identity_continuity_test.go index 9bf3c735..a14ad3f0 100644 --- a/x/audit/v1/keeper/identity_continuity_test.go +++ b/x/audit/v1/keeper/identity_continuity_test.go @@ -36,6 +36,25 @@ func testAddress(t *testing.T, f *fixture, bz []byte) string { return address } +func TestBuildCurrentAccountTransitionPlanUsesNextEpochBoundary(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{111, 112, 113, 114}) + current := testAddress(t, f, []byte{121, 122, 123, 124}) + params := f.keeper.GetParams(f.ctx).WithDefaults() + const currentEpoch uint64 = 7 + ctx := f.ctx.WithBlockHeight(int64(params.EpochZeroHeight) + int64(currentEpoch)*int64(params.EpochLengthBlocks)) + + plan, err := f.keeper.BuildCurrentAccountTransitionPlan(ctx, old, current) + require.NoError(t, err) + require.NoError(t, f.keeper.ApplyAccountTransitionPlan(ctx, plan)) + got, err := f.keeper.AccountForEpoch(ctx, old, currentEpoch) + require.NoError(t, err) + require.Equal(t, old, got) + got, err = f.keeper.AccountForEpoch(ctx, old, currentEpoch+1) + require.NoError(t, err) + require.Equal(t, current, got) +} + func TestAccountTransitionLineageBoundariesAndTwoHop(t *testing.T) { f := initFixture(t) old := testAddress(t, f, []byte{1, 2, 3, 4}) diff --git a/x/evmigration/keeper/keeper.go b/x/evmigration/keeper/keeper.go index a787f503..e8f81f47 100644 --- a/x/evmigration/keeper/keeper.go +++ b/x/evmigration/keeper/keeper.go @@ -68,6 +68,7 @@ type Keeper struct { authzKeeper types.AuthzKeeper feegrantKeeper types.FeegrantKeeper supernodeKeeper types.SupernodeKeeper + auditKeeper types.AuditKeeper actionKeeper types.ActionKeeper } @@ -83,6 +84,7 @@ func NewKeeper( authzKeeper types.AuthzKeeper, feegrantKeeper types.FeegrantKeeper, supernodeKeeper types.SupernodeKeeper, + auditKeeper types.AuditKeeper, actionKeeper types.ActionKeeper, ) Keeper { if _, err := addressCodec.BytesToString(authority); err != nil { @@ -111,6 +113,7 @@ func NewKeeper( authzKeeper: authzKeeper, feegrantKeeper: feegrantKeeper, supernodeKeeper: supernodeKeeper, + auditKeeper: auditKeeper, actionKeeper: actionKeeper, // Allocate once so value-copies of Keeper (e.g. app.EvmigrationKeeper diff --git a/x/evmigration/keeper/keeper_test.go b/x/evmigration/keeper/keeper_test.go index ac20d4fd..c0f6dcea 100644 --- a/x/evmigration/keeper/keeper_test.go +++ b/x/evmigration/keeper/keeper_test.go @@ -48,6 +48,7 @@ func initFixture(t *testing.T) *fixture { nil, // authzKeeper nil, // feegrantKeeper nil, // supernodeKeeper + nil, // auditKeeper nil, // actionKeeper ) diff --git a/x/evmigration/keeper/migrate_supernode.go b/x/evmigration/keeper/migrate_supernode.go index 199da204..d1d1c1ed 100644 --- a/x/evmigration/keeper/migrate_supernode.go +++ b/x/evmigration/keeper/migrate_supernode.go @@ -8,14 +8,36 @@ import ( sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" ) -// MigrateSupernode updates the SupernodeAccount field if legacyAddr is a supernode. -// Also records the migration in PrevSupernodeAccounts history. +// MigrateSupernode preserves the same continuity guarantees as the production +// account-migration handler. All plans are built before the first write. func (k Keeper) MigrateSupernode(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { + cacheCtx, commit := ctx.CacheContext() + if err := k.migrateSupernodeWithContinuity(cacheCtx, legacyAddr, newAddr); err != nil { + return err + } + commit() + return nil +} + +func (k Keeper) migrateSupernodeWithContinuity(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { sn, found, err := k.supernodeKeeper.StrictGetSuperNodeByAccount(ctx, legacyAddr.String()) if err != nil { return fmt.Errorf("resolve source supernode ownership: %w", err) } - return k.migrateValidatedSupernode(ctx, newAddr, sn, found) + if !found { + return nil + } + if err := k.validateDestinationSupernodeOwnership(ctx, newAddr); err != nil { + return err + } + auditPlan, err := k.auditKeeper.BuildCurrentAccountTransitionPlan(ctx, legacyAddr.String(), newAddr.String()) + if err != nil { + return fmt.Errorf("build audit account transition: %w", err) + } + if err := k.auditKeeper.ApplyAccountTransitionPlan(ctx, auditPlan); err != nil { + return fmt.Errorf("apply audit account transition: %w", err) + } + return k.migrateValidatedSupernode(ctx, newAddr, sn, true) } // migrateValidatedSupernode mutates the exact record returned by the strict diff --git a/x/evmigration/keeper/migrate_test.go b/x/evmigration/keeper/migrate_test.go index 63a9ede6..7e123d2b 100644 --- a/x/evmigration/keeper/migrate_test.go +++ b/x/evmigration/keeper/migrate_test.go @@ -2,7 +2,6 @@ package keeper_test import ( "errors" - "fmt" "sort" "strings" "testing" @@ -30,6 +29,7 @@ import ( "go.uber.org/mock/gomock" actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/keeper" evmigrationmocks "github.com/LumeraProtocol/lumera/x/evmigration/mocks" module "github.com/LumeraProtocol/lumera/x/evmigration/module" @@ -51,6 +51,7 @@ type mockFixture struct { authzKeeper *evmigrationmocks.MockAuthzKeeper feegrantKeeper *evmigrationmocks.MockFeegrantKeeper supernodeKeeper *evmigrationmocks.MockSupernodeKeeper + auditKeeper *evmigrationmocks.MockAuditKeeper actionKeeper *evmigrationmocks.MockActionKeeper } @@ -66,7 +67,15 @@ func initMockFixture(t *testing.T) *mockFixture { authzKeeper := evmigrationmocks.NewMockAuthzKeeper(ctrl) feegrantKeeper := evmigrationmocks.NewMockFeegrantKeeper(ctrl) supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) + auditKeeper := evmigrationmocks.NewMockAuditKeeper(ctrl) actionKeeper := evmigrationmocks.NewMockActionKeeper(ctrl) + supernodeKeeper.EXPECT().BuildIdentityMigrationPlan(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ sdk.Context, source, destination sdk.ValAddress) (sntypes.IdentityMigrationPlan, error) { + return sntypes.NewIdentityMigrationPlan(source, destination, nil, nil, nil, nil, nil), nil + }).AnyTimes() + supernodeKeeper.EXPECT().ApplyIdentityMigrationPlan(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + auditKeeper.EXPECT().BuildCurrentAccountTransitionPlan(gomock.Any(), gomock.Any(), gomock.Any()).Return(auditkeeper.AccountTransitionPlan{}, nil).AnyTimes() + auditKeeper.EXPECT().ApplyAccountTransitionPlan(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() encCfg := moduletestutil.MakeTestEncodingConfig(module.AppModule{}) addrCodec := addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) @@ -100,6 +109,7 @@ func initMockFixture(t *testing.T) *mockFixture { authzKeeper, feegrantKeeper, supernodeKeeper, + auditKeeper, actionKeeper, ) @@ -122,6 +132,7 @@ func initMockFixture(t *testing.T) *mockFixture { authzKeeper: authzKeeper, feegrantKeeper: feegrantKeeper, supernodeKeeper: supernodeKeeper, + auditKeeper: auditKeeper, actionKeeper: actionKeeper, } } @@ -1130,6 +1141,7 @@ func TestMigrateSupernode_Found(t *testing.T) { } f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacy.String()).Return(sn, true, nil) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()). DoAndReturn(func(_ any, updated sntypes.SuperNode) error { require.Equal(t, newAddr.String(), updated.SupernodeAccount) @@ -2551,73 +2563,6 @@ func TestMigrateValidatorDelegations_RedelegationReplayIsDeterministic(t *testin require.Equal(t, expectedOrder, replayOrder, "redelegations must replay in deterministic store-key order") } -// --- Validator-supernode metrics tests --- - -// TestMigrateValidatorSupernode_WithMetrics verifies that metrics state is -// re-keyed when the supernode has metrics. -func TestMigrateValidatorSupernode_WithMetrics(t *testing.T) { - f := initMockFixture(t) - oldValAddr := sdk.ValAddress(testAccAddr()) - newValAddr := sdk.ValAddress(testAccAddr()) - newAddr := sdk.AccAddress(newValAddr) - - sn := sntypes.SuperNode{ - ValidatorAddress: oldValAddr.String(), - SupernodeAccount: sdk.AccAddress(oldValAddr).String(), - } - metrics := sntypes.SupernodeMetricsState{ - ValidatorAddress: oldValAddr.String(), - } - - f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), sdk.AccAddress(oldValAddr).String()).Return(sn, true, nil) - f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) - f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(metrics, true) - f.supernodeKeeper.EXPECT().SetMetricsState(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ any, updated sntypes.SupernodeMetricsState) error { - require.Equal(t, newValAddr.String(), updated.ValidatorAddress) - return nil - }) - f.supernodeKeeper.EXPECT().DeleteMetricsState(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ any, updated sntypes.SuperNode) error { - require.Equal(t, newAddr.String(), updated.SupernodeAccount) - return nil - }) - - err := f.keeper.MigrateValidatorSupernode(f.ctx, oldValAddr, newValAddr, sdk.AccAddress(oldValAddr), newAddr) - require.NoError(t, err) -} - -// TestMigrateValidatorSupernode_MetricsWriteFails verifies that a failure -// writing metrics state propagates as an error. -func TestMigrateValidatorSupernode_MetricsWriteFails(t *testing.T) { - f := initMockFixture(t) - oldValAddr := sdk.ValAddress(testAccAddr()) - newValAddr := sdk.ValAddress(testAccAddr()) - newAddr := sdk.AccAddress(newValAddr) - - sn := sntypes.SuperNode{ - ValidatorAddress: oldValAddr.String(), - SupernodeAccount: sdk.AccAddress(oldValAddr).String(), - } - metrics := sntypes.SupernodeMetricsState{ - ValidatorAddress: oldValAddr.String(), - } - - f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), sdk.AccAddress(oldValAddr).String()).Return(sn, true, nil) - f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) - f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(metrics, true) - f.supernodeKeeper.EXPECT().SetMetricsState(gomock.Any(), gomock.Any()).Return( - fmt.Errorf("metrics store write failed"), - ) - - err := f.keeper.MigrateValidatorSupernode(f.ctx, oldValAddr, newValAddr, sdk.AccAddress(oldValAddr), newAddr) - require.Error(t, err) - require.Contains(t, err.Error(), "metrics store write failed") -} - // TestMigrateValidatorSupernode_NotFound verifies no-op when not a supernode. func TestMigrateValidatorSupernode_NotFound(t *testing.T) { f := initMockFixture(t) @@ -2652,8 +2597,8 @@ func TestMigrateValidatorSupernode_EvidenceAddressMigrated(t *testing.T) { f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), sdk.AccAddress(oldValAddr).String()).Return(sn, true, nil) f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(sntypes.SupernodeMetricsState{}, false) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Len(t, updated.Evidence, 2) @@ -2693,8 +2638,8 @@ func TestMigrateValidatorSupernode_AccountHistoryPreserved(t *testing.T) { f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), sdk.AccAddress(oldValAddr).String()).Return(sn, true, nil) f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(sntypes.SupernodeMetricsState{}, false) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Len(t, updated.PrevSupernodeAccounts, 3) @@ -2724,8 +2669,8 @@ func TestMigrateValidatorSupernode_AlternateEncodingSelfOwnedMigratesOnce(t *tes f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return(sn, true, nil) f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr).Times(1) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(sntypes.SupernodeMetricsState{}, false) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Equal(t, newValAddr.String(), updated.ValidatorAddress) @@ -2763,7 +2708,6 @@ func TestMigrateValidatorSupernode_IndependentAccountPreserved(t *testing.T) { f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), independentSNAccount).Return(sn, true, nil) f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(sntypes.SupernodeMetricsState{}, false) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { // Validator address should be re-keyed. @@ -2795,6 +2739,7 @@ func TestMigrateValidatorSupernode_AccountOwnedUnderAnotherValidator(t *testing. f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return(accountOwned, true, nil) f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sntypes.SuperNode{}, false) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Equal(t, accountOwnedVal.String(), updated.ValidatorAddress) @@ -2826,6 +2771,7 @@ func TestMigrateValidatorSupernode_TwoDistinctRecords(t *testing.T) { f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return(accountOwned, true, nil) f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(validatorAssociated, true) f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), independentAccount.String()).Return(validatorAssociated, true, nil) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Equal(t, accountOwnedVal.String(), updated.ValidatorAddress) @@ -2833,7 +2779,6 @@ func TestMigrateValidatorSupernode_TwoDistinctRecords(t *testing.T) { return nil }) f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return(sntypes.SupernodeMetricsState{}, false) f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, updated sntypes.SuperNode) error { require.Equal(t, newValAddr.String(), updated.ValidatorAddress) diff --git a/x/evmigration/keeper/migrate_validator.go b/x/evmigration/keeper/migrate_validator.go index 8b6220a2..0bfebc8b 100644 --- a/x/evmigration/keeper/migrate_validator.go +++ b/x/evmigration/keeper/migrate_validator.go @@ -7,6 +7,7 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" ) @@ -277,11 +278,44 @@ func (k Keeper) MigrateValidatorDistribution(ctx sdk.Context, oldValAddr, newVal // MigrateValidatorSupernode migrates every validated SuperNode dimension affected // by a validator operator migration. func (k Keeper) MigrateValidatorSupernode(ctx sdk.Context, oldValAddr, newValAddr sdk.ValAddress, legacyAddr, newAddr sdk.AccAddress) error { - plan, err := k.validateValidatorSupernodeOwnership(ctx, oldValAddr, legacyAddr) + cacheCtx, commit := ctx.CacheContext() + if err := k.migrateValidatorSupernodeWithContinuity(cacheCtx, oldValAddr, newValAddr, legacyAddr, newAddr); err != nil { + return err + } + commit() + return nil +} + +func (k Keeper) migrateValidatorSupernodeWithContinuity(ctx sdk.Context, oldValAddr, newValAddr sdk.ValAddress, legacyAddr, newAddr sdk.AccAddress) error { + ownershipPlan, err := k.validateValidatorSupernodeOwnership(ctx, oldValAddr, legacyAddr) if err != nil { return err } - return k.migrateValidatedValidatorSupernodes(ctx, oldValAddr, newValAddr, legacyAddr, newAddr, plan) + if ownershipPlan.hasAccountOwned { + if err := k.validateDestinationSupernodeOwnership(ctx, newAddr); err != nil { + return err + } + } + identityPlan, err := k.supernodeKeeper.BuildIdentityMigrationPlan(ctx, oldValAddr, newValAddr) + if err != nil { + return fmt.Errorf("build supernode identity migration: %w", err) + } + var auditPlan auditkeeper.AccountTransitionPlan + if ownershipPlan.hasAccountOwned { + auditPlan, err = k.auditKeeper.BuildCurrentAccountTransitionPlan(ctx, legacyAddr.String(), newAddr.String()) + if err != nil { + return fmt.Errorf("build audit account transition: %w", err) + } + } + if err := k.supernodeKeeper.ApplyIdentityMigrationPlan(ctx, identityPlan); err != nil { + return fmt.Errorf("apply supernode identity migration: %w", err) + } + if ownershipPlan.hasAccountOwned { + if err := k.auditKeeper.ApplyAccountTransitionPlan(ctx, auditPlan); err != nil { + return fmt.Errorf("apply audit account transition: %w", err) + } + } + return k.migrateValidatedValidatorSupernodes(ctx, oldValAddr, newValAddr, legacyAddr, newAddr, ownershipPlan) } type validatorSupernodeMigrationPlan struct { @@ -435,16 +469,6 @@ func (k Keeper) migrateValidatedValidatorSupernode( } } - // Migrate metrics state: write under new key, delete old key. - metrics, found := k.supernodeKeeper.GetMetricsState(ctx, oldValAddr) - if found { - metrics.ValidatorAddress = newValAddr.String() - if err := k.supernodeKeeper.SetMetricsState(ctx, metrics); err != nil { - return err - } - k.supernodeKeeper.DeleteMetricsState(ctx, oldValAddr) - } - return k.supernodeKeeper.SetSuperNode(ctx, sn) } diff --git a/x/evmigration/keeper/msg_server_claim_legacy.go b/x/evmigration/keeper/msg_server_claim_legacy.go index dbc60506..f1a460a0 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy.go +++ b/x/evmigration/keeper/msg_server_claim_legacy.go @@ -11,6 +11,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" lcfg "github.com/LumeraProtocol/lumera/config" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/types" "github.com/LumeraProtocol/lumera/x/evmigration/types/sigverify" sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" @@ -95,9 +96,16 @@ func (ms msgServer) ClaimLegacyAccount(goCtx context.Context, msg *types.MsgClai return nil, err } } + var auditPlan auditkeeper.AccountTransitionPlan + if hasSupernode { + auditPlan, err = ms.auditKeeper.BuildCurrentAccountTransitionPlan(ctx, legacyAddr.String(), newAddr.String()) + if err != nil { + return nil, fmt.Errorf("build audit account transition: %w", err) + } + } // --- Execute migration steps --- - if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode); err != nil { + if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode, auditPlan); err != nil { return nil, err } @@ -120,6 +128,19 @@ func (ms msgServer) preChecks(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddres if !params.EnableMigration { return types.ErrMigrationDisabled } + if len(params.CanaryLegacyAddresses) > 0 { + canonicalLegacy := legacyAddr.String() + allowed := false + for _, address := range params.CanaryLegacyAddresses { + if address == canonicalLegacy { + allowed = true + break + } + } + if !allowed { + return types.ErrMigrationNotCanary + } + } // 2. Migration window if params.MigrationEndTime > 0 { @@ -191,6 +212,7 @@ func (ms msgServer) migrateAccount( destProof *types.MigrationProof, supernode sntypes.SuperNode, hasSupernode bool, + auditPlan auditkeeper.AccountTransitionPlan, ) error { // Snapshot the original withdraw address before MigrateDistribution // may temporarily redirect it to self (see redirectWithdrawAddrIfMigrated). @@ -234,7 +256,13 @@ func (ms msgServer) migrateAccount( return fmt.Errorf("migrate feegrant: %w", err) } - // Step 6: Update the prevalidated supernode account field. + // Step 6: Apply Audit continuity before updating the prevalidated SuperNode + // account field. A returned error lets BaseApp roll back earlier module writes. + if hasSupernode { + if err := ms.auditKeeper.ApplyAccountTransitionPlan(ctx, auditPlan); err != nil { + return fmt.Errorf("apply audit account transition: %w", err) + } + } if err := ms.migrateValidatedSupernode(ctx, newAddr, supernode, hasSupernode); err != nil { return fmt.Errorf("migrate supernode: %w", err) } diff --git a/x/evmigration/keeper/msg_server_claim_legacy_test.go b/x/evmigration/keeper/msg_server_claim_legacy_test.go index 778ea37b..f2e59e5a 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy_test.go +++ b/x/evmigration/keeper/msg_server_claim_legacy_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/keeper" evmigrationmocks "github.com/LumeraProtocol/lumera/x/evmigration/mocks" module "github.com/LumeraProtocol/lumera/x/evmigration/module" @@ -32,7 +33,14 @@ import ( // the full ClaimLegacyAccount and MigrateValidator message handlers. type msgServerFixture struct { *mockFixture - msgServer types.MsgServer + msgServer types.MsgServer + auditBuildCalls *int + auditApplyCalls *int + auditBuildErr *error + identityBuildCalls *int + identityApplyCalls *int + identityBuildErr *error + continuityEvents *[]string } // newSingleKeyProofNew builds a valid new-side MigrationProof (eth_secp256k1, @@ -105,7 +113,37 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { authzKeeper := evmigrationmocks.NewMockAuthzKeeper(ctrl) feegrantKeeper := evmigrationmocks.NewMockFeegrantKeeper(ctrl) supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) + auditKeeper := evmigrationmocks.NewMockAuditKeeper(ctrl) actionKeeper := evmigrationmocks.NewMockActionKeeper(ctrl) + auditBuildCalls := 0 + auditApplyCalls := 0 + var auditBuildErr error + identityBuildCalls := 0 + identityApplyCalls := 0 + var identityBuildErr error + continuityEvents := make([]string, 0, 3) + supernodeKeeper.EXPECT().BuildIdentityMigrationPlan(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(_ sdk.Context, source, destination sdk.ValAddress) (sntypes.IdentityMigrationPlan, error) { + identityBuildCalls++ + return sntypes.NewIdentityMigrationPlan(source, destination, nil, nil, nil, nil, nil), identityBuildErr + }).AnyTimes() + supernodeKeeper.EXPECT().ApplyIdentityMigrationPlan(gomock.Any(), gomock.Any()). + DoAndReturn(func(sdk.Context, sntypes.IdentityMigrationPlan) error { + identityApplyCalls++ + continuityEvents = append(continuityEvents, "identity") + return nil + }).AnyTimes() + auditKeeper.EXPECT().BuildCurrentAccountTransitionPlan(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(sdk.Context, string, string) (auditkeeper.AccountTransitionPlan, error) { + auditBuildCalls++ + return auditkeeper.AccountTransitionPlan{}, auditBuildErr + }).AnyTimes() + auditKeeper.EXPECT().ApplyAccountTransitionPlan(gomock.Any(), gomock.Any()). + DoAndReturn(func(sdk.Context, auditkeeper.AccountTransitionPlan) error { + auditApplyCalls++ + continuityEvents = append(continuityEvents, "audit") + return nil + }).AnyTimes() encCfg := moduletestutil.MakeTestEncodingConfig(module.AppModule{}) addrCodec := addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) @@ -136,6 +174,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { authzKeeper, feegrantKeeper, supernodeKeeper, + auditKeeper, actionKeeper, ) @@ -162,12 +201,20 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { authzKeeper: authzKeeper, feegrantKeeper: feegrantKeeper, supernodeKeeper: supernodeKeeper, + auditKeeper: auditKeeper, actionKeeper: actionKeeper, } return &msgServerFixture{ - mockFixture: mf, - msgServer: keeper.NewMsgServerImpl(k), + mockFixture: mf, + msgServer: keeper.NewMsgServerImpl(k), + auditBuildCalls: &auditBuildCalls, + auditApplyCalls: &auditApplyCalls, + auditBuildErr: &auditBuildErr, + identityBuildCalls: &identityBuildCalls, + identityApplyCalls: &identityApplyCalls, + identityBuildErr: &identityBuildErr, + continuityEvents: &continuityEvents, } } @@ -192,6 +239,51 @@ func TestPreChecks_MigrationDisabled(t *testing.T) { require.ErrorIs(t, err, types.ErrMigrationDisabled) } +func TestPreChecks_CanaryAccess(t *testing.T) { + tests := []struct { + name string + enabled bool + listSource bool + open bool + wantErr error + }{ + {name: "disabled", enabled: false, wantErr: types.ErrMigrationDisabled}, + {name: "canary listed", enabled: true, listSource: true}, + {name: "canary unlisted", enabled: true, wantErr: types.ErrMigrationNotCanary}, + {name: "open empty", enabled: true, open: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + f := initMsgServerFixture(t) + legacyKey := secp256k1.GenPrivKey() + legacyAddr := sdk.AccAddress(legacyKey.PubKey().Address()) + newKey, newAddr := testNewMigrationAccount(t) + params := types.NewParams(tc.enabled, 0, 50, 2000, 20) + if !tc.open { + canary := testAccAddr().String() + if tc.listSource { + canary = legacyAddr.String() + } + params.CanaryLegacyAddresses = []string{canary} + } + require.NoError(t, f.keeper.Params.Set(f.ctx, params)) + + if tc.wantErr == nil { + f.accountKeeper.EXPECT().GetAccount(gomock.Any(), legacyAddr).Return(authtypes.NewBaseAccountWithAddress(legacyAddr)) + f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), sdk.ValAddress(legacyAddr)).Return(stakingtypes.Validator{}, fmt.Errorf("stop after access gate")) + } + _, err := f.msgServer.ClaimLegacyAccount(f.ctx, newClaimMigrationMsg(t, legacyKey, legacyAddr, newKey, newAddr)) + if tc.wantErr != nil { + require.ErrorIs(t, err, tc.wantErr) + } else { + require.ErrorContains(t, err, "stop after access gate") + require.NotErrorIs(t, err, types.ErrMigrationNotCanary) + } + }) + } +} + // TestPreChecks_MigrationWindowClosed verifies that migration is rejected // after the configured end time. func TestPreChecks_MigrationWindowClosed(t *testing.T) { @@ -477,10 +569,12 @@ func TestClaimLegacyAccount_Success(t *testing.T) { // Step 5: MigrateFeegrant — no allowances. f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) - // Strict execution preflight: source account owns no SuperNode. - f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return( - sntypes.SuperNode{}, false, nil, - ) + // Strict execution preflight: source account owns a SuperNode. Audit continuity + // is built before migration writes and applied exactly once before SetSuperNode. + sn := sntypes.SuperNode{ValidatorAddress: sdk.ValAddress(testAccAddr()).String(), SupernodeAccount: legacyAddr.String()} + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return(sn, true, nil) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) + f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).Return(nil).Times(1) // Step 7: MigrateActions — no matching actions. f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), gomock.Any()).Return(nil, nil) @@ -491,6 +585,8 @@ func TestClaimLegacyAccount_Success(t *testing.T) { resp, err := f.msgServer.ClaimLegacyAccount(f.ctx, msg) require.NoError(t, err) require.NotNil(t, resp) + require.Equal(t, 1, *f.auditBuildCalls) + require.Equal(t, 1, *f.auditApplyCalls) // Verify migration record was stored. record, err := f.keeper.MigrationRecords.Get(f.ctx, legacyAddr.String()) @@ -598,6 +694,7 @@ func TestClaimLegacyAccount_MigratedThirdPartyWithdrawAddress(t *testing.T) { resp, err := f.msgServer.ClaimLegacyAccount(f.ctx, msg) require.NoError(t, err) require.NotNil(t, resp) + require.Zero(t, *f.auditBuildCalls, "non-SuperNode account must not build an Audit transition") } // --- Failure-path / atomicity tests --- @@ -641,6 +738,23 @@ func setupPassingPreChecks(t *testing.T, f *msgServerFixture, ownership ...stric return privKey, legacyAddr, newAddr, msg } +func TestClaimLegacyAccount_AuditBuildRejectsBeforeFirstWrite(t *testing.T) { + f := initMsgServerFixture(t) + sn := sntypes.SuperNode{ValidatorAddress: sdk.ValAddress(testAccAddr()).String()} + _, legacyAddr, newAddr, msg := setupPassingPreChecks(t, f, strictSupernodeLookupResult{sn: sn, found: true}) + sn.SupernodeAccount = legacyAddr.String() + // setupPassingPreChecks returns the value captured above; only ownership is + // relevant to the production preflight, so the empty account field is safe. + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), newAddr.String()).Return(sntypes.SuperNode{}, false, nil) + *f.auditBuildErr = fmt.Errorf("audit transition rejected") + + _, err := f.msgServer.ClaimLegacyAccount(f.ctx, msg) + require.ErrorContains(t, err, "build audit account transition") + require.Equal(t, 1, *f.auditBuildCalls) + require.Zero(t, *f.auditApplyCalls) + assertNoFinalization(t, f, legacyAddr) +} + // assertNoFinalization verifies that no migration record or counter was stored. func assertNoFinalization(t *testing.T, f *msgServerFixture, legacyAddr sdk.AccAddress) { t.Helper() @@ -979,6 +1093,19 @@ func setupV1toV4(f *mockFixture, oldValAddr, newValAddr sdk.ValAddress) { // no staking calls. } +func TestMigrateValidator_IdentityBuildRejectsBeforeFirstWrite(t *testing.T) { + f := initMsgServerFixture(t) + legacyAddr, _, _, _, msg := setupPassingValPreChecks(t, f) + *f.identityBuildErr = fmt.Errorf("identity plan rejected") + + _, err := f.msgServer.MigrateValidator(f.ctx, msg) + require.ErrorContains(t, err, "build supernode identity migration") + require.Equal(t, 1, *f.identityBuildCalls) + require.Zero(t, *f.identityApplyCalls) + require.Zero(t, *f.auditBuildCalls) + assertNoValFinalization(t, f, legacyAddr) +} + func TestMigrateValidator_RejectsSourceOwnershipCorruptionBeforeMutation(t *testing.T) { f := initMsgServerFixture(t) legacyAddr, _, _, _, msg := setupPassingValPreChecksWithOwnership(t, f, @@ -1129,6 +1256,31 @@ func TestMigrateValidator_FailAtValidatorDelegations(t *testing.T) { assertNoValFinalization(t, f, legacyAddr) } +func TestMigrateValidator_ValidatorAssociatedSupernodeSkipsAudit(t *testing.T) { + f := initMsgServerFixture(t) + legacyAddr, _, oldValAddr, newValAddr, msg := setupPassingValPreChecksWithOwnership(t, f, + func(f *msgServerFixture, legacyAddr sdk.AccAddress, oldValAddr sdk.ValAddress) { + account := testAccAddr().String() + sn := sntypes.SuperNode{ValidatorAddress: oldValAddr.String(), SupernodeAccount: account} + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return(sntypes.SuperNode{}, false, nil) + f.supernodeKeeper.EXPECT().QuerySuperNode(gomock.Any(), oldValAddr).Return(sn, true) + f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), account).Return(sn, true, nil) + }, + ) + setupV1toV4(f.mockFixture, oldValAddr, newValAddr) + f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) + f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).Return(nil) + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("stop after continuity")) + + _, err := f.msgServer.MigrateValidator(f.ctx, msg) + require.ErrorContains(t, err, "stop after continuity") + require.Equal(t, 1, *f.identityBuildCalls) + require.Equal(t, 1, *f.identityApplyCalls) + require.Zero(t, *f.auditBuildCalls) + require.Equal(t, []string{"identity"}, *f.continuityEvents) + assertNoValFinalization(t, f, legacyAddr) +} + // TestMigrateValidator_FailAtValidatorSupernode verifies that a failure in // MigrateValidatorSupernode (step V5) propagates and no record is stored. func TestMigrateValidator_FailAtValidatorSupernode(t *testing.T) { @@ -1150,16 +1302,19 @@ func TestMigrateValidator_FailAtValidatorSupernode(t *testing.T) { // Step V5: supernode re-key fails. f.supernodeKeeper.EXPECT().DeleteSuperNode(gomock.Any(), oldValAddr) - f.supernodeKeeper.EXPECT().GetMetricsState(gomock.Any(), oldValAddr).Return( - sntypes.SupernodeMetricsState{}, false, - ) - f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).Return( - fmt.Errorf("supernode store write failed"), + f.supernodeKeeper.EXPECT().SetSuperNode(gomock.Any(), gomock.Any()).DoAndReturn( + func(sdk.Context, sntypes.SuperNode) error { + *f.continuityEvents = append(*f.continuityEvents, "primary") + return fmt.Errorf("supernode store write failed") + }, ) _, err := f.msgServer.MigrateValidator(f.ctx, msg) require.Error(t, err) require.Contains(t, err.Error(), "migrate validator supernode") + require.Equal(t, []string{"identity", "audit", "primary"}, *f.continuityEvents) + require.Equal(t, 1, *f.auditBuildCalls) + require.Equal(t, 1, *f.auditApplyCalls) assertNoValFinalization(t, f, legacyAddr) } diff --git a/x/evmigration/keeper/msg_server_migrate_validator.go b/x/evmigration/keeper/msg_server_migrate_validator.go index 692d47d7..dff5b117 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator.go +++ b/x/evmigration/keeper/msg_server_migrate_validator.go @@ -10,6 +10,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" lcfg "github.com/LumeraProtocol/lumera/config" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/types" "github.com/LumeraProtocol/lumera/x/evmigration/types/sigverify" ) @@ -152,6 +153,17 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat return nil, err } } + identityPlan, err := ms.supernodeKeeper.BuildIdentityMigrationPlan(ctx, oldValAddr, newValAddr) + if err != nil { + return nil, fmt.Errorf("build supernode identity migration: %w", err) + } + var auditPlan auditkeeper.AccountTransitionPlan + if validatorSupernodePlan.hasAccountOwned { + auditPlan, err = ms.auditKeeper.BuildCurrentAccountTransitionPlan(ctx, legacyAddr.String(), newAddr.String()) + if err != nil { + return nil, fmt.Errorf("build audit account transition: %w", err) + } + } // --- Step V1: Withdraw all commission and delegation rewards --- // Must happen before re-keying so rewards accrue to the correct addresses. @@ -214,7 +226,16 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat return nil, fmt.Errorf("migrate validator delegations: %w", err) } - // --- Step V5: Mutate both prevalidated SuperNode ownership dimensions --- + // --- Step V5: Apply continuity, then mutate prevalidated ownership --- + // Continuity must observe the source primary before PR196 moves it. + if err := ms.supernodeKeeper.ApplyIdentityMigrationPlan(ctx, identityPlan); err != nil { + return nil, fmt.Errorf("apply supernode identity migration: %w", err) + } + if validatorSupernodePlan.hasAccountOwned { + if err := ms.auditKeeper.ApplyAccountTransitionPlan(ctx, auditPlan); err != nil { + return nil, fmt.Errorf("apply audit account transition: %w", err) + } + } if err := ms.migrateValidatedValidatorSupernodes( ctx, oldValAddr, newValAddr, legacyAddr, newAddr, validatorSupernodePlan, ); err != nil { diff --git a/x/evmigration/keeper/msg_server_migrate_validator_test.go b/x/evmigration/keeper/msg_server_migrate_validator_test.go index 9148e9c8..834a93c0 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator_test.go +++ b/x/evmigration/keeper/msg_server_migrate_validator_test.go @@ -354,6 +354,9 @@ func TestMigrateValidator_Success(t *testing.T) { resp, err := f.msgServer.MigrateValidator(f.ctx, msg) require.NoError(t, err) require.NotNil(t, resp) + require.Equal(t, 1, *f.identityBuildCalls) + require.Equal(t, 1, *f.identityApplyCalls) + require.Zero(t, *f.auditBuildCalls, "validator without source-owned SuperNode must not build Audit continuity") // Verify migration record was stored. record, err := f.keeper.MigrationRecords.Get(f.ctx, legacyAddr.String()) diff --git a/x/evmigration/keeper/msg_update_params_test.go b/x/evmigration/keeper/msg_update_params_test.go index b671143f..06af35f6 100644 --- a/x/evmigration/keeper/msg_update_params_test.go +++ b/x/evmigration/keeper/msg_update_params_test.go @@ -66,3 +66,25 @@ func TestMsgUpdateParams(t *testing.T) { }) } } + +func TestMsgUpdateParamsReplacesCanaryList(t *testing.T) { + f := initFixture(t) + ms := keeper.NewMsgServerImpl(f.keeper) + authority, err := f.addressCodec.BytesToString(f.keeper.GetAuthority()) + require.NoError(t, err) + + params := types.DefaultParams() + params.CanaryLegacyAddresses = []string{authority} + _, err = ms.UpdateParams(f.ctx, &types.MsgUpdateParams{Authority: authority, Params: params}) + require.NoError(t, err) + stored, err := f.keeper.Params.Get(f.ctx) + require.NoError(t, err) + require.Equal(t, []string{authority}, stored.CanaryLegacyAddresses) + + params.CanaryLegacyAddresses = nil + _, err = ms.UpdateParams(f.ctx, &types.MsgUpdateParams{Authority: authority, Params: params}) + require.NoError(t, err) + stored, err = f.keeper.Params.Get(f.ctx) + require.NoError(t, err) + require.Empty(t, stored.CanaryLegacyAddresses) +} diff --git a/x/evmigration/mocks/expected_keepers_mock.go b/x/evmigration/mocks/expected_keepers_mock.go index b36578d3..fb6f679c 100644 --- a/x/evmigration/mocks/expected_keepers_mock.go +++ b/x/evmigration/mocks/expected_keepers_mock.go @@ -20,6 +20,7 @@ import ( address "cosmossdk.io/core/address" feegrant "cosmossdk.io/x/feegrant" types "github.com/LumeraProtocol/lumera/x/action/v1/types" + keeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" types0 "github.com/LumeraProtocol/lumera/x/supernode/v1/types" types1 "github.com/cosmos/cosmos-sdk/types" authz "github.com/cosmos/cosmos-sdk/x/authz" @@ -1109,6 +1110,35 @@ func (m *MockSupernodeKeeper) EXPECT() *MockSupernodeKeeperMockRecorder { return m.recorder } +// ApplyIdentityMigrationPlan mocks base method. +func (m *MockSupernodeKeeper) ApplyIdentityMigrationPlan(ctx types1.Context, plan types0.IdentityMigrationPlan) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyIdentityMigrationPlan", ctx, plan) + ret0, _ := ret[0].(error) + return ret0 +} + +// ApplyIdentityMigrationPlan indicates an expected call of ApplyIdentityMigrationPlan. +func (mr *MockSupernodeKeeperMockRecorder) ApplyIdentityMigrationPlan(ctx, plan any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyIdentityMigrationPlan", reflect.TypeOf((*MockSupernodeKeeper)(nil).ApplyIdentityMigrationPlan), ctx, plan) +} + +// BuildIdentityMigrationPlan mocks base method. +func (m *MockSupernodeKeeper) BuildIdentityMigrationPlan(ctx types1.Context, sourceValidator, destinationValidator types1.ValAddress) (types0.IdentityMigrationPlan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildIdentityMigrationPlan", ctx, sourceValidator, destinationValidator) + ret0, _ := ret[0].(types0.IdentityMigrationPlan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildIdentityMigrationPlan indicates an expected call of BuildIdentityMigrationPlan. +func (mr *MockSupernodeKeeperMockRecorder) BuildIdentityMigrationPlan(ctx, sourceValidator, destinationValidator any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildIdentityMigrationPlan", reflect.TypeOf((*MockSupernodeKeeper)(nil).BuildIdentityMigrationPlan), ctx, sourceValidator, destinationValidator) +} + // DeleteMetricsState mocks base method. func (m *MockSupernodeKeeper) DeleteMetricsState(ctx types1.Context, valAddr types1.ValAddress) { m.ctrl.T.Helper() @@ -1223,6 +1253,59 @@ func (mr *MockSupernodeKeeperMockRecorder) StrictGetSuperNodeByAccount(ctx, supe return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StrictGetSuperNodeByAccount", reflect.TypeOf((*MockSupernodeKeeper)(nil).StrictGetSuperNodeByAccount), ctx, supernodeAccount) } +// MockAuditKeeper is a mock of AuditKeeper interface. +type MockAuditKeeper struct { + ctrl *gomock.Controller + recorder *MockAuditKeeperMockRecorder + isgomock struct{} +} + +// MockAuditKeeperMockRecorder is the mock recorder for MockAuditKeeper. +type MockAuditKeeperMockRecorder struct { + mock *MockAuditKeeper +} + +// NewMockAuditKeeper creates a new mock instance. +func NewMockAuditKeeper(ctrl *gomock.Controller) *MockAuditKeeper { + mock := &MockAuditKeeper{ctrl: ctrl} + mock.recorder = &MockAuditKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAuditKeeper) EXPECT() *MockAuditKeeperMockRecorder { + return m.recorder +} + +// ApplyAccountTransitionPlan mocks base method. +func (m *MockAuditKeeper) ApplyAccountTransitionPlan(ctx types1.Context, plan keeper.AccountTransitionPlan) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ApplyAccountTransitionPlan", ctx, plan) + ret0, _ := ret[0].(error) + return ret0 +} + +// ApplyAccountTransitionPlan indicates an expected call of ApplyAccountTransitionPlan. +func (mr *MockAuditKeeperMockRecorder) ApplyAccountTransitionPlan(ctx, plan any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplyAccountTransitionPlan", reflect.TypeOf((*MockAuditKeeper)(nil).ApplyAccountTransitionPlan), ctx, plan) +} + +// BuildCurrentAccountTransitionPlan mocks base method. +func (m *MockAuditKeeper) BuildCurrentAccountTransitionPlan(ctx types1.Context, source, destination string) (keeper.AccountTransitionPlan, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BuildCurrentAccountTransitionPlan", ctx, source, destination) + ret0, _ := ret[0].(keeper.AccountTransitionPlan) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BuildCurrentAccountTransitionPlan indicates an expected call of BuildCurrentAccountTransitionPlan. +func (mr *MockAuditKeeperMockRecorder) BuildCurrentAccountTransitionPlan(ctx, source, destination any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildCurrentAccountTransitionPlan", reflect.TypeOf((*MockAuditKeeper)(nil).BuildCurrentAccountTransitionPlan), ctx, source, destination) +} + // MockActionKeeper is a mock of ActionKeeper interface. type MockActionKeeper struct { ctrl *gomock.Controller diff --git a/x/evmigration/module/depinject.go b/x/evmigration/module/depinject.go index f9ca5b1b..c783ad97 100644 --- a/x/evmigration/module/depinject.go +++ b/x/evmigration/module/depinject.go @@ -16,6 +16,7 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" actionkeeper "github.com/LumeraProtocol/lumera/x/action/v1/keeper" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/types" snkeeper "github.com/LumeraProtocol/lumera/x/supernode/v1/keeper" @@ -50,6 +51,7 @@ type ModuleInputs struct { AuthzKeeper authzkeeper.Keeper FeegrantKeeper feegrantkeeper.Keeper SupernodeKeeper *snkeeper.Keeper + AuditKeeper auditkeeper.Keeper ActionKeeper actionkeeper.Keeper } @@ -78,6 +80,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { in.AuthzKeeper, in.FeegrantKeeper, in.SupernodeKeeper, + in.AuditKeeper, &in.ActionKeeper, ) m := NewAppModule(in.Cdc, k) diff --git a/x/evmigration/types/errors.go b/x/evmigration/types/errors.go index b25fbb36..89855b31 100644 --- a/x/evmigration/types/errors.go +++ b/x/evmigration/types/errors.go @@ -44,4 +44,5 @@ var ( // or both multisig); when both multisig, threshold (K) and sub-key count // (N) must match. A 2-of-3 legacy must migrate to a 2-of-3 destination. ErrMirrorSourceMismatch = errors.Register(ModuleName, 1121, "legacy and new proofs violate the mirror-source rule") + ErrMigrationNotCanary = errors.Register(ModuleName, 1122, "legacy address is not enabled for canary migration") ) diff --git a/x/evmigration/types/expected_keepers.go b/x/evmigration/types/expected_keepers.go index e99bbf56..9099704c 100644 --- a/x/evmigration/types/expected_keepers.go +++ b/x/evmigration/types/expected_keepers.go @@ -14,6 +14,7 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" + auditkeeper "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" ) @@ -128,6 +129,14 @@ type SupernodeKeeper interface { GetMetricsState(ctx sdk.Context, valAddr sdk.ValAddress) (sntypes.SupernodeMetricsState, bool) SetMetricsState(ctx sdk.Context, state sntypes.SupernodeMetricsState) error DeleteMetricsState(ctx sdk.Context, valAddr sdk.ValAddress) + BuildIdentityMigrationPlan(ctx sdk.Context, sourceValidator, destinationValidator sdk.ValAddress) (sntypes.IdentityMigrationPlan, error) + ApplyIdentityMigrationPlan(ctx sdk.Context, plan sntypes.IdentityMigrationPlan) error +} + +// AuditKeeper defines the identity-continuity surface owned by x/audit. +type AuditKeeper interface { + BuildCurrentAccountTransitionPlan(ctx sdk.Context, source, destination string) (auditkeeper.AccountTransitionPlan, error) + ApplyAccountTransitionPlan(ctx sdk.Context, plan auditkeeper.AccountTransitionPlan) error } // ActionKeeper defines the expected interface for the x/action module. diff --git a/x/evmigration/types/genesis_test.go b/x/evmigration/types/genesis_test.go index 419a3050..d36e3bf6 100644 --- a/x/evmigration/types/genesis_test.go +++ b/x/evmigration/types/genesis_test.go @@ -4,10 +4,17 @@ import ( "testing" "github.com/LumeraProtocol/lumera/x/evmigration/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) func TestGenesisState_Validate(t *testing.T) { + canaryAddress := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + canaryParams := types.NewParams(true, 1000000, 100, 3000, 20) + canaryParams.CanaryLegacyAddresses = []string{canaryAddress} + invalidCanaryParams := canaryParams + invalidCanaryParams.CanaryLegacyAddresses = []string{"not-an-address"} tests := []struct { desc string genState *types.GenesisState @@ -25,6 +32,16 @@ func TestGenesisState_Validate(t *testing.T) { }, valid: true, }, + { + desc: "valid genesis state with canary", + genState: &types.GenesisState{Params: canaryParams}, + valid: true, + }, + { + desc: "invalid genesis state with malformed canary", + genState: &types.GenesisState{Params: invalidCanaryParams}, + valid: false, + }, { desc: "invalid: zero max_migrations_per_block", genState: &types.GenesisState{ diff --git a/x/evmigration/types/params.go b/x/evmigration/types/params.go index 6f64698e..5645601c 100644 --- a/x/evmigration/types/params.go +++ b/x/evmigration/types/params.go @@ -7,7 +7,7 @@ // // # Parameters // -// EnableMigration (bool, default: true) +// EnableMigration (bool, default: false) // // Master switch. When false the module rejects every MsgClaimLegacyAccount // and MsgMigrateValidator regardless of other parameter values. Governance @@ -35,11 +35,20 @@ // that exceed the cap must shed delegations before migrating. package types -import "fmt" +import ( + "fmt" + "sort" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MaxCanaryLegacyAddresses bounds the consensus validation and lookup work +// required by the migration canary gate. +const MaxCanaryLegacyAddresses = 64 var ( // DefaultEnableMigration is the default value for the EnableMigration param. - DefaultEnableMigration = true + DefaultEnableMigration = false // DefaultMigrationEndTime of 0 means no deadline is enforced. DefaultMigrationEndTime int64 = 0 // DefaultMaxMigrationsPerBlock caps claim messages per block. @@ -68,6 +77,7 @@ func NewParams( MaxMigrationsPerBlock: maxMigrationsPerBlock, MaxValidatorDelegations: maxValidatorDelegations, MaxMultisigSubKeys: maxMultisigSubKeys, + CanaryLegacyAddresses: nil, } } @@ -93,5 +103,26 @@ func (p Params) Validate() error { if p.MaxMultisigSubKeys == 0 { return fmt.Errorf("max_multisig_sub_keys must be positive") } + if len(p.CanaryLegacyAddresses) > MaxCanaryLegacyAddresses { + return fmt.Errorf("canary_legacy_addresses must contain at most %d entries", MaxCanaryLegacyAddresses) + } + for i, address := range p.CanaryLegacyAddresses { + if address == "" { + return fmt.Errorf("canary_legacy_addresses[%d] must not be empty", i) + } + decoded, err := sdk.AccAddressFromBech32(address) + if err != nil { + return fmt.Errorf("canary_legacy_addresses[%d] is invalid: %w", i, err) + } + if canonical := decoded.String(); address != canonical { + return fmt.Errorf("canary_legacy_addresses[%d] must use canonical account encoding %q", i, canonical) + } + if i > 0 && !sort.StringsAreSorted(p.CanaryLegacyAddresses[i-1:i+1]) { + return fmt.Errorf("canary_legacy_addresses must be sorted lexicographically") + } + if i > 0 && p.CanaryLegacyAddresses[i-1] == address { + return fmt.Errorf("canary_legacy_addresses must not contain duplicates") + } + } return nil } diff --git a/x/evmigration/types/params.pb.go b/x/evmigration/types/params.pb.go index 1ad608f9..ff975826 100644 --- a/x/evmigration/types/params.pb.go +++ b/x/evmigration/types/params.pb.go @@ -32,7 +32,7 @@ type Params struct { // When false, all MsgClaimLegacyAccount and MsgMigrateValidator messages // are rejected regardless of other parameter values. // Governance should set this to false once the migration window closes. - // Default: true. + // Default: false; governance must enable canary or open mode explicitly. EnableMigration bool `protobuf:"varint,1,opt,name=enable_migration,json=enableMigration,proto3" json:"enable_migration,omitempty"` // migration_end_time is an optional hard deadline expressed as a unix // timestamp (seconds). If non-zero, any migration message whose block time @@ -57,6 +57,11 @@ type Params struct { // account's MultisigProof. Bounds per-tx verification cost. // Default: 20. MaxMultisigSubKeys uint32 `protobuf:"varint,5,opt,name=max_multisig_sub_keys,json=maxMultisigSubKeys,proto3" json:"max_multisig_sub_keys,omitempty"` + // canary_legacy_addresses optionally restricts migration to the exact, + // canonical legacy source addresses listed here. An empty list leaves + // migration open when enable_migration is true. Entries must be unique and + // sorted lexicographically; at most 64 entries are permitted. + CanaryLegacyAddresses []string `protobuf:"bytes,6,rep,name=canary_legacy_addresses,json=canaryLegacyAddresses,proto3" json:"canary_legacy_addresses,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -127,6 +132,13 @@ func (m *Params) GetMaxMultisigSubKeys() uint32 { return 0 } +func (m *Params) GetCanaryLegacyAddresses() []string { + if m != nil { + return m.CanaryLegacyAddresses + } + return nil +} + func init() { proto.RegisterType((*Params)(nil), "lumera.evmigration.Params") } @@ -134,28 +146,31 @@ func init() { func init() { proto.RegisterFile("lumera/evmigration/params.proto", fileDescriptor_67201e42422b4468) } var fileDescriptor_67201e42422b4468 = []byte{ - // 334 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x29, 0xcd, 0x4d, - 0x2d, 0x4a, 0xd4, 0x4f, 0x2d, 0xcb, 0xcd, 0x4c, 0x2f, 0x4a, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2f, - 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x82, 0x28, 0xd0, - 0x43, 0x52, 0x20, 0x25, 0x98, 0x98, 0x9b, 0x99, 0x97, 0xaf, 0x0f, 0x26, 0x21, 0xca, 0xa4, 0x44, - 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x4c, 0x7d, 0x10, 0x0b, 0x22, 0xaa, 0xb4, 0x9e, 0x89, 0x8b, 0x2d, - 0x00, 0x6c, 0x9a, 0x90, 0x26, 0x97, 0x40, 0x6a, 0x5e, 0x62, 0x52, 0x4e, 0x6a, 0x3c, 0xdc, 0x1c, - 0x09, 0x46, 0x05, 0x46, 0x0d, 0x8e, 0x20, 0x7e, 0x88, 0xb8, 0x2f, 0x4c, 0x58, 0x48, 0x87, 0x4b, - 0x08, 0xae, 0x26, 0x3e, 0x35, 0x2f, 0x25, 0xbe, 0x24, 0x33, 0x37, 0x55, 0x82, 0x49, 0x81, 0x51, - 0x83, 0x39, 0x48, 0x00, 0x2e, 0xe3, 0x9a, 0x97, 0x12, 0x92, 0x99, 0x9b, 0x2a, 0x64, 0xce, 0x25, - 0x91, 0x9b, 0x58, 0x81, 0x30, 0xb5, 0x38, 0xbe, 0x20, 0xb5, 0x28, 0x3e, 0x29, 0x27, 0x3f, 0x39, - 0x5b, 0x82, 0x59, 0x81, 0x51, 0x83, 0x25, 0x48, 0x34, 0x37, 0xb1, 0x02, 0x6e, 0x7a, 0x71, 0x40, - 0x6a, 0x91, 0x13, 0x48, 0x52, 0xc8, 0x8a, 0x4b, 0x12, 0xa4, 0xb1, 0x2c, 0x31, 0x27, 0x33, 0x25, - 0xb1, 0x24, 0xbf, 0x28, 0x3e, 0x25, 0x35, 0x27, 0x35, 0x1d, 0xa2, 0x48, 0x82, 0x05, 0xac, 0x53, - 0x3c, 0x37, 0xb1, 0x22, 0x0c, 0x26, 0xef, 0x82, 0x90, 0x16, 0x32, 0xe4, 0x12, 0x05, 0x5b, 0x5a, - 0x9a, 0x53, 0x92, 0x59, 0x9c, 0x99, 0x1e, 0x5f, 0x5c, 0x9a, 0x14, 0x9f, 0x9d, 0x5a, 0x59, 0x2c, - 0xc1, 0xaa, 0xc0, 0xa8, 0xc1, 0x1b, 0x24, 0x04, 0xb2, 0x11, 0x2a, 0x17, 0x5c, 0x9a, 0xe4, 0x9d, - 0x5a, 0x59, 0x6c, 0xa5, 0xf2, 0x62, 0x81, 0x3c, 0x63, 0xd7, 0xf3, 0x0d, 0x5a, 0xd2, 0xd0, 0x20, - 0xaf, 0x40, 0x09, 0x74, 0x48, 0x30, 0x39, 0xe9, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, - 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, - 0x1c, 0x43, 0x94, 0x30, 0xaa, 0xfa, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0x70, 0x38, 0x1b, - 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x4c, 0x50, 0x45, 0xc7, 0x01, 0x00, 0x00, + // 370 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xc1, 0x6a, 0xdb, 0x40, + 0x10, 0x86, 0xbd, 0xb6, 0x6b, 0xda, 0x85, 0x52, 0x77, 0x5b, 0x63, 0xd5, 0x05, 0x59, 0x94, 0x1e, + 0xd4, 0xd2, 0x5a, 0x94, 0x42, 0x0b, 0xbe, 0xc5, 0x24, 0xa7, 0x24, 0x60, 0x94, 0x90, 0x43, 0x2e, + 0xcb, 0xca, 0x1a, 0xc4, 0x62, 0xad, 0x56, 0xec, 0x4a, 0x46, 0x7a, 0x85, 0x9c, 0xf2, 0x08, 0x79, + 0x84, 0x3c, 0x46, 0x8e, 0x3e, 0xe6, 0x18, 0xec, 0x83, 0xf3, 0x18, 0x41, 0x92, 0x2d, 0xc7, 0x97, + 0x65, 0x98, 0xef, 0x9b, 0x7f, 0x60, 0x07, 0x0f, 0xc3, 0x54, 0x80, 0x62, 0x0e, 0x2c, 0x04, 0x0f, + 0x14, 0x4b, 0xb8, 0x8c, 0x9c, 0x98, 0x29, 0x26, 0xf4, 0x28, 0x56, 0x32, 0x91, 0x84, 0x54, 0xc2, + 0xe8, 0x95, 0x30, 0xf8, 0xc8, 0x04, 0x8f, 0xa4, 0x53, 0xbe, 0x95, 0x36, 0xf8, 0x1c, 0xc8, 0x40, + 0x96, 0xa5, 0x53, 0x54, 0x55, 0xf7, 0xdb, 0xa6, 0x89, 0x3b, 0xd3, 0x32, 0x8d, 0xfc, 0xc0, 0x5d, + 0x88, 0x98, 0x17, 0x02, 0xad, 0x73, 0x0c, 0x64, 0x21, 0xfb, 0xad, 0xfb, 0xa1, 0xea, 0x9f, 0xef, + 0xda, 0xe4, 0x17, 0x26, 0xb5, 0x43, 0x21, 0xf2, 0x69, 0xc2, 0x05, 0x18, 0x4d, 0x0b, 0xd9, 0x2d, + 0xb7, 0x5b, 0x93, 0x93, 0xc8, 0xbf, 0xe4, 0x02, 0xc8, 0x7f, 0x6c, 0x08, 0x96, 0xed, 0x53, 0x35, + 0x8d, 0x41, 0x51, 0x2f, 0x94, 0xb3, 0xb9, 0xd1, 0xb2, 0x90, 0xdd, 0x76, 0x7b, 0x82, 0x65, 0x75, + 0xba, 0x9e, 0x82, 0x9a, 0x14, 0x90, 0x8c, 0xf1, 0x97, 0x62, 0x70, 0xc1, 0x42, 0xee, 0xb3, 0x44, + 0x2a, 0xea, 0x43, 0x08, 0x41, 0x25, 0x19, 0xed, 0x72, 0xb2, 0x2f, 0x58, 0x76, 0xb5, 0xe3, 0xc7, + 0x7b, 0x4c, 0xfe, 0xe0, 0x5e, 0xb9, 0x34, 0x0d, 0x13, 0xae, 0x79, 0x40, 0x75, 0xea, 0xd1, 0x39, + 0xe4, 0xda, 0x78, 0x63, 0x21, 0xfb, 0xbd, 0x4b, 0x8a, 0x8d, 0x5b, 0x76, 0x91, 0x7a, 0xa7, 0x90, + 0x6b, 0xf2, 0x0f, 0xf7, 0x67, 0x2c, 0x62, 0x2a, 0xa7, 0x45, 0xcc, 0x2c, 0xa7, 0xcc, 0xf7, 0x15, + 0x68, 0x0d, 0xda, 0xe8, 0x58, 0x2d, 0xfb, 0x9d, 0xdb, 0xab, 0xf0, 0x59, 0x49, 0x8f, 0x76, 0x70, + 0xfc, 0xfd, 0xf9, 0x6e, 0x88, 0x6e, 0x36, 0xf7, 0x3f, 0xbf, 0x6e, 0x4f, 0x95, 0x1d, 0x1c, 0xab, + 0xfa, 0xde, 0xc9, 0xef, 0x87, 0x95, 0x89, 0x96, 0x2b, 0x13, 0x3d, 0xad, 0x4c, 0x74, 0xbb, 0x36, + 0x1b, 0xcb, 0xb5, 0xd9, 0x78, 0x5c, 0x9b, 0x8d, 0xeb, 0x4f, 0x87, 0x7e, 0x92, 0xc7, 0xa0, 0xbd, + 0x4e, 0x79, 0x9f, 0xbf, 0x2f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x12, 0xa0, 0x62, 0xac, 0xff, 0x01, + 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -192,6 +207,14 @@ func (this *Params) Equal(that interface{}) bool { if this.MaxMultisigSubKeys != that1.MaxMultisigSubKeys { return false } + if len(this.CanaryLegacyAddresses) != len(that1.CanaryLegacyAddresses) { + return false + } + for i := range this.CanaryLegacyAddresses { + if this.CanaryLegacyAddresses[i] != that1.CanaryLegacyAddresses[i] { + return false + } + } return true } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -214,6 +237,15 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.CanaryLegacyAddresses) > 0 { + for iNdEx := len(m.CanaryLegacyAddresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.CanaryLegacyAddresses[iNdEx]) + copy(dAtA[i:], m.CanaryLegacyAddresses[iNdEx]) + i = encodeVarintParams(dAtA, i, uint64(len(m.CanaryLegacyAddresses[iNdEx]))) + i-- + dAtA[i] = 0x32 + } + } if m.MaxMultisigSubKeys != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MaxMultisigSubKeys)) i-- @@ -279,6 +311,12 @@ func (m *Params) Size() (n int) { if m.MaxMultisigSubKeys != 0 { n += 1 + sovParams(uint64(m.MaxMultisigSubKeys)) } + if len(m.CanaryLegacyAddresses) > 0 { + for _, s := range m.CanaryLegacyAddresses { + l = len(s) + n += 1 + l + sovParams(uint64(l)) + } + } return n } @@ -413,6 +451,38 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CanaryLegacyAddresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CanaryLegacyAddresses = append(m.CanaryLegacyAddresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/evmigration/types/params_test.go b/x/evmigration/types/params_test.go new file mode 100644 index 00000000..e6f0ea48 --- /dev/null +++ b/x/evmigration/types/params_test.go @@ -0,0 +1,55 @@ +package types_test + +import ( + "sort" + "strings" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + +func TestParamsValidateCanaryLegacyAddresses(t *testing.T) { + addresses := make([]string, types.MaxCanaryLegacyAddresses+1) + for i := range addresses { + addresses[i] = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() + } + sort.Strings(addresses) + + tests := []struct { + name string + addresses []string + wantErr string + }{ + {name: "open empty"}, + {name: "valid sorted unique", addresses: addresses[:2]}, + {name: "duplicate", addresses: []string{addresses[0], addresses[0]}, wantErr: "duplicates"}, + {name: "unsorted", addresses: []string{addresses[1], addresses[0]}, wantErr: "sorted lexicographically"}, + {name: "empty entry", addresses: []string{""}, wantErr: "must not be empty"}, + {name: "noncanonical alternate encoding", addresses: []string{strings.ToUpper(addresses[0])}, wantErr: "canonical account encoding"}, + {name: "at cap", addresses: addresses[:types.MaxCanaryLegacyAddresses]}, + {name: "cap plus one", addresses: addresses, wantErr: "at most 64"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + params := types.DefaultParams() + params.CanaryLegacyAddresses = tc.addresses + err := params.Validate() + if tc.wantErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.wantErr) + } + }) + } +} + +func TestNewParamsDefaultsToOpenCanaryList(t *testing.T) { + params := types.NewParams(true, 0, 50, 2500, 20) + require.Empty(t, params.CanaryLegacyAddresses) + require.NoError(t, params.Validate()) +} From 9015af7e45d7f7b41b647a9f7cbeb073df7f6f58 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 09:00:59 +0000 Subject: [PATCH 04/18] feat(audit): expose logical and current assignments --- docs/static/openapi.yml | 2 +- proto/lumera/audit/v1/query.proto | 13 + x/audit/v1/keeper/identity_continuity.go | 28 +- x/audit/v1/keeper/query_assigned_targets.go | 32 +- .../query_assigned_targets_identity_test.go | 300 +++++++++ x/audit/v1/types/query.pb.go | 637 ++++++++++++++---- 6 files changed, 855 insertions(+), 157 deletions(-) create mode 100644 x/audit/v1/keeper/query_assigned_targets_identity_test.go diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 6488f5c1..8b515ed3 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -1 +1 @@ -{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"current_submitter":{"description":"current_submitter is the live account that authenticated submission. It is\nintentionally distinct from supernode_account, the epoch-logical identity.\nEmpty decodes preserve reports written before identity continuity shipped.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"canary_legacy_addresses":{"description":"canary_legacy_addresses optionally restricts migration to the exact,\ncanonical legacy source addresses listed here. An empty list leaves\nmigration open when enable_migration is true. Entries must be unique and\nsorted lexicographically; at most 64 entries are permitted.","items":{"type":"string"},"type":"array"},"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: true.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file +{"id":"github.com/LumeraProtocol/lumera","consumes":["application/json"],"produces":["application/json"],"swagger":"2.0","info":{"contact":{"name":"github.com/LumeraProtocol/lumera"},"description":"Chain github.com/LumeraProtocol/lumera REST API","title":"Lumera REST API","version":"version not set"},"paths":{"/LumeraProtocol/lumera/action/v1/get_action/{actionID}":{"get":{"operationId":"Query_GetAction","parameters":[{"description":"The ID of the action to query","in":"path","name":"actionID","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GetAction queries a single action by ID.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/get_action_fee/{dataSize}":{"get":{"operationId":"Query_GetActionFee","parameters":[{"in":"path","name":"dataSize","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryGetActionFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetActionFee items.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions":{"get":{"operationId":"Query_ListActions","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"default":"ACTION_STATE_UNSPECIFIED","description":" - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"in":"query","name":"actionState","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions with optional type and state filters.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_block_height/{blockHeight}":{"get":{"operationId":"Query_ListActionsByBlockHeight","parameters":[{"format":"int64","in":"path","name":"blockHeight","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByBlockHeightResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created at a specific block height.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_creator/{creator}":{"get":{"operationId":"Query_ListActionsByCreator","parameters":[{"in":"path","name":"creator","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsByCreatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions created by a specific address.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_actions_by_supernode/{superNodeAddress}":{"get":{"operationId":"Query_ListActionsBySuperNode","parameters":[{"in":"path","name":"superNodeAddress","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListActionsBySuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List actions for a specific supernode.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/list_expired_actions":{"get":{"operationId":"Query_ListExpiredActions","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryListExpiredActionsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"List expired actions.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/action/v1/query_action_by_metadata":{"get":{"operationId":"Query_QueryActionByMetadata","parameters":[{"default":"ACTION_TYPE_UNSPECIFIED","description":" - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"in":"query","name":"actionType","required":false,"type":"string"},{"description":"e.g., \"field=value\"","in":"query","name":"metadataQuery","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.QueryActionByMetadataResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Query actions based on metadata.","tags":["Query"]}},"/lumera.action.v1.Msg/ApproveAction":{"post":{"operationId":"Msg_ApproveAction","parameters":[{"description":"MsgApproveAction is the Msg/ApproveAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgApproveActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ApproveAction defines a message for approving an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/FinalizeAction":{"post":{"operationId":"Msg_FinalizeAction","parameters":[{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgFinalizeActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FinalizeAction defines a message for finalizing an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/RequestAction":{"post":{"operationId":"Msg_RequestAction","parameters":[{"description":"MsgRequestAction is the Msg/RequestAction request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestAction"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgRequestActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RequestAction defines a message for requesting an action.","tags":["Msg"]}},"/lumera.action.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.action.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/audit/v1/assigned_targets/{supernode_account}":{"get":{"operationId":"Query_AssignedTargets","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryAssignedTargetsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"AssignedTargets returns the prober -\u003e targets assignment for a given supernode_account.\nIf filter_by_epoch_id is false, it returns the assignments for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch":{"get":{"operationId":"Query_CurrentEpoch","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpoch returns the current derived epoch boundaries at the current chain height.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/current_epoch_anchor":{"get":{"operationId":"Query_CurrentEpochAnchor","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryCurrentEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CurrentEpochAnchor returns the persisted epoch anchor for the current epoch.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_anchor/{epoch_id}":{"get":{"operationId":"Query_EpochAnchor","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochAnchorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochAnchor returns the persisted epoch anchor for the given epoch_id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_report/{epoch_id}/{supernode_account}":{"get":{"operationId":"Query_EpochReport","parameters":[{"format":"uint64","in":"path","name":"epoch_id","required":true,"type":"string"},{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReport returns the submitted epoch report for (epoch_id, supernode_account).","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/epoch_reports_by_reporter/{supernode_account}":{"get":{"operationId":"Query_EpochReportsByReporter","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEpochReportsByReporterResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EpochReportsByReporter returns epoch reports submitted by the given reporter across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_action/{action_id}":{"get":{"operationId":"Query_EvidenceByAction","parameters":[{"in":"path","name":"action_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByActionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceByAction queries evidence records by action id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/by_subject/{subject_address}":{"get":{"operationId":"Query_EvidenceBySubject","parameters":[{"in":"path","name":"subject_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceBySubjectResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceBySubject queries evidence records by subject address.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/evidence/{evidence_id}":{"get":{"operationId":"Query_EvidenceById","parameters":[{"format":"uint64","in":"path","name":"evidence_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryEvidenceByIdResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EvidenceById queries a single evidence record by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_op/{heal_op_id}":{"get":{"operationId":"Query_HealOp","parameters":[{"format":"uint64","in":"path","name":"heal_op_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOp returns a single storage-truth heal operation by id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_status/{status}":{"get":{"operationId":"Query_HealOpsByStatus","parameters":[{"enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"in":"path","name":"status","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByStatusResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByStatus returns storage-truth heal operations filtered by status.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/heal_ops/by_ticket/{ticket_id}":{"get":{"operationId":"Query_HealOpsByTicket","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHealOpsByTicketResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HealOpsByTicket returns storage-truth heal operations for a ticket id.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/host_reports/{supernode_account}":{"get":{"operationId":"Query_HostReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryHostReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"HostReports returns host reports submitted by the given supernode_account across epochs.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/node_suspicion_state/{supernode_account}":{"get":{"operationId":"Query_NodeSuspicionState","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryNodeSuspicionStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"NodeSuspicionState returns storage-truth node suspicion state for a supernode account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/reporter_reliability_state/{reporter_supernode_account}":{"get":{"operationId":"Query_ReporterReliabilityState","parameters":[{"in":"path","name":"reporter_supernode_account","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryReporterReliabilityStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ReporterReliabilityState returns storage-truth reporter reliability state for a reporter account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/storage_challenge_reports/{supernode_account}":{"get":{"operationId":"Query_StorageChallengeReports","parameters":[{"in":"path","name":"supernode_account","required":true,"type":"string"},{"format":"uint64","in":"query","name":"epoch_id","required":false,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"},{"in":"query","name":"filter_by_epoch_id","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryStorageChallengeReportsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"StorageChallengeReports returns all reports that include storage-challenge observations about the given supernode_account.","tags":["Query"]}},"/LumeraProtocol/lumera/audit/v1/ticket_deterioration_state/{ticket_id}":{"get":{"operationId":"Query_TicketDeteriorationState","parameters":[{"in":"path","name":"ticket_id","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.QueryTicketDeteriorationStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TicketDeteriorationState returns storage-truth ticket deterioration state for a ticket id.","tags":["Query"]}},"/lumera.audit.v1.Msg/ClaimHealComplete":{"post":{"operationId":"Msg_ClaimHealComplete","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealComplete"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgClaimHealCompleteResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimHealComplete defines the healer claim path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEpochReport":{"post":{"operationId":"Msg_SubmitEpochReport","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReport"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEpochReportResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitEvidence":{"post":{"operationId":"Msg_SubmitEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitEvidence defines the SubmitEvidence RPC.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitHealVerification":{"post":{"operationId":"Msg_SubmitHealVerification","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerification"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitHealVerificationResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitHealVerification defines the verifier submission path for a chain-tracked heal op.","tags":["Msg"]}},"/lumera.audit.v1.Msg/SubmitStorageRecheckEvidence":{"post":{"operationId":"Msg_SubmitStorageRecheckEvidence","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidence"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SubmitStorageRecheckEvidence defines the storage-truth recheck submission path.","tags":["Msg"]}},"/lumera.audit.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.audit.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/claim/claim_record/{address}":{"get":{"operationId":"Query_ClaimRecord","parameters":[{"in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryClaimRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ClaimRecord items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/list_claimed/{vestedTerm}":{"get":{"operationId":"Query_ListClaimed","parameters":[{"format":"int64","in":"path","name":"vestedTerm","required":true,"type":"integer"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryListClaimedResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of ListClaimed items.","tags":["Query"]}},"/LumeraProtocol/lumera/claim/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.claim.Msg/Claim":{"post":{"operationId":"Msg_Claim","parameters":[{"description":"MsgClaim is the Msg/Claim request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Claim defines a message for claiming tokens.","tags":["Msg"]}},"/lumera.claim.Msg/DelayedClaim":{"post":{"operationId":"Msg_DelayedClaim","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaim"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgDelayedClaimResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.claim.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.claim.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.erc20policy.Msg/SetRegistrationPolicy":{"post":{"operationId":"Msg_SetRegistrationPolicy","parameters":[{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicy"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.erc20policy.MsgSetRegistrationPolicyResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SetRegistrationPolicy sets the IBC voucher ERC20 auto-registration policy.\nOnly the governance module account (x/gov authority) may call this.","tags":["Msg"]}},"/lumera/evmigration/legacy_accounts":{"get":{"operationId":"Query_LegacyAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryLegacyAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"LegacyAccounts lists accounts that still use secp256k1 pubkey and have\nnon-zero balance or delegations (i.e. accounts that should migrate).","tags":["Query"]}},"/lumera/evmigration/migrated_accounts":{"get":{"operationId":"Query_MigratedAccounts","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigratedAccountsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigratedAccounts lists all completed migrations with full detail.","tags":["Query"]}},"/lumera/evmigration/migration_estimate/{legacy_address}":{"get":{"operationId":"Query_MigrationEstimate","parameters":[{"description":"legacy_address is the coin-type-118 address to estimate migration for.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationEstimateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationEstimate returns a dry-run estimate of what would be migrated\nfor a given legacy address (delegation count, unbonding count, etc.).\nUseful for validators to pre-check before submitting MsgMigrateValidator.","tags":["Query"]}},"/lumera/evmigration/migration_record/{legacy_address}":{"get":{"operationId":"Query_MigrationRecord","parameters":[{"description":"legacy_address is the coin-type-118 address to look up.","in":"path","name":"legacy_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecord returns the migration record for a single legacy address.\nReturns nil record if the address has not been migrated.","tags":["Query"]}},"/lumera/evmigration/migration_record_by_new_address/{new_address}":{"get":{"operationId":"Query_MigrationRecordByNewAddress","parameters":[{"description":"new_address is the coin-type-60 destination address to look up.","in":"path","name":"new_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordByNewAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecordByNewAddress returns the migration record for a single new address.\nReturns nil record if the new address has not been used as a migration destination.","tags":["Query"]}},"/lumera/evmigration/migration_records":{"get":{"operationId":"Query_MigrationRecords","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationRecordsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationRecords returns all completed migration records with pagination.","tags":["Query"]}},"/lumera/evmigration/migration_stats":{"get":{"operationId":"Query_MigrationStats","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryMigrationStatsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrationStats returns aggregate counters: total migrated, total legacy,\ntotal legacy staked, total validators migrated/legacy.","tags":["Query"]}},"/lumera/evmigration/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params returns the current migration parameters.","tags":["Query"]}},"/lumera.evmigration.Msg/ClaimLegacyAccount":{"post":{"operationId":"Msg_ClaimLegacyAccount","parameters":[{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccount"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgClaimLegacyAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ClaimLegacyAccount migrates all on-chain state from a legacy (coin-type-118)\naddress to a new (coin-type-60) address. Requires dual-signature proof.","tags":["Msg"]}},"/lumera.evmigration.Msg/MigrateValidator":{"post":{"operationId":"Msg_MigrateValidator","parameters":[{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidator"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgMigrateValidatorResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"MigrateValidator migrates a validator operator from legacy to new address,\nincluding all delegations, distribution state, supernode records, and\naccount-level state.","tags":["Msg"]}},"/lumera.evmigration.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.evmigration.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/lumeraid/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/lumera.lumeraid.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.lumeraid.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node/{validatorAddress}":{"get":{"operationId":"Query_GetSuperNode","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by validatorAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_super_node_by_address/{supernodeAddress}":{"get":{"operationId":"Query_GetSuperNodeBySuperNodeAddress","parameters":[{"in":"path","name":"supernodeAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a SuperNode by supernodeAddress.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/get_top_super_nodes_for_block/{blockHeight}":{"get":{"operationId":"Query_GetTopSuperNodesForBlock","parameters":[{"format":"int32","in":"path","name":"blockHeight","required":true,"type":"integer"},{"format":"int32","in":"query","name":"limit","required":false,"type":"integer"},{"in":"query","name":"state","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of GetTopSuperNodesForBlock items.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/list_super_nodes":{"get":{"operationId":"Query_ListSuperNodes","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryListSuperNodesResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries a list of SuperNodes.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/metrics/{validatorAddress}":{"get":{"operationId":"Query_GetMetrics","parameters":[{"in":"path","name":"validatorAddress","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryGetMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Queries the latest metrics state for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Parameters queries the parameters of the module.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/payout_history/{validator_address}":{"get":{"operationId":"Query_PayoutHistory","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"},{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPayoutHistoryResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PayoutHistory returns distribution payout history for a validator.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/pool_state":{"get":{"operationId":"Query_PoolState","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QueryPoolStateResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"PoolState queries the current state of the Everlight pool.","tags":["Query"]}},"/LumeraProtocol/lumera/supernode/v1/sn_eligibility/{validator_address}":{"get":{"operationId":"Query_SNEligibility","parameters":[{"in":"path","name":"validator_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.QuerySNEligibilityResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"SNEligibility queries whether a specific SuperNode is eligible for payouts.","tags":["Query"]}},"/lumera.supernode.v1.Msg/DeregisterSupernode":{"post":{"operationId":"Msg_DeregisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgDeregisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/RegisterSupernode":{"post":{"operationId":"Msg_RegisterSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgRegisterSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/ReportSupernodeMetrics":{"post":{"operationId":"Msg_ReportSupernodeMetrics","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetrics"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgReportSupernodeMetricsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StartSupernode":{"post":{"operationId":"Msg_StartSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStartSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/StopSupernode":{"post":{"operationId":"Msg_StopSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgStopSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a (governance) operation for updating the module\nparameters. The authority defaults to the x/gov module account.","tags":["Msg"]}},"/lumera.supernode.v1.Msg/UpdateSupernode":{"post":{"operationId":"Msg_UpdateSupernode","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernode"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/lumera.supernode.v1.MsgUpdateSupernodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"tags":["Msg"]}},"/cosmos/evm/erc20/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params retrieves the erc20 module params","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs":{"get":{"operationId":"Query_TokenPairs","parameters":[{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","in":"query","name":"pagination.key","required":false,"type":"string"},{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","in":"query","name":"pagination.offset","required":false,"type":"string"},{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","in":"query","name":"pagination.limit","required":false,"type":"string"},{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","in":"query","name":"pagination.count_total","required":false,"type":"boolean"},{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","in":"query","name":"pagination.reverse","required":false,"type":"boolean"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPairs retrieves registered token pairs (mappings)x","tags":["Query"]}},"/cosmos/evm/erc20/v1/token_pairs/{token}":{"get":{"operationId":"Query_TokenPair","parameters":[{"description":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","in":"path","name":"token","pattern":".+","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.QueryTokenPairResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TokenPair retrieves a registered token pair (mapping)","tags":["Query"]}},"/cosmos.evm.erc20.v1.Msg/RegisterERC20":{"post":{"operationId":"Msg_RegisterERC20","parameters":[{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgRegisterERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterERC20 defines a governance operation for registering a token pair\nfor the specified erc20 contract. The authority is hard-coded to the Cosmos\nSDK x/gov module account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/ToggleConversion":{"post":{"operationId":"Msg_ToggleConversion","parameters":[{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversion"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgToggleConversionResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ToggleConversion defines a governance operation for enabling/disabling a\ntoken pair conversion. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos.evm.erc20.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defines a governance operation for updating the x/erc20 module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_coin":{"get":{"operationId":"Msg_ConvertCoin","parameters":[{"in":"query","name":"coin.denom","required":false,"type":"string"},{"in":"query","name":"coin.amount","required":false,"type":"string"},{"description":"receiver is the hex address to receive ERC20 token","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the cosmos bech32 address from the owner of the given Cosmos\ncoins","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertCoinResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertCoin mints a ERC20 token representation of the native Cosmos coin\nthat is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/erc20/v1/tx/convert_erc20":{"get":{"operationId":"Msg_ConvertERC20","parameters":[{"description":"contract_address of an ERC20 token contract, that is registered in a token\npair","in":"query","name":"contract_address","required":false,"type":"string"},{"description":"amount of ERC20 tokens to convert","in":"query","name":"amount","required":false,"type":"string"},{"description":"receiver is the bech32 address to receive native Cosmos coins","in":"query","name":"receiver","required":false,"type":"string"},{"description":"sender is the hex address from the owner of the given ERC20 tokens","in":"query","name":"sender","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.erc20.v1.MsgConvertERC20Response"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ConvertERC20 mints a native Cosmos coin representation of the ERC20 token\ncontract that is registered on the token mapping.","tags":["Msg"]}},"/cosmos/evm/feemarket/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block.","tags":["Query"]}},"/cosmos/evm/feemarket/v1/block_gas":{"get":{"operationId":"Query_BlockGas","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryBlockGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BlockGas queries the gas used at a given block height","tags":["Query"]}},"/cosmos/evm/feemarket/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/feemarket module.","tags":["Query"]}},"/cosmos.evm.feemarket.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/feemarket\nmodule parameters. The authority is hard-coded to the Cosmos SDK x/gov\nmodule account","tags":["Msg"]}},"/cosmos/evm/precisebank/v1/fractional_balance/{address}":{"get":{"operationId":"Query_FractionalBalance","parameters":[{"description":"address is the account address to query fractional balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"FractionalBalance returns only the fractional balance of an address. This\ndoes not include any integer balance.","tags":["Query"]}},"/cosmos/evm/precisebank/v1/remainder":{"get":{"operationId":"Query_Remainder","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.precisebank.v1.QueryRemainderResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Remainder returns the amount backed by the reserve, but not yet owned by\nany account, i.e. not in circulation.","tags":["Query"]}},"/cosmos/evm/vm/v1/account/{address}":{"get":{"operationId":"Query_Account","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Account queries an Ethereum account.","tags":["Query"]}},"/cosmos/evm/vm/v1/balances/{address}":{"get":{"operationId":"Query_Balance","parameters":[{"description":"address is the ethereum hex address to query the balance for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBalanceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Balance queries the balance of a the EVM denomination for a single\naccount.","tags":["Query"]}},"/cosmos/evm/vm/v1/base_fee":{"get":{"operationId":"Query_BaseFee","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryBaseFeeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"BaseFee queries the base fee of the parent block of the current block,\nit's similar to feemarket module's method, but also checks london hardfork\nstatus.","tags":["Query"]}},"/cosmos/evm/vm/v1/codes/{address}":{"get":{"operationId":"Query_Code","parameters":[{"description":"address is the ethereum hex address to query the code for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCodeResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Code queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/config":{"get":{"operationId":"Query_Config","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryConfigResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Config queries the EVM configuration","tags":["Query"]}},"/cosmos/evm/vm/v1/cosmos_account/{address}":{"get":{"operationId":"Query_CosmosAccount","parameters":[{"description":"address is the ethereum hex address to query the account for.","in":"path","name":"address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryCosmosAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"CosmosAccount queries an Ethereum account's Cosmos Address.","tags":["Query"]}},"/cosmos/evm/vm/v1/estimate_gas":{"get":{"operationId":"Query_EstimateGas","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.EstimateGasResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EstimateGas implements the `eth_estimateGas` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/eth_call":{"get":{"operationId":"Query_EthCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"state overrides encoded as json","format":"byte","in":"query","name":"overrides","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthCall implements the `eth_call` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/min_gas_price":{"get":{"operationId":"Query_GlobalMinGasPrice","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"GlobalMinGasPrice queries the MinGasPrice\nit's similar to feemarket module's method,\nbut makes the conversion to 18 decimals\nwhen the evm denom is represented with a different precision.","tags":["Query"]}},"/cosmos/evm/vm/v1/params":{"get":{"operationId":"Query_Params","responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Params queries the parameters of x/vm module.","tags":["Query"]}},"/cosmos/evm/vm/v1/storage/{address}/{key}":{"get":{"operationId":"Query_Storage","parameters":[{"description":"address is the ethereum hex address to query the storage state for.","in":"path","name":"address","required":true,"type":"string"},{"description":"key defines the key of the storage state","in":"path","name":"key","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryStorageResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"Storage queries the balance of all coins for a single account.","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_block":{"get":{"operationId":"Query_TraceBlock","parameters":[{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of the traced block","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash (hex) of the traced block","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of the traced block","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the address of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the traced block","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceBlockResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceBlock implements the `debug_traceBlockByNumber` and\n`debug_traceBlockByHash` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_call":{"get":{"operationId":"Query_TraceCall","parameters":[{"description":"args uses the same json format as the json rpc api.","format":"byte","in":"query","name":"args","required":false,"type":"string"},{"description":"gas_cap defines the default gas cap to be used","format":"uint64","in":"query","name":"gas_cap","required":false,"type":"string"},{"description":"proposer_address of the requested block in hex format","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"chain_id is the the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceCallResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceCall implements the `debug_traceCall` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/trace_tx":{"get":{"operationId":"Query_TraceTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"msg.from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"msg.raw","required":false,"type":"string"},{"description":"tracer is a custom javascript tracer","in":"query","name":"trace_config.tracer","required":false,"type":"string"},{"description":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","in":"query","name":"trace_config.timeout","required":false,"type":"string"},{"description":"reexec defines the number of blocks the tracer is willing to go back","format":"uint64","in":"query","name":"trace_config.reexec","required":false,"type":"string"},{"description":"disable_stack switches stack capture","in":"query","name":"trace_config.disable_stack","required":false,"type":"boolean"},{"description":"disable_storage switches storage capture","in":"query","name":"trace_config.disable_storage","required":false,"type":"boolean"},{"description":"debug can be used to print output during capture end","in":"query","name":"trace_config.debug","required":false,"type":"boolean"},{"description":"limit defines the maximum length of output, but zero means unlimited","format":"int32","in":"query","name":"trace_config.limit","required":false,"type":"integer"},{"description":"homestead_block switch (nil no fork, 0 = already homestead)","in":"query","name":"trace_config.overrides.homestead_block","required":false,"type":"string"},{"description":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","in":"query","name":"trace_config.overrides.dao_fork_block","required":false,"type":"string"},{"description":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","in":"query","name":"trace_config.overrides.dao_fork_support","required":false,"type":"boolean"},{"description":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","in":"query","name":"trace_config.overrides.eip150_block","required":false,"type":"string"},{"description":"eip155_block: EIP155Block HF block","in":"query","name":"trace_config.overrides.eip155_block","required":false,"type":"string"},{"description":"eip158_block: EIP158 HF block","in":"query","name":"trace_config.overrides.eip158_block","required":false,"type":"string"},{"description":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","in":"query","name":"trace_config.overrides.byzantium_block","required":false,"type":"string"},{"description":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","in":"query","name":"trace_config.overrides.constantinople_block","required":false,"type":"string"},{"description":"petersburg_block: Petersburg switch block (nil same as Constantinople)","in":"query","name":"trace_config.overrides.petersburg_block","required":false,"type":"string"},{"description":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","in":"query","name":"trace_config.overrides.istanbul_block","required":false,"type":"string"},{"description":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.muir_glacier_block","required":false,"type":"string"},{"description":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","in":"query","name":"trace_config.overrides.berlin_block","required":false,"type":"string"},{"description":"london_block: London switch block (nil = no fork, 0 = already on london)","in":"query","name":"trace_config.overrides.london_block","required":false,"type":"string"},{"description":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.arrow_glacier_block","required":false,"type":"string"},{"description":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","in":"query","name":"trace_config.overrides.gray_glacier_block","required":false,"type":"string"},{"description":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","in":"query","name":"trace_config.overrides.merge_netsplit_block","required":false,"type":"string"},{"description":"chain_id is the id of the chain (EIP-155)","format":"uint64","in":"query","name":"trace_config.overrides.chain_id","required":false,"type":"string"},{"description":"denom is the denomination used on the EVM","in":"query","name":"trace_config.overrides.denom","required":false,"type":"string"},{"description":"decimals is the real decimal precision of the denomination used on the EVM","format":"uint64","in":"query","name":"trace_config.overrides.decimals","required":false,"type":"string"},{"description":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","in":"query","name":"trace_config.overrides.shanghai_time","required":false,"type":"string"},{"description":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","in":"query","name":"trace_config.overrides.cancun_time","required":false,"type":"string"},{"description":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","in":"query","name":"trace_config.overrides.prague_time","required":false,"type":"string"},{"description":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","in":"query","name":"trace_config.overrides.verkle_time","required":false,"type":"string"},{"description":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","in":"query","name":"trace_config.overrides.osaka_time","required":false,"type":"string"},{"description":"enable_memory switches memory capture","in":"query","name":"trace_config.enable_memory","required":false,"type":"boolean"},{"description":"enable_return_data switches the capture of return data","in":"query","name":"trace_config.enable_return_data","required":false,"type":"boolean"},{"description":"tracer_json_config configures the tracer using a JSON string","in":"query","name":"trace_config.tracer_json_config","required":false,"type":"string"},{"description":"block_number of requested transaction","format":"int64","in":"query","name":"block_number","required":false,"type":"string"},{"description":"block_hash of requested transaction","in":"query","name":"block_hash","required":false,"type":"string"},{"description":"block_time of requested transaction","format":"date-time","in":"query","name":"block_time","required":false,"type":"string"},{"description":"proposer_address is the proposer of the requested block","format":"byte","in":"query","name":"proposer_address","required":false,"type":"string"},{"description":"chain_id is the eip155 chain id parsed from the requested block header","format":"int64","in":"query","name":"chain_id","required":false,"type":"string"},{"description":"block_max_gas of the block of the requested transaction","format":"int64","in":"query","name":"block_max_gas","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryTraceTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"TraceTx implements the `debug_traceTransaction` rpc api","tags":["Query"]}},"/cosmos/evm/vm/v1/validator_account/{cons_address}":{"get":{"operationId":"Query_ValidatorAccount","parameters":[{"description":"cons_address is the validator cons address to query the account for.","in":"path","name":"cons_address","required":true,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.QueryValidatorAccountResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"ValidatorAccount queries an Ethereum account's from a validator consensus\nAddress.","tags":["Query"]}},"/cosmos.evm.vm.v1.Msg/RegisterPreinstalls":{"post":{"operationId":"Msg_RegisterPreinstalls","parameters":[{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstalls"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"RegisterPreinstalls defines a governance operation for directly registering\npreinstalled contracts in the EVM. The authority is the same as is used for\nParams updates.","tags":["Msg"]}},"/cosmos.evm.vm.v1.Msg/UpdateParams":{"post":{"operationId":"Msg_UpdateParams","parameters":[{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","in":"body","name":"body","required":true,"schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParams"}}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgUpdateParamsResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"UpdateParams defined a governance operation for updating the x/vm module\nparameters. The authority is hard-coded to the Cosmos SDK x/gov module\naccount","tags":["Msg"]}},"/cosmos/evm/vm/v1/ethereum_tx":{"post":{"operationId":"Msg_EthereumTx","parameters":[{"description":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","format":"byte","in":"query","name":"from","required":false,"type":"string"},{"description":"raw is the raw ethereum transaction","format":"byte","in":"query","name":"raw","required":false,"type":"string"}],"responses":{"200":{"description":"A successful response.","schema":{"$ref":"#/definitions/cosmos.evm.vm.v1.MsgEthereumTxResponse"}},"default":{"description":"An unexpected error response.","schema":{"$ref":"#/definitions/google.rpc.Status"}}},"summary":"EthereumTx defines a method submitting Ethereum transactions.","tags":["Msg"]}}},"definitions":{"cosmos.base.query.v1beta1.PageRequest":{"description":"message SomeRequest {\n Foo some_parameter = 1;\n PageRequest pagination = 2;\n }","properties":{"count_total":{"description":"count_total is set to true to indicate that the result set should include\na count of the total number of items available for pagination in UIs.\ncount_total is only respected when offset is used. It is ignored when key\nis set.","type":"boolean"},"key":{"description":"key is a value returned in PageResponse.next_key to begin\nquerying the next page most efficiently. Only one of offset or key\nshould be set.","format":"byte","type":"string"},"limit":{"description":"limit is the total number of results to be returned in the result page.\nIf left empty it will default to a value to be set by each app.","format":"uint64","type":"string"},"offset":{"description":"offset is a numeric offset that can be used when key is unavailable.\nIt is less efficient than using key. Only one of offset or key should\nbe set.","format":"uint64","type":"string"},"reverse":{"description":"reverse is set to true if results are to be returned in the descending order.\n\nSince: cosmos-sdk 0.43","type":"boolean"}},"title":"PageRequest is to be embedded in gRPC request messages for efficient\npagination. Ex:","type":"object"},"cosmos.base.query.v1beta1.PageResponse":{"description":"PageResponse is to be embedded in gRPC response messages where the\ncorresponding request message has used PageRequest.\n\n message SomeResponse {\n repeated Bar results = 1;\n PageResponse page = 2;\n }","properties":{"next_key":{"description":"next_key is the key to be passed to PageRequest.key to\nquery the next page most efficiently. It will be empty if\nthere are no more results.","format":"byte","type":"string"},"total":{"format":"uint64","title":"total is total number of results available if PageRequest.count_total\nwas set, its value is undefined otherwise","type":"string"}},"type":"object"},"cosmos.base.v1beta1.Coin":{"description":"Coin defines a token with a denomination and an amount.\n\nNOTE: The amount field is an Int which implements the custom method\nsignatures required by gogoproto.","properties":{"amount":{"type":"string"},"denom":{"type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgConvertCoinResponse":{"title":"MsgConvertCoinResponse returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgConvertERC20Response":{"title":"MsgConvertERC20Response returns no fields","type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20":{"description":"MsgRegisterERC20 is the Msg/RegisterERC20 request type for registering\nan Erc20 contract token pair.","properties":{"erc20addresses":{"items":{"type":"string"},"title":"erc20addresses is a slice of ERC20 token contract hex addresses","type":"array"},"signer":{"title":"signer is the address registering the erc20 pairs","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgRegisterERC20Response":{"description":"MsgRegisterERC20Response defines the response structure for executing a\nMsgRegisterERC20 message.","type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversion":{"description":"MsgToggleConversion is the Msg/MsgToggleConversion request type for toggling\nan Erc20 contract conversion capability.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"token":{"title":"token identifier can be either the hex contract address of the ERC20 or the\nCosmos base denomination","type":"string"}},"type":"object"},"cosmos.evm.erc20.v1.MsgToggleConversionResponse":{"description":"MsgToggleConversionResponse defines the response structure for executing a\nToggleConversion message.","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParams":{"properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"title":"MsgUpdateParams is the Msg/UpdateParams request type for Erc20 parameters.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.MsgUpdateParamsResponse":{"title":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.\nSince: cosmos-sdk 0.47","type":"object"},"cosmos.evm.erc20.v1.Owner":{"default":"OWNER_UNSPECIFIED","description":"Owner enumerates the ownership of a ERC20 contract.\n\n - OWNER_UNSPECIFIED: OWNER_UNSPECIFIED defines an invalid/undefined owner.\n - OWNER_MODULE: OWNER_MODULE - erc20 is owned by the erc20 module account.\n - OWNER_EXTERNAL: OWNER_EXTERNAL - erc20 is owned by an external account.","enum":["OWNER_UNSPECIFIED","OWNER_MODULE","OWNER_EXTERNAL"],"type":"string"},"cosmos.evm.erc20.v1.Params":{"properties":{"enable_erc20":{"description":"enable_erc20 is the parameter to enable the conversion of Cosmos coins \u003c--\u003e\nERC20 tokens.","type":"boolean"},"permissionless_registration":{"title":"permissionless_registration is the parameter that allows ERC20s to be\npermissionlessly registered to be converted to bank tokens and vice versa","type":"boolean"}},"title":"Params defines the erc20 module params","type":"object"},"cosmos.evm.erc20.v1.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC\nmethod.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Params","title":"params are the erc20 module parameters"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairResponse":{"description":"QueryTokenPairResponse is the response type for the Query/TokenPair RPC\nmethod.","properties":{"token_pair":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","title":"token_pairs returns the info about a registered token pair for the erc20\nmodule"}},"type":"object"},"cosmos.evm.erc20.v1.QueryTokenPairsResponse":{"description":"QueryTokenPairsResponse is the response type for the Query/TokenPairs RPC\nmethod.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"token_pairs":{"items":{"$ref":"#/definitions/cosmos.evm.erc20.v1.TokenPair","type":"object"},"title":"token_pairs is a slice of registered token pairs for the erc20 module","type":"array"}},"type":"object"},"cosmos.evm.erc20.v1.TokenPair":{"description":"TokenPair defines an instance that records a pairing (mapping) consisting of a native\nCosmos Coin and an ERC20 token address. The \"pair\" does not imply an asset swap exchange.","properties":{"contract_owner":{"$ref":"#/definitions/cosmos.evm.erc20.v1.Owner","title":"contract_owner is the an ENUM specifying the type of ERC20 owner (0\ninvalid, 1 ModuleAccount, 2 external address)"},"denom":{"title":"denom defines the cosmos base denomination to be mapped to","type":"string"},"enabled":{"title":"enabled defines the token mapping enable status","type":"boolean"},"erc20_address":{"title":"erc20_address is the hex address of ERC20 contract token","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/feemarket module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params defines the x/feemarket parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.feemarket.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.feemarket.v1.Params":{"properties":{"base_fee":{"description":"base_fee for EIP-1559 blocks.","type":"string"},"base_fee_change_denominator":{"description":"base_fee_change_denominator bounds the amount the base fee can change\nbetween blocks.","format":"int64","type":"integer"},"elasticity_multiplier":{"description":"elasticity_multiplier bounds the maximum gas limit an EIP-1559 block may\nhave.","format":"int64","type":"integer"},"enable_height":{"description":"enable_height defines at which block height the base fee calculation is\nenabled.","format":"int64","type":"string"},"min_gas_multiplier":{"title":"min_gas_multiplier bounds the minimum gas used to be charged\nto senders based on gas limit","type":"string"},"min_gas_price":{"title":"min_gas_price defines the minimum gas price value for cosmos and eth\ntransactions","type":"string"},"no_base_fee":{"title":"no_base_fee forces the EIP-1559 base fee to 0 (needed for 0 price calls)","type":"boolean"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.feemarket.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryBlockGasResponse":{"description":"QueryBlockGasResponse returns block gas used for a given height.","properties":{"gas":{"format":"int64","title":"gas is the returned block gas","type":"string"}},"type":"object"},"cosmos.evm.feemarket.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.feemarket.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryFractionalBalanceResponse":{"description":"QueryFractionalBalanceResponse defines the response type for\nQuery/FractionalBalance method.","properties":{"fractional_balance":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"fractional_balance is the fractional balance of the address."}},"type":"object"},"cosmos.evm.precisebank.v1.QueryRemainderResponse":{"description":"QueryRemainderResponse defines the response type for Query/Remainder method.","properties":{"remainder":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","description":"remainder is the amount backed by the reserve, but not yet owned by any\naccount, i.e. not in circulation."}},"type":"object"},"cosmos.evm.vm.v1.AccessControl":{"properties":{"call":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"call defines the permission policy for calling contracts"},"create":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControlType","title":"create defines the permission policy for creating contracts"}},"title":"AccessControl defines the permission policy of the EVM\nfor creating and calling contracts","type":"object"},"cosmos.evm.vm.v1.AccessControlType":{"properties":{"access_control_list":{"items":{"type":"string"},"title":"access_control_list defines defines different things depending on the\nAccessType:\n- ACCESS_TYPE_PERMISSIONLESS: list of addresses that are blocked from\nperforming the operation\n- ACCESS_TYPE_RESTRICTED: ignored\n- ACCESS_TYPE_PERMISSIONED: list of addresses that are allowed to perform\nthe operation","type":"array"},"access_type":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessType","title":"access_type defines which type of permission is required for the operation"}},"title":"AccessControlType defines the permission type for policies","type":"object"},"cosmos.evm.vm.v1.AccessType":{"default":"ACCESS_TYPE_PERMISSIONLESS","description":"- ACCESS_TYPE_PERMISSIONLESS: ACCESS_TYPE_PERMISSIONLESS does not restrict the operation to anyone\n - ACCESS_TYPE_RESTRICTED: ACCESS_TYPE_RESTRICTED restrict the operation to anyone\n - ACCESS_TYPE_PERMISSIONED: ACCESS_TYPE_PERMISSIONED only allows the operation for specific addresses","enum":["ACCESS_TYPE_PERMISSIONLESS","ACCESS_TYPE_RESTRICTED","ACCESS_TYPE_PERMISSIONED"],"title":"AccessType defines the types of permissions for the operations","type":"string"},"cosmos.evm.vm.v1.ChainConfig":{"description":"ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values\ninstead of *big.Int.","properties":{"arrow_glacier_block":{"title":"arrow_glacier_block: Eip-4345 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"berlin_block":{"title":"berlin_block: Berlin switch block (nil = no fork, 0 = already on berlin)","type":"string"},"byzantium_block":{"title":"byzantium_block: Byzantium switch block (nil no fork, 0 = already on\nbyzantium)","type":"string"},"cancun_time":{"title":"cancun_time: Cancun switch time (nil = no fork, 0 = already on cancun)","type":"string"},"chain_id":{"format":"uint64","title":"chain_id is the id of the chain (EIP-155)","type":"string"},"constantinople_block":{"title":"constantinople_block: Constantinople switch block (nil no fork, 0 = already\nactivated)","type":"string"},"dao_fork_block":{"title":"dao_fork_block corresponds to TheDAO hard-fork switch block (nil no fork)","type":"string"},"dao_fork_support":{"title":"dao_fork_support defines whether the nodes supports or opposes the DAO\nhard-fork","type":"boolean"},"decimals":{"format":"uint64","title":"decimals is the real decimal precision of the denomination used on the EVM","type":"string"},"denom":{"title":"denom is the denomination used on the EVM","type":"string"},"eip150_block":{"title":"eip150_block: EIP150 implements the Gas price changes\n(https://github.com/ethereum/EIPs/issues/150) EIP150 HF block (nil no fork)","type":"string"},"eip155_block":{"title":"eip155_block: EIP155Block HF block","type":"string"},"eip158_block":{"title":"eip158_block: EIP158 HF block","type":"string"},"gray_glacier_block":{"title":"gray_glacier_block: EIP-5133 (bomb delay) switch block (nil = no fork, 0 =\nalready activated)","type":"string"},"homestead_block":{"title":"homestead_block switch (nil no fork, 0 = already homestead)","type":"string"},"istanbul_block":{"title":"istanbul_block: Istanbul switch block (nil no fork, 0 = already on\nistanbul)","type":"string"},"london_block":{"title":"london_block: London switch block (nil = no fork, 0 = already on london)","type":"string"},"merge_netsplit_block":{"title":"merge_netsplit_block: Virtual fork after The Merge to use as a network\nsplitter","type":"string"},"muir_glacier_block":{"title":"muir_glacier_block: Eip-2384 (bomb delay) switch block (nil no fork, 0 =\nalready activated)","type":"string"},"osaka_time":{"title":"osaka_time: Osaka switch time (nil = no fork, 0 = already on osaka)","type":"string"},"petersburg_block":{"title":"petersburg_block: Petersburg switch block (nil same as Constantinople)","type":"string"},"prague_time":{"title":"prague_time: Prague switch time (nil = no fork, 0 = already on prague)","type":"string"},"shanghai_time":{"title":"shanghai_time: Shanghai switch time (nil = no fork, 0 = already on\nshanghai)","type":"string"},"verkle_time":{"title":"verkle_time: Verkle switch time (nil = no fork, 0 = already on verkle)","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.EstimateGasResponse":{"properties":{"gas":{"format":"uint64","title":"gas returns the estimated gas","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"title":"EstimateGasResponse defines EstimateGas response","type":"object"},"cosmos.evm.vm.v1.ExtendedDenomOptions":{"properties":{"extended_denom":{"type":"string"}},"type":"object"},"cosmos.evm.vm.v1.Log":{"description":"Log represents an protobuf compatible Ethereum Log that defines a contract\nlog event. These events are generated by the LOG opcode and stored/indexed by\nthe node.\n\nNOTE: address, topics and data are consensus fields. The rest of the fields\nare derived, i.e. filled in by the nodes, but not secured by consensus.","properties":{"address":{"title":"address of the contract that generated the event","type":"string"},"block_hash":{"title":"block_hash of the block in which the transaction was included","type":"string"},"block_number":{"format":"uint64","title":"block_number of the block in which the transaction was included","type":"string"},"block_timestamp":{"format":"uint64","title":"block_timestamp is the timestamp of the block in which the transaction was","type":"string"},"data":{"format":"byte","title":"data which is supplied by the contract, usually ABI-encoded","type":"string"},"index":{"format":"uint64","title":"index of the log in the block","type":"string"},"removed":{"description":"removed is true if this log was reverted due to a chain\nreorganisation. You must pay attention to this field if you receive logs\nthrough a filter query.","type":"boolean"},"topics":{"description":"topics is a list of topics provided by the contract.","items":{"type":"string"},"type":"array"},"tx_hash":{"title":"tx_hash is the transaction hash","type":"string"},"tx_index":{"format":"uint64","title":"tx_index of the transaction in the block","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTx":{"description":"MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.","properties":{"from":{"format":"byte","title":"from is the bytes of ethereum signer address. This address value is checked\nagainst the address derived from the signature (V, R, S) using the\nsecp256k1 elliptic curve","type":"string"},"raw":{"format":"byte","title":"raw is the raw ethereum transaction","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgEthereumTxResponse":{"description":"MsgEthereumTxResponse defines the Msg/EthereumTx response type.","properties":{"block_hash":{"format":"byte","title":"include the block hash for json-rpc to use","type":"string"},"block_timestamp":{"format":"uint64","title":"include the block timestamp for json-rpc to use","type":"string"},"gas_used":{"format":"uint64","title":"gas_used specifies how much gas was consumed by the transaction","type":"string"},"hash":{"title":"hash of the ethereum transaction in hex format. This hash differs from the\nCometBFT sha256 hash of the transaction bytes. See\nhttps://github.com/tendermint/tendermint/issues/6539 for reference","type":"string"},"logs":{"description":"logs contains the transaction hash and the proto-compatible ethereum\nlogs.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Log","type":"object"},"type":"array"},"max_used_gas":{"format":"uint64","title":"max_used_gas specifies the gas consumed by the transaction, not including refunds","type":"string"},"ret":{"format":"byte","title":"ret is the returned data from evm function (result or data supplied with\nrevert opcode)","type":"string"},"vm_error":{"title":"vm_error is the error returned by vm execution","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstalls":{"description":"MsgRegisterPreinstalls defines a Msg for creating preinstalls in evm state.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"preinstalls":{"description":"preinstalls defines the preinstalls to create.","items":{"$ref":"#/definitions/cosmos.evm.vm.v1.Preinstall","type":"object"},"type":"array"}},"type":"object"},"cosmos.evm.vm.v1.MsgRegisterPreinstallsResponse":{"description":"MsgRegisterPreinstallsResponse defines the response structure for executing a\nMsgRegisterPreinstalls message.","type":"object"},"cosmos.evm.vm.v1.MsgUpdateParams":{"description":"MsgUpdateParams defines a Msg for updating the x/vm module parameters.","properties":{"authority":{"description":"authority is the address of the governance account.","type":"string"},"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params defines the x/vm parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"cosmos.evm.vm.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"cosmos.evm.vm.v1.Params":{"properties":{"access_control":{"$ref":"#/definitions/cosmos.evm.vm.v1.AccessControl","title":"access_control defines the permission policy of the EVM"},"active_static_precompiles":{"items":{"type":"string"},"title":"active_static_precompiles defines the slice of hex addresses of the\nprecompiled contracts that are active","type":"array"},"evm_channels":{"items":{"type":"string"},"title":"evm_channels is the list of channel identifiers from EVM compatible chains","type":"array"},"evm_denom":{"description":"evm_denom represents the token denomination used to run the EVM state\ntransitions.","type":"string"},"extended_denom_options":{"$ref":"#/definitions/cosmos.evm.vm.v1.ExtendedDenomOptions"},"extra_eips":{"items":{"format":"int64","type":"string"},"title":"extra_eips defines the additional EIPs for the vm.Config","type":"array"},"history_serve_window":{"format":"uint64","type":"string"}},"title":"Params defines the EVM module parameters","type":"object"},"cosmos.evm.vm.v1.Preinstall":{"properties":{"address":{"title":"address in hex format of the preinstall contract","type":"string"},"code":{"title":"code in hex format for the preinstall contract","type":"string"},"name":{"title":"name of the preinstall contract","type":"string"}},"title":"Preinstall defines a contract that is preinstalled on-chain with a specific\ncontract address and bytecode","type":"object"},"cosmos.evm.vm.v1.QueryAccountResponse":{"description":"QueryAccountResponse is the response type for the Query/Account RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"},"code_hash":{"description":"code_hash is the hex-formatted code bytes from the EOA.","type":"string"},"nonce":{"description":"nonce is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBalanceResponse":{"description":"QueryBalanceResponse is the response type for the Query/Balance RPC method.","properties":{"balance":{"description":"balance is the balance of the EVM denomination.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryBaseFeeResponse":{"description":"QueryBaseFeeResponse returns the EIP1559 base fee.","properties":{"base_fee":{"title":"base_fee is the EIP1559 base fee","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryCodeResponse":{"description":"QueryCodeResponse is the response type for the Query/Code RPC\nmethod.","properties":{"code":{"description":"code represents the code bytes from an ethereum address.","format":"byte","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryConfigResponse":{"description":"QueryConfigResponse returns the EVM config.","properties":{"config":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"config is the evm configuration"}},"type":"object"},"cosmos.evm.vm.v1.QueryCosmosAccountResponse":{"description":"QueryCosmosAccountResponse is the response type for the Query/CosmosAccount\nRPC method.","properties":{"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"cosmos_address":{"description":"cosmos_address is the cosmos address of the account.","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryGlobalMinGasPriceResponse":{"properties":{"min_gas_price":{"title":"min_gas_price is the feemarket's min_gas_price","type":"string"}},"title":"QueryGlobalMinGasPriceResponse returns the GlobalMinGasPrice","type":"object"},"cosmos.evm.vm.v1.QueryParamsResponse":{"description":"QueryParamsResponse defines the response type for querying x/vm parameters.","properties":{"params":{"$ref":"#/definitions/cosmos.evm.vm.v1.Params","description":"params define the evm module parameters."}},"type":"object"},"cosmos.evm.vm.v1.QueryStorageResponse":{"description":"QueryStorageResponse is the response type for the Query/Storage RPC\nmethod.","properties":{"value":{"description":"value defines the storage state value hash associated with the given key.","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.QueryTraceBlockResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceBlockResponse defines TraceBlock response","type":"object"},"cosmos.evm.vm.v1.QueryTraceCallResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceCallResponse defines TraceCall response","type":"object"},"cosmos.evm.vm.v1.QueryTraceTxResponse":{"properties":{"data":{"format":"byte","title":"data is the response serialized in bytes","type":"string"}},"title":"QueryTraceTxResponse defines TraceTx response","type":"object"},"cosmos.evm.vm.v1.QueryValidatorAccountResponse":{"description":"QueryValidatorAccountResponse is the response type for the\nQuery/ValidatorAccount RPC method.","properties":{"account_address":{"description":"account_address is the cosmos address of the account in bech32 format.","type":"string"},"account_number":{"format":"uint64","title":"account_number is the account number","type":"string"},"sequence":{"description":"sequence is the account's sequence number.","format":"uint64","type":"string"}},"type":"object"},"cosmos.evm.vm.v1.TraceConfig":{"description":"TraceConfig holds extra parameters to trace functions.","properties":{"debug":{"title":"debug can be used to print output during capture end","type":"boolean"},"disable_stack":{"title":"disable_stack switches stack capture","type":"boolean"},"disable_storage":{"title":"disable_storage switches storage capture","type":"boolean"},"enable_memory":{"title":"enable_memory switches memory capture","type":"boolean"},"enable_return_data":{"title":"enable_return_data switches the capture of return data","type":"boolean"},"limit":{"format":"int32","title":"limit defines the maximum length of output, but zero means unlimited","type":"integer"},"overrides":{"$ref":"#/definitions/cosmos.evm.vm.v1.ChainConfig","title":"overrides can be used to execute a trace using future fork rules"},"reexec":{"format":"uint64","title":"reexec defines the number of blocks the tracer is willing to go back","type":"string"},"timeout":{"title":"timeout overrides the default timeout of 5 seconds for JavaScript-based\ntracing calls","type":"string"},"tracer":{"title":"tracer is a custom javascript tracer","type":"string"},"tracer_json_config":{"title":"tracer_json_config configures the tracer using a JSON string","type":"string"}},"type":"object"},"google.protobuf.Any":{"additionalProperties":{},"properties":{"@type":{"type":"string"}},"type":"object"},"google.rpc.Status":{"properties":{"code":{"format":"int32","type":"integer"},"details":{"items":{"$ref":"#/definitions/google.protobuf.Any","type":"object"},"type":"array"},"message":{"type":"string"}},"type":"object"},"lumera.action.v1.Action":{"description":"Action represents a specific action within the Lumera protocol.","properties":{"actionID":{"type":"string"},"actionType":{"$ref":"#/definitions/lumera.action.v1.ActionType"},"app_pubkey":{"format":"byte","type":"string"},"blockHeight":{"format":"int64","type":"string"},"creator":{"type":"string"},"expirationTime":{"format":"int64","type":"string"},"fileSizeKbs":{"format":"int64","type":"string"},"metadata":{"format":"byte","type":"string"},"price":{"type":"string"},"state":{"$ref":"#/definitions/lumera.action.v1.ActionState"},"superNodes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.action.v1.ActionState":{"default":"ACTION_STATE_UNSPECIFIED","description":"ActionState enum represents the various states an action can be in.\n\n - ACTION_STATE_UNSPECIFIED: The default state, used when the state is not specified.\n - ACTION_STATE_PENDING: The action is pending and has not yet been processed.\n - ACTION_STATE_PROCESSING: The action is currently being processed.\n - ACTION_STATE_DONE: The action has been completed successfully.\n - ACTION_STATE_APPROVED: The action has been approved.\n - ACTION_STATE_REJECTED: The action has been rejected.\n - ACTION_STATE_FAILED: The action has failed.\n - ACTION_STATE_EXPIRED: The action has expired and is no longer valid.","enum":["ACTION_STATE_UNSPECIFIED","ACTION_STATE_PENDING","ACTION_STATE_PROCESSING","ACTION_STATE_DONE","ACTION_STATE_APPROVED","ACTION_STATE_REJECTED","ACTION_STATE_FAILED","ACTION_STATE_EXPIRED"],"type":"string"},"lumera.action.v1.ActionType":{"default":"ACTION_TYPE_UNSPECIFIED","description":"ActionType enum represents the various types of actions that can be performed.\n\n - ACTION_TYPE_UNSPECIFIED: The default action type, used when the type is not specified.\n - ACTION_TYPE_SENSE: The action type for sense operations.\n - ACTION_TYPE_CASCADE: The action type for cascade operations.","enum":["ACTION_TYPE_UNSPECIFIED","ACTION_TYPE_SENSE","ACTION_TYPE_CASCADE"],"type":"string"},"lumera.action.v1.MsgApproveAction":{"description":"MsgApproveAction is the Msg/ApproveAction request type.","properties":{"actionId":{"type":"string"},"creator":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgApproveActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgApproveActionResponse defines the response structure for executing a MsgApproveAction","type":"object"},"lumera.action.v1.MsgFinalizeAction":{"description":"MsgFinalizeAction is the Msg/FinalizeAction request type.","properties":{"actionId":{"type":"string"},"actionType":{"type":"string"},"creator":{"title":"must be supernode address","type":"string"},"metadata":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgFinalizeActionResponse":{"title":"MsgFinalizeActionResponse defines the response structure for executing a MsgFinalizeAction","type":"object"},"lumera.action.v1.MsgRequestAction":{"description":"MsgRequestAction is the Msg/RequestAction request type.","properties":{"actionType":{"type":"string"},"app_pubkey":{"format":"byte","type":"string"},"creator":{"type":"string"},"expirationTime":{"type":"string"},"fileSizeKbs":{"type":"string"},"metadata":{"type":"string"},"price":{"type":"string"}},"type":"object"},"lumera.action.v1.MsgRequestActionResponse":{"properties":{"actionId":{"type":"string"},"status":{"type":"string"}},"title":"MsgRequestActionResponse defines the response structure for executing a MsgRequestAction","type":"object"},"lumera.action.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.action.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.action.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"base_action_fee":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","title":"Fees"},"expiration_duration":{"title":"Time Constraints","type":"string"},"fee_per_kbyte":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"foundation_fee_share":{"type":"string"},"max_actions_per_block":{"format":"uint64","title":"Limits","type":"string"},"max_dd_and_fingerprints":{"format":"uint64","type":"string"},"max_processing_time":{"type":"string"},"max_raptor_q_symbols":{"format":"uint64","type":"string"},"min_processing_time":{"type":"string"},"min_super_nodes":{"format":"uint64","type":"string"},"super_node_fee_share":{"title":"Reward Distribution","type":"string"},"svc_challenge_count":{"description":"Number of chunks to challenge (default: 8)","format":"int64","title":"LEP-5: Storage Verification Challenge parameters","type":"integer"},"svc_min_chunks_for_challenge":{"format":"int64","title":"Minimum chunks required for SVC (default: 4)","type":"integer"}},"type":"object"},"lumera.action.v1.QueryActionByMetadataResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryActionByMetadataResponse is a response type to query actions by metadata","type":"object"},"lumera.action.v1.QueryGetActionFeeResponse":{"properties":{"amount":{"type":"string"}},"title":"QueryGetActionFeeResponse is a response type to get action fee","type":"object"},"lumera.action.v1.QueryGetActionResponse":{"properties":{"action":{"$ref":"#/definitions/lumera.action.v1.Action"}},"title":"Response type for GetAction","type":"object"},"lumera.action.v1.QueryListActionsByBlockHeightResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByBlockHeightResponse is a response type to list actions by block height","type":"object"},"lumera.action.v1.QueryListActionsByCreatorResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsByCreatorResponse is a response type to list actions for a specific creator","type":"object"},"lumera.action.v1.QueryListActionsBySuperNodeResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsBySuperNodeResponse is a response type to list actions for a specific supernode","type":"object"},"lumera.action.v1.QueryListActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListActionsResponse is a response type to list actions","type":"object"},"lumera.action.v1.QueryListExpiredActionsResponse":{"properties":{"actions":{"items":{"$ref":"#/definitions/lumera.action.v1.Action","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"total":{"format":"uint64","type":"string"}},"title":"QueryListExpiredActionsResponse is a response type to list expired actions","type":"object"},"lumera.action.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.action.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.audit.v1.AccountIdentityMapping":{"description":"AccountIdentityMapping pairs an epoch-logical account with the current\naccount that should be contacted for that identity.","properties":{"current_account":{"type":"string"},"logical_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.EpochAnchor":{"description":"EpochAnchor is a minimal per-epoch on-chain anchor that freezes the deterministic seed\nand the eligible supernode sets used for deterministic selection off-chain.","properties":{"active_set_commitment":{"format":"byte","type":"string"},"active_supernode_accounts":{"description":"active_supernode_accounts is the sorted list of ACTIVE supernodes at epoch start.","items":{"type":"string"},"type":"array"},"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"params_commitment":{"description":"params_commitment is a hash commitment to Params (with defaults) at epoch start.","format":"byte","type":"string"},"seed":{"description":"seed is a fixed 32-byte value derived at epoch start (domain-separated).","format":"byte","type":"string"},"target_supernode_accounts":{"description":"target_supernode_accounts is the sorted list of eligible targets at epoch start:\nACTIVE + POSTPONED supernodes.","items":{"type":"string"},"type":"array"},"targets_set_commitment":{"format":"byte","type":"string"}},"type":"object"},"lumera.audit.v1.EpochReport":{"description":"EpochReport is a single per-epoch report submitted by a Supernode.","properties":{"current_submitter":{"description":"current_submitter is the live account that authenticated submission. It is\nintentionally distinct from supernode_account, the epoch-logical identity.\nEmpty decodes preserve reports written before identity continuity shipped.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"},"supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.Evidence":{"description":"Evidence is a stable outer record that stores evidence about an audited subject.\nType-specific fields are encoded into the `metadata` bytes field.","properties":{"action_id":{"description":"action_id optionally links this evidence to a specific action.","type":"string"},"evidence_id":{"description":"evidence_id is a chain-assigned unique identifier.","format":"uint64","type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType","description":"evidence_type is a stable discriminator used to interpret metadata."},"metadata":{"description":"metadata is protobuf-binary bytes of a type-specific Evidence metadata message.","format":"byte","type":"string"},"reported_height":{"description":"reported_height is the block height when the evidence was submitted.","format":"uint64","type":"string"},"reporter_address":{"description":"reporter_address is the submitter of the evidence.","type":"string"},"subject_address":{"description":"subject_address is the audited subject (e.g. supernode-related actor).","type":"string"}},"type":"object"},"lumera.audit.v1.EvidenceType":{"default":"EVIDENCE_TYPE_UNSPECIFIED","description":" - EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE: action finalization rejected due to an invalid signature / signature-derived data.\n - EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10: action finalization rejected because the attempted finalizer is not in the top-10 supernodes.\n - EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE: storage challenge failure evidence submitted by the deterministic challenger.\n - EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE: client-observed cascade flow failure (upload/download).","enum":["EVIDENCE_TYPE_UNSPECIFIED","EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE","EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10","EVIDENCE_TYPE_ACTION_EXPIRED","EVIDENCE_TYPE_STORAGE_CHALLENGE_FAILURE","EVIDENCE_TYPE_CASCADE_CLIENT_FAILURE"],"type":"string"},"lumera.audit.v1.HealOp":{"description":"HealOp is the chain-tracked storage-truth healing operation state.","properties":{"created_height":{"format":"uint64","type":"string"},"deadline_epoch_id":{"format":"uint64","type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"healer_supernode_account":{"type":"string"},"notes":{"type":"string"},"result_hash":{"type":"string"},"scheduled_epoch_id":{"format":"uint64","type":"string"},"status":{"$ref":"#/definitions/lumera.audit.v1.HealOpStatus"},"ticket_id":{"type":"string"},"updated_height":{"format":"uint64","type":"string"},"verifier_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.HealOpStatus":{"default":"HEAL_OP_STATUS_UNSPECIFIED","enum":["HEAL_OP_STATUS_UNSPECIFIED","HEAL_OP_STATUS_SCHEDULED","HEAL_OP_STATUS_IN_PROGRESS","HEAL_OP_STATUS_HEALER_REPORTED","HEAL_OP_STATUS_VERIFIED","HEAL_OP_STATUS_FAILED","HEAL_OP_STATUS_EXPIRED"],"type":"string"},"lumera.audit.v1.HostReport":{"description":"HostReport is the Supernode's self-reported host metrics and counters for an epoch.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes, self-reported by the SuperNode.\nCarried on HostReport purely as a metric-courier on the audit epoch report\nchannel — the audit module does NOT consume this value for its own\nconsensus logic (LEP-6 §12). On successful epoch-report acceptance the\naudit handler bridges this value into x/supernode SupernodeMetricsState,\nwhich is the sole source consulted by Everlight payout / eligibility.\nMUST be finite and non-negative; zero is valid (empty Kademlia store).","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"failed_actions_count":{"format":"int64","type":"integer"},"inbound_port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"mem_usage_percent":{"format":"double","type":"number"}},"type":"object"},"lumera.audit.v1.HostReportEntry":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"report_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealComplete":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_manifest_hash":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgClaimHealCompleteResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEpochReport":{"properties":{"creator":{"description":"creator is the transaction signer.","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"host_report":{"$ref":"#/definitions/lumera.audit.v1.HostReport"},"storage_challenge_observations":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeObservation","type":"object"},"type":"array"},"storage_proof_results":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResult","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.MsgSubmitEpochReportResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitEvidence":{"properties":{"action_id":{"type":"string"},"creator":{"type":"string"},"evidence_type":{"$ref":"#/definitions/lumera.audit.v1.EvidenceType"},"metadata":{"description":"metadata is JSON for the type-specific Evidence metadata message.\nThe chain stores protobuf-binary bytes derived from this JSON.","type":"string"},"subject_address":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitEvidenceResponse":{"properties":{"evidence_id":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerification":{"properties":{"creator":{"type":"string"},"details":{"type":"string"},"heal_op_id":{"format":"uint64","type":"string"},"verification_hash":{"type":"string"},"verified":{"type":"boolean"}},"type":"object"},"lumera.audit.v1.MsgSubmitHealVerificationResponse":{"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidence":{"properties":{"challenged_result_transcript_hash":{"type":"string"},"challenged_supernode_account":{"type":"string"},"creator":{"type":"string"},"details":{"type":"string"},"epoch_id":{"format":"uint64","type":"string"},"recheck_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"recheck_transcript_hash":{"type":"string"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.audit.v1.MsgSubmitStorageRecheckEvidenceResponse":{"type":"object"},"lumera.audit.v1.MsgUpdateParams":{"properties":{"authority":{"type":"string"},"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.MsgUpdateParamsResponse":{"type":"object"},"lumera.audit.v1.NodeSuspicionState":{"description":"NodeSuspicionState is the persisted storage-truth node-level suspicion snapshot.","properties":{"class_a_count_window":{"format":"int64","type":"integer"},"class_b_count_window":{"format":"int64","type":"integer"},"clean_pass_count":{"format":"int64","type":"integer"},"clean_pass_count_at_postpone":{"description":"Per 121-F8 — recovery delta from snapshot, not cumulative.","format":"int64","type":"integer"},"distinct_ticket_fail_window":{"format":"int64","type":"integer"},"last_class_a_epoch":{"format":"uint64","type":"string"},"last_class_b_epoch":{"format":"uint64","type":"string"},"last_clean_pass_epoch":{"format":"uint64","type":"string"},"last_index_fail_epoch":{"format":"uint64","type":"string"},"last_old_fail_epoch":{"format":"uint64","type":"string"},"last_recent_fail_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"supernode_account":{"type":"string"},"suspicion_score":{"format":"int64","type":"string"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.Params":{"description":"Params defines the parameters for the audit module.","properties":{"action_finalization_not_in_top10_consecutive_epochs":{"description":"action_finalization_not_in_top10_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_not_in_top10_evidences_per_epoch":{"description":"action_finalization_not_in_top10_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_NOT_IN_TOP_10.","format":"int64","type":"integer"},"action_finalization_recovery_epochs":{"description":"action_finalization_recovery_epochs is the number of epochs to wait before considering recovery.","format":"int64","type":"integer"},"action_finalization_recovery_max_total_bad_evidences":{"description":"action_finalization_recovery_max_total_bad_evidences is the maximum allowed total count of bad\naction-finalization evidences in the recovery epoch-span for auto-recovery to occur.\nRecovery happens ONLY IF total_bad \u003c this value.","format":"int64","type":"integer"},"action_finalization_signature_failure_consecutive_epochs":{"description":"action_finalization_signature_failure_consecutive_epochs is the consecutive epochs threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"action_finalization_signature_failure_evidences_per_epoch":{"description":"action_finalization_signature_failure_evidences_per_epoch is the per-epoch count threshold\nfor EVIDENCE_TYPE_ACTION_FINALIZATION_SIGNATURE_FAILURE.","format":"int64","type":"integer"},"consecutive_epochs_to_postpone":{"description":"Number of consecutive epochs a required port must be reported CLOSED by peers\nat or above peer_port_postpone_threshold_percent before postponing the supernode.","format":"int64","type":"integer"},"epoch_length_blocks":{"format":"uint64","type":"string"},"epoch_zero_height":{"description":"epoch_zero_height defines the reference chain height at which epoch_id = 0 starts.\nThis makes epoch boundaries deterministic from genesis without needing to query state.","format":"uint64","type":"string"},"keep_last_epoch_entries":{"description":"How many completed epochs to keep in state for epoch-scoped data like EpochReport\nand related indices. Pruning runs at epoch end.","format":"uint64","type":"string"},"max_probe_targets_per_epoch":{"format":"int64","type":"integer"},"min_cpu_free_percent":{"description":"Minimum required host free capacity (self reported).\nfree% = 100 - usage%\nA usage% of 0 is treated as \"unknown\" (no action).","format":"int64","type":"integer"},"min_disk_free_percent":{"format":"int64","type":"integer"},"min_mem_free_percent":{"format":"int64","type":"integer"},"min_probe_targets_per_epoch":{"format":"int64","type":"integer"},"peer_port_postpone_threshold_percent":{"description":"Minimum percent (1-100) of peer reports that must report a required port as CLOSED\nfor the port to be treated as CLOSED for postponement purposes.\n\n100 means unanimous.\nExample: to approximate a 2/3 threshold, use 66 (since 2/3 ≈ 66.6%).","format":"int64","type":"integer"},"peer_quorum_reports":{"format":"int64","type":"integer"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"sc_challengers_per_epoch":{"format":"int64","type":"integer"},"sc_enabled":{"description":"Storage Challenge (SC) params.","type":"boolean"},"storage_truth_challenge_target_divisor":{"format":"int64","type":"integer"},"storage_truth_class_a_fault_window":{"description":"Class A and B fault windows.","format":"int64","type":"integer"},"storage_truth_class_b_fault_window":{"format":"int64","type":"integer"},"storage_truth_compound_range_len_bytes":{"format":"int64","type":"integer"},"storage_truth_compound_ranges_per_artifact":{"format":"int64","type":"integer"},"storage_truth_contradiction_window_epochs":{"description":"Contradiction confirmation window in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_divergence_window_epochs":{"description":"Statistical divergence scoring params.","format":"int64","type":"integer"},"storage_truth_enforcement_mode":{"$ref":"#/definitions/lumera.audit.v1.StorageTruthEnforcementMode","description":"Storage-truth rollout gate."},"storage_truth_heal_deadline_epochs":{"description":"Heal deadline in epochs (default 3).","format":"int64","type":"integer"},"storage_truth_heal_verifier_count":{"description":"Number of verifier supernodes assigned per heal-op (NEW-B-3, default 2).\nVerifiers cross-check the healer's recovery; making this a Param allows\ngovernance to tune redundancy if heal volume / failure rate shifts.","format":"int64","type":"integer"},"storage_truth_max_self_heal_ops_per_epoch":{"description":"Storage-truth scoring and healing params.","format":"int64","type":"integer"},"storage_truth_node_suspicion_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_postpone":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_probation":{"format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_strong_postpone":{"description":"Strong-postpone threshold (default 140).","format":"int64","type":"string"},"storage_truth_node_suspicion_threshold_watch":{"format":"int64","type":"string"},"storage_truth_old_bucket_min_blocks":{"format":"uint64","type":"string"},"storage_truth_old_class_a_fault_window":{"description":"OLD Class-A distinct-ticket window in epochs (default 21).","format":"int64","type":"integer"},"storage_truth_pattern_escalation_window":{"description":"Pattern escalation window in epochs (default 14).","format":"int64","type":"integer"},"storage_truth_probation_epochs":{"format":"int64","type":"integer"},"storage_truth_recent_bucket_max_blocks":{"description":"Storage-truth challenge shape params.","format":"uint64","type":"string"},"storage_truth_recovery_clean_pass_count":{"description":"Recovery requires this many clean passes (default 3).","format":"int64","type":"integer"},"storage_truth_reporter_ineligible_duration_epochs":{"description":"Reporter challenger ineligibility duration in epochs (default 7).","format":"int64","type":"integer"},"storage_truth_reporter_min_reports_for_divergence":{"format":"int64","type":"integer"},"storage_truth_reporter_reliability_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_degraded_threshold":{"description":"New LEP-6 spec-alignment params.\nReporter reliability degraded threshold (positive-penalty model).","format":"int64","type":"string"},"storage_truth_reporter_reliability_ineligible_threshold":{"format":"int64","type":"string"},"storage_truth_reporter_reliability_low_trust_threshold":{"format":"int64","type":"string"},"storage_truth_strong_recovery_clean_pass_count":{"description":"Strong-band recovery clean-pass requirement (F121-F12, default 5).","format":"int64","type":"integer"},"storage_truth_ticket_deterioration_decay_per_epoch":{"format":"int64","type":"string"},"storage_truth_ticket_deterioration_heal_threshold":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.PortState":{"default":"PORT_STATE_UNKNOWN","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.audit.v1.QueryAssignedTargetsResponse":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"description":"reporter_supernode_account is the epoch-logical identity corresponding to\nthe current account supplied in the request.","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"target_account_mappings":{"description":"target_account_mappings preserves target_supernode_accounts order while\nalso exposing the live account to contact for each logical target.","items":{"$ref":"#/definitions/lumera.audit.v1.AccountIdentityMapping","type":"object"},"type":"array"},"target_supernode_accounts":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryCurrentEpochResponse":{"properties":{"epoch_end_height":{"format":"int64","type":"string"},"epoch_id":{"format":"uint64","type":"string"},"epoch_start_height":{"format":"int64","type":"string"}},"type":"object"},"lumera.audit.v1.QueryEpochAnchorResponse":{"properties":{"anchor":{"$ref":"#/definitions/lumera.audit.v1.EpochAnchor"}},"type":"object"},"lumera.audit.v1.QueryEpochReportResponse":{"properties":{"report":{"$ref":"#/definitions/lumera.audit.v1.EpochReport"}},"type":"object"},"lumera.audit.v1.QueryEpochReportsByReporterResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.EpochReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByActionResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryEvidenceByIdResponse":{"properties":{"evidence":{"$ref":"#/definitions/lumera.audit.v1.Evidence"}},"type":"object"},"lumera.audit.v1.QueryEvidenceBySubjectResponse":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.audit.v1.Evidence","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpResponse":{"properties":{"heal_op":{"$ref":"#/definitions/lumera.audit.v1.HealOp"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByStatusResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHealOpsByTicketResponse":{"properties":{"heal_ops":{"items":{"$ref":"#/definitions/lumera.audit.v1.HealOp","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.audit.v1.QueryHostReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.HostReportEntry","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryNodeSuspicionStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.NodeSuspicionState"}},"type":"object"},"lumera.audit.v1.QueryParamsResponse":{"properties":{"params":{"$ref":"#/definitions/lumera.audit.v1.Params"}},"type":"object"},"lumera.audit.v1.QueryReporterReliabilityStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.ReporterReliabilityState"}},"type":"object"},"lumera.audit.v1.QueryStorageChallengeReportsResponse":{"properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"reports":{"items":{"$ref":"#/definitions/lumera.audit.v1.StorageChallengeReport","type":"object"},"type":"array"}},"type":"object"},"lumera.audit.v1.QueryTicketDeteriorationStateResponse":{"properties":{"state":{"$ref":"#/definitions/lumera.audit.v1.TicketDeteriorationState"}},"type":"object"},"lumera.audit.v1.ReporterReliabilityState":{"description":"ReporterReliabilityState is the persisted storage-truth reporter reliability snapshot.","properties":{"contradiction_count":{"format":"uint64","type":"string"},"ineligible_until_epoch":{"format":"uint64","type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"reliability_score":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"},"trust_band":{"$ref":"#/definitions/lumera.audit.v1.ReporterTrustBand"},"window_negative_count":{"format":"int64","type":"integer"},"window_positive_count":{"format":"int64","type":"integer"},"window_start_epoch":{"format":"uint64","type":"string"}},"type":"object"},"lumera.audit.v1.ReporterTrustBand":{"default":"REPORTER_TRUST_BAND_UNSPECIFIED","enum":["REPORTER_TRUST_BAND_UNSPECIFIED","REPORTER_TRUST_BAND_NORMAL","REPORTER_TRUST_BAND_LOW_TRUST","REPORTER_TRUST_BAND_CHALLENGER_INELIGIBLE","REPORTER_TRUST_BAND_DEGRADED"],"type":"string"},"lumera.audit.v1.StorageChallengeObservation":{"description":"StorageChallengeObservation is a prober's reachability observation about an assigned target.","properties":{"port_states":{"description":"port_states[i] refers to required_open_ports[i] for the epoch.","items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"target_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageChallengeReport":{"properties":{"epoch_id":{"format":"uint64","type":"string"},"port_states":{"items":{"$ref":"#/definitions/lumera.audit.v1.PortState"},"type":"array"},"report_height":{"format":"int64","type":"string"},"reporter_supernode_account":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofArtifactClass":{"default":"STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_ARTIFACT_CLASS_UNSPECIFIED","STORAGE_PROOF_ARTIFACT_CLASS_INDEX","STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL"],"type":"string"},"lumera.audit.v1.StorageProofBucketType":{"default":"STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","enum":["STORAGE_PROOF_BUCKET_TYPE_UNSPECIFIED","STORAGE_PROOF_BUCKET_TYPE_RECENT","STORAGE_PROOF_BUCKET_TYPE_OLD","STORAGE_PROOF_BUCKET_TYPE_PROBATION","STORAGE_PROOF_BUCKET_TYPE_RECHECK"],"type":"string"},"lumera.audit.v1.StorageProofResult":{"description":"StorageProofResult captures one storage-truth storage-proof check outcome.\n\nNOTE: StorageProofResult stores transcript_hash plus a compact deterministic\nderivation/signature envelope so transcript disagreements become explicit on-chain.","properties":{"artifact_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofArtifactClass"},"artifact_count":{"description":"artifact_count is the class-specific denominator used for deterministic\nordinal selection: artifact_ordinal = H(...) mod artifact_count.","format":"int64","type":"integer"},"artifact_key":{"type":"string"},"artifact_ordinal":{"description":"artifact_ordinal is the deterministic ordinal selected inside the artifact class.","format":"int64","type":"integer"},"bucket_type":{"$ref":"#/definitions/lumera.audit.v1.StorageProofBucketType"},"challenger_signature":{"description":"challenger_signature is the challenger's signature over transcript commitment.","type":"string"},"challenger_supernode_account":{"type":"string"},"derivation_input_hash":{"description":"derivation_input_hash commits deterministic derivation inputs (seed, range\nselection inputs, and resolver inputs) used off-chain for transcript build.","type":"string"},"details":{"description":"details is an optional short diagnostic summary for non-pass outcomes.","type":"string"},"observer_attestation_signatures":{"description":"observer_attestation_signatures carries observer attestations for the\ntranscript commitment when available.","items":{"type":"string"},"type":"array"},"result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"target_supernode_account":{"type":"string"},"ticket_id":{"description":"ticket_id identifies the ticket selected by deterministic bucket logic.","type":"string"},"transcript_hash":{"type":"string"}},"type":"object"},"lumera.audit.v1.StorageProofResultClass":{"default":"STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","enum":["STORAGE_PROOF_RESULT_CLASS_UNSPECIFIED","STORAGE_PROOF_RESULT_CLASS_PASS","STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH","STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE","STORAGE_PROOF_RESULT_CLASS_OBSERVER_QUORUM_FAIL","STORAGE_PROOF_RESULT_CLASS_NO_ELIGIBLE_TICKET","STORAGE_PROOF_RESULT_CLASS_INVALID_TRANSCRIPT","STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL"],"type":"string"},"lumera.audit.v1.StorageTruthEnforcementMode":{"default":"STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","enum":["STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED","STORAGE_TRUTH_ENFORCEMENT_MODE_SHADOW","STORAGE_TRUTH_ENFORCEMENT_MODE_SOFT","STORAGE_TRUTH_ENFORCEMENT_MODE_FULL"],"type":"string"},"lumera.audit.v1.TicketDeteriorationState":{"description":"TicketDeteriorationState is the persisted storage-truth ticket deterioration snapshot.","properties":{"active_heal_op_id":{"format":"uint64","type":"string"},"contradiction_count":{"format":"uint64","type":"string"},"deterioration_score":{"format":"int64","type":"string"},"distinct_holder_failure_count":{"format":"int64","type":"integer"},"last_failure_epoch":{"format":"uint64","type":"string"},"last_heal_epoch":{"format":"uint64","type":"string"},"last_index_failure_epoch":{"format":"uint64","type":"string"},"last_reporter_supernode_account":{"type":"string"},"last_result_class":{"$ref":"#/definitions/lumera.audit.v1.StorageProofResultClass"},"last_result_epoch":{"format":"uint64","type":"string"},"last_target_supernode_account":{"type":"string"},"last_updated_epoch":{"format":"uint64","type":"string"},"old_bucket_failure_epoch":{"format":"uint64","type":"string"},"probation_until_epoch":{"format":"uint64","type":"string"},"recent_bucket_failure_epoch":{"format":"uint64","type":"string"},"recent_failure_epoch_count":{"format":"int64","type":"integer"},"ticket_id":{"type":"string"}},"type":"object"},"lumera.claim.ClaimRecord":{"description":"ClaimRecord represents a record of a claim made by a user.","properties":{"balance":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"claimTime":{"format":"int64","type":"string"},"claimed":{"type":"boolean"},"destAddress":{"type":"string"},"oldAddress":{"type":"string"},"vestedTier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgClaim":{"description":"MsgClaim is the Msg/Claim request type.","properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"}},"type":"object"},"lumera.claim.MsgClaimResponse":{"title":"MsgClaimResponse defines the response structure for executing a","type":"object"},"lumera.claim.MsgDelayedClaim":{"properties":{"creator":{"type":"string"},"newAddress":{"type":"string"},"oldAddress":{"type":"string"},"pubKey":{"type":"string"},"signature":{"type":"string"},"tier":{"format":"int64","type":"integer"}},"type":"object"},"lumera.claim.MsgDelayedClaimResponse":{"type":"object"},"lumera.claim.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.\nMsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params defines the x/claim parameters to update.\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.claim.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.claim.Params":{"description":"Params defines the parameters for the module.","properties":{"claim_end_time":{"format":"int64","type":"string"},"enable_claims":{"type":"boolean"},"max_claims_per_block":{"format":"uint64","type":"string"}},"type":"object"},"lumera.claim.QueryClaimRecordResponse":{"description":"QueryClaimRecordResponse is response type for the Query/ClaimRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.claim.ClaimRecord"}},"type":"object"},"lumera.claim.QueryListClaimedResponse":{"properties":{"claims":{"items":{"$ref":"#/definitions/lumera.claim.ClaimRecord","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.claim.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.claim.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.erc20policy.AllowedBaseDenomTrace":{"description":"AllowedBaseDenomTrace binds a base denomination to a specific IBC provenance\npath. The trace is the full expected sequence of hops for the received denom:\n[{destPort, destChannel}, ...priorHops]. An empty trace is a valid placeholder\nthat never matches a real IBC packet (all packets have at least one hop).","properties":{"base_denom":{"type":"string"},"trace":{"items":{"$ref":"#/definitions/lumera.erc20policy.SourceHop","type":"object"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicy":{"description":"MsgSetRegistrationPolicy configures the IBC voucher ERC20 auto-registration\npolicy. It allows governance to control which IBC denoms are automatically\nregistered as ERC20 token pairs on first IBC receive.","properties":{"add_base_denom_traces":{"description":"add_base_denom_traces adds provenance-bound base denom entries to the\nallowlist. Each entry binds a base denom (e.g. \"uatom\") to a specific\nIBC trace (the full expected hop sequence). Governance must provide the\ntrace to activate a base denom entry.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"add_denoms":{"description":"add_denoms is a list of exact IBC denoms (e.g. \"ibc/HASH...\") to add to\nthe allowlist. Only meaningful when mode is \"allowlist\".","items":{"type":"string"},"type":"array"},"authority":{"description":"authority is the address that controls the policy (defaults to x/gov).","type":"string"},"mode":{"description":"mode is the registration policy mode: \"all\", \"allowlist\", or \"none\".\nIf empty, the mode is not changed.","type":"string"},"remove_base_denom_traces":{"description":"remove_base_denom_traces removes provenance-bound base denom entries.","items":{"$ref":"#/definitions/lumera.erc20policy.AllowedBaseDenomTrace","type":"object"},"type":"array"},"remove_denoms":{"description":"remove_denoms is a list of exact IBC denoms to remove from the allowlist.","items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.erc20policy.MsgSetRegistrationPolicyResponse":{"description":"MsgSetRegistrationPolicyResponse is the response type for\nMsgSetRegistrationPolicy.","type":"object"},"lumera.erc20policy.SourceHop":{"description":"SourceHop represents a single port/channel pair in an IBC denom trace.","properties":{"channel_id":{"type":"string"},"port_id":{"type":"string"}},"type":"object"},"lumera.evmigration.LegacyAccountInfo":{"description":"LegacyAccountInfo provides summary information about a legacy account\nthat has not yet been migrated.","properties":{"address":{"description":"address is the bech32 account address.","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms.","type":"string"},"has_delegations":{"description":"has_delegations is true if the account has active staking delegations.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the account is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.MigrationProof":{"properties":{"multisig":{"$ref":"#/definitions/lumera.evmigration.MultisigProof"},"single":{"$ref":"#/definitions/lumera.evmigration.SingleKeyProof"}},"type":"object"},"lumera.evmigration.MigrationRecord":{"description":"MigrationRecord stores the result of a completed legacy account migration,\nrecording the source and destination addresses plus the time and height.","properties":{"legacy_address":{"description":"legacy_address is the coin-type-118 source address that was migrated.","type":"string"},"migration_height":{"description":"migration_height is the block height when migration completed.","format":"int64","type":"string"},"migration_time":{"description":"migration_time is the block time (unix seconds) when migration completed.","format":"int64","type":"string"},"new_address":{"description":"new_address is the coin-type-60 destination address.","type":"string"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccount":{"description":"MsgClaimLegacyAccount migrates on-chain state from legacy_address to new_address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgClaimLegacyAccountResponse":{"description":"MsgClaimLegacyAccountResponse is the response type for MsgClaimLegacyAccount.","type":"object"},"lumera.evmigration.MsgMigrateValidator":{"description":"MsgMigrateValidator migrates a validator operator from legacy to new address.","properties":{"legacy_address":{"type":"string"},"legacy_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"},"new_address":{"type":"string"},"new_proof":{"$ref":"#/definitions/lumera.evmigration.MigrationProof"}},"type":"object"},"lumera.evmigration.MsgMigrateValidatorResponse":{"description":"MsgMigrateValidatorResponse is the response type for MsgMigrateValidator.","type":"object"},"lumera.evmigration.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params defines the module parameters to update.\n\nNOTE: All parameters must be supplied."}},"type":"object"},"lumera.evmigration.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.evmigration.MultisigProof":{"properties":{"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signer_indices":{"items":{"format":"int64","type":"integer"},"type":"array"},"sub_pub_keys":{"items":{"format":"byte","type":"string"},"type":"array"},"sub_signatures":{"items":{"format":"byte","type":"string"},"type":"array"},"threshold":{"format":"int64","type":"integer"}},"type":"object"},"lumera.evmigration.Params":{"description":"Params defines the governance-controlled parameters for the evmigration module.\nThese knobs determine when migrations are accepted and how much work the\nchain performs per block during the legacy-to-EVM migration window.","properties":{"canary_legacy_addresses":{"description":"canary_legacy_addresses optionally restricts migration to the exact,\ncanonical legacy source addresses listed here. An empty list leaves\nmigration open when enable_migration is true. Entries must be unique and\nsorted lexicographically; at most 64 entries are permitted.","items":{"type":"string"},"type":"array"},"enable_migration":{"description":"enable_migration is the master switch for the migration window.\nWhen false, all MsgClaimLegacyAccount and MsgMigrateValidator messages\nare rejected regardless of other parameter values.\nGovernance should set this to false once the migration window closes.\nDefault: false; governance must enable canary or open mode explicitly.","type":"boolean"},"max_migrations_per_block":{"description":"max_migrations_per_block is the maximum number of MsgClaimLegacyAccount\nmessages processed in a single block. Once this limit is reached,\nadditional claims in the same block are rejected. This prevents a burst\nof migrations from consuming excessive block gas.\nDefault: 50.","format":"uint64","type":"string"},"max_multisig_sub_keys":{"description":"max_multisig_sub_keys caps the number of sub-keys in a multisig legacy\naccount's MultisigProof. Bounds per-tx verification cost.\nDefault: 20.","format":"int64","type":"integer"},"max_validator_delegations":{"description":"max_validator_delegations is the safety cap for MsgMigrateValidator.\nA validator migration must re-key every delegation and unbonding-delegation\nrecord. If the total count exceeds this threshold the message is rejected\nbecause the gas cost of iterating all records would be prohibitive.\nValidators that exceed the cap must shed delegations before migrating.\nDefault: 2000.","format":"uint64","type":"string"},"migration_end_time":{"description":"migration_end_time is an optional hard deadline expressed as a unix\ntimestamp (seconds). If non-zero, any migration message whose block time\nexceeds this value is rejected. A value of 0 disables the deadline,\nleaving enable_migration as the sole on/off control.\nDefault: 0 (no deadline).","format":"int64","type":"string"}},"type":"object"},"lumera.evmigration.QueryLegacyAccountsResponse":{"description":"QueryLegacyAccountsResponse is the response type for the Query/LegacyAccounts RPC method.","properties":{"accounts":{"description":"accounts is the list of legacy accounts that need migration.","items":{"$ref":"#/definitions/lumera.evmigration.LegacyAccountInfo","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."}},"type":"object"},"lumera.evmigration.QueryMigratedAccountsResponse":{"description":"QueryMigratedAccountsResponse is the response type for the Query/MigratedAccounts RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationEstimateResponse":{"description":"QueryMigrationEstimateResponse is the response type for the Query/MigrationEstimate RPC method.\nIt provides a dry-run estimate of what would be migrated.","properties":{"action_count":{"description":"action_count is the number of action records where this address appears\neither as creator or in the SuperNodes list.","format":"uint64","type":"string"},"authz_grant_count":{"description":"authz_grant_count is the number of authz grants as granter or grantee.","format":"uint64","type":"string"},"balance_summary":{"description":"balance_summary is a human-readable total balance across all denoms (e.g. \"10000000000ulume\").","type":"string"},"delegation_count":{"description":"delegation_count is the number of active delegations from this address.","format":"uint64","type":"string"},"feegrant_count":{"description":"feegrant_count is the number of fee allowances as granter or grantee.","format":"uint64","type":"string"},"has_supernode":{"description":"has_supernode is true if the legacy address owns a registered supernode.","type":"boolean"},"is_multisig":{"description":"is_multisig is true when the account's on-chain pubkey is a flat Cosmos\nmultisig of secp256k1 sub-keys.","type":"boolean"},"is_validator":{"description":"is_validator is true if the legacy address is a validator operator.","type":"boolean"},"num_signers":{"description":"num_signers is N for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"redelegation_count":{"description":"redelegation_count is the number of redelegation entries.","format":"uint64","type":"string"},"rejection_reason":{"description":"rejection_reason is non-empty if would_succeed is false.","type":"string"},"threshold":{"description":"threshold is K for K-of-N multisig (0 when !is_multisig).","format":"int64","type":"integer"},"total_touched":{"description":"total_touched is the sum of all records that would be re-keyed.","format":"uint64","type":"string"},"unbonding_count":{"description":"unbonding_count is the number of unbonding delegation entries.","format":"uint64","type":"string"},"val_delegation_count":{"description":"val_delegation_count is delegations TO this validator (from all delegators).\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_redelegation_count":{"description":"val_redelegation_count is redelegations referencing this validator as src or dst.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"val_unbonding_count":{"description":"val_unbonding_count is unbonding delegations TO this validator.\nPopulated only when is_validator is true.","format":"uint64","type":"string"},"validator_jailed":{"description":"validator_jailed is the staking jailed flag of the validator entity.\nPopulated only when is_validator is true. A jailed validator is always\nalso Unbonding or Unbonded; surfacing both fields lets callers\ndistinguish \"jailed for downtime/equivocation\" (actionable: unjail\nafter slashing window) from \"voluntarily unbonded\" (not actionable).","type":"boolean"},"validator_status":{"description":"validator_status is the staking BondStatus of the validator entity, as\na stable enum string (\"BOND_STATUS_BONDED\" | \"BOND_STATUS_UNBONDING\" |\n\"BOND_STATUS_UNBONDED\" | \"BOND_STATUS_UNSPECIFIED\"). Populated only when\nis_validator is true; empty otherwise. Surfaced so callers can show why\nwould_succeed is false without a separate staking query.","type":"string"},"would_succeed":{"description":"would_succeed is false if migration would be rejected.","type":"boolean"}},"type":"object"},"lumera.evmigration.QueryMigrationRecordByNewAddressResponse":{"description":"QueryMigrationRecordByNewAddressResponse is the response type for the Query/MigrationRecordByNewAddress RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordResponse":{"description":"QueryMigrationRecordResponse is the response type for the Query/MigrationRecord RPC method.","properties":{"record":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","description":"record is the migration record, or nil if not found."}},"type":"object"},"lumera.evmigration.QueryMigrationRecordsResponse":{"description":"QueryMigrationRecordsResponse is the response type for the Query/MigrationRecords RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse","description":"pagination defines the pagination in the response."},"records":{"description":"records is the list of completed migration records.","items":{"$ref":"#/definitions/lumera.evmigration.MigrationRecord","type":"object"},"type":"array"}},"type":"object"},"lumera.evmigration.QueryMigrationStatsResponse":{"description":"QueryMigrationStatsResponse is the response type for the Query/MigrationStats RPC method.\nIt provides aggregate counters for the migration dashboard.","properties":{"total_legacy":{"description":"total_legacy is the number of accounts that still have legacy state.","format":"uint64","type":"string"},"total_legacy_staked":{"description":"total_legacy_staked is the subset of total_legacy with active delegations.","format":"uint64","type":"string"},"total_legacy_with_pubkey":{"description":"total_legacy_with_pubkey is the subset of total_legacy whose pubkey is already on-chain.","format":"uint64","type":"string"},"total_legacy_without_pubkey":{"description":"total_legacy_without_pubkey is the subset of total_legacy whose pubkey is nil on-chain.","format":"uint64","type":"string"},"total_migrated":{"description":"total_migrated is the number of accounts that completed migration (O(1) from state counter).","format":"uint64","type":"string"},"total_validators_legacy":{"description":"total_validators_legacy is the number of validators with legacy operator address.","format":"uint64","type":"string"},"total_validators_migrated":{"description":"total_validators_migrated is the number of validators that completed migration.","format":"uint64","type":"string"}},"type":"object"},"lumera.evmigration.QueryParamsResponse":{"description":"QueryParamsResponse is the response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.evmigration.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.evmigration.SigFormat":{"default":"SIG_FORMAT_UNSPECIFIED","description":"SigFormat enumerates accepted signing envelopes for migration proofs.\n\n - SIG_FORMAT_CLI: Sign(SHA256(payload)) via Cosmos keyring; Sign(payload → Keccak256) for eth keyring\n - SIG_FORMAT_ADR036: ADR-036 signArbitrary canonical JSON\n - SIG_FORMAT_EIP191: Eth \"\\x19Ethereum Signed Message:\\n…\" envelope — new-side single-key proofs only","enum":["SIG_FORMAT_UNSPECIFIED","SIG_FORMAT_CLI","SIG_FORMAT_ADR036","SIG_FORMAT_EIP191"],"type":"string"},"lumera.evmigration.SingleKeyProof":{"properties":{"pub_key":{"format":"byte","type":"string"},"sig_format":{"$ref":"#/definitions/lumera.evmigration.SigFormat"},"signature":{"format":"byte","type":"string"}},"type":"object"},"lumera.lumeraid.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.lumeraid.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.lumeraid.Params":{"description":"Params defines the parameters for the module.","type":"object"},"lumera.lumeraid.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.lumeraid.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.Evidence":{"description":"Evidence defines the evidence structure for the supernode module.","properties":{"action_id":{"type":"string"},"description":{"type":"string"},"evidence_type":{"type":"string"},"height":{"format":"int32","type":"integer"},"reporter_address":{"type":"string"},"severity":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.IPAddressHistory":{"properties":{"address":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.MetricValue":{"properties":{"name":{"type":"string"},"value":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.MetricsAggregate":{"properties":{"height":{"format":"int64","type":"string"},"metrics":{"items":{"$ref":"#/definitions/lumera.supernode.v1.MetricValue","type":"object"},"type":"array"},"report_count":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgDeregisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgRegisterSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgRegisterSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetrics":{"properties":{"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgReportSupernodeMetricsResponse":{"properties":{"compliant":{"type":"boolean"},"issues":{"items":{"type":"string"},"type":"array"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernode":{"properties":{"creator":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStartSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgStopSupernode":{"properties":{"creator":{"type":"string"},"reason":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgStopSupernodeResponse":{"type":"object"},"lumera.supernode.v1.MsgUpdateParams":{"description":"MsgUpdateParams is the Msg/UpdateParams request type.","properties":{"authority":{"description":"authority is the address that controls the module (defaults to x/gov unless overwritten).","type":"string"},"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"NOTE: All parameters must be supplied."}},"type":"object"},"lumera.supernode.v1.MsgUpdateParamsResponse":{"description":"MsgUpdateParamsResponse defines the response structure for executing a\nMsgUpdateParams message.","type":"object"},"lumera.supernode.v1.MsgUpdateSupernode":{"properties":{"creator":{"type":"string"},"ipAddress":{"type":"string"},"note":{"type":"string"},"p2p_port":{"type":"string"},"supernodeAccount":{"type":"string"},"validatorAddress":{"type":"string"}},"type":"object"},"lumera.supernode.v1.MsgUpdateSupernodeResponse":{"type":"object"},"lumera.supernode.v1.Params":{"description":"Params defines the parameters for the module.","properties":{"evidence_retention_period":{"type":"string"},"inactivity_penalty_period":{"type":"string"},"max_cpu_usage_percent":{"format":"uint64","type":"string"},"max_mem_usage_percent":{"format":"uint64","type":"string"},"max_storage_usage_percent":{"format":"uint64","type":"string"},"metrics_freshness_max_blocks":{"description":"Maximum acceptable staleness (in blocks) for a metrics report when\nvalidating freshness.","format":"uint64","type":"string"},"metrics_grace_period_blocks":{"description":"Additional grace (in blocks) before marking metrics overdue/stale.","format":"uint64","type":"string"},"metrics_thresholds":{"type":"string"},"metrics_update_interval_blocks":{"description":"Expected cadence (in blocks) between supernode metrics reports. The daemon\ncan run on a timer using expected block time, but the chain enforces\nheight-based staleness strictly in blocks.","format":"uint64","type":"string"},"min_cpu_cores":{"format":"uint64","type":"string"},"min_mem_gb":{"format":"uint64","type":"string"},"min_storage_gb":{"format":"uint64","type":"string"},"min_supernode_version":{"type":"string"},"minimum_stake_for_sn":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin"},"reporting_threshold":{"format":"uint64","type":"string"},"required_open_ports":{"items":{"format":"int64","type":"integer"},"type":"array"},"reward_distribution":{"$ref":"#/definitions/lumera.supernode.v1.RewardDistribution"},"slashing_fraction":{"type":"string"},"slashing_threshold":{"format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.PayoutHistoryEntry":{"properties":{"amount":{"items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"effective_weight":{"format":"double","type":"number"},"height":{"format":"int64","type":"string"},"ramp_weight":{"format":"double","type":"number"},"raw_bytes":{"format":"double","type":"number"},"smoothed_bytes":{"format":"double","type":"number"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.PortState":{"default":"PORT_STATE_UNKNOWN","description":"PortState defines tri-state port reporting. UNKNOWN is the default for proto3\nand is treated as \"not reported / not measured\".","enum":["PORT_STATE_UNKNOWN","PORT_STATE_OPEN","PORT_STATE_CLOSED"],"type":"string"},"lumera.supernode.v1.PortStatus":{"description":"PortStatus reports the state of a specific TCP port.","properties":{"port":{"format":"int64","type":"integer"},"state":{"$ref":"#/definitions/lumera.supernode.v1.PortState"}},"type":"object"},"lumera.supernode.v1.QueryGetMetricsResponse":{"description":"QueryGetMetricsResponse is response type for the Query/GetMetrics RPC method.","properties":{"metrics_state":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetricsState"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeBySuperNodeAddressResponse":{"description":"QueryGetSuperNodeBySuperNodeAddressResponse is response type for the Query/GetSuperNodeBySuperNodeAddress RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetSuperNodeResponse":{"description":"QueryGetSuperNodeResponse is response type for the Query/GetSuperNode RPC method.","properties":{"supernode":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode"}},"type":"object"},"lumera.supernode.v1.QueryGetTopSuperNodesForBlockResponse":{"description":"QueryGetTopSuperNodesForBlockResponse is response type for the Query/GetTopSuperNodesForBlock RPC method.","properties":{"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryListSuperNodesResponse":{"description":"QueryListSuperNodesResponse is response type for the Query/ListSuperNodes RPC method.","properties":{"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"},"supernodes":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNode","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QueryParamsResponse":{"description":"QueryParamsResponse is response type for the Query/Params RPC method.","properties":{"params":{"$ref":"#/definitions/lumera.supernode.v1.Params","description":"params holds all the parameters of this module."}},"type":"object"},"lumera.supernode.v1.QueryPayoutHistoryResponse":{"properties":{"entries":{"items":{"$ref":"#/definitions/lumera.supernode.v1.PayoutHistoryEntry","type":"object"},"type":"array"},"pagination":{"$ref":"#/definitions/cosmos.base.query.v1beta1.PageResponse"}},"type":"object"},"lumera.supernode.v1.QueryPoolStateResponse":{"description":"QueryPoolStateResponse is response type for the Query/PoolState RPC method.","properties":{"balance":{"description":"balance is the current undistributed pool balance.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"},"eligible_sn_count":{"description":"eligible_sn_count is the number of SuperNodes currently eligible for payouts.","format":"uint64","type":"string"},"last_distribution_height":{"description":"last_distribution_height is the block height of the last distribution.","format":"int64","type":"string"},"total_distributed":{"description":"total_distributed is the cumulative amount distributed.","items":{"$ref":"#/definitions/cosmos.base.v1beta1.Coin","type":"object"},"type":"array"}},"type":"object"},"lumera.supernode.v1.QuerySNEligibilityResponse":{"description":"QuerySNEligibilityResponse is response type for the Query/SNEligibility RPC method.","properties":{"cascade_kademlia_db_bytes":{"format":"double","type":"number"},"eligible":{"type":"boolean"},"reason":{"type":"string"},"smoothed_weight":{"format":"double","type":"number"}},"type":"object"},"lumera.supernode.v1.RewardDistribution":{"description":"RewardDistribution governs the Everlight reward pool's payout cadence,\neligibility floor, ramp-up, smoothing window and growth cap. All fields\nare governance-mutable via supernode MsgUpdateParams.","properties":{"measurement_smoothing_periods":{"description":"Rolling average window (in payment periods) for weight smoothing.","format":"uint64","type":"string"},"min_cascade_bytes_for_payment":{"description":"Minimum cascade_kademlia_db_bytes for a SuperNode to qualify for payouts.","format":"uint64","type":"string"},"new_sn_ramp_up_periods":{"description":"Number of payment periods for new SuperNode payout ramp-up.","format":"uint64","type":"string"},"payment_period_blocks":{"description":"Distribution period in blocks. Pool balance distributed every this many blocks.","format":"uint64","type":"string"},"registration_fee_share_bps":{"description":"Share of action registration fees routed to Everlight pool, in basis points.","format":"uint64","type":"string"},"usage_growth_cap_bps_per_period":{"description":"Maximum rate of reported cascade bytes increase per period, in basis points.","format":"uint64","type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNode":{"properties":{"evidence":{"items":{"$ref":"#/definitions/lumera.supernode.v1.Evidence","type":"object"},"type":"array"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.MetricsAggregate"},"note":{"type":"string"},"p2p_port":{"type":"string"},"prev_ip_addresses":{"items":{"$ref":"#/definitions/lumera.supernode.v1.IPAddressHistory","type":"object"},"type":"array"},"prev_supernode_accounts":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeAccountHistory","type":"object"},"type":"array"},"states":{"items":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeStateRecord","type":"object"},"type":"array"},"supernode_account":{"type":"string"},"validator_address":{"type":"string"}},"type":"object"},"lumera.supernode.v1.SuperNodeState":{"default":"SUPERNODE_STATE_UNSPECIFIED","description":"SuperNodeState is the lifecycle state of a SuperNode. Transitions are\ngoverned by the supernode and audit modules; see x/supernode/v1/keeper\nand x/audit/v1/keeper for the authoritative state machine.\n\n - SUPERNODE_STATE_UNSPECIFIED: SUPERNODE_STATE_UNSPECIFIED is the proto3 zero value; never persisted.\n - SUPERNODE_STATE_ACTIVE: SUPERNODE_STATE_ACTIVE: SuperNode is healthy and eligible for all duties.\n - SUPERNODE_STATE_DISABLED: SUPERNODE_STATE_DISABLED: operator-disabled (deregistered) SuperNode.\n - SUPERNODE_STATE_STOPPED: SUPERNODE_STATE_STOPPED: operator-stopped SuperNode (recoverable).\n - SUPERNODE_STATE_PENALIZED: SUPERNODE_STATE_PENALIZED: penalized by chain enforcement (e.g. slashing).\n - SUPERNODE_STATE_POSTPONED: SUPERNODE_STATE_POSTPONED: temporarily ineligible due to missing/overdue\nmetrics or compliance violations; recovers on the next healthy report.\n - SUPERNODE_STATE_STORAGE_FULL: SUPERNODE_STATE_STORAGE_FULL: storage usage above max threshold;\nexcluded from Cascade duties but still eligible for Sense/Agents.","enum":["SUPERNODE_STATE_UNSPECIFIED","SUPERNODE_STATE_ACTIVE","SUPERNODE_STATE_DISABLED","SUPERNODE_STATE_STOPPED","SUPERNODE_STATE_PENALIZED","SUPERNODE_STATE_POSTPONED","SUPERNODE_STATE_STORAGE_FULL"],"type":"string"},"lumera.supernode.v1.SuperNodeStateRecord":{"description":"SuperNodeStateRecord is one entry in the append-only state history of a\nSuperNode. The latest entry is the current state.","properties":{"height":{"format":"int64","type":"string"},"reason":{"description":"reason is an optional string describing why the state transition occurred.\nIt is currently set only for transitions into POSTPONED.","type":"string"},"state":{"$ref":"#/definitions/lumera.supernode.v1.SuperNodeState"}},"type":"object"},"lumera.supernode.v1.SupernodeAccountHistory":{"properties":{"account":{"type":"string"},"height":{"format":"int64","type":"string"}},"type":"object"},"lumera.supernode.v1.SupernodeMetrics":{"description":"SupernodeMetrics defines the structured metrics reported by a supernode.","properties":{"cascade_kademlia_db_bytes":{"description":"Cascade Kademlia DB size in bytes (LEP-4 metric for Everlight payouts).","format":"double","type":"number"},"cpu_cores_total":{"description":"CPU metrics.","format":"double","type":"number"},"cpu_usage_percent":{"format":"double","type":"number"},"disk_free_gb":{"format":"double","type":"number"},"disk_total_gb":{"description":"Storage metrics (GB).","format":"double","type":"number"},"disk_usage_percent":{"format":"double","type":"number"},"mem_free_gb":{"format":"double","type":"number"},"mem_total_gb":{"description":"Memory metrics (GB).","format":"double","type":"number"},"mem_usage_percent":{"format":"double","type":"number"},"open_ports":{"description":"Tri-state port reporting for required ports.","items":{"$ref":"#/definitions/lumera.supernode.v1.PortStatus","type":"object"},"type":"array"},"peers_count":{"format":"int64","type":"integer"},"uptime_seconds":{"description":"Uptime and connectivity.","format":"double","type":"number"},"version_major":{"description":"Semantic version of the supernode software.","format":"int64","type":"integer"},"version_minor":{"format":"int64","type":"integer"},"version_patch":{"format":"int64","type":"integer"}},"type":"object"},"lumera.supernode.v1.SupernodeMetricsState":{"description":"SupernodeMetricsState stores the latest metrics state for a validator.","properties":{"height":{"format":"int64","type":"string"},"metrics":{"$ref":"#/definitions/lumera.supernode.v1.SupernodeMetrics"},"report_count":{"format":"uint64","type":"string"},"validator_address":{"type":"string"}},"type":"object"}}} \ No newline at end of file diff --git a/proto/lumera/audit/v1/query.proto b/proto/lumera/audit/v1/query.proto index f6a3a9f4..32c24949 100644 --- a/proto/lumera/audit/v1/query.proto +++ b/proto/lumera/audit/v1/query.proto @@ -175,6 +175,19 @@ message QueryAssignedTargetsResponse { int64 epoch_start_height = 2; repeated uint32 required_open_ports = 3; repeated string target_supernode_accounts = 4 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; + // reporter_supernode_account is the epoch-logical identity corresponding to + // the current account supplied in the request. + string reporter_supernode_account = 5 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; + // target_account_mappings preserves target_supernode_accounts order while + // also exposing the live account to contact for each logical target. + repeated AccountIdentityMapping target_account_mappings = 6 [(gogoproto.nullable) = false]; +} + +// AccountIdentityMapping pairs an epoch-logical account with the current +// account that should be contacted for that identity. +message AccountIdentityMapping { + string logical_account = 1 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; + string current_account = 2 [(cosmos_proto.scalar) = "cosmos.AccAddressString"]; } message QueryEpochReportRequest { diff --git a/x/audit/v1/keeper/identity_continuity.go b/x/audit/v1/keeper/identity_continuity.go index ea3dac5a..041a0349 100644 --- a/x/audit/v1/keeper/identity_continuity.go +++ b/x/audit/v1/keeper/identity_continuity.go @@ -302,19 +302,35 @@ func (k Keeper) CurrentAccount(ctx sdk.Context, account string) (string, error) } func (k Keeper) forwardTransition(ctx sdk.Context, source string) (types.AccountTransition, bool, error) { - transition, found, err := k.transitionAtKey(ctx, types.AccountTransitionForwardKey(source)) - if err == nil && found && transition.SourceAccount != source { + forwardKey := types.AccountTransitionForwardKey(source) + transition, found, err := k.transitionAtKey(ctx, forwardKey) + if err != nil || !found { + return transition, found, err + } + if transition.SourceAccount != source { return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: forward index key does not match source account") } - return transition, found, err + store := k.kvStore(ctx) + if !bytes.Equal(store.Get(forwardKey), store.Get(types.AccountTransitionReverseKey(transition.DestinationAccount))) { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: forward and reverse indexes disagree") + } + return transition, true, nil } func (k Keeper) reverseTransition(ctx sdk.Context, destination string) (types.AccountTransition, bool, error) { - transition, found, err := k.transitionAtKey(ctx, types.AccountTransitionReverseKey(destination)) - if err == nil && found && transition.DestinationAccount != destination { + reverseKey := types.AccountTransitionReverseKey(destination) + transition, found, err := k.transitionAtKey(ctx, reverseKey) + if err != nil || !found { + return transition, found, err + } + if transition.DestinationAccount != destination { return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: reverse index key does not match destination account") } - return transition, found, err + store := k.kvStore(ctx) + if !bytes.Equal(store.Get(reverseKey), store.Get(types.AccountTransitionForwardKey(transition.SourceAccount))) { + return types.AccountTransition{}, false, fmt.Errorf("malformed account transition: forward and reverse indexes disagree") + } + return transition, true, nil } func (k Keeper) transitionAtKey(ctx sdk.Context, key []byte) (types.AccountTransition, bool, error) { diff --git a/x/audit/v1/keeper/query_assigned_targets.go b/x/audit/v1/keeper/query_assigned_targets.go index 82c74336..7edeb68c 100644 --- a/x/audit/v1/keeper/query_assigned_targets.go +++ b/x/audit/v1/keeper/query_assigned_targets.go @@ -72,11 +72,35 @@ func (q queryServer) AssignedTargets(ctx context.Context, req *types.QueryAssign if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + targetMappings, err := q.k.ResolveAccountIdentityMappings(sdkCtx, targets) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } return &types.QueryAssignedTargetsResponse{ - EpochId: epochID, - EpochStartHeight: epochStart, - RequiredOpenPorts: append([]uint32(nil), assignParams.RequiredOpenPorts...), - TargetSupernodeAccounts: targets, + EpochId: epochID, + EpochStartHeight: epochStart, + RequiredOpenPorts: append([]uint32(nil), assignParams.RequiredOpenPorts...), + TargetSupernodeAccounts: targets, + ReporterSupernodeAccount: logicalAccount, + TargetAccountMappings: targetMappings, }, nil } + +// ResolveAccountIdentityMappings resolves each logical account independently +// through the indexed lineage. It intentionally preserves input order and +// fails the whole operation if any lineage cannot be trusted. +func (k Keeper) ResolveAccountIdentityMappings(ctx sdk.Context, logicalAccounts []string) ([]types.AccountIdentityMapping, error) { + mappings := make([]types.AccountIdentityMapping, len(logicalAccounts)) + for i, logicalAccount := range logicalAccounts { + currentAccount, err := k.CurrentAccount(ctx, logicalAccount) + if err != nil { + return nil, err + } + mappings[i] = types.AccountIdentityMapping{ + LogicalAccount: logicalAccount, + CurrentAccount: currentAccount, + } + } + return mappings, nil +} diff --git a/x/audit/v1/keeper/query_assigned_targets_identity_test.go b/x/audit/v1/keeper/query_assigned_targets_identity_test.go new file mode 100644 index 00000000..d8c4c5eb --- /dev/null +++ b/x/audit/v1/keeper/query_assigned_targets_identity_test.go @@ -0,0 +1,300 @@ +package keeper_test + +import ( + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestAssignedTargetIdentityResolutionUnmigratedAndOrdered(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{1, 2, 3, 4}) + b := testAddress(t, f, []byte{5, 6, 7, 8}) + + got, err := f.keeper.ResolveAccountIdentityMappings(f.ctx, []string{b, a}) + require.NoError(t, err) + require.Equal(t, []types.AccountIdentityMapping{ + {LogicalAccount: b, CurrentAccount: b}, + {LogicalAccount: a, CurrentAccount: a}, + }, got) +} + +func TestAssignedTargetIdentityResolutionFollowsTwoHopLineage(t *testing.T) { + f := initFixture(t) + old := testAddress(t, f, []byte{11, 12, 13, 14}) + mid := testAddress(t, f, []byte{21, 22, 23, 24}) + current := testAddress(t, f, []byte{31, 32, 33, 34}) + other := testAddress(t, f, []byte{41, 42, 43, 44}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: old, DestinationAccount: mid, EffectiveEpoch: 2, + })) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: mid, DestinationAccount: current, EffectiveEpoch: 3, + })) + + got, err := f.keeper.ResolveAccountIdentityMappings(f.ctx, []string{old, other}) + require.NoError(t, err) + require.Len(t, got, 2) + require.Equal(t, []types.AccountIdentityMapping{ + {LogicalAccount: old, CurrentAccount: current}, + {LogicalAccount: other, CurrentAccount: other}, + }, got) +} + +func TestAssignedTargetIdentityResolutionFailsClosed(t *testing.T) { + t.Run("malformed lineage", func(t *testing.T) { + f := initFixture(t) + account := testAddress(t, f, []byte{51, 52, 53, 54}) + f.ctx.KVStore(f.storeKey).Set(types.AccountTransitionForwardKey(account), []byte("not protobuf")) + + got, err := f.keeper.ResolveAccountIdentityMappings(f.ctx, []string{account}) + require.ErrorContains(t, err, "malformed account transition") + require.Nil(t, got) + }) + + t.Run("cyclic lineage", func(t *testing.T) { + f := initFixture(t) + a := testAddress(t, f, []byte{61, 62, 63, 64}) + b := testAddress(t, f, []byte{71, 72, 73, 74}) + store := f.ctx.KVStore(f.storeKey) + ab, err := proto.Marshal(&types.AccountTransition{SourceAccount: a, DestinationAccount: b, EffectiveEpoch: 2}) + require.NoError(t, err) + ba, err := proto.Marshal(&types.AccountTransition{SourceAccount: b, DestinationAccount: a, EffectiveEpoch: 3}) + require.NoError(t, err) + store.Set(types.AccountTransitionForwardKey(a), ab) + store.Set(types.AccountTransitionReverseKey(b), ab) + store.Set(types.AccountTransitionForwardKey(b), ba) + store.Set(types.AccountTransitionReverseKey(a), ba) + + got, err := f.keeper.ResolveAccountIdentityMappings(f.ctx, []string{a}) + require.ErrorContains(t, err, "account transition cycle") + require.Nil(t, got) + }) +} + +func TestAssignedTargetsResponseLegacyWireFieldsRemainCompatible(t *testing.T) { + legacyFieldsOnly := &types.QueryAssignedTargetsResponse{ + EpochId: 7, + EpochStartHeight: 123, + RequiredOpenPorts: []uint32{4444, 5555}, + TargetSupernodeAccounts: []string{"logical-a", "logical-b"}, + } + wire, err := proto.Marshal(legacyFieldsOnly) + require.NoError(t, err) + + var decoded types.QueryAssignedTargetsResponse + require.NoError(t, proto.Unmarshal(wire, &decoded)) + require.Equal(t, legacyFieldsOnly.EpochId, decoded.EpochId) + require.Equal(t, legacyFieldsOnly.EpochStartHeight, decoded.EpochStartHeight) + require.Equal(t, legacyFieldsOnly.RequiredOpenPorts, decoded.RequiredOpenPorts) + require.Equal(t, legacyFieldsOnly.TargetSupernodeAccounts, decoded.TargetSupernodeAccounts) + require.Empty(t, decoded.ReporterSupernodeAccount) + require.Empty(t, decoded.TargetAccountMappings) +} + +func TestAssignedTargetsReturnsEpochLogicalReporterAndCurrentTargets(t *testing.T) { + f := initFixture(t) + reporterLogical := testAddress(t, f, []byte{81, 82, 83, 84}) + reporterCurrent := testAddress(t, f, []byte{85, 86, 87, 88}) + targetLogical := testAddress(t, f, []byte{91, 92, 93, 94}) + targetCurrent := testAddress(t, f, []byte{95, 96, 97, 98}) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: reporterLogical, DestinationAccount: reporterCurrent, EffectiveEpoch: 2, + })) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: targetLogical, DestinationAccount: targetCurrent, EffectiveEpoch: 2, + })) + require.NoError(t, f.keeper.SetEpochAnchor(f.ctx, types.EpochAnchor{ + EpochId: 1, + Seed: []byte("01234567890123456789012345678901"), + ActiveSupernodeAccounts: []string{reporterLogical, targetLogical}, + TargetSupernodeAccounts: []string{reporterLogical, targetLogical}, + })) + require.NoError(t, f.keeper.SetEpochAnchor(f.ctx, types.EpochAnchor{ + EpochId: 2, + Seed: []byte("01234567890123456789012345678902"), + ActiveSupernodeAccounts: []string{reporterCurrent, targetCurrent}, + TargetSupernodeAccounts: []string{reporterCurrent, targetCurrent}, + })) + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), reporterCurrent). + Return(sntypes.SuperNode{SupernodeAccount: reporterCurrent}, true, nil). + Times(2) + + response, err := keeper.NewQueryServerImpl(f.keeper).AssignedTargets(f.ctx, &types.QueryAssignedTargetsRequest{ + SupernodeAccount: reporterCurrent, + EpochId: 1, + FilterByEpochId: true, + }) + require.NoError(t, err) + require.Equal(t, reporterLogical, response.ReporterSupernodeAccount) + require.Equal(t, response.TargetSupernodeAccounts, logicalAccounts(response.TargetAccountMappings)) + require.Len(t, response.TargetAccountMappings, 1) + require.Equal(t, targetLogical, response.TargetAccountMappings[0].LogicalAccount) + require.Equal(t, targetCurrent, response.TargetAccountMappings[0].CurrentAccount) + + nextEpochResponse, err := keeper.NewQueryServerImpl(f.keeper).AssignedTargets(f.ctx, &types.QueryAssignedTargetsRequest{ + SupernodeAccount: reporterCurrent, + EpochId: 2, + FilterByEpochId: true, + }) + require.NoError(t, err) + require.Equal(t, reporterCurrent, nextEpochResponse.ReporterSupernodeAccount) + require.Equal(t, nextEpochResponse.TargetSupernodeAccounts, logicalAccounts(nextEpochResponse.TargetAccountMappings)) + require.Len(t, nextEpochResponse.TargetAccountMappings, 1) + require.Equal(t, targetCurrent, nextEpochResponse.TargetAccountMappings[0].LogicalAccount) + require.Equal(t, targetCurrent, nextEpochResponse.TargetAccountMappings[0].CurrentAccount) +} + +func TestAssignedTargetIdentityResolutionRejectsBrokenMirrorIndexes(t *testing.T) { + for _, tt := range brokenMirrorIndexCases() { + t.Run(tt.name, func(t *testing.T) { + f := initFixture(t) + account := tt.arrange(t, f) + + got, err := f.keeper.ResolveAccountIdentityMappings(f.ctx, []string{account}) + require.ErrorContains(t, err, "forward and reverse indexes disagree") + require.Nil(t, got) + }) + } +} + +func TestAssignedTargetsPreservesMixedTargetOrderAndCardinality(t *testing.T) { + f := initFixture(t) + reporter := testAddress(t, f, []byte{141, 142, 143, 144}) + migratedLogical := testAddress(t, f, []byte{145, 146, 147, 148}) + migratedCurrent := testAddress(t, f, []byte{151, 152, 153, 154}) + unmigrated := testAddress(t, f, []byte{155, 156, 157, 158}) + params := types.DefaultParams() + params.StorageTruthEnforcementMode = types.StorageTruthEnforcementMode_STORAGE_TRUTH_ENFORCEMENT_MODE_UNSPECIFIED + require.NoError(t, f.keeper.SetParams(f.ctx, params)) + + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: migratedLogical, DestinationAccount: migratedCurrent, EffectiveEpoch: 2, + })) + require.NoError(t, f.keeper.SetEpochAnchor(f.ctx, types.EpochAnchor{ + EpochId: 1, + Seed: []byte("01234567890123456789012345678903"), + ActiveSupernodeAccounts: []string{reporter, migratedLogical, unmigrated}, + TargetSupernodeAccounts: []string{reporter, migratedLogical, unmigrated}, + })) + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), reporter). + Return(sntypes.SuperNode{SupernodeAccount: reporter}, true, nil) + + response, err := keeper.NewQueryServerImpl(f.keeper).AssignedTargets(f.ctx, &types.QueryAssignedTargetsRequest{ + SupernodeAccount: reporter, + EpochId: 1, + FilterByEpochId: true, + }) + require.NoError(t, err) + require.Len(t, response.TargetSupernodeAccounts, 2) + require.Len(t, response.TargetAccountMappings, len(response.TargetSupernodeAccounts)) + require.Equal(t, response.TargetSupernodeAccounts, logicalAccounts(response.TargetAccountMappings)) + + currentByLogical := make(map[string]string, len(response.TargetAccountMappings)) + for _, mapping := range response.TargetAccountMappings { + currentByLogical[mapping.LogicalAccount] = mapping.CurrentAccount + } + require.Equal(t, migratedCurrent, currentByLogical[migratedLogical]) + require.Equal(t, unmigrated, currentByLogical[unmigrated]) +} + +func TestAssignedTargetsRejectsBrokenMirrorIndexesWithoutPartialResponse(t *testing.T) { + for _, tt := range brokenMirrorIndexCases() { + t.Run(tt.name, func(t *testing.T) { + f := initFixture(t) + reporter := testAddress(t, f, []byte{201, 202, 203, 204}) + target := tt.arrange(t, f) + require.NoError(t, f.keeper.SetEpochAnchor(f.ctx, types.EpochAnchor{ + EpochId: 1, + Seed: []byte("01234567890123456789012345678904"), + ActiveSupernodeAccounts: []string{reporter, target}, + TargetSupernodeAccounts: []string{reporter, target}, + })) + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), reporter). + Return(sntypes.SuperNode{SupernodeAccount: reporter}, true, nil) + + response, err := keeper.NewQueryServerImpl(f.keeper).AssignedTargets(f.ctx, &types.QueryAssignedTargetsRequest{ + SupernodeAccount: reporter, + EpochId: 1, + FilterByEpochId: true, + }) + require.Nil(t, response) + require.Equal(t, codes.Internal, status.Code(err)) + require.ErrorContains(t, err, "forward and reverse indexes disagree") + }) + } +} + +type brokenMirrorIndexCase struct { + name string + arrange func(*testing.T, *fixture) string +} + +func brokenMirrorIndexCases() []brokenMirrorIndexCase { + return []brokenMirrorIndexCase{ + { + name: "orphan forward", + arrange: func(t *testing.T, f *fixture) string { + source := testAddress(t, f, []byte{101, 102, 103, 104}) + destination := testAddress(t, f, []byte{105, 106, 107, 108}) + setTransitionIndex(t, f, types.AccountTransitionForwardKey(source), types.AccountTransition{ + SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2, + }) + return source + }, + }, + { + name: "orphan reverse", + arrange: func(t *testing.T, f *fixture) string { + source := testAddress(t, f, []byte{111, 112, 113, 114}) + destination := testAddress(t, f, []byte{115, 116, 117, 118}) + setTransitionIndex(t, f, types.AccountTransitionReverseKey(destination), types.AccountTransition{ + SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2, + }) + return destination + }, + }, + { + name: "mismatched mirror", + arrange: func(t *testing.T, f *fixture) string { + source := testAddress(t, f, []byte{121, 122, 123, 124}) + other := testAddress(t, f, []byte{125, 126, 127, 128}) + destination := testAddress(t, f, []byte{131, 132, 133, 134}) + setTransitionIndex(t, f, types.AccountTransitionForwardKey(source), types.AccountTransition{ + SourceAccount: source, DestinationAccount: destination, EffectiveEpoch: 2, + }) + setTransitionIndex(t, f, types.AccountTransitionReverseKey(destination), types.AccountTransition{ + SourceAccount: other, DestinationAccount: destination, EffectiveEpoch: 2, + }) + return source + }, + }, + } +} + +func setTransitionIndex(t *testing.T, f *fixture, key []byte, transition types.AccountTransition) { + t.Helper() + bz, err := proto.Marshal(&transition) + require.NoError(t, err) + f.ctx.KVStore(f.storeKey).Set(key, bz) +} + +func logicalAccounts(mappings []types.AccountIdentityMapping) []string { + accounts := make([]string, len(mappings)) + for i := range mappings { + accounts[i] = mappings[i].LogicalAccount + } + return accounts +} diff --git a/x/audit/v1/types/query.pb.go b/x/audit/v1/types/query.pb.go index 9aea906d..340b35ee 100644 --- a/x/audit/v1/types/query.pb.go +++ b/x/audit/v1/types/query.pb.go @@ -737,6 +737,12 @@ type QueryAssignedTargetsResponse struct { EpochStartHeight int64 `protobuf:"varint,2,opt,name=epoch_start_height,json=epochStartHeight,proto3" json:"epoch_start_height,omitempty"` RequiredOpenPorts []uint32 `protobuf:"varint,3,rep,packed,name=required_open_ports,json=requiredOpenPorts,proto3" json:"required_open_ports,omitempty"` TargetSupernodeAccounts []string `protobuf:"bytes,4,rep,name=target_supernode_accounts,json=targetSupernodeAccounts,proto3" json:"target_supernode_accounts,omitempty"` + // reporter_supernode_account is the epoch-logical identity corresponding to + // the current account supplied in the request. + ReporterSupernodeAccount string `protobuf:"bytes,5,opt,name=reporter_supernode_account,json=reporterSupernodeAccount,proto3" json:"reporter_supernode_account,omitempty"` + // target_account_mappings preserves target_supernode_accounts order while + // also exposing the live account to contact for each logical target. + TargetAccountMappings []AccountIdentityMapping `protobuf:"bytes,6,rep,name=target_account_mappings,json=targetAccountMappings,proto3" json:"target_account_mappings"` } func (m *QueryAssignedTargetsResponse) Reset() { *m = QueryAssignedTargetsResponse{} } @@ -800,6 +806,74 @@ func (m *QueryAssignedTargetsResponse) GetTargetSupernodeAccounts() []string { return nil } +func (m *QueryAssignedTargetsResponse) GetReporterSupernodeAccount() string { + if m != nil { + return m.ReporterSupernodeAccount + } + return "" +} + +func (m *QueryAssignedTargetsResponse) GetTargetAccountMappings() []AccountIdentityMapping { + if m != nil { + return m.TargetAccountMappings + } + return nil +} + +// AccountIdentityMapping pairs an epoch-logical account with the current +// account that should be contacted for that identity. +type AccountIdentityMapping struct { + LogicalAccount string `protobuf:"bytes,1,opt,name=logical_account,json=logicalAccount,proto3" json:"logical_account,omitempty"` + CurrentAccount string `protobuf:"bytes,2,opt,name=current_account,json=currentAccount,proto3" json:"current_account,omitempty"` +} + +func (m *AccountIdentityMapping) Reset() { *m = AccountIdentityMapping{} } +func (m *AccountIdentityMapping) String() string { return proto.CompactTextString(m) } +func (*AccountIdentityMapping) ProtoMessage() {} +func (*AccountIdentityMapping) Descriptor() ([]byte, []int) { + return fileDescriptor_e98945621bbc9485, []int{16} +} +func (m *AccountIdentityMapping) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AccountIdentityMapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AccountIdentityMapping.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AccountIdentityMapping) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccountIdentityMapping.Merge(m, src) +} +func (m *AccountIdentityMapping) XXX_Size() int { + return m.Size() +} +func (m *AccountIdentityMapping) XXX_DiscardUnknown() { + xxx_messageInfo_AccountIdentityMapping.DiscardUnknown(m) +} + +var xxx_messageInfo_AccountIdentityMapping proto.InternalMessageInfo + +func (m *AccountIdentityMapping) GetLogicalAccount() string { + if m != nil { + return m.LogicalAccount + } + return "" +} + +func (m *AccountIdentityMapping) GetCurrentAccount() string { + if m != nil { + return m.CurrentAccount + } + return "" +} + type QueryEpochReportRequest struct { EpochId uint64 `protobuf:"varint,1,opt,name=epoch_id,json=epochId,proto3" json:"epoch_id,omitempty"` SupernodeAccount string `protobuf:"bytes,2,opt,name=supernode_account,json=supernodeAccount,proto3" json:"supernode_account,omitempty"` @@ -809,7 +883,7 @@ func (m *QueryEpochReportRequest) Reset() { *m = QueryEpochReportRequest func (m *QueryEpochReportRequest) String() string { return proto.CompactTextString(m) } func (*QueryEpochReportRequest) ProtoMessage() {} func (*QueryEpochReportRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{16} + return fileDescriptor_e98945621bbc9485, []int{17} } func (m *QueryEpochReportRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -860,7 +934,7 @@ func (m *QueryEpochReportResponse) Reset() { *m = QueryEpochReportRespon func (m *QueryEpochReportResponse) String() string { return proto.CompactTextString(m) } func (*QueryEpochReportResponse) ProtoMessage() {} func (*QueryEpochReportResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{17} + return fileDescriptor_e98945621bbc9485, []int{18} } func (m *QueryEpochReportResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -907,7 +981,7 @@ func (m *QueryEpochReportsByReporterRequest) Reset() { *m = QueryEpochRe func (m *QueryEpochReportsByReporterRequest) String() string { return proto.CompactTextString(m) } func (*QueryEpochReportsByReporterRequest) ProtoMessage() {} func (*QueryEpochReportsByReporterRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{18} + return fileDescriptor_e98945621bbc9485, []int{19} } func (m *QueryEpochReportsByReporterRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -973,7 +1047,7 @@ func (m *QueryEpochReportsByReporterResponse) Reset() { *m = QueryEpochR func (m *QueryEpochReportsByReporterResponse) String() string { return proto.CompactTextString(m) } func (*QueryEpochReportsByReporterResponse) ProtoMessage() {} func (*QueryEpochReportsByReporterResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{19} + return fileDescriptor_e98945621bbc9485, []int{20} } func (m *QueryEpochReportsByReporterResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1027,7 +1101,7 @@ func (m *QueryStorageChallengeReportsRequest) Reset() { *m = QueryStorag func (m *QueryStorageChallengeReportsRequest) String() string { return proto.CompactTextString(m) } func (*QueryStorageChallengeReportsRequest) ProtoMessage() {} func (*QueryStorageChallengeReportsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{20} + return fileDescriptor_e98945621bbc9485, []int{21} } func (m *QueryStorageChallengeReportsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1095,7 +1169,7 @@ func (m *StorageChallengeReport) Reset() { *m = StorageChallengeReport{} func (m *StorageChallengeReport) String() string { return proto.CompactTextString(m) } func (*StorageChallengeReport) ProtoMessage() {} func (*StorageChallengeReport) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{21} + return fileDescriptor_e98945621bbc9485, []int{22} } func (m *StorageChallengeReport) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1161,7 +1235,7 @@ func (m *QueryStorageChallengeReportsResponse) Reset() { *m = QueryStora func (m *QueryStorageChallengeReportsResponse) String() string { return proto.CompactTextString(m) } func (*QueryStorageChallengeReportsResponse) ProtoMessage() {} func (*QueryStorageChallengeReportsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{22} + return fileDescriptor_e98945621bbc9485, []int{23} } func (m *QueryStorageChallengeReportsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1215,7 +1289,7 @@ func (m *QueryHostReportsRequest) Reset() { *m = QueryHostReportsRequest func (m *QueryHostReportsRequest) String() string { return proto.CompactTextString(m) } func (*QueryHostReportsRequest) ProtoMessage() {} func (*QueryHostReportsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{23} + return fileDescriptor_e98945621bbc9485, []int{24} } func (m *QueryHostReportsRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1282,7 +1356,7 @@ func (m *HostReportEntry) Reset() { *m = HostReportEntry{} } func (m *HostReportEntry) String() string { return proto.CompactTextString(m) } func (*HostReportEntry) ProtoMessage() {} func (*HostReportEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{24} + return fileDescriptor_e98945621bbc9485, []int{25} } func (m *HostReportEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1341,7 +1415,7 @@ func (m *QueryHostReportsResponse) Reset() { *m = QueryHostReportsRespon func (m *QueryHostReportsResponse) String() string { return proto.CompactTextString(m) } func (*QueryHostReportsResponse) ProtoMessage() {} func (*QueryHostReportsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{25} + return fileDescriptor_e98945621bbc9485, []int{26} } func (m *QueryHostReportsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1392,7 +1466,7 @@ func (m *QueryNodeSuspicionStateRequest) Reset() { *m = QueryNodeSuspici func (m *QueryNodeSuspicionStateRequest) String() string { return proto.CompactTextString(m) } func (*QueryNodeSuspicionStateRequest) ProtoMessage() {} func (*QueryNodeSuspicionStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{26} + return fileDescriptor_e98945621bbc9485, []int{27} } func (m *QueryNodeSuspicionStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1436,7 +1510,7 @@ func (m *QueryNodeSuspicionStateResponse) Reset() { *m = QueryNodeSuspic func (m *QueryNodeSuspicionStateResponse) String() string { return proto.CompactTextString(m) } func (*QueryNodeSuspicionStateResponse) ProtoMessage() {} func (*QueryNodeSuspicionStateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{27} + return fileDescriptor_e98945621bbc9485, []int{28} } func (m *QueryNodeSuspicionStateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1480,7 +1554,7 @@ func (m *QueryReporterReliabilityStateRequest) Reset() { *m = QueryRepor func (m *QueryReporterReliabilityStateRequest) String() string { return proto.CompactTextString(m) } func (*QueryReporterReliabilityStateRequest) ProtoMessage() {} func (*QueryReporterReliabilityStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{28} + return fileDescriptor_e98945621bbc9485, []int{29} } func (m *QueryReporterReliabilityStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1524,7 +1598,7 @@ func (m *QueryReporterReliabilityStateResponse) Reset() { *m = QueryRepo func (m *QueryReporterReliabilityStateResponse) String() string { return proto.CompactTextString(m) } func (*QueryReporterReliabilityStateResponse) ProtoMessage() {} func (*QueryReporterReliabilityStateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{29} + return fileDescriptor_e98945621bbc9485, []int{30} } func (m *QueryReporterReliabilityStateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1568,7 +1642,7 @@ func (m *QueryTicketDeteriorationStateRequest) Reset() { *m = QueryTicke func (m *QueryTicketDeteriorationStateRequest) String() string { return proto.CompactTextString(m) } func (*QueryTicketDeteriorationStateRequest) ProtoMessage() {} func (*QueryTicketDeteriorationStateRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{30} + return fileDescriptor_e98945621bbc9485, []int{31} } func (m *QueryTicketDeteriorationStateRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1612,7 +1686,7 @@ func (m *QueryTicketDeteriorationStateResponse) Reset() { *m = QueryTick func (m *QueryTicketDeteriorationStateResponse) String() string { return proto.CompactTextString(m) } func (*QueryTicketDeteriorationStateResponse) ProtoMessage() {} func (*QueryTicketDeteriorationStateResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{31} + return fileDescriptor_e98945621bbc9485, []int{32} } func (m *QueryTicketDeteriorationStateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1656,7 +1730,7 @@ func (m *QueryHealOpRequest) Reset() { *m = QueryHealOpRequest{} } func (m *QueryHealOpRequest) String() string { return proto.CompactTextString(m) } func (*QueryHealOpRequest) ProtoMessage() {} func (*QueryHealOpRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{32} + return fileDescriptor_e98945621bbc9485, []int{33} } func (m *QueryHealOpRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1700,7 +1774,7 @@ func (m *QueryHealOpResponse) Reset() { *m = QueryHealOpResponse{} } func (m *QueryHealOpResponse) String() string { return proto.CompactTextString(m) } func (*QueryHealOpResponse) ProtoMessage() {} func (*QueryHealOpResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{33} + return fileDescriptor_e98945621bbc9485, []int{34} } func (m *QueryHealOpResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1745,7 +1819,7 @@ func (m *QueryHealOpsByTicketRequest) Reset() { *m = QueryHealOpsByTicke func (m *QueryHealOpsByTicketRequest) String() string { return proto.CompactTextString(m) } func (*QueryHealOpsByTicketRequest) ProtoMessage() {} func (*QueryHealOpsByTicketRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{34} + return fileDescriptor_e98945621bbc9485, []int{35} } func (m *QueryHealOpsByTicketRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1797,7 +1871,7 @@ func (m *QueryHealOpsByTicketResponse) Reset() { *m = QueryHealOpsByTick func (m *QueryHealOpsByTicketResponse) String() string { return proto.CompactTextString(m) } func (*QueryHealOpsByTicketResponse) ProtoMessage() {} func (*QueryHealOpsByTicketResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{35} + return fileDescriptor_e98945621bbc9485, []int{36} } func (m *QueryHealOpsByTicketResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1849,7 +1923,7 @@ func (m *QueryHealOpsByStatusRequest) Reset() { *m = QueryHealOpsByStatu func (m *QueryHealOpsByStatusRequest) String() string { return proto.CompactTextString(m) } func (*QueryHealOpsByStatusRequest) ProtoMessage() {} func (*QueryHealOpsByStatusRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{36} + return fileDescriptor_e98945621bbc9485, []int{37} } func (m *QueryHealOpsByStatusRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1901,7 +1975,7 @@ func (m *QueryHealOpsByStatusResponse) Reset() { *m = QueryHealOpsByStat func (m *QueryHealOpsByStatusResponse) String() string { return proto.CompactTextString(m) } func (*QueryHealOpsByStatusResponse) ProtoMessage() {} func (*QueryHealOpsByStatusResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_e98945621bbc9485, []int{37} + return fileDescriptor_e98945621bbc9485, []int{38} } func (m *QueryHealOpsByStatusResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1961,6 +2035,7 @@ func init() { proto.RegisterType((*QueryCurrentEpochAnchorResponse)(nil), "lumera.audit.v1.QueryCurrentEpochAnchorResponse") proto.RegisterType((*QueryAssignedTargetsRequest)(nil), "lumera.audit.v1.QueryAssignedTargetsRequest") proto.RegisterType((*QueryAssignedTargetsResponse)(nil), "lumera.audit.v1.QueryAssignedTargetsResponse") + proto.RegisterType((*AccountIdentityMapping)(nil), "lumera.audit.v1.AccountIdentityMapping") proto.RegisterType((*QueryEpochReportRequest)(nil), "lumera.audit.v1.QueryEpochReportRequest") proto.RegisterType((*QueryEpochReportResponse)(nil), "lumera.audit.v1.QueryEpochReportResponse") proto.RegisterType((*QueryEpochReportsByReporterRequest)(nil), "lumera.audit.v1.QueryEpochReportsByReporterRequest") @@ -1988,130 +2063,135 @@ func init() { func init() { proto.RegisterFile("lumera/audit/v1/query.proto", fileDescriptor_e98945621bbc9485) } var fileDescriptor_e98945621bbc9485 = []byte{ - // 1957 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0x5d, 0x6c, 0x14, 0xd7, - 0x15, 0xf6, 0xb5, 0x8d, 0x7f, 0xae, 0xc1, 0x3f, 0x17, 0x84, 0xd7, 0x6b, 0xb3, 0x58, 0x03, 0x05, - 0xe3, 0xe2, 0x9d, 0xda, 0x80, 0x4b, 0x6b, 0x0a, 0x78, 0x8d, 0x8d, 0xdd, 0x62, 0x30, 0xbb, 0xb4, - 0x88, 0x4a, 0x68, 0x34, 0xbb, 0x73, 0xbb, 0x3b, 0x74, 0x3d, 0xb3, 0xcc, 0xcc, 0x5a, 0x5d, 0x59, - 0x5b, 0xa9, 0xf0, 0x5e, 0xb5, 0xaa, 0xfa, 0xc6, 0x4b, 0x55, 0x51, 0x55, 0x15, 0x52, 0x5b, 0x95, - 0xe6, 0x47, 0xca, 0x5b, 0x5e, 0xc8, 0x1b, 0x22, 0x2f, 0x49, 0xa4, 0x44, 0x11, 0x44, 0x8a, 0x94, - 0x27, 0xa4, 0xbc, 0x27, 0xd1, 0xdc, 0x9f, 0xdd, 0x99, 0x9d, 0xb9, 0x3b, 0xbb, 0xc8, 0x8e, 0x50, - 0xf2, 0x92, 0xac, 0xef, 0x3d, 0xe7, 0xdc, 0xef, 0x3b, 0xe7, 0xdc, 0x33, 0xf7, 0x1c, 0x01, 0xc7, - 0x8b, 0xe5, 0x4d, 0x6c, 0xa9, 0xb2, 0x5a, 0xd6, 0x74, 0x47, 0xde, 0x9a, 0x95, 0xef, 0x96, 0xb1, - 0x55, 0x49, 0x96, 0x2c, 0xd3, 0x31, 0xd1, 0x10, 0xdd, 0x4c, 0x92, 0xcd, 0xe4, 0xd6, 0x6c, 0x7c, - 0x44, 0xdd, 0xd4, 0x0d, 0x53, 0x26, 0xff, 0xa5, 0x32, 0xf1, 0x03, 0x79, 0x33, 0x6f, 0x92, 0x9f, - 0xb2, 0xfb, 0x8b, 0xad, 0x4e, 0xe4, 0x4d, 0x33, 0x5f, 0xc4, 0xb2, 0x5a, 0xd2, 0x65, 0xd5, 0x30, - 0x4c, 0x47, 0x75, 0x74, 0xd3, 0xb0, 0xd9, 0xee, 0x58, 0xce, 0xb4, 0x37, 0x4d, 0x5b, 0xa1, 0x6a, - 0xf4, 0x0f, 0xb6, 0x35, 0x4d, 0xff, 0x92, 0xb3, 0xaa, 0x8d, 0x29, 0x16, 0x79, 0x6b, 0x36, 0x8b, - 0x1d, 0x75, 0x56, 0x2e, 0xa9, 0x79, 0xdd, 0x20, 0x76, 0xf8, 0x21, 0x8d, 0xd8, 0x4b, 0xaa, 0xa5, - 0x6e, 0x72, 0x4b, 0x01, 0x66, 0x94, 0x05, 0xdd, 0x4c, 0x34, 0x6e, 0xe2, 0x2d, 0x5d, 0xc3, 0x46, - 0x0e, 0x8b, 0x94, 0x71, 0xc9, 0xcc, 0x15, 0xe8, 0xa6, 0x74, 0x00, 0xa2, 0xeb, 0x2e, 0xb2, 0x0d, - 0x72, 0x5c, 0x1a, 0xdf, 0x2d, 0x63, 0xdb, 0x91, 0xae, 0xc3, 0xfd, 0xbe, 0x55, 0xbb, 0x64, 0x1a, - 0x36, 0x46, 0x3f, 0x85, 0x3d, 0x14, 0x56, 0x0c, 0x4c, 0x82, 0xa9, 0x81, 0xb9, 0xd1, 0x64, 0x83, - 0x53, 0x93, 0x54, 0x21, 0xd5, 0xff, 0xe4, 0x93, 0xc3, 0x1d, 0xff, 0xfc, 0xfc, 0x3f, 0xd3, 0x20, - 0xcd, 0x34, 0xa4, 0x05, 0x18, 0x23, 0x26, 0x97, 0x19, 0xb8, 0x54, 0x65, 0x4d, 0x63, 0xc7, 0xa1, - 0xc3, 0x70, 0x80, 0x63, 0x56, 0x74, 0x8d, 0x18, 0xef, 0x4e, 0x43, 0xbe, 0xb4, 0xa6, 0x49, 0xb7, - 0xe1, 0x58, 0x88, 0x32, 0x43, 0x75, 0x11, 0xf6, 0x71, 0x51, 0x86, 0x6b, 0x2c, 0x80, 0xab, 0xa6, - 0xe8, 0x41, 0x56, 0xd3, 0x92, 0xfe, 0x05, 0xe0, 0xa1, 0x06, 0xfb, 0x99, 0x72, 0xf6, 0x0e, 0xce, - 0x39, 0x1c, 0xe1, 0x22, 0x1c, 0xb2, 0xe9, 0x8a, 0xa2, 0x6a, 0x9a, 0x85, 0x6d, 0xea, 0x82, 0xfe, - 0x54, 0xec, 0xd9, 0xe3, 0x99, 0x03, 0x2c, 0xea, 0x8b, 0x74, 0x27, 0xe3, 0x58, 0xba, 0x91, 0x4f, - 0x0f, 0x32, 0x05, 0xb6, 0x8a, 0x56, 0x20, 0xac, 0x47, 0x3d, 0xd6, 0x49, 0x80, 0x1e, 0x4b, 0x32, - 0x55, 0x37, 0x45, 0x92, 0x34, 0x5d, 0x59, 0x8a, 0x24, 0x37, 0xd4, 0x3c, 0x66, 0xc7, 0xa7, 0x3d, - 0x9a, 0xd2, 0x3f, 0x00, 0x4c, 0x88, 0xc0, 0x32, 0x8f, 0x2c, 0xf8, 0x3c, 0xd2, 0xd5, 0xdc, 0x23, - 0xdd, 0xae, 0x47, 0xea, 0xce, 0x40, 0x97, 0x43, 0x70, 0x1e, 0x8f, 0xc4, 0x49, 0x4f, 0xf6, 0x01, - 0xbd, 0x0f, 0xe0, 0x44, 0x03, 0xd0, 0xc5, 0x9c, 0xbb, 0xc3, 0x9d, 0x3a, 0x0e, 0xfb, 0x55, 0xb2, - 0xc0, 0x83, 0xde, 0x9f, 0xee, 0xa3, 0x0b, 0x6b, 0xda, 0x8e, 0xb9, 0xeb, 0x61, 0x30, 0xb6, 0x1c, - 0xc5, 0x6b, 0xe5, 0xad, 0x38, 0xbb, 0x1f, 0x4b, 0x65, 0xcb, 0xc2, 0x86, 0xb3, 0xec, 0xde, 0x51, - 0x7e, 0x1d, 0xff, 0x08, 0x58, 0xfe, 0xfb, 0x37, 0x19, 0xfe, 0x31, 0xd8, 0x47, 0x6e, 0x74, 0xfd, - 0xea, 0xf4, 0x92, 0xbf, 0xd7, 0x34, 0x74, 0x12, 0x22, 0xba, 0x65, 0x3b, 0xaa, 0xe5, 0x28, 0x05, - 0xac, 0xe7, 0x0b, 0x0e, 0x41, 0xd9, 0x95, 0x1e, 0x26, 0x3b, 0x19, 0x77, 0x63, 0x95, 0xac, 0xa3, - 0x29, 0x48, 0xd7, 0x14, 0x6c, 0x68, 0x5c, 0xb6, 0x8b, 0xc8, 0x0e, 0x92, 0xf5, 0x65, 0x43, 0xa3, - 0x92, 0xd2, 0x69, 0x38, 0x4a, 0x7d, 0xea, 0x2e, 0x2f, 0x1a, 0xb9, 0x82, 0x69, 0xf1, 0xa0, 0x8a, - 0xd1, 0x48, 0xbf, 0xe2, 0x25, 0xc0, 0xab, 0x55, 0x2f, 0x2d, 0x2a, 0x59, 0x61, 0x57, 0x78, 0x22, - 0x18, 0x82, 0xba, 0x16, 0x8b, 0x02, 0xd3, 0x90, 0x26, 0xd9, 0x85, 0xf0, 0x7a, 0xc7, 0x07, 0x4a, - 0xba, 0x0d, 0x0f, 0x0b, 0x25, 0x76, 0x00, 0xc0, 0xbf, 0x01, 0x1c, 0x27, 0xf6, 0x17, 0x6d, 0x5b, - 0xcf, 0x1b, 0x58, 0xbb, 0xa1, 0x5a, 0x79, 0xec, 0xf0, 0x72, 0x8a, 0x56, 0xe1, 0x88, 0x5d, 0x2e, - 0x61, 0xcb, 0x30, 0x35, 0xac, 0xa8, 0xb9, 0x9c, 0x59, 0x36, 0x1c, 0x56, 0x3f, 0xc6, 0x9f, 0x3d, - 0x9e, 0x19, 0xe5, 0xf5, 0x23, 0x97, 0xf3, 0x97, 0x90, 0xe1, 0x9a, 0xd6, 0x22, 0x55, 0xf2, 0x79, - 0xb7, 0xd3, 0x1f, 0xeb, 0x1f, 0x42, 0xf4, 0x1b, 0xbd, 0xe8, 0x60, 0x4b, 0xc9, 0x56, 0x94, 0x9a, - 0x90, 0x1b, 0xbf, 0xbe, 0xf4, 0x10, 0xdd, 0x49, 0x51, 0xd7, 0xaf, 0x69, 0xd2, 0x4b, 0x7e, 0x37, - 0x03, 0x88, 0x77, 0x3a, 0xa9, 0x92, 0x70, 0xbf, 0x85, 0xef, 0x96, 0x75, 0x0b, 0x6b, 0x8a, 0x59, - 0xc2, 0x86, 0x52, 0x32, 0x2d, 0xc7, 0x8e, 0x75, 0x4d, 0x76, 0x4d, 0xed, 0x4b, 0x8f, 0xf0, 0xad, - 0x6b, 0x25, 0x6c, 0x6c, 0xb8, 0x1b, 0xe8, 0x26, 0x1c, 0x73, 0x08, 0x16, 0x25, 0xe0, 0x32, 0x3b, - 0xd6, 0x3d, 0xd9, 0x15, 0xe5, 0xb3, 0x51, 0xaa, 0x9d, 0x69, 0xf0, 0x9c, 0x2d, 0xfd, 0xde, 0x9b, - 0xb3, 0x69, 0xec, 0xc2, 0x88, 0xce, 0xd9, 0xf0, 0xd0, 0x75, 0xbe, 0x42, 0xe8, 0xfc, 0xd9, 0xcf, - 0xcf, 0xaf, 0x27, 0x9f, 0x45, 0x56, 0x9a, 0x27, 0x1f, 0xd5, 0xe2, 0xc9, 0x47, 0x35, 0xa4, 0xaf, - 0x00, 0x94, 0x1a, 0x0d, 0xdb, 0xa9, 0x0a, 0xfd, 0x81, 0xad, 0x6f, 0x35, 0x07, 0xfd, 0x45, 0xbb, - 0xeb, 0x55, 0x8b, 0xb6, 0x20, 0x97, 0xbb, 0xc3, 0x73, 0xf9, 0x11, 0x80, 0x47, 0x9a, 0x3a, 0x80, - 0x39, 0xf9, 0x1c, 0xec, 0xa5, 0x2e, 0xb3, 0x59, 0x99, 0x6f, 0xc5, 0xcb, 0x5c, 0x65, 0xe7, 0x0a, - 0xfd, 0xd7, 0x1c, 0x6e, 0xc6, 0x31, 0x2d, 0x35, 0x8f, 0x97, 0x0a, 0x6a, 0xb1, 0x88, 0x0d, 0x57, - 0x9a, 0x9c, 0xf4, 0xdd, 0x0f, 0xd8, 0x4b, 0x00, 0x0f, 0x86, 0x93, 0x47, 0xb7, 0x60, 0xdc, 0x62, - 0x71, 0x53, 0x5e, 0x89, 0x7d, 0x8c, 0xab, 0x67, 0xda, 0xf0, 0xc2, 0x11, 0xb8, 0x8f, 0xaa, 0xf9, - 0xbf, 0x7a, 0x7b, 0xe9, 0x22, 0x2b, 0x64, 0x0b, 0x70, 0x80, 0x88, 0xd8, 0x8e, 0xea, 0x60, 0x5a, - 0x8a, 0x06, 0xe7, 0xe2, 0xc1, 0x17, 0xb0, 0x69, 0x39, 0x19, 0x57, 0x24, 0x0d, 0x4b, 0xfc, 0xa7, - 0x2d, 0xbd, 0x05, 0xe0, 0xd1, 0xe6, 0x41, 0x67, 0x49, 0x7a, 0xb9, 0x31, 0x49, 0x8f, 0x07, 0x4e, - 0x08, 0x37, 0xb1, 0x6b, 0xf9, 0xfa, 0x25, 0x60, 0x85, 0x73, 0xd5, 0xb4, 0x9d, 0xef, 0x4d, 0x8e, - 0xfe, 0x15, 0xc0, 0xa1, 0x3a, 0xe1, 0x65, 0xc3, 0xb1, 0x2a, 0xcd, 0x3e, 0x13, 0x81, 0x0c, 0xea, - 0x0c, 0xc9, 0xa0, 0x14, 0x1c, 0x28, 0x98, 0xb6, 0xa3, 0xb0, 0x52, 0x4f, 0x99, 0x8c, 0x07, 0xe2, - 0x5b, 0x3f, 0x96, 0xc5, 0x14, 0x16, 0x6a, 0x2b, 0xee, 0x73, 0x36, 0x16, 0x8c, 0x46, 0xad, 0x13, - 0x6a, 0x48, 0x9e, 0xc9, 0x26, 0xc6, 0x09, 0xa7, 0x5d, 0xcb, 0x9a, 0x3b, 0xec, 0x4d, 0x76, 0xd5, - 0xd4, 0x70, 0xa6, 0x6c, 0x97, 0xf4, 0x9c, 0x6e, 0x1a, 0xf4, 0x5e, 0xec, 0x74, 0xee, 0x48, 0x59, - 0xf6, 0xba, 0x0b, 0x3b, 0x8b, 0x79, 0xe6, 0x02, 0xdc, 0x43, 0xee, 0x2d, 0xfb, 0xbe, 0x1e, 0x09, - 0xf8, 0x25, 0xa8, 0xcb, 0x5c, 0x43, 0xf5, 0xa4, 0x3f, 0xf0, 0x0b, 0x5c, 0xff, 0xac, 0x14, 0x75, - 0x35, 0xab, 0x17, 0x75, 0xa7, 0xe2, 0xa3, 0xb5, 0x7b, 0x15, 0x4c, 0x32, 0xe0, 0x0f, 0x22, 0x20, - 0x30, 0xb6, 0xcb, 0x7e, 0xb6, 0x27, 0x02, 0x6c, 0x45, 0x16, 0xfc, 0x9c, 0x97, 0x18, 0xe5, 0x1b, - 0x7a, 0xee, 0xb7, 0xd8, 0xb9, 0x84, 0x1d, 0x6c, 0xe9, 0xa6, 0x45, 0xe2, 0xeb, 0xa3, 0x3c, 0x0e, - 0xfb, 0x1d, 0x22, 0xe2, 0xe9, 0xe3, 0xe8, 0xc2, 0x9a, 0x56, 0x03, 0x2d, 0x36, 0xd2, 0x2a, 0x68, - 0x91, 0x05, 0x3f, 0xe8, 0x39, 0x36, 0xd0, 0x58, 0xc5, 0x6a, 0xf1, 0x5a, 0x89, 0x43, 0x9c, 0x80, - 0xb0, 0x80, 0xd5, 0xa2, 0x62, 0x96, 0xea, 0x97, 0xb7, 0xaf, 0x40, 0x44, 0xd6, 0x34, 0x69, 0x9d, - 0x8d, 0x3b, 0xb8, 0x0e, 0x43, 0x34, 0x0f, 0x7b, 0x99, 0x92, 0x70, 0xde, 0x41, 0x35, 0xf8, 0x8b, - 0x8c, 0x1a, 0x94, 0xee, 0xf1, 0x76, 0x80, 0xee, 0xda, 0x29, 0x46, 0xbe, 0x15, 0x7f, 0xed, 0x58, - 0xdf, 0xfb, 0x37, 0xfe, 0xc2, 0x0f, 0x80, 0x60, 0xec, 0xce, 0xc2, 0x3e, 0xc6, 0x8e, 0x57, 0x8b, - 0x08, 0x7a, 0xbd, 0x94, 0xde, 0x0e, 0x16, 0x89, 0x07, 0x01, 0x47, 0xb9, 0x01, 0x2d, 0xd7, 0x3e, - 0x2f, 0x67, 0x60, 0x8f, 0x4d, 0x16, 0x88, 0x97, 0x06, 0xe7, 0x0e, 0x09, 0x00, 0x32, 0x2d, 0x26, - 0xbc, 0x8b, 0x2e, 0xe4, 0xf0, 0x5e, 0x1b, 0x17, 0xce, 0xfd, 0x37, 0x0e, 0xf7, 0x10, 0x8c, 0xe8, - 0x3e, 0x80, 0x3d, 0x74, 0xfc, 0x86, 0x82, 0xe5, 0x2d, 0x38, 0xe3, 0x8b, 0x1f, 0x6d, 0x2e, 0x44, - 0xcf, 0x92, 0x92, 0xf7, 0xde, 0xff, 0xec, 0x2f, 0x9d, 0x53, 0xe8, 0x98, 0x7c, 0x85, 0x48, 0x6f, - 0x58, 0xa6, 0x63, 0xe6, 0xcc, 0xa2, 0x1c, 0x3e, 0xaf, 0x44, 0x0f, 0x01, 0xdc, 0xeb, 0x9d, 0xd2, - 0xa1, 0x13, 0xe1, 0xc7, 0x84, 0x8c, 0x01, 0xe3, 0xd3, 0xad, 0x88, 0x32, 0x5c, 0xe7, 0x09, 0xae, - 0xb3, 0x68, 0x3e, 0x0a, 0x17, 0x9f, 0xd4, 0xc8, 0xdb, 0x9e, 0x11, 0x63, 0x15, 0xbd, 0x0b, 0xe0, - 0x48, 0x60, 0x80, 0x86, 0x92, 0x51, 0x08, 0xfc, 0x63, 0xc1, 0xb8, 0xdc, 0xb2, 0x3c, 0x83, 0xbd, - 0x4e, 0x60, 0x5f, 0x46, 0xcb, 0x2d, 0xc3, 0xce, 0x56, 0x14, 0x36, 0x48, 0x94, 0xb7, 0x1b, 0x46, - 0x90, 0x55, 0xf4, 0x36, 0x80, 0xc3, 0x8d, 0x73, 0x2d, 0x34, 0x13, 0x05, 0xca, 0x37, 0x85, 0x8b, - 0x27, 0x5b, 0x15, 0x67, 0x14, 0x56, 0x08, 0x85, 0x8b, 0xe8, 0x7c, 0x3b, 0x14, 0xe8, 0x58, 0x4f, - 0xde, 0xae, 0xcd, 0xfb, 0xaa, 0xe8, 0x01, 0x80, 0x7b, 0xbd, 0xf3, 0x18, 0x51, 0xa6, 0x84, 0x0c, - 0xc4, 0x44, 0x99, 0x12, 0x36, 0x1e, 0x93, 0xce, 0x10, 0xbc, 0x32, 0x9a, 0x89, 0xc2, 0x9b, 0xa3, - 0xda, 0xf4, 0x69, 0x88, 0xfe, 0x0e, 0xe0, 0x80, 0x67, 0xe2, 0x83, 0xa6, 0x04, 0x6e, 0x0a, 0x0c, - 0x9b, 0xe2, 0x27, 0x5a, 0x90, 0x64, 0xd8, 0x2e, 0x10, 0x6c, 0x3f, 0x41, 0x3f, 0x8e, 0xf4, 0x25, - 0x79, 0x77, 0xd2, 0x71, 0x93, 0xbc, 0xcd, 0x5f, 0xa1, 0x55, 0xf4, 0x7f, 0x00, 0x51, 0x70, 0xa8, - 0x85, 0xe4, 0x68, 0xff, 0xf8, 0x31, 0xff, 0xa8, 0x75, 0x05, 0x06, 0xfd, 0x1c, 0x81, 0x3e, 0x8f, - 0x4e, 0xb7, 0xe5, 0x56, 0x46, 0x01, 0xbd, 0x03, 0xe0, 0x50, 0xc3, 0xe8, 0x09, 0x9d, 0x0c, 0xc7, - 0x10, 0x3e, 0x53, 0x8b, 0xcf, 0xb4, 0x28, 0xcd, 0xe0, 0x5e, 0x21, 0x70, 0x57, 0xd0, 0xa5, 0x28, - 0xb8, 0x2a, 0x33, 0xa0, 0xd0, 0x39, 0x92, 0xed, 0xde, 0xba, 0x86, 0x57, 0x5c, 0x15, 0xbd, 0xc9, - 0x93, 0x83, 0xb5, 0xad, 0xcd, 0x92, 0xc3, 0x37, 0x6a, 0x6a, 0x9a, 0x1c, 0xfe, 0xa1, 0x90, 0x94, - 0x21, 0x90, 0xd7, 0xd1, 0x2f, 0x5a, 0x4b, 0x0e, 0xfa, 0x6c, 0xf4, 0x24, 0x47, 0x28, 0xf2, 0x8f, - 0x00, 0x3c, 0x18, 0x3e, 0x27, 0x41, 0xa7, 0x22, 0xa1, 0x05, 0xc7, 0x4a, 0xf1, 0xd3, 0xed, 0x29, - 0x31, 0x6a, 0xbf, 0x24, 0xd4, 0xae, 0xa1, 0xf5, 0x76, 0xa8, 0xd9, 0x6e, 0xdf, 0xc6, 0x1f, 0xc7, - 0xa1, 0xe4, 0x3e, 0x06, 0x70, 0x54, 0xd0, 0x60, 0x23, 0x01, 0xd0, 0xe6, 0x43, 0x98, 0xf8, 0x99, - 0x36, 0xb5, 0xda, 0xe5, 0x67, 0x53, 0x43, 0x4a, 0x8e, 0x5b, 0xe2, 0x5c, 0x43, 0xf9, 0x3d, 0x02, - 0x70, 0xc0, 0xd3, 0xf7, 0x89, 0xd2, 0x2e, 0xd8, 0xa8, 0x8b, 0xd2, 0x2e, 0xa4, 0x89, 0x94, 0x56, - 0x09, 0xf6, 0x14, 0xba, 0x18, 0x85, 0xdd, 0xd3, 0xcb, 0x86, 0xc3, 0x7d, 0x0f, 0x40, 0x14, 0xec, - 0xab, 0x44, 0xc5, 0x49, 0xd8, 0x29, 0x8a, 0x8a, 0x93, 0xb8, 0xdd, 0x93, 0x36, 0x08, 0x87, 0x9f, - 0xa3, 0xd5, 0x28, 0x0e, 0x04, 0xb0, 0xcd, 0x8d, 0xd0, 0xd9, 0x4e, 0x28, 0x97, 0x2f, 0x00, 0x8c, - 0x89, 0xba, 0x26, 0x24, 0xc8, 0x92, 0x88, 0x56, 0x31, 0x3e, 0xdf, 0xae, 0x1a, 0x63, 0xa7, 0x10, - 0x76, 0xb7, 0xd0, 0xcd, 0x28, 0x76, 0xb5, 0x46, 0xd4, 0xaa, 0x9b, 0xe2, 0x1c, 0xc5, 0x4d, 0x6a, - 0x15, 0x7d, 0x08, 0x60, 0x4c, 0xd4, 0x6d, 0x89, 0xc8, 0x46, 0x34, 0x89, 0x22, 0xb2, 0x51, 0x6d, - 0xa1, 0x74, 0x95, 0x90, 0x5d, 0x45, 0x2b, 0x51, 0x64, 0x59, 0x4b, 0xa5, 0x79, 0x4d, 0x71, 0xb2, - 0xb5, 0x76, 0xab, 0x8a, 0xfe, 0x0c, 0x60, 0x0f, 0x7d, 0x93, 0x8b, 0x9e, 0xc9, 0xbe, 0xce, 0x51, - 0xf4, 0x4c, 0xf6, 0xb7, 0x8a, 0xad, 0x7f, 0x0d, 0x59, 0xbf, 0x20, 0x6f, 0xd7, 0xdb, 0xd1, 0x2a, - 0x7a, 0x03, 0xc0, 0xa1, 0x86, 0x36, 0x4d, 0xf4, 0x35, 0x0c, 0x6f, 0x29, 0x45, 0x5f, 0x43, 0x41, - 0xef, 0xd7, 0xfa, 0x1b, 0x8e, 0xb7, 0x37, 0xee, 0x1b, 0x8e, 0x3a, 0xd1, 0xe7, 0xcc, 0xff, 0x79, - 0x81, 0xd3, 0xe6, 0x28, 0x12, 0xb8, 0xaf, 0xc5, 0x8b, 0x04, 0xee, 0xef, 0xb8, 0xa4, 0x25, 0x02, - 0xfc, 0x67, 0x68, 0xa1, 0x1d, 0xe0, 0xb4, 0x2d, 0x94, 0xb7, 0xe9, 0xff, 0xab, 0xa9, 0xe9, 0x27, - 0xcf, 0x13, 0xe0, 0xe9, 0xf3, 0x04, 0xf8, 0xf4, 0x79, 0x02, 0xfc, 0xe9, 0x45, 0xa2, 0xe3, 0xe9, - 0x8b, 0x44, 0xc7, 0x07, 0x2f, 0x12, 0x1d, 0xbf, 0x1e, 0xfe, 0x9d, 0x27, 0x9f, 0x2a, 0x25, 0x6c, - 0x67, 0x7b, 0xc8, 0x3f, 0x93, 0x38, 0xf5, 0x4d, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb3, 0xa8, 0x22, - 0x87, 0x5c, 0x22, 0x00, 0x00, + // 2048 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0x5f, 0x6c, 0x1c, 0x47, + 0x19, 0xcf, 0xd8, 0x89, 0x63, 0x7f, 0x4e, 0xed, 0x64, 0x1a, 0xe2, 0xf3, 0x39, 0x75, 0xac, 0x4d, + 0x69, 0x9d, 0x50, 0xdf, 0x12, 0x37, 0x09, 0x85, 0x94, 0x36, 0xbe, 0xc4, 0x89, 0x0d, 0x75, 0xe3, + 0x9e, 0x0b, 0x55, 0x91, 0xaa, 0xd5, 0xfa, 0x76, 0xb8, 0xdb, 0x72, 0xde, 0xdd, 0xec, 0xec, 0x45, + 0x9c, 0xac, 0x43, 0xa2, 0x7d, 0x47, 0x20, 0xc4, 0x5b, 0x5f, 0x10, 0x14, 0x21, 0x54, 0x89, 0x7f, + 0xe5, 0x9f, 0xc4, 0x1b, 0x2f, 0xe5, 0xad, 0x2a, 0x2f, 0x80, 0x04, 0x42, 0x09, 0x12, 0x12, 0x4f, + 0x48, 0xbc, 0x03, 0xda, 0x99, 0x6f, 0xee, 0x6e, 0x6f, 0x77, 0x6e, 0xef, 0x8a, 0x8d, 0x2a, 0xfa, + 0x92, 0xdc, 0xce, 0x7c, 0xdf, 0x37, 0xbf, 0xdf, 0x37, 0xdf, 0x7c, 0x33, 0xdf, 0x27, 0xc3, 0x42, + 0xa3, 0xb9, 0xc7, 0x42, 0xdb, 0xb4, 0x9b, 0x8e, 0x1b, 0x99, 0xf7, 0x2e, 0x99, 0x77, 0x9b, 0x2c, + 0x6c, 0x95, 0x82, 0xd0, 0x8f, 0x7c, 0x3a, 0x2b, 0x27, 0x4b, 0x62, 0xb2, 0x74, 0xef, 0x52, 0xf1, + 0x94, 0xbd, 0xe7, 0x7a, 0xbe, 0x29, 0xfe, 0x95, 0x32, 0xc5, 0xd3, 0x35, 0xbf, 0xe6, 0x8b, 0x9f, + 0x66, 0xfc, 0x0b, 0x47, 0xcf, 0xd6, 0x7c, 0xbf, 0xd6, 0x60, 0xa6, 0x1d, 0xb8, 0xa6, 0xed, 0x79, + 0x7e, 0x64, 0x47, 0xae, 0xef, 0x71, 0x9c, 0x9d, 0xaf, 0xfa, 0x7c, 0xcf, 0xe7, 0x96, 0x54, 0x93, + 0x1f, 0x38, 0x75, 0x51, 0x7e, 0x99, 0xbb, 0x36, 0x67, 0x12, 0x8b, 0x79, 0xef, 0xd2, 0x2e, 0x8b, + 0xec, 0x4b, 0x66, 0x60, 0xd7, 0x5c, 0x4f, 0xd8, 0x51, 0x8b, 0xf4, 0x63, 0x0f, 0xec, 0xd0, 0xde, + 0x53, 0x96, 0x52, 0xcc, 0x24, 0x0b, 0x39, 0xb9, 0xd8, 0x3f, 0xc9, 0xee, 0xb9, 0x0e, 0xf3, 0xaa, + 0x4c, 0xa7, 0xcc, 0x02, 0xbf, 0x5a, 0x97, 0x93, 0xc6, 0x69, 0xa0, 0x2f, 0xc4, 0xc8, 0xb6, 0xc5, + 0x72, 0x15, 0x76, 0xb7, 0xc9, 0x78, 0x64, 0xbc, 0x00, 0x0f, 0x27, 0x46, 0x79, 0xe0, 0x7b, 0x9c, + 0xd1, 0x4f, 0xc1, 0x84, 0x84, 0x55, 0x20, 0x4b, 0x64, 0x79, 0x7a, 0x75, 0xae, 0xd4, 0xe7, 0xd4, + 0x92, 0x54, 0x28, 0x4f, 0xbd, 0xf3, 0xe7, 0x73, 0x47, 0xbe, 0xff, 0xb7, 0x1f, 0x5d, 0x24, 0x15, + 0xd4, 0x30, 0xae, 0x41, 0x41, 0x98, 0x5c, 0x47, 0x70, 0xe5, 0xd6, 0xa6, 0x83, 0xcb, 0xd1, 0x73, + 0x30, 0xad, 0x30, 0x5b, 0xae, 0x23, 0x8c, 0x1f, 0xad, 0x80, 0x1a, 0xda, 0x74, 0x8c, 0x57, 0x60, + 0x3e, 0x43, 0x19, 0x51, 0x5d, 0x87, 0x49, 0x25, 0x8a, 0xb8, 0xe6, 0x53, 0xb8, 0x3a, 0x8a, 0x3d, + 0xc8, 0x3a, 0x5a, 0xc6, 0x0f, 0x08, 0x3c, 0xd2, 0x67, 0x7f, 0xa7, 0xb9, 0xfb, 0x2a, 0xab, 0x46, + 0x0a, 0xe1, 0x1a, 0xcc, 0x72, 0x39, 0x62, 0xd9, 0x8e, 0x13, 0x32, 0x2e, 0x5d, 0x30, 0x55, 0x2e, + 0xbc, 0xf7, 0xf6, 0xca, 0x69, 0xdc, 0xf5, 0x35, 0x39, 0xb3, 0x13, 0x85, 0xae, 0x57, 0xab, 0xcc, + 0xa0, 0x02, 0x8e, 0xd2, 0x5b, 0x00, 0xdd, 0x5d, 0x2f, 0x8c, 0x09, 0xa0, 0x8f, 0x95, 0x50, 0x35, + 0x0e, 0x91, 0x92, 0x0c, 0x57, 0x0c, 0x91, 0xd2, 0xb6, 0x5d, 0x63, 0xb8, 0x7c, 0xa5, 0x47, 0xd3, + 0xf8, 0x1e, 0x81, 0x45, 0x1d, 0x58, 0xf4, 0xc8, 0xb5, 0x84, 0x47, 0xc6, 0x07, 0x7b, 0xe4, 0x68, + 0xec, 0x91, 0xae, 0x33, 0xe8, 0xed, 0x0c, 0x9c, 0x8f, 0xe7, 0xe2, 0x94, 0x2b, 0x27, 0x80, 0xbe, + 0x4e, 0xe0, 0x6c, 0x1f, 0xd0, 0xb5, 0x6a, 0x3c, 0xa3, 0x9c, 0xba, 0x00, 0x53, 0xb6, 0x18, 0x50, + 0x9b, 0x3e, 0x55, 0x99, 0x94, 0x03, 0x9b, 0xce, 0x81, 0xb9, 0xeb, 0xcd, 0xf4, 0xde, 0x2a, 0x14, + 0x1f, 0x28, 0x6f, 0x15, 0xf1, 0x7c, 0xdc, 0x68, 0x86, 0x21, 0xf3, 0xa2, 0xf5, 0xf8, 0x8c, 0xaa, + 0xe3, 0xf8, 0x35, 0x82, 0xf1, 0x9f, 0x9c, 0x44, 0xfc, 0xf3, 0x30, 0x29, 0x4e, 0x74, 0xf7, 0xe8, + 0x1c, 0x17, 0xdf, 0x9b, 0x0e, 0x7d, 0x02, 0xa8, 0x9c, 0xe2, 0x91, 0x1d, 0x46, 0x56, 0x9d, 0xb9, + 0xb5, 0x7a, 0x24, 0x50, 0x8e, 0x57, 0x4e, 0x8a, 0x99, 0x9d, 0x78, 0x62, 0x43, 0x8c, 0xd3, 0x65, + 0x90, 0x63, 0x16, 0xf3, 0x1c, 0x25, 0x3b, 0x2e, 0x64, 0x67, 0xc4, 0xf8, 0xba, 0xe7, 0x48, 0x49, + 0xe3, 0x32, 0xcc, 0x49, 0x9f, 0xc6, 0xc3, 0x6b, 0x5e, 0xb5, 0xee, 0x87, 0x6a, 0x53, 0xf5, 0x68, + 0x8c, 0xcf, 0xab, 0x14, 0xd0, 0xab, 0xd5, 0x4d, 0x2d, 0xb6, 0x18, 0xc1, 0x23, 0x7c, 0x36, 0xbd, + 0x05, 0x5d, 0x2d, 0xdc, 0x05, 0xd4, 0x30, 0x96, 0xf0, 0x40, 0xf4, 0x7a, 0x27, 0x01, 0xca, 0x78, + 0x05, 0xce, 0x69, 0x25, 0x0e, 0x00, 0xc0, 0x0f, 0x09, 0x2c, 0x08, 0xfb, 0x6b, 0x9c, 0xbb, 0x35, + 0x8f, 0x39, 0x2f, 0xda, 0x61, 0x8d, 0x45, 0x2a, 0x9d, 0xd2, 0x0d, 0x38, 0xc5, 0x9b, 0x01, 0x0b, + 0x3d, 0xdf, 0x61, 0x96, 0x5d, 0xad, 0xfa, 0x4d, 0x2f, 0xc2, 0xfc, 0xb1, 0xf0, 0xde, 0xdb, 0x2b, + 0x73, 0x2a, 0x7f, 0x54, 0xab, 0xc9, 0x14, 0x72, 0xb2, 0xa3, 0xb5, 0x26, 0x95, 0x12, 0xde, 0x1d, + 0x4b, 0xee, 0xf5, 0xc7, 0x80, 0x7e, 0xd1, 0x6d, 0x44, 0x2c, 0xb4, 0x76, 0x5b, 0x56, 0x47, 0x28, + 0xde, 0xbf, 0xc9, 0xca, 0xac, 0x9c, 0x29, 0x4b, 0xd7, 0x6f, 0x3a, 0xc6, 0x4f, 0xc6, 0xf1, 0x6c, + 0xa6, 0x10, 0x1f, 0x74, 0x50, 0x95, 0xe0, 0xe1, 0x90, 0xdd, 0x6d, 0xba, 0x21, 0x73, 0x2c, 0x3f, + 0x60, 0x9e, 0x15, 0xf8, 0x61, 0xc4, 0x0b, 0xe3, 0x4b, 0xe3, 0xcb, 0x0f, 0x55, 0x4e, 0xa9, 0xa9, + 0x3b, 0x01, 0xf3, 0xb6, 0xe3, 0x09, 0xfa, 0x12, 0xcc, 0x47, 0x02, 0x8b, 0x95, 0x72, 0x19, 0x2f, + 0x1c, 0x5d, 0x1a, 0xcf, 0xf3, 0xd9, 0x9c, 0xd4, 0xde, 0xe9, 0xf3, 0x1c, 0xa7, 0x2f, 0x43, 0x31, + 0x64, 0xf1, 0xe2, 0x2c, 0x4c, 0x9b, 0x2e, 0x1c, 0xcb, 0xdf, 0x8d, 0x82, 0x52, 0xef, 0xb7, 0x4d, + 0x19, 0xe0, 0xaa, 0xca, 0x9c, 0xb5, 0x67, 0x07, 0x81, 0xeb, 0xd5, 0x78, 0x61, 0x42, 0x24, 0x94, + 0xc7, 0x53, 0xc1, 0x84, 0xaa, 0x9b, 0x0e, 0xf3, 0x22, 0x37, 0x6a, 0x6d, 0x49, 0x79, 0x8c, 0xab, + 0x8f, 0x48, 0x6b, 0x28, 0x83, 0x73, 0xdc, 0xf8, 0x2e, 0x81, 0x33, 0xd9, 0x7a, 0xf4, 0x26, 0xcc, + 0x36, 0xfc, 0x9a, 0x5b, 0xb5, 0x1b, 0xa3, 0xc4, 0xd7, 0x0c, 0xea, 0x28, 0x1e, 0x37, 0x61, 0xb6, + 0x2a, 0x4f, 0x48, 0xc7, 0xca, 0xd8, 0x10, 0x56, 0x50, 0x07, 0xad, 0x18, 0x5f, 0xe9, 0x4d, 0x0e, + 0x15, 0xe1, 0xb3, 0xfc, 0xe4, 0x90, 0x7d, 0x46, 0xc6, 0xde, 0xc7, 0x19, 0x49, 0xa6, 0x19, 0xb5, + 0x7e, 0xf7, 0x94, 0xcb, 0x5d, 0x1c, 0x7c, 0xca, 0xa5, 0x96, 0x3a, 0xe5, 0x52, 0xc3, 0xf8, 0x17, + 0x01, 0xa3, 0xdf, 0x30, 0x2f, 0xb7, 0x2a, 0x18, 0x15, 0xff, 0xd3, 0xc3, 0x9e, 0xbc, 0x1d, 0xc7, + 0xdf, 0xef, 0xed, 0xa8, 0x49, 0x1a, 0x47, 0xb3, 0x93, 0xc6, 0x5b, 0x04, 0xce, 0x0f, 0x74, 0x00, + 0x3a, 0xf9, 0x69, 0x38, 0x2e, 0x5d, 0xc6, 0xf1, 0x3e, 0x1d, 0xc6, 0xcb, 0x4a, 0xe5, 0xe0, 0x6e, + 0xd4, 0x7f, 0x2b, 0xb8, 0x3b, 0x91, 0x1f, 0xda, 0x35, 0x76, 0xa3, 0x6e, 0x37, 0x1a, 0xcc, 0x8b, + 0xa5, 0xc5, 0x4a, 0xff, 0xff, 0x1b, 0xf6, 0x0f, 0x02, 0x67, 0xb2, 0xc9, 0xe7, 0x64, 0x43, 0xf2, + 0xdf, 0x64, 0xc3, 0x01, 0x5e, 0x38, 0x0f, 0x0f, 0x49, 0xb5, 0xe4, 0xf3, 0xe2, 0x84, 0x1c, 0xc4, + 0x1b, 0xe3, 0x1a, 0x4c, 0x0b, 0x11, 0x1e, 0xd9, 0x11, 0x93, 0x39, 0x7f, 0x66, 0xb5, 0x98, 0x2e, + 0x35, 0xfc, 0x30, 0xda, 0x89, 0x45, 0x2a, 0x10, 0xa8, 0x9f, 0xdc, 0xf8, 0x25, 0x81, 0x47, 0x07, + 0x6f, 0x3a, 0x06, 0xe9, 0xed, 0xfe, 0x20, 0x4d, 0xe7, 0xe8, 0x6c, 0x13, 0x87, 0x16, 0xaf, 0xff, + 0x24, 0x98, 0x38, 0x37, 0x7c, 0x1e, 0x7d, 0x68, 0x62, 0xf4, 0x5b, 0x04, 0x66, 0xbb, 0x84, 0xd7, + 0xbd, 0x28, 0x6c, 0x0d, 0xba, 0x26, 0x52, 0x11, 0x34, 0x96, 0x11, 0x41, 0x65, 0x98, 0xae, 0xfb, + 0x3c, 0xb2, 0x30, 0xd5, 0x4b, 0x26, 0x0b, 0xa9, 0xfd, 0xed, 0x2e, 0x8b, 0x7b, 0x0a, 0xf5, 0xce, + 0x48, 0x5c, 0x37, 0x14, 0xd2, 0xbb, 0xd1, 0x29, 0x39, 0xfb, 0x82, 0x67, 0x69, 0x80, 0x71, 0xc1, + 0xe9, 0xd0, 0xa2, 0xe6, 0x55, 0x7c, 0xfc, 0x3e, 0xef, 0x3b, 0x6c, 0xa7, 0xc9, 0x03, 0xb7, 0xea, + 0xfa, 0x9e, 0x3c, 0x17, 0x07, 0x1d, 0x3b, 0xc6, 0x2e, 0x3e, 0xa3, 0xb3, 0xd6, 0x42, 0xcf, 0x3c, + 0x0b, 0xc7, 0xc4, 0xb9, 0xc5, 0xfb, 0xf5, 0x7c, 0xca, 0x2f, 0x69, 0x5d, 0x74, 0x8d, 0xd4, 0x33, + 0xbe, 0xaa, 0x0e, 0x70, 0xf7, 0x5a, 0x69, 0xb8, 0xf6, 0xae, 0xdb, 0x70, 0xa3, 0x56, 0x82, 0xd6, + 0xe1, 0x65, 0x30, 0xc3, 0x83, 0x8f, 0xe6, 0x40, 0x40, 0xb6, 0xeb, 0x49, 0xb6, 0x17, 0x52, 0x6c, + 0x75, 0x16, 0x92, 0x9c, 0x6f, 0x20, 0xe5, 0x17, 0xdd, 0xea, 0x97, 0x58, 0x74, 0x93, 0x45, 0x2c, + 0x74, 0xfd, 0x50, 0xec, 0x6f, 0x82, 0xf2, 0x02, 0x4c, 0x45, 0x42, 0xa4, 0xa7, 0x60, 0x96, 0x03, + 0x9b, 0x4e, 0x07, 0xb4, 0xde, 0xc8, 0xb0, 0xa0, 0x75, 0x16, 0x92, 0xa0, 0x57, 0xb1, 0x73, 0xb4, + 0xc1, 0xec, 0xc6, 0x9d, 0x40, 0x41, 0x3c, 0x0b, 0x50, 0x67, 0x76, 0xc3, 0xf2, 0x83, 0xee, 0xe1, + 0x9d, 0xac, 0x0b, 0x91, 0x4d, 0xc7, 0xd8, 0xc2, 0xbe, 0x92, 0xd2, 0x41, 0x44, 0x57, 0xe1, 0x38, + 0x2a, 0x69, 0x1b, 0x4b, 0x52, 0x43, 0xbd, 0xc8, 0xa4, 0x41, 0xe3, 0x35, 0x55, 0x77, 0xc9, 0x59, + 0x5e, 0x46, 0xf2, 0xc3, 0xf8, 0xeb, 0xc0, 0x1a, 0x0c, 0xdf, 0x56, 0x6d, 0x8e, 0x14, 0x08, 0x64, + 0xf7, 0x14, 0x4c, 0x22, 0x3b, 0x95, 0x2d, 0x72, 0xe8, 0x1d, 0x97, 0xf4, 0x0e, 0x30, 0x49, 0xbc, + 0x91, 0x72, 0x54, 0xbc, 0xa1, 0xcd, 0xce, 0xf5, 0x72, 0x05, 0x26, 0xb8, 0x18, 0x10, 0x5e, 0x9a, + 0x59, 0x7d, 0x44, 0x03, 0x10, 0xb5, 0x50, 0xf8, 0x10, 0x5d, 0xa8, 0xe0, 0x7d, 0x60, 0x5c, 0xb8, + 0xfa, 0xe3, 0x22, 0x1c, 0x13, 0x18, 0xe9, 0xeb, 0x04, 0x26, 0x64, 0x9f, 0x93, 0xa6, 0xd3, 0x5b, + 0xba, 0x99, 0x5a, 0x7c, 0x74, 0xb0, 0x90, 0x5c, 0xcb, 0x28, 0xbd, 0xf6, 0xbb, 0xbf, 0x7e, 0x73, + 0x6c, 0x99, 0x3e, 0x66, 0x3e, 0x27, 0xa4, 0xb7, 0x43, 0x3f, 0xf2, 0xab, 0x7e, 0xc3, 0xcc, 0x6e, + 0x0c, 0xd3, 0x37, 0x09, 0x9c, 0xe8, 0x6d, 0x87, 0xd2, 0x0b, 0xd9, 0xcb, 0x64, 0xf4, 0x5b, 0x8b, + 0x17, 0x87, 0x11, 0x45, 0x5c, 0xcf, 0x08, 0x5c, 0x4f, 0xd1, 0xab, 0x79, 0xb8, 0x54, 0x4b, 0xcc, + 0xdc, 0xef, 0xe9, 0xe5, 0xb6, 0xe9, 0x6f, 0x08, 0x9c, 0x4a, 0x75, 0x2a, 0x69, 0x29, 0x0f, 0x41, + 0xb2, 0xff, 0x5a, 0x34, 0x87, 0x96, 0x47, 0xd8, 0x5b, 0x02, 0xf6, 0x6d, 0xba, 0x3e, 0x34, 0xec, + 0xdd, 0x96, 0x85, 0x1d, 0x5b, 0x73, 0xbf, 0xaf, 0xd7, 0xdb, 0xa6, 0xbf, 0x22, 0x70, 0xb2, 0xbf, + 0x81, 0x48, 0x57, 0xf2, 0x40, 0x25, 0xda, 0x9d, 0xc5, 0xd2, 0xb0, 0xe2, 0x48, 0xe1, 0x96, 0xa0, + 0x70, 0x9d, 0x3e, 0x33, 0x0a, 0x05, 0xd9, 0x3f, 0x35, 0xf7, 0x3b, 0x8d, 0xd5, 0x36, 0x7d, 0x83, + 0xc0, 0x89, 0xde, 0xc6, 0x97, 0x2e, 0x52, 0x32, 0x3a, 0x8f, 0xba, 0x48, 0xc9, 0xea, 0x43, 0x1a, + 0x57, 0x04, 0x5e, 0x93, 0xae, 0xe4, 0xe1, 0x55, 0x3d, 0x06, 0xf1, 0xa6, 0xa3, 0xdf, 0x21, 0x30, + 0xdd, 0xd3, 0x5a, 0xa3, 0xcb, 0x1a, 0x37, 0xa5, 0xba, 0x7a, 0xc5, 0x0b, 0x43, 0x48, 0x22, 0xb6, + 0x67, 0x05, 0xb6, 0x4f, 0xd2, 0x4f, 0xe4, 0xfa, 0x52, 0xbc, 0x3b, 0x65, 0x5f, 0xcf, 0xdc, 0x57, + 0xaf, 0xd0, 0x36, 0xfd, 0x19, 0x01, 0x9a, 0xee, 0x1e, 0x52, 0x33, 0xdf, 0x3f, 0x49, 0xcc, 0x1f, + 0x1f, 0x5e, 0x01, 0xa1, 0x3f, 0x2d, 0xa0, 0x5f, 0xa5, 0x97, 0x47, 0x72, 0x2b, 0x52, 0xa0, 0xbf, + 0x26, 0x30, 0xdb, 0xd7, 0xe3, 0xa3, 0x4f, 0x64, 0x63, 0xc8, 0x6e, 0x5e, 0x16, 0x57, 0x86, 0x94, + 0x46, 0xb8, 0xcf, 0x09, 0xb8, 0xb7, 0xe8, 0xcd, 0x3c, 0xb8, 0x36, 0x1a, 0xb0, 0x64, 0xb3, 0x8b, + 0xc7, 0xa7, 0xae, 0xef, 0x15, 0xd7, 0xa6, 0xbf, 0x50, 0xc1, 0x81, 0x65, 0xeb, 0xa0, 0xe0, 0x48, + 0xb4, 0x9a, 0x06, 0x06, 0x47, 0xb2, 0x29, 0x64, 0xec, 0x08, 0xc8, 0x5b, 0xf4, 0xb3, 0xc3, 0x05, + 0x87, 0x7c, 0x36, 0xf6, 0x04, 0x47, 0x26, 0xf2, 0x3f, 0x12, 0x38, 0x93, 0xdd, 0x27, 0xa1, 0x4f, + 0xe6, 0x42, 0x4b, 0xb7, 0x95, 0x8a, 0x97, 0x47, 0x53, 0x42, 0x6a, 0x9f, 0x13, 0xd4, 0xee, 0xd0, + 0xad, 0x51, 0xa8, 0xf1, 0xb8, 0x6e, 0x53, 0x8f, 0xe3, 0x4c, 0x72, 0x7f, 0x22, 0x30, 0xa7, 0x29, + 0xb0, 0xa9, 0x06, 0xe8, 0xe0, 0x26, 0x4c, 0xf1, 0xca, 0x88, 0x5a, 0xa3, 0xf2, 0xe3, 0xd2, 0x90, + 0x55, 0x55, 0x96, 0x14, 0xd7, 0x4c, 0x7e, 0x6f, 0x11, 0x98, 0xee, 0xa9, 0xfb, 0x74, 0x61, 0x97, + 0x2e, 0xd4, 0x75, 0x61, 0x97, 0x51, 0x44, 0x1a, 0x1b, 0x02, 0x7b, 0x99, 0x5e, 0xcf, 0xc3, 0xde, + 0x53, 0xcb, 0x66, 0xc3, 0xfd, 0x2d, 0x01, 0x9a, 0xae, 0xab, 0x74, 0xc9, 0x49, 0x5b, 0x29, 0xea, + 0x92, 0x93, 0xbe, 0xdc, 0x33, 0xb6, 0x05, 0x87, 0xcf, 0xd0, 0x8d, 0x3c, 0x0e, 0x02, 0x30, 0x57, + 0x46, 0x64, 0x6f, 0x27, 0x93, 0xcb, 0xdf, 0x09, 0x14, 0x74, 0x55, 0x13, 0xd5, 0x44, 0x49, 0x4e, + 0xa9, 0x58, 0xbc, 0x3a, 0xaa, 0x1a, 0xb2, 0xb3, 0x04, 0xbb, 0x97, 0xe9, 0x4b, 0x79, 0xec, 0x3a, + 0x85, 0x68, 0xd8, 0x35, 0xa5, 0x38, 0xea, 0x8b, 0xd4, 0x36, 0xfd, 0x03, 0x81, 0x82, 0xae, 0xda, + 0xd2, 0x91, 0xcd, 0x29, 0x12, 0x75, 0x64, 0xf3, 0xca, 0x42, 0xe3, 0x79, 0x41, 0x76, 0x83, 0xde, + 0xca, 0x23, 0x8b, 0x25, 0x95, 0xd3, 0x6b, 0x4a, 0x91, 0xed, 0x94, 0x5b, 0x6d, 0xfa, 0x0d, 0x02, + 0x13, 0xf2, 0x4d, 0xae, 0x7b, 0x26, 0x27, 0x2a, 0x47, 0xdd, 0x33, 0x39, 0x59, 0x2a, 0x0e, 0x7f, + 0x1b, 0x62, 0xbd, 0x60, 0xee, 0x77, 0xcb, 0xd1, 0x36, 0xfd, 0x39, 0x81, 0xd9, 0xbe, 0x32, 0x4d, + 0x77, 0x1b, 0x66, 0x97, 0x94, 0xba, 0xdb, 0x50, 0x53, 0xfb, 0x0d, 0xff, 0x86, 0x53, 0xe5, 0x4d, + 0xfc, 0x86, 0x93, 0x4e, 0x4c, 0x38, 0xf3, 0xa7, 0xbd, 0xc0, 0x65, 0x71, 0x94, 0x0b, 0x3c, 0x51, + 0xe2, 0xe5, 0x02, 0x4f, 0x56, 0x5c, 0xc6, 0x0d, 0x01, 0xfc, 0xd3, 0xf4, 0xda, 0x28, 0xc0, 0x65, + 0x59, 0x68, 0xee, 0xcb, 0xff, 0xdb, 0xe5, 0x8b, 0xef, 0xdc, 0x5f, 0x24, 0xef, 0xde, 0x5f, 0x24, + 0x7f, 0xb9, 0xbf, 0x48, 0xbe, 0xfe, 0x60, 0xf1, 0xc8, 0xbb, 0x0f, 0x16, 0x8f, 0xfc, 0xfe, 0xc1, + 0xe2, 0x91, 0x2f, 0x9c, 0xfc, 0x72, 0x4f, 0x3c, 0xb5, 0x02, 0xc6, 0x77, 0x27, 0xc4, 0xdf, 0xa3, + 0x3c, 0xf9, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0x84, 0xcd, 0x86, 0xc5, 0x23, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -3387,6 +3467,27 @@ func (m *QueryAssignedTargetsResponse) MarshalToSizedBuffer(dAtA []byte) (int, e _ = i var l int _ = l + if len(m.TargetAccountMappings) > 0 { + for iNdEx := len(m.TargetAccountMappings) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.TargetAccountMappings[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + } + } + if len(m.ReporterSupernodeAccount) > 0 { + i -= len(m.ReporterSupernodeAccount) + copy(dAtA[i:], m.ReporterSupernodeAccount) + i = encodeVarintQuery(dAtA, i, uint64(len(m.ReporterSupernodeAccount))) + i-- + dAtA[i] = 0x2a + } if len(m.TargetSupernodeAccounts) > 0 { for iNdEx := len(m.TargetSupernodeAccounts) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.TargetSupernodeAccounts[iNdEx]) @@ -3427,6 +3528,43 @@ func (m *QueryAssignedTargetsResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *AccountIdentityMapping) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AccountIdentityMapping) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AccountIdentityMapping) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CurrentAccount) > 0 { + i -= len(m.CurrentAccount) + copy(dAtA[i:], m.CurrentAccount) + i = encodeVarintQuery(dAtA, i, uint64(len(m.CurrentAccount))) + i-- + dAtA[i] = 0x12 + } + if len(m.LogicalAccount) > 0 { + i -= len(m.LogicalAccount) + copy(dAtA[i:], m.LogicalAccount) + i = encodeVarintQuery(dAtA, i, uint64(len(m.LogicalAccount))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *QueryEpochReportRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -4584,6 +4722,33 @@ func (m *QueryAssignedTargetsResponse) Size() (n int) { n += 1 + l + sovQuery(uint64(l)) } } + l = len(m.ReporterSupernodeAccount) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.TargetAccountMappings) > 0 { + for _, e := range m.TargetAccountMappings { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *AccountIdentityMapping) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.LogicalAccount) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.CurrentAccount) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } return n } @@ -6454,6 +6619,186 @@ func (m *QueryAssignedTargetsResponse) Unmarshal(dAtA []byte) error { } m.TargetSupernodeAccounts = append(m.TargetSupernodeAccounts, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReporterSupernodeAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReporterSupernodeAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetAccountMappings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TargetAccountMappings = append(m.TargetAccountMappings, AccountIdentityMapping{}) + if err := m.TargetAccountMappings[len(m.TargetAccountMappings)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AccountIdentityMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AccountIdentityMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AccountIdentityMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LogicalAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LogicalAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CurrentAccount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CurrentAccount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) From 2c5818c5cb09b37b1fe050322081e09b7994dfa1 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 09:32:36 +0000 Subject: [PATCH 05/18] fix(evmigration): preserve finalized action provenance --- x/action/v1/keeper/action.go | 7 +- x/evmigration/keeper/migrate_action.go | 162 ++++++++++++--- .../keeper/migrate_action_lifecycle_test.go | 187 ++++++++++++++++++ x/evmigration/keeper/migrate_test.go | 7 + 4 files changed, 330 insertions(+), 33 deletions(-) create mode 100644 x/evmigration/keeper/migrate_action_lifecycle_test.go diff --git a/x/action/v1/keeper/action.go b/x/action/v1/keeper/action.go index c9b0d965..2b39485e 100644 --- a/x/action/v1/keeper/action.go +++ b/x/action/v1/keeper/action.go @@ -546,9 +546,10 @@ func (k *Keeper) getActionsByIndexPrefix(ctx sdk.Context, indexPrefix string) ([ actionID := string(iter.Key()[len(prefixBytes):]) action, found := k.GetActionByID(ctx, actionID) if !found { - // Stale or corrupted index entry; skip but keep scanning. - k.Logger().Error("action referenced in index not found", "action_id", actionID, "index_prefix", indexPrefix) - continue + return nil, fmt.Errorf("action %s referenced by index %s not found", actionID, indexPrefix) + } + if action.ActionID != actionID { + return nil, fmt.Errorf("action index %s resolves mismatched action id %s", indexPrefix, action.ActionID) } actions = append(actions, action) } diff --git a/x/evmigration/keeper/migrate_action.go b/x/evmigration/keeper/migrate_action.go index b3914b0c..03de5cae 100644 --- a/x/evmigration/keeper/migrate_action.go +++ b/x/evmigration/keeper/migrate_action.go @@ -1,62 +1,164 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" ) -// MigrateActions updates action records where legacyAddr is the creator or is -// listed in the SuperNodes field (which stores AccAddress, not ValAddress). -// -// Rather than scanning the entire action store, it resolves the affected -// actions through the creator and supernode secondary indexes, so cost scales -// with the number of actions this address touches, not the global action count. +type actionIndexRefs struct { + creator bool + supernode bool +} + +// MigrateActions preserves action identity continuity without rewriting historical +// participants. Pending and processing actions remain fully actionable by the new +// account, while done actions only move their creator (the party that can still +// need creator-scoped access). Terminal actions are immutable history. func (k Keeper) MigrateActions(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { legacyStr := legacyAddr.String() newStr := newAddr.String() byCreator, err := k.actionKeeper.GetActionsByCreator(ctx, legacyStr) if err != nil { - return err + return fmt.Errorf("get actions by creator %s: %w", legacyStr, err) } bySuperNode, err := k.actionKeeper.GetActionsBySuperNode(ctx, legacyStr) if err != nil { - return err + return fmt.Errorf("get actions by supernode %s: %w", legacyStr, err) } - // An action may reference the legacy address as both creator and supernode, - // and each index lookup returns an independent copy. Dedupe by action ID so - // the record is written exactly once, preserving encounter order. - seen := make(map[string]*actiontypes.Action, len(byCreator)+len(bySuperNode)) + // Keep the index encounter order deterministic, but use index values only to + // discover IDs. The primary action store is the sole canonical data source. + refs := make(map[string]actionIndexRefs, len(byCreator)+len(bySuperNode)) order := make([]string, 0, len(byCreator)+len(bySuperNode)) - for _, action := range byCreator { - if _, ok := seen[action.ActionID]; !ok { - seen[action.ActionID] = action - order = append(order, action.ActionID) + addIndexRows := func(rows []*actiontypes.Action, creatorIndex bool) error { + for i, indexed := range rows { + if indexed == nil { + return fmt.Errorf("malformed action index row %d: nil action", i) + } + if indexed.ActionID == "" { + return fmt.Errorf("malformed action index row %d: empty action ID", i) + } + if creatorIndex { + if indexed.Creator != legacyStr { + return fmt.Errorf("stale creator index for action %s", indexed.ActionID) + } + } else if countAddress(indexed.SuperNodes, legacyStr) == 0 { + return fmt.Errorf("stale supernode index for action %s", indexed.ActionID) + } + + ref, exists := refs[indexed.ActionID] + if !exists { + order = append(order, indexed.ActionID) + } + if creatorIndex { + ref.creator = true + } else { + ref.supernode = true + } + refs[indexed.ActionID] = ref } + return nil } - for _, action := range bySuperNode { - if _, ok := seen[action.ActionID]; !ok { - seen[action.ActionID] = action - order = append(order, action.ActionID) - } + if err := addIndexRows(byCreator, true); err != nil { + return err + } + if err := addIndexRows(bySuperNode, false); err != nil { + return err } + // Resolve and validate every canonical row before writing any of them. This + // makes index corruption and destination collisions fail closed. + updates := make([]*actiontypes.Action, 0, len(order)) for _, id := range order { - action := seen[id] - if action.Creator == legacyStr { - action.Creator = newStr + canonical, found := k.actionKeeper.GetActionByID(ctx, id) + if !found || canonical == nil { + return fmt.Errorf("canonical action %s referenced by index not found", id) } - for i, sn := range action.SuperNodes { - if sn == legacyStr { - action.SuperNodes[i] = newStr - } + if canonical.ActionID == "" || canonical.ActionID != id { + return fmt.Errorf("malformed canonical action for index ID %s: got %q", id, canonical.ActionID) + } + + ref := refs[id] + legacySNCount := countAddress(canonical.SuperNodes, legacyStr) + if ref.creator && canonical.Creator != legacyStr { + return fmt.Errorf("stale creator index conflicts with canonical action %s", id) } - if err := k.actionKeeper.SetAction(ctx, action); err != nil { - return err + if ref.supernode && legacySNCount == 0 { + return fmt.Errorf("stale supernode index conflicts with canonical action %s", id) + } + + switch canonical.State { + case actiontypes.ActionStatePending, actiontypes.ActionStateProcessing: + if legacySNCount > 1 { + return fmt.Errorf("action %s has duplicate legacy supernode entries", id) + } + if legacySNCount == 1 && countAddress(canonical.SuperNodes, newStr) != 0 { + return fmt.Errorf("action %s already contains destination supernode", id) + } + updated := cloneAction(canonical) + changed := false + if updated.Creator == legacyStr { + updated.Creator = newStr + changed = true + } + for i, supernode := range updated.SuperNodes { + if supernode == legacyStr { + updated.SuperNodes[i] = newStr + changed = true + } + } + if !changed { + return fmt.Errorf("live action %s index does not reference legacy address", id) + } + updates = append(updates, updated) + + case actiontypes.ActionStateDone: + if canonical.Creator == legacyStr { + updated := cloneAction(canonical) + updated.Creator = newStr + updates = append(updates, updated) + } + + case actiontypes.ActionStateApproved, actiontypes.ActionStateRejected, + actiontypes.ActionStateFailed, actiontypes.ActionStateExpired, + actiontypes.ActionStateUnspecified: + // Immutable historical record. + + default: + return fmt.Errorf("action %s has malformed state %d", id, canonical.State) } } + // A direct helper call must be as atomic as a message execution. SetAction + // updates several indexes, so discard the cache if any later write fails. + cacheCtx, commit := ctx.CacheContext() + for _, action := range updates { + if err := k.actionKeeper.SetAction(cacheCtx, action); err != nil { + return fmt.Errorf("set action %s: %w", action.ActionID, err) + } + } + commit() return nil } + +func countAddress(addresses []string, target string) int { + count := 0 + for _, address := range addresses { + if address == target { + count++ + } + } + return count +} + +func cloneAction(action *actiontypes.Action) *actiontypes.Action { + clone := *action + clone.Metadata = append([]byte(nil), action.Metadata...) + clone.AppPubkey = append([]byte(nil), action.AppPubkey...) + clone.SuperNodes = append([]string(nil), action.SuperNodes...) + return &clone +} diff --git a/x/evmigration/keeper/migrate_action_lifecycle_test.go b/x/evmigration/keeper/migrate_action_lifecycle_test.go new file mode 100644 index 00000000..cecdaa40 --- /dev/null +++ b/x/evmigration/keeper/migrate_action_lifecycle_test.go @@ -0,0 +1,187 @@ +package keeper_test + +import ( + "errors" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + + actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types" + evmigrationtypes "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + +func TestMigrateActions_LifecycleMatrix(t *testing.T) { + tests := []struct { + name string + state actiontypes.ActionState + wantWrite bool + wantCreatorMoved bool + wantSNMoved bool + }{ + {"pending", actiontypes.ActionStatePending, true, true, true}, + {"processing", actiontypes.ActionStateProcessing, true, true, true}, + {"done", actiontypes.ActionStateDone, true, true, false}, + {"approved", actiontypes.ActionStateApproved, false, false, false}, + {"rejected", actiontypes.ActionStateRejected, false, false, false}, + {"failed", actiontypes.ActionStateFailed, false, false, false}, + {"expired", actiontypes.ActionStateExpired, false, false, false}, + {"unspecified", actiontypes.ActionStateUnspecified, false, false, false}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + f := initMockFixture(t) + legacy, destination, other := testAccAddr(), testAccAddr(), testAccAddr() + indexedCreator := &actiontypes.Action{ActionID: "matrix", Creator: legacy.String()} + indexedSN := &actiontypes.Action{ActionID: "matrix", SuperNodes: []string{legacy.String()}} + canonical := &actiontypes.Action{ + ActionID: "matrix", Creator: legacy.String(), State: tc.state, + ActionType: actiontypes.ActionTypeCascade, Metadata: []byte{1, 2, 3}, + Price: "7ulume", ExpirationTime: 99, BlockHeight: 42, + SuperNodes: []string{legacy.String(), other.String()}, FileSizeKbs: 123, + AppPubkey: []byte{4, 5, 6}, + } + before := *canonical + before.Metadata = append([]byte(nil), canonical.Metadata...) + before.SuperNodes = append([]string(nil), canonical.SuperNodes...) + before.AppPubkey = append([]byte(nil), canonical.AppPubkey...) + + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexedCreator}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexedSN}, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "matrix").Return(canonical, true).Times(1) + if tc.wantWrite { + f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()).DoAndReturn(func(_ any, got *actiontypes.Action) error { + require.Equal(t, before.ActionID, got.ActionID) + require.Equal(t, before.ActionType, got.ActionType) + require.Equal(t, before.Metadata, got.Metadata) + require.Equal(t, before.Price, got.Price) + require.Equal(t, before.ExpirationTime, got.ExpirationTime) + require.Equal(t, before.State, got.State) + require.Equal(t, before.BlockHeight, got.BlockHeight) + require.Equal(t, before.FileSizeKbs, got.FileSizeKbs) + require.Equal(t, before.AppPubkey, got.AppPubkey) + if tc.wantCreatorMoved { + require.Equal(t, destination.String(), got.Creator) + } else { + require.Equal(t, legacy.String(), got.Creator) + } + if tc.wantSNMoved { + require.Equal(t, destination.String(), got.SuperNodes[0]) + } else { + require.Equal(t, legacy.String(), got.SuperNodes[0]) + } + // Prove the writable value does not alias canonical store/index data. + got.Metadata[0], got.AppPubkey[0], got.SuperNodes[1] = 9, 9, "mutated" + return nil + }) + } + + require.NoError(t, f.keeper.MigrateActions(f.ctx, legacy, destination)) + require.Equal(t, before, *canonical, "canonical value must remain immutable") + }) + } +} + +func TestMigrateActions_CreatorOnlyUsesCanonicalValue(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + indexCopy := &actiontypes.Action{ActionID: "creator-only", Creator: legacy.String(), Metadata: []byte("stale-copy")} + canonical := &actiontypes.Action{ActionID: "creator-only", Creator: legacy.String(), State: actiontypes.ActionStatePending, Metadata: []byte("canonical")} + + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexCopy}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return(nil, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "creator-only").Return(canonical, true).Times(1) + f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()).DoAndReturn(func(_ any, got *actiontypes.Action) error { + require.Equal(t, []byte("canonical"), got.Metadata) + require.Equal(t, destination.String(), got.Creator) + return nil + }) + + require.NoError(t, f.keeper.MigrateActions(f.ctx, legacy, destination)) +} + +func TestMigrateActions_FailsClosedOnBadIndexOrCanonicalData(t *testing.T) { + t.Run("nil index row", func(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{nil}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return(nil, nil) + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "nil action") + }) + + t.Run("missing canonical row", func(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + indexed := &actiontypes.Action{ActionID: "missing", Creator: legacy.String()} + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexed}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return(nil, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "missing").Return(nil, false) + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "not found") + }) + + t.Run("stale creator index conflicts with canonical", func(t *testing.T) { + f := initMockFixture(t) + legacy, destination, other := testAccAddr(), testAccAddr(), testAccAddr() + indexed := &actiontypes.Action{ActionID: "stale", Creator: legacy.String()} + canonical := &actiontypes.Action{ActionID: "stale", Creator: other.String(), State: actiontypes.ActionStatePending} + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexed}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return(nil, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "stale").Return(canonical, true) + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "conflicts") + }) + + t.Run("duplicate legacy supernodes", func(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + indexed := &actiontypes.Action{ActionID: "duplicate", SuperNodes: []string{legacy.String()}} + canonical := &actiontypes.Action{ActionID: "duplicate", State: actiontypes.ActionStatePending, SuperNodes: []string{legacy.String(), legacy.String()}} + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return(nil, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{indexed}, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "duplicate").Return(canonical, true) + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "duplicate legacy") + }) +} + +func TestMigrateActions_ValidatesDestinationCollisionBeforeAnyWrite(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + firstIndex := &actiontypes.Action{ActionID: "first", Creator: legacy.String()} + secondIndex := &actiontypes.Action{ActionID: "second", SuperNodes: []string{legacy.String()}} + first := &actiontypes.Action{ActionID: "first", Creator: legacy.String(), State: actiontypes.ActionStatePending} + second := &actiontypes.Action{ActionID: "second", State: actiontypes.ActionStateProcessing, SuperNodes: []string{legacy.String(), destination.String()}} + + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{firstIndex}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{secondIndex}, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "first").Return(first, true) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "second").Return(second, true) + // No SetAction expectation: the first prepared update must not be written. + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "destination supernode") +} + +func TestMigrateActions_LateWriteFailureRollsBackCache(t *testing.T) { + f := initMockFixture(t) + legacy, destination := testAccAddr(), testAccAddr() + firstIndex := &actiontypes.Action{ActionID: "first", Creator: legacy.String()} + secondIndex := &actiontypes.Action{ActionID: "second", Creator: legacy.String()} + first := &actiontypes.Action{ActionID: "first", Creator: legacy.String(), State: actiontypes.ActionStatePending} + second := &actiontypes.Action{ActionID: "second", Creator: legacy.String(), State: actiontypes.ActionStatePending} + + f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return([]*actiontypes.Action{firstIndex, secondIndex}, nil) + f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()).Return(nil, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "first").Return(first, true) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "second").Return(second, true) + gomock.InOrder( + f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()).DoAndReturn(func(cacheCtx sdk.Context, _ *actiontypes.Action) error { + return f.keeper.MigrationRecords.Set(cacheCtx, "rollback-probe", evmigrationtypes.MigrationRecord{ + LegacyAddress: "legacy", NewAddress: "destination", + }) + }), + f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()).Return(errors.New("late write failure")), + ) + + require.ErrorContains(t, f.keeper.MigrateActions(f.ctx, legacy, destination), "late write failure") + _, err := f.keeper.MigrationRecords.Get(f.ctx, "rollback-probe") + require.Error(t, err, "write made through the first SetAction cache context must be discarded") +} diff --git a/x/evmigration/keeper/migrate_test.go b/x/evmigration/keeper/migrate_test.go index 7e123d2b..dbb3ceba 100644 --- a/x/evmigration/keeper/migrate_test.go +++ b/x/evmigration/keeper/migrate_test.go @@ -1186,17 +1186,21 @@ func TestMigrateActions_CreatorAndSuperNodes(t *testing.T) { ActionID: "action-1", Creator: legacy.String(), SuperNodes: []string{legacy.String(), otherAddr.String()}, + State: actiontypes.ActionStatePending, } bySuperNode := &actiontypes.Action{ ActionID: "action-1", Creator: legacy.String(), SuperNodes: []string{legacy.String(), otherAddr.String()}, + State: actiontypes.ActionStatePending, } + canonical := *byCreator f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()). Return([]*actiontypes.Action{byCreator}, nil) f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()). Return([]*actiontypes.Action{bySuperNode}, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "action-1").Return(&canonical, true) f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()). DoAndReturn(func(_ any, updated *actiontypes.Action) error { require.Equal(t, "action-1", updated.ActionID) @@ -1223,11 +1227,14 @@ func TestMigrateActions_SuperNodeOnly(t *testing.T) { ActionID: "action-2", Creator: creator.String(), SuperNodes: []string{legacy.String()}, + State: actiontypes.ActionStateProcessing, } + canonical := *action f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), legacy.String()).Return(nil, nil) f.actionKeeper.EXPECT().GetActionsBySuperNode(gomock.Any(), legacy.String()). Return([]*actiontypes.Action{action}, nil) + f.actionKeeper.EXPECT().GetActionByID(gomock.Any(), "action-2").Return(&canonical, true) f.actionKeeper.EXPECT().SetAction(gomock.Any(), gomock.Any()). DoAndReturn(func(_ any, updated *actiontypes.Action) error { require.Equal(t, creator.String(), updated.Creator) From 1864d947008a10ab2fd342e97dd49b913dd403b1 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 11:47:39 +0000 Subject: [PATCH 06/18] fix(evmigration): preserve staking maturity queues --- x/evmigration/keeper/migrate_staking.go | 162 +---- x/evmigration/keeper/migrate_test.go | 106 ++- x/evmigration/keeper/migrate_validator.go | 105 +-- .../keeper/msg_server_claim_legacy.go | 21 +- .../keeper/msg_server_claim_legacy_test.go | 45 +- .../keeper/msg_server_migrate_validator.go | 35 +- .../msg_server_migrate_validator_test.go | 5 + .../keeper/staking_migration_plan.go | 677 ++++++++++++++++++ .../keeper/staking_migration_plan_test.go | 131 ++++ x/evmigration/mocks/expected_keepers_mock.go | 15 + x/evmigration/types/expected_keepers.go | 1 + 11 files changed, 1065 insertions(+), 238 deletions(-) create mode 100644 x/evmigration/keeper/staking_migration_plan.go create mode 100644 x/evmigration/keeper/staking_migration_plan_test.go diff --git a/x/evmigration/keeper/migrate_staking.go b/x/evmigration/keeper/migrate_staking.go index 9eba4322..f25b7f43 100644 --- a/x/evmigration/keeper/migrate_staking.go +++ b/x/evmigration/keeper/migrate_staking.go @@ -1,83 +1,88 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// MigrateStaking re-keys all delegations, unbonding delegations, and redelegations -// from legacyAddr to newAddr. origWithdrawAddr is the withdraw address that was -// set *before* MigrateDistribution may have temporarily redirected it to self. +// MigrateStaking atomically re-keys an account's staking state. The complete +// UBD/RED primary and maturity-queue plan is validated before the cache context +// receives its first write. func (k Keeper) MigrateStaking(ctx sdk.Context, legacyAddr, newAddr, origWithdrawAddr sdk.AccAddress) error { - // Active delegations. - if err := k.migrateActiveDelegations(ctx, legacyAddr, newAddr); err != nil { + cacheCtx, commit := ctx.CacheContext() + delegations, ubds, reds, err := k.accountStakingRecords(cacheCtx, legacyAddr) + if err != nil { return err } - - // Unbonding delegations. - if err := k.migrateUnbondingDelegations(ctx, legacyAddr, newAddr); err != nil { + plan, err := k.buildStakingMigrationPlan(cacheCtx, delegations, ubds, reds, stakingAddressTransform{ + oldDelegator: legacyAddr, + newDelegator: newAddr, + }) + if err != nil { return err } - - // Redelegations — we need to check all validators the legacy address has - // redelegations from. Get delegator's redelegations by iterating all validators. - if err := k.migrateRedelegations(ctx, legacyAddr, newAddr); err != nil { + if err := k.migrateAccountStakingWithPlan(cacheCtx, legacyAddr, newAddr, origWithdrawAddr, delegations, plan); err != nil { return err } - - // Migrate withdraw address using the original (pre-redirect) value. - return k.migrateWithdrawAddress(ctx, legacyAddr, newAddr, origWithdrawAddr) + commit() + return nil } -// migrateActiveDelegations re-keys all active delegations and their distribution -// starting info from legacyAddr to newAddr. -func (k Keeper) migrateActiveDelegations(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { - delegations, err := k.stakingKeeper.GetDelegatorDelegations(ctx, legacyAddr, ^uint16(0)) - if err != nil { +func (k Keeper) migrateAccountStakingWithPlan( + ctx sdk.Context, + legacyAddr, newAddr, origWithdrawAddr sdk.AccAddress, + delegations []stakingtypes.Delegation, + plan stakingMigrationPlan, +) error { + // Validate all raw source/destination primaries and apply queue-backed records + // before keeper calls mutate active delegation primaries. + if err := k.applyStakingMigrationPlan(ctx, plan, false); err != nil { + return err + } + if err := k.migrateActiveDelegations(ctx, legacyAddr, newAddr, delegations); err != nil { return err } + return k.migrateWithdrawAddress(ctx, legacyAddr, newAddr, origWithdrawAddr) +} +// migrateActiveDelegations re-keys supplied active delegations and their +// distribution starting info. Supplying the preflight snapshot avoids a second +// unbounded keeper query after writes begin. +func (k Keeper) migrateActiveDelegations(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress, delegations []stakingtypes.Delegation) error { for _, del := range delegations { + if del.DelegatorAddress != legacyAddr.String() { + return fmt.Errorf("delegation snapshot contains unexpected delegator %s", del.DelegatorAddress) + } valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress) if err != nil { return err } - // Delete old distribution starting info. if err := k.distributionKeeper.DeleteDelegatorStartingInfo(ctx, valAddr, legacyAddr); err != nil { return err } - - // Remove old delegation. if err := k.stakingKeeper.RemoveDelegation(ctx, del); err != nil { return err } - - // Create new delegation with same shares. newDel := stakingtypes.NewDelegation(newAddr.String(), del.ValidatorAddress, del.Shares) if err := k.stakingKeeper.SetDelegation(ctx, newDel); err != nil { return err } - // Initialize fresh distribution starting info for the new delegation. - // The old starting info was deleted above, so we always create new info - // anchored at the current block height and rewards period. currentRewards, err := k.distributionKeeper.GetValidatorCurrentRewards(ctx, valAddr) if err != nil { return err } - sdkCtx := sdk.UnwrapSDKContext(ctx) previousPeriod := currentRewards.Period - 1 - // Distribution stores stake as tokens (TokensFromSharesTruncated), not - // raw shares; for an ever-slashed validator (exchange rate < 1) storing - // shares overstates stake and panics the next reward/undelegate tx. val, err := k.stakingKeeper.GetValidator(ctx, valAddr) if err != nil { return err } startingInfo := distrtypes.DelegatorStartingInfo{ - Height: uint64(sdkCtx.BlockHeight()), + Height: uint64(ctx.BlockHeight()), PreviousPeriod: previousPeriod, Stake: val.TokensFromSharesTruncated(del.Shares), } @@ -88,105 +93,17 @@ func (k Keeper) migrateActiveDelegations(ctx sdk.Context, legacyAddr, newAddr sd return err } } - - return nil -} - -// migrateUnbondingDelegations re-keys all unbonding delegations from legacyAddr -// to newAddr, including unbonding queue entries and UnbondingID indexes. -func (k Keeper) migrateUnbondingDelegations(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { - unbondings, err := k.stakingKeeper.GetUnbondingDelegations(ctx, legacyAddr, ^uint16(0)) - if err != nil { - return err - } - - for _, ubd := range unbondings { - // Remove old unbonding delegation. - // The full record is already loaded, so we do not need to rediscover it - // through active delegations, which would miss validators that were fully - // undelegated before migration. - if err := k.stakingKeeper.RemoveUnbondingDelegation(ctx, ubd); err != nil { - return err - } - - // Create new with same entries but newAddr as delegator. - newUbd := stakingtypes.UnbondingDelegation{ - DelegatorAddress: newAddr.String(), - ValidatorAddress: ubd.ValidatorAddress, - Entries: ubd.Entries, - } - if err := k.stakingKeeper.SetUnbondingDelegation(ctx, newUbd); err != nil { - return err - } - - // Re-insert into unbonding queue and re-key UnbondingID indexes. - for _, entry := range newUbd.Entries { - if err := k.stakingKeeper.InsertUBDQueue(ctx, newUbd, entry.CompletionTime); err != nil { - return err - } - if entry.UnbondingId > 0 { - if err := k.stakingKeeper.SetUnbondingDelegationByUnbondingID(ctx, newUbd, entry.UnbondingId); err != nil { - return err - } - } - } - } - - return nil -} - -// migrateRedelegations re-keys all redelegations where legacyAddr is the -// delegator, including redelegation queue entries and UnbondingID indexes. -func (k Keeper) migrateRedelegations(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { - redelegations, err := k.stakingKeeper.GetRedelegations(ctx, legacyAddr, ^uint16(0)) - if err != nil { - return err - } - - for _, red := range redelegations { - // Remove old redelegation. - if err := k.stakingKeeper.RemoveRedelegation(ctx, red); err != nil { - return err - } - - // Create new with newAddr as delegator. - newRed := stakingtypes.Redelegation{ - DelegatorAddress: newAddr.String(), - ValidatorSrcAddress: red.ValidatorSrcAddress, - ValidatorDstAddress: red.ValidatorDstAddress, - Entries: red.Entries, - } - if err := k.stakingKeeper.SetRedelegation(ctx, newRed); err != nil { - return err - } - - // Re-insert into queue and re-key UnbondingID indexes. - for _, entry := range newRed.Entries { - if err := k.stakingKeeper.InsertRedelegationQueue(ctx, newRed, entry.CompletionTime); err != nil { - return err - } - if entry.UnbondingId > 0 { - if err := k.stakingKeeper.SetRedelegationByUnbondingID(ctx, newRed, entry.UnbondingId); err != nil { - return err - } - } - } - } - return nil } // migrateWithdrawAddress updates the delegator withdraw address. origWithdrawAddr // is the withdraw address that was set before MigrateDistribution may have -// temporarily redirected it to self for safe reward withdrawal. +// temporarily redirected it to self. func (k Keeper) migrateWithdrawAddress(ctx sdk.Context, legacyAddr, newAddr, origWithdrawAddr sdk.AccAddress) error { - // If the original withdraw address was self (legacy) or nil, update to new address. if origWithdrawAddr == nil || origWithdrawAddr.Equals(legacyAddr) { return k.distributionKeeper.SetDelegatorWithdrawAddr(ctx, newAddr, newAddr) } - // Third-party withdraw address: if it was migrated, follow the record - // to the new address so future rewards reach the right account. resolvedAddr := origWithdrawAddr record, err := k.MigrationRecords.Get(ctx, origWithdrawAddr.String()) if err == nil && record.NewAddress != "" { @@ -195,6 +112,5 @@ func (k Keeper) migrateWithdrawAddress(ctx sdk.Context, legacyAddr, newAddr, ori resolvedAddr = resolved } } - return k.distributionKeeper.SetDelegatorWithdrawAddr(ctx, newAddr, resolvedAddr) } diff --git a/x/evmigration/keeper/migrate_test.go b/x/evmigration/keeper/migrate_test.go index dbb3ceba..39d66df4 100644 --- a/x/evmigration/keeper/migrate_test.go +++ b/x/evmigration/keeper/migrate_test.go @@ -64,6 +64,7 @@ func initMockFixture(t *testing.T) *mockFixture { bankKeeper := evmigrationmocks.NewMockBankKeeper(ctrl) stakingKeeper := evmigrationmocks.NewMockStakingKeeper(ctrl) distributionKeeper := evmigrationmocks.NewMockDistributionKeeper(ctrl) + distributionKeeper.EXPECT().HasDelegatorStartingInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() authzKeeper := evmigrationmocks.NewMockAuthzKeeper(ctrl) feegrantKeeper := evmigrationmocks.NewMockFeegrantKeeper(ctrl) supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) @@ -112,6 +113,7 @@ func initMockFixture(t *testing.T) *mockFixture { auditKeeper, actionKeeper, ) + k.SetStakingStoreService(stakingStoreService) // Initialize params with migration enabled. params := types.NewParams(true, 0, 50, 2000, 20) @@ -142,6 +144,21 @@ func (f *mockFixture) wireScopedMigrationStores() { f.keeper.SetDistributionStoreService(f.distributionStore) } +func seedDelegationPrimary(t *testing.T, f *mockFixture, delegation stakingtypes.Delegation) { + t.Helper() + + delegator, err := sdk.AccAddressFromBech32(delegation.DelegatorAddress) + require.NoError(t, err) + validator, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) + require.NoError(t, err) + + store := f.stakingStore.OpenKVStore(f.ctx) + require.NoError(t, store.Set( + stakingtypes.GetDelegationKey(delegator, validator), + stakingtypes.MustMarshalDelegation(f.cdc, delegation), + )) +} + func (f *mockFixture) writeRedelegation(red stakingtypes.Redelegation) { delegator, err := sdk.AccAddressFromBech32(red.DelegatorAddress) if err != nil { @@ -167,6 +184,59 @@ func (f *mockFixture) writeRedelegation(red stakingtypes.Redelegation) { if err := store.Set(stakingtypes.GetREDByValDstIndexKey(delegator, src, dst), []byte{}); err != nil { panic(err) } + triplet := stakingtypes.DVVTriplet{DelegatorAddress: red.DelegatorAddress, ValidatorSrcAddress: red.ValidatorSrcAddress, ValidatorDstAddress: red.ValidatorDstAddress} + seen := map[int64]bool{} + for _, entry := range red.Entries { + if seen[entry.CompletionTime.UnixNano()] { + continue + } + seen[entry.CompletionTime.UnixNano()] = true + key := stakingtypes.GetRedelegationTimeKey(entry.CompletionTime) + var slice stakingtypes.DVVTriplets + if existing, err := store.Get(key); err != nil { + panic(err) + } else if existing != nil { + f.cdc.MustUnmarshal(existing, &slice) + } + slice.Triplets = append(slice.Triplets, triplet) + if err := store.Set(key, f.cdc.MustMarshal(&slice)); err != nil { + panic(err) + } + } +} + +func (f *mockFixture) writeUnbondingDelegation(ubd stakingtypes.UnbondingDelegation) { + delegator, err := sdk.AccAddressFromBech32(ubd.DelegatorAddress) + if err != nil { + panic(err) + } + validator, err := sdk.ValAddressFromBech32(ubd.ValidatorAddress) + if err != nil { + panic(err) + } + store := f.stakingStore.OpenKVStore(f.ctx) + if err := store.Set(stakingtypes.GetUBDKey(delegator, validator), stakingtypes.MustMarshalUBD(f.cdc, ubd)); err != nil { + panic(err) + } + pair := stakingtypes.DVPair{DelegatorAddress: ubd.DelegatorAddress, ValidatorAddress: ubd.ValidatorAddress} + seen := map[int64]bool{} + for _, entry := range ubd.Entries { + if seen[entry.CompletionTime.UnixNano()] { + continue + } + seen[entry.CompletionTime.UnixNano()] = true + key := stakingtypes.GetUnbondingDelegationTimeKey(entry.CompletionTime) + var slice stakingtypes.DVPairs + if existing, err := store.Get(key); err != nil { + panic(err) + } else if existing != nil { + f.cdc.MustUnmarshal(existing, &slice) + } + slice.Pairs = append(slice.Pairs, pair) + if err := store.Set(key, f.cdc.MustMarshal(&slice)); err != nil { + panic(err) + } + } } func (f *mockFixture) writeRedelegationIndexes(delegator sdk.AccAddress, src, dst sdk.ValAddress) { @@ -1271,6 +1341,7 @@ func TestMigrateStaking_ActiveDelegations(t *testing.T) { valAddr := sdk.ValAddress(testAccAddr()) del := stakingtypes.NewDelegation(legacy.String(), valAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f, del) // migrateActiveDelegations f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.Delegation{del}, nil) @@ -1311,6 +1382,7 @@ func TestMigrateStaking_SlashedValidatorStoresTokensNotShares(t *testing.T) { valAddr := sdk.ValAddress(testAccAddr()) del := stakingtypes.NewDelegation(legacy.String(), valAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f, del) // Slashed validator: 90 tokens / 100 shares → TokensFromSharesTruncated(100) = 90. slashedVal := stakingtypes.Validator{ @@ -1423,6 +1495,7 @@ func TestMigrateStaking_WithUnbondingDelegation(t *testing.T) { valAddr := sdk.ValAddress(testAccAddr()) del := stakingtypes.NewDelegation(legacy.String(), valAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f, del) completionTime := f.ctx.BlockTime().Add(21 * 24 * 3600 * 1e9) // 21 days ubd := stakingtypes.UnbondingDelegation{ DelegatorAddress: legacy.String(), @@ -1437,6 +1510,7 @@ func TestMigrateStaking_WithUnbondingDelegation(t *testing.T) { }, }, } + f.writeUnbondingDelegation(ubd) // migrateActiveDelegations f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.Delegation{del}, nil) @@ -1461,7 +1535,6 @@ func TestMigrateStaking_WithUnbondingDelegation(t *testing.T) { require.Len(t, newUbd.Entries, 1) return nil }) - f.stakingKeeper.EXPECT().InsertUBDQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetUnbondingDelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(42)).Return(nil) // migrateRedelegations @@ -1484,6 +1557,7 @@ func TestMigrateStaking_WithRedelegation(t *testing.T) { dstValAddr := sdk.ValAddress(testAccAddr()) del := stakingtypes.NewDelegation(legacy.String(), srcValAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f, del) completionTime := f.ctx.BlockTime().Add(21 * 24 * 3600 * 1e9) red := stakingtypes.Redelegation{ DelegatorAddress: legacy.String(), @@ -1499,6 +1573,7 @@ func TestMigrateStaking_WithRedelegation(t *testing.T) { }, }, } + f.writeRedelegation(red) // migrateActiveDelegations f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.Delegation{del}, nil) @@ -1527,7 +1602,6 @@ func TestMigrateStaking_WithRedelegation(t *testing.T) { require.Len(t, newRed.Entries, 1) return nil }) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(99)).Return(nil) // migrateWithdrawAddress — origWithdrawAddr is nil (not set). @@ -1560,6 +1634,7 @@ func TestMigrateStaking_UnbondingWithoutActiveDelegation(t *testing.T) { }, }, } + f.writeUnbondingDelegation(ubd) // migrateActiveDelegations f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) @@ -1574,7 +1649,6 @@ func TestMigrateStaking_UnbondingWithoutActiveDelegation(t *testing.T) { require.Len(t, newUbd.Entries, 1) return nil }) - f.stakingKeeper.EXPECT().InsertUBDQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetUnbondingDelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(77)).Return(nil) // migrateRedelegations @@ -1616,6 +1690,7 @@ func TestMigrateValidatorDelegations_WithUnbondingAndRedelegation(t *testing.T) }, }, } + f.writeUnbondingDelegation(ubd) f.stakingKeeper.EXPECT().RemoveUnbondingDelegation(gomock.Any(), ubd).Return(nil) f.stakingKeeper.EXPECT().SetUnbondingDelegation(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, newUbd stakingtypes.UnbondingDelegation) error { @@ -1623,7 +1698,6 @@ func TestMigrateValidatorDelegations_WithUnbondingAndRedelegation(t *testing.T) require.Equal(t, delegator.String(), newUbd.DelegatorAddress) return nil }) - f.stakingKeeper.EXPECT().InsertUBDQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetUnbondingDelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(77)).Return(nil) // Two redelegations with an UnbondingId: one where the migrated validator is @@ -1658,15 +1732,8 @@ func TestMigrateValidatorDelegations_WithUnbondingAndRedelegation(t *testing.T) }, }, } - // Redelegations are discovered by an internal scan; this fixture leaves the - // scoped store unwired, so the scan falls back to IterateRedelegations. - f.stakingKeeper.EXPECT().IterateRedelegations(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ any, fn func(int64, stakingtypes.Redelegation) bool) error { - require.False(t, fn(0, srcRed)) - require.False(t, fn(1, dstRed)) - return nil - }, - ) + f.writeRedelegation(srcRed) + f.writeRedelegation(dstRed) f.stakingKeeper.EXPECT().RemoveRedelegation(gomock.Any(), srcRed).Return(nil) f.stakingKeeper.EXPECT().SetRedelegation(gomock.Any(), gomock.Any()).DoAndReturn( func(_ any, newRed stakingtypes.Redelegation) error { @@ -1675,7 +1742,6 @@ func TestMigrateValidatorDelegations_WithUnbondingAndRedelegation(t *testing.T) return nil }, ) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(88)).Return(nil) f.stakingKeeper.EXPECT().RemoveRedelegation(gomock.Any(), dstRed).Return(nil) f.stakingKeeper.EXPECT().SetRedelegation(gomock.Any(), gomock.Any()).DoAndReturn( @@ -1685,14 +1751,13 @@ func TestMigrateValidatorDelegations_WithUnbondingAndRedelegation(t *testing.T) return nil }, ) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(89)).Return(nil) err := f.keeper.MigrateValidatorDelegations( f.ctx, oldValAddr, newValAddr, nil, []stakingtypes.UnbondingDelegation{ubd}, - nil, + []stakingtypes.Redelegation{srcRed, dstRed}, ) require.NoError(t, err) } @@ -1759,7 +1824,6 @@ func TestMigrateValidatorDelegations_UsesScopedRedelegationIndexes(t *testing.T) return nil }, ).Times(2) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(2) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) // V4's internal scoped scan discovers the two related redelegations @@ -1804,7 +1868,6 @@ func TestMigrateValidatorDelegations_DeduplicatesSourceAndDestinationIndexes(t * return nil }, ).Times(1) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(1) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(101)).Return(nil).Times(1) // V4's internal scan collects the doubly-indexed redelegation exactly once @@ -1834,6 +1897,7 @@ func TestMigrateValidatorDelegations_UsesPreloadedRedelegations(t *testing.T) { UnbondingId: 111, }}, } + f.writeRedelegation(red) f.stakingKeeper.EXPECT().RemoveRedelegation(gomock.Any(), red).Return(nil).Times(1) f.stakingKeeper.EXPECT().SetRedelegation(gomock.Any(), gomock.Any()).DoAndReturn( @@ -1843,7 +1907,6 @@ func TestMigrateValidatorDelegations_UsesPreloadedRedelegations(t *testing.T) { return nil }, ).Times(1) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(1) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(111)).Return(nil).Times(1) // The staking store intentionally has no redelegation rows. Passing a @@ -1953,7 +2016,6 @@ func TestMigrateValidatorDelegations_RekeysMultipleSourceRedelegations(t *testin return nil }, ).Times(2) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(2) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) // Both redelegations share the val-src index prefix; V4's internal scan must @@ -1984,6 +2046,7 @@ func TestMigrateValidatorDelegations_SetsHistoricalRewardsRefCountOnce(t *testin dels := make([]stakingtypes.Delegation, 3) for i := range dels { dels[i] = stakingtypes.NewDelegation(testAccAddr().String(), oldValAddr.String(), math.LegacyNewDec(int64(10*(i+1)))) + seedDelegationPrimary(t, f, dels[i]) } // Current rewards period 5 → target (previous) period 4. @@ -2045,6 +2108,7 @@ func TestMigrateValidatorDelegations_SlashedValidatorStoresTokensNotShares(t *te newValAddr := sdk.ValAddress(testAccAddr()) del := stakingtypes.NewDelegation(testAccAddr().String(), oldValAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f, del) // Slashed validator: 90 tokens back 100 shares (exchange rate 0.9), so // TokensFromSharesTruncated(100) = 90, strictly less than the 100 shares. @@ -2481,7 +2545,6 @@ func TestMigrateValidatorScopedIteration_SimulatesGlobalStateImprovement(t *test return nil }, ).Times(2) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(2) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(2) err = f.keeper.MigrateValidatorDelegations(f.ctx, oldValAddr, newValAddr, nil, nil, []stakingtypes.Redelegation{srcRed, dstRed}) @@ -2560,7 +2623,6 @@ func TestMigrateValidatorDelegations_RedelegationReplayIsDeterministic(t *testin return nil }, ).Times(numReds) - f.stakingKeeper.EXPECT().InsertRedelegationQueue(gomock.Any(), gomock.Any(), completionTime).Return(nil).Times(numReds) f.stakingKeeper.EXPECT().SetRedelegationByUnbondingID(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(numReds) // Passing nil redelegations forces the internal scoped scan (the map path). diff --git a/x/evmigration/keeper/migrate_validator.go b/x/evmigration/keeper/migrate_validator.go index 0bfebc8b..bfb3ad08 100644 --- a/x/evmigration/keeper/migrate_validator.go +++ b/x/evmigration/keeper/migrate_validator.go @@ -79,6 +79,40 @@ func (k Keeper) MigrateValidatorDelegations( ubds []stakingtypes.UnbondingDelegation, reds []stakingtypes.Redelegation, ) error { + cacheCtx, commit := ctx.CacheContext() + if reds == nil { + var err error + reds, err = k.redelegationsForValidator(cacheCtx, oldValAddr) + if err != nil { + return err + } + } + plan, err := k.buildStakingMigrationPlan(cacheCtx, delegations, ubds, reds, stakingAddressTransform{ + oldValidator: oldValAddr, + newValidator: newValAddr, + }) + if err != nil { + return err + } + if err := k.migrateValidatorDelegationsWithPlan(cacheCtx, oldValAddr, newValAddr, delegations, plan); err != nil { + return err + } + commit() + return nil +} + +func (k Keeper) migrateValidatorDelegationsWithPlan( + ctx sdk.Context, + oldValAddr, newValAddr sdk.ValAddress, + delegations []stakingtypes.Delegation, + plan stakingMigrationPlan, +) error { + // Validate all raw source/destination primaries and apply queue-backed records + // before keeper calls mutate active delegation primaries. + if err := k.applyStakingMigrationPlan(ctx, plan, false); err != nil { + return err + } + // All delegations reference the same period (currentRewards.Period - 1). Its // reference count becomes base(1) + one per re-keyed delegation. Set it in a // single write here instead of resetting to 1 and incrementing once per @@ -139,77 +173,6 @@ func (k Keeper) MigrateValidatorDelegations( } } - // Re-key unbonding delegations. (ubds supplied by the caller.) - for _, ubd := range ubds { - if err := k.stakingKeeper.RemoveUnbondingDelegation(ctx, ubd); err != nil { - return err - } - - newUbd := stakingtypes.UnbondingDelegation{ - DelegatorAddress: ubd.DelegatorAddress, - ValidatorAddress: newValAddr.String(), - Entries: ubd.Entries, - } - if err := k.stakingKeeper.SetUnbondingDelegation(ctx, newUbd); err != nil { - return err - } - - for _, entry := range newUbd.Entries { - if err := k.stakingKeeper.InsertUBDQueue(ctx, newUbd, entry.CompletionTime); err != nil { - return err - } - if entry.UnbondingId > 0 { - if err := k.stakingKeeper.SetUnbondingDelegationByUnbondingID(ctx, newUbd, entry.UnbondingId); err != nil { - return err - } - } - } - } - - // Re-key redelegations where oldValAddr appears as either source or - // destination validator. Existing in-flight redelegations must continue to - // point at the migrated validator record after operator migration. - if reds == nil { - var err error - reds, err = k.redelegationsForValidator(ctx, oldValAddr) - if err != nil { - return err - } - } - - for _, red := range reds { - if err := k.stakingKeeper.RemoveRedelegation(ctx, red); err != nil { - return err - } - - newRed := stakingtypes.Redelegation{ - DelegatorAddress: red.DelegatorAddress, - ValidatorSrcAddress: red.ValidatorSrcAddress, - ValidatorDstAddress: red.ValidatorDstAddress, - Entries: red.Entries, - } - if red.ValidatorSrcAddress == oldValAddr.String() { - newRed.ValidatorSrcAddress = newValAddr.String() - } - if red.ValidatorDstAddress == oldValAddr.String() { - newRed.ValidatorDstAddress = newValAddr.String() - } - if err := k.stakingKeeper.SetRedelegation(ctx, newRed); err != nil { - return err - } - - for _, entry := range newRed.Entries { - if err := k.stakingKeeper.InsertRedelegationQueue(ctx, newRed, entry.CompletionTime); err != nil { - return err - } - if entry.UnbondingId > 0 { - if err := k.stakingKeeper.SetRedelegationByUnbondingID(ctx, newRed, entry.UnbondingId); err != nil { - return err - } - } - } - } - return nil } diff --git a/x/evmigration/keeper/msg_server_claim_legacy.go b/x/evmigration/keeper/msg_server_claim_legacy.go index f1a460a0..93876cf3 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy.go +++ b/x/evmigration/keeper/msg_server_claim_legacy.go @@ -104,8 +104,23 @@ func (ms msgServer) ClaimLegacyAccount(goCtx context.Context, msg *types.MsgClai } } + // Build the complete staking plan against pristine state. This validates + // destination primaries and every touched maturity timeslice before reward + // withdrawal performs the first write. + delegations, ubds, reds, err := ms.accountStakingRecords(ctx, legacyAddr) + if err != nil { + return nil, fmt.Errorf("preflight account staking records: %w", err) + } + stakingPlan, err := ms.buildStakingMigrationPlan(ctx, delegations, ubds, reds, stakingAddressTransform{ + oldDelegator: legacyAddr, + newDelegator: newAddr, + }) + if err != nil { + return nil, fmt.Errorf("preflight account staking queues: %w", err) + } + // --- Execute migration steps --- - if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode, auditPlan); err != nil { + if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode, auditPlan, delegations, stakingPlan); err != nil { return nil, err } @@ -213,6 +228,8 @@ func (ms msgServer) migrateAccount( supernode sntypes.SuperNode, hasSupernode bool, auditPlan auditkeeper.AccountTransitionPlan, + delegations []stakingtypes.Delegation, + stakingPlan stakingMigrationPlan, ) error { // Snapshot the original withdraw address before MigrateDistribution // may temporarily redirect it to self (see redirectWithdrawAddrIfMigrated). @@ -224,7 +241,7 @@ func (ms msgServer) migrateAccount( } // Step 2: Re-key staking (delegations, unbonding, redelegations). - if err := ms.MigrateStaking(ctx, legacyAddr, newAddr, origWithdrawAddr); err != nil { + if err := ms.migrateAccountStakingWithPlan(ctx, legacyAddr, newAddr, origWithdrawAddr, delegations, stakingPlan); err != nil { return fmt.Errorf("migrate staking: %w", err) } diff --git a/x/evmigration/keeper/msg_server_claim_legacy_test.go b/x/evmigration/keeper/msg_server_claim_legacy_test.go index f2e59e5a..ead8274b 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy_test.go +++ b/x/evmigration/keeper/msg_server_claim_legacy_test.go @@ -110,6 +110,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { bankKeeper := evmigrationmocks.NewMockBankKeeper(ctrl) stakingKeeper := evmigrationmocks.NewMockStakingKeeper(ctrl) distributionKeeper := evmigrationmocks.NewMockDistributionKeeper(ctrl) + distributionKeeper.EXPECT().HasDelegatorStartingInfo(gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil).AnyTimes() authzKeeper := evmigrationmocks.NewMockAuthzKeeper(ctrl) feegrantKeeper := evmigrationmocks.NewMockFeegrantKeeper(ctrl) supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) @@ -773,16 +774,14 @@ func TestClaimLegacyAccount_FailAtDistribution(t *testing.T) { f := initMsgServerFixture(t) _, legacyAddr, _, msg := setupPassingPreChecks(t, f) - // Snapshot + redirectWithdrawAddrIfMigrated both call GetDelegatorWithdrawAddr. - f.distributionKeeper.EXPECT().GetDelegatorWithdrawAddr(gomock.Any(), legacyAddr).Return(legacyAddr, nil).Times(2) - // Step 1: MigrateDistribution fails — GetDelegatorDelegations returns error. + // Staking discovery is a pristine-state preflight before distribution. f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return( nil, fmt.Errorf("staking store corrupted"), ) _, err := f.msgServer.ClaimLegacyAccount(f.ctx, msg) require.Error(t, err) - require.Contains(t, err.Error(), "migrate distribution") + require.Contains(t, err.Error(), "preflight account staking records") assertNoFinalization(t, f, legacyAddr) } @@ -790,16 +789,18 @@ func TestClaimLegacyAccount_FailAtDistribution(t *testing.T) { // MigrateStaking (step 2) propagates and no record is stored. func TestClaimLegacyAccount_FailAtStaking(t *testing.T) { f := initMsgServerFixture(t) - _, legacyAddr, _, msg := setupPassingPreChecks(t, f) + _, legacyAddr, newAddr, msg := setupPassingPreChecks(t, f) - // Snapshot + redirectWithdrawAddrIfMigrated both call GetDelegatorWithdrawAddr. + // The complete staking snapshot is loaded before distribution performs the + // first write. Distribution then performs its own delegation query. + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil).Times(2) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) f.distributionKeeper.EXPECT().GetDelegatorWithdrawAddr(gomock.Any(), legacyAddr).Return(legacyAddr, nil).Times(2) - // Step 1: MigrateDistribution succeeds (no delegations). - f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) - - // Step 2: MigrateStaking — migrateActiveDelegations fails. - f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return( - nil, fmt.Errorf("staking index corrupted"), + // With no staking primaries, the final staking operation is the withdraw + // address re-key. Fail it to exercise the staking apply error boundary. + f.distributionKeeper.EXPECT().SetDelegatorWithdrawAddr(gomock.Any(), newAddr, newAddr).Return( + fmt.Errorf("staking withdraw-address index corrupted"), ) _, err := f.msgServer.ClaimLegacyAccount(f.ctx, msg) @@ -1007,6 +1008,11 @@ func setupPassingValPreChecksWithOwnership( newPrivKey, newAddr := testNewMigrationAccount(t) oldValAddr := sdk.ValAddress(legacyAddr) newValAddr := sdk.ValAddress(newAddr) + for i := range ubds { + if ubds[i].ValidatorAddress == "" { + ubds[i].ValidatorAddress = oldValAddr.String() + } + } baseAcc := authtypes.NewBaseAccountWithAddress(legacyAddr) f.accountKeeper.EXPECT().GetAccount(gomock.Any(), legacyAddr).Return(baseAcc) @@ -1026,6 +1032,10 @@ func setupPassingValPreChecksWithOwnership( // wired (empty) store. f.stakingKeeper.EXPECT().GetValidatorDelegations(gomock.Any(), oldValAddr).Return(nil, nil) f.stakingKeeper.EXPECT().GetUnbondingDelegationsFromValidator(gomock.Any(), oldValAddr).Return(ubds, nil) + // Account-scoped preflight is performed once after proof and ownership plans. + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil).AnyTimes() + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil).AnyTimes() + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil).AnyTimes() msg := newValidatorMigrationMsg(t, privKey, legacyAddr, newPrivKey, newAddr) @@ -1214,9 +1224,10 @@ func TestMigrateValidator_FailAtValidatorDelegations(t *testing.T) { // regular delegation would trigger, keeping this focused on the V4 re-key. ubd := stakingtypes.UnbondingDelegation{ DelegatorAddress: testAccAddr().String(), - ValidatorAddress: sdk.ValAddress(testAccAddr()).String(), } legacyAddr, _, oldValAddr, newValAddr, msg := setupPassingValPreChecks(t, f, ubd) + ubd.ValidatorAddress = oldValAddr.String() + f.writeUnbondingDelegation(ubd) // Steps V1-V3 succeed. f.distributionKeeper.EXPECT().WithdrawValidatorCommission(gomock.Any(), oldValAddr).Return(sdk.Coins{}, nil) @@ -1356,11 +1367,8 @@ func TestMigrateValidator_FailAtAuth(t *testing.T) { f.distributionKeeper.EXPECT().GetDelegatorWithdrawAddr(gomock.Any(), legacyAddr).Return(legacyAddr, nil) // MigrateDistribution: redirect check (self → no-op), no delegations to other validators. f.distributionKeeper.EXPECT().GetDelegatorWithdrawAddr(gomock.Any(), legacyAddr).Return(legacyAddr, nil) - f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) - // MigrateStaking: no delegations/unbonding/redelegations to other validators. - f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) - f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) - f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacyAddr, ^uint16(0)).Return(nil, nil) + // The helper already expects the account staking preflight queries; V7 + // consumes the preloaded snapshot and does not query UBD/RED state again. f.distributionKeeper.EXPECT().SetDelegatorWithdrawAddr(gomock.Any(), newAddr, newAddr).Return(nil) // MigrateAuth fails — Phase 1 probe of newAddr succeeds (fresh), then legacy not found. @@ -1386,6 +1394,7 @@ func TestClaimLegacyAccount_WithDelegations(t *testing.T) { baseAcc := authtypes.NewBaseAccountWithAddress(legacyAddr) del := stakingtypes.NewDelegation(legacyAddr.String(), valAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f.mockFixture, del) // preChecks: account exists and is not a module account. f.accountKeeper.EXPECT().GetAccount(gomock.Any(), legacyAddr).Return(baseAcc) diff --git a/x/evmigration/keeper/msg_server_migrate_validator.go b/x/evmigration/keeper/msg_server_migrate_validator.go index dff5b117..f51007ff 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator.go +++ b/x/evmigration/keeper/msg_server_migrate_validator.go @@ -165,6 +165,37 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat } } + // Account-owned staking positions are a separate bounded dimension. Build + // their snapshot only after all proof/ownership checks, but before any write. + accountDelegations, accountUBDs, accountREDs, err := ms.accountStakingRecords(ctx, legacyAddr) + if err != nil { + return nil, fmt.Errorf("preflight validator account staking records: %w", err) + } + + // Build one final-state plan for the union of validator- and account-scoped + // records. In particular, a self UBD moves (legacy, oldVal) directly to + // (new, newVal), and each queue row is decoded and marshalled only once. + allUBDs := append(append([]stakingtypes.UnbondingDelegation(nil), ubds...), accountUBDs...) + allREDs := append(append([]stakingtypes.Redelegation(nil), reds...), accountREDs...) + allDelegations := append(append([]stakingtypes.Delegation(nil), delegations...), accountDelegations...) + stakingPlan, err := ms.buildStakingMigrationPlan(ctx, allDelegations, allUBDs, allREDs, stakingAddressTransform{ + oldDelegator: legacyAddr, + newDelegator: newAddr, + oldValidator: oldValAddr, + newValidator: newValAddr, + }) + if err != nil { + return nil, fmt.Errorf("preflight validator staking queues: %w", err) + } + + // V4 changes the validator component of account delegation keys before V7 + // changes their delegator component. Carry that exact intermediate snapshot. + for i := range accountDelegations { + if accountDelegations[i].ValidatorAddress == oldValAddr.String() { + accountDelegations[i].ValidatorAddress = newValAddr.String() + } + } + // --- Step V1: Withdraw all commission and delegation rewards --- // Must happen before re-keying so rewards accrue to the correct addresses. if _, err := ms.distributionKeeper.WithdrawValidatorCommission(ctx, oldValAddr); err != nil { @@ -222,7 +253,7 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat // MaxValidatorDelegations pre-check above; nothing since (reward withdrawal, // record re-key, distribution re-key) mutates these staking records, so a // second read would be pure overhead on a validator with many delegations. - if err := ms.MigrateValidatorDelegations(ctx, oldValAddr, newValAddr, delegations, ubds, reds); err != nil { + if err := ms.migrateValidatorDelegationsWithPlan(ctx, oldValAddr, newValAddr, delegations, stakingPlan); err != nil { return nil, fmt.Errorf("migrate validator delegations: %w", err) } @@ -264,7 +295,7 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat // Re-key the operator's delegations, unbonding delegations, and // redelegations to OTHER validators (V4 handled delegations TO this validator). - if err := ms.MigrateStaking(ctx, legacyAddr, newAddr, origWithdrawAddr); err != nil { + if err := ms.migrateAccountStakingWithPlan(ctx, legacyAddr, newAddr, origWithdrawAddr, accountDelegations, stakingMigrationPlan{}); err != nil { return nil, fmt.Errorf("migrate staking: %w", err) } diff --git a/x/evmigration/keeper/msg_server_migrate_validator_test.go b/x/evmigration/keeper/msg_server_migrate_validator_test.go index 834a93c0..c7d267a1 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator_test.go +++ b/x/evmigration/keeper/msg_server_migrate_validator_test.go @@ -230,6 +230,7 @@ func TestMigrateValidator_Success(t *testing.T) { // Delegation count check — 1 delegation, no unbonding/redelegations. del := stakingtypes.NewDelegation(legacyAddr.String(), oldValAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f.mockFixture, del) f.stakingKeeper.EXPECT().GetValidatorDelegations(gomock.Any(), oldValAddr).Return( []stakingtypes.Delegation{del}, nil, ) @@ -408,6 +409,7 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { // Self-delegation only (to own validator). selfDel := stakingtypes.NewDelegation(legacyAddr.String(), oldValAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f.mockFixture, selfDel) f.stakingKeeper.EXPECT().GetValidatorDelegations(gomock.Any(), oldValAddr).Return( []stakingtypes.Delegation{selfDel}, nil, ) @@ -477,6 +479,7 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { // Operator has a delegation to otherValAddr. MigrateDistribution and // MigrateStaking must handle it. otherDel := stakingtypes.NewDelegation(legacyAddr.String(), otherValAddr.String(), math.LegacyNewDec(50)) + seedDelegationPrimary(t, f.mockFixture, otherDel) // Snapshot withdraw address. f.distributionKeeper.EXPECT().GetDelegatorWithdrawAddr(gomock.Any(), legacyAddr).Return(legacyAddr, nil) @@ -603,7 +606,9 @@ func TestMigrateValidator_ThirdPartyWithdrawAddrPreserved(t *testing.T) { // Two delegations: self-delegation + third-party delegator. selfDel := stakingtypes.NewDelegation(legacyAddr.String(), oldValAddr.String(), math.LegacyNewDec(100)) + seedDelegationPrimary(t, f.mockFixture, selfDel) thirdDel := stakingtypes.NewDelegation(thirdPartyDelegator.String(), oldValAddr.String(), math.LegacyNewDec(50)) + seedDelegationPrimary(t, f.mockFixture, thirdDel) allDels := []stakingtypes.Delegation{selfDel, thirdDel} // Fetched once for the pre-check count; V4 reuses the same slices instead of re-reading. f.stakingKeeper.EXPECT().GetValidatorDelegations(gomock.Any(), oldValAddr).Return(allDels, nil) diff --git a/x/evmigration/keeper/staking_migration_plan.go b/x/evmigration/keeper/staking_migration_plan.go new file mode 100644 index 00000000..fdb1d4d8 --- /dev/null +++ b/x/evmigration/keeper/staking_migration_plan.go @@ -0,0 +1,677 @@ +package keeper + +import ( + "bytes" + "fmt" + "sort" + "time" + + corestore "cosmossdk.io/core/store" + sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// MaxAccountStakingMigrationRecords bounds each account migration's staking +// work. It is deliberately consensus-static: changing it requires a software +// upgrade and cannot accidentally make a governance parameter exceed the +// uint16-bounded staking keeper query API. +const MaxAccountStakingMigrationRecords uint16 = 2500 + +type stakingQueueWrite struct { + key []byte + expected []byte + value []byte +} + +type stakingStorePrecondition struct { + key []byte + expected []byte // nil means the key must remain absent + description string +} + +type ubdMigration struct { + old []byte + new []byte +} + +type redMigration struct { + old []byte + new []byte +} + +type delegationMigration struct { + old, new []byte + oldDelegator, newDelegator sdk.AccAddress + oldValidator, newValidator sdk.ValAddress +} + +// stakingMigrationPlan is a fully marshalled, immutable description of the +// UBD/RED primary and maturity-queue changes. No staking write is performed +// while it is built. +type stakingMigrationPlan struct { + delegations []delegationMigration + ubds []ubdMigration + reds []redMigration + queueWrites []stakingQueueWrite + preconditions []stakingStorePrecondition +} + +type stakingAddressTransform struct { + oldDelegator sdk.AccAddress + newDelegator sdk.AccAddress + oldValidator sdk.ValAddress + newValidator sdk.ValAddress +} + +func (x stakingAddressTransform) delegation(old stakingtypes.Delegation) stakingtypes.Delegation { + out := old + if x.oldDelegator != nil && old.DelegatorAddress == x.oldDelegator.String() { + out.DelegatorAddress = x.newDelegator.String() + } + if x.oldValidator != nil && old.ValidatorAddress == x.oldValidator.String() { + out.ValidatorAddress = x.newValidator.String() + } + return out +} + +func (x stakingAddressTransform) ubd(old stakingtypes.UnbondingDelegation) stakingtypes.UnbondingDelegation { + out := old + if x.oldDelegator != nil && old.DelegatorAddress == x.oldDelegator.String() { + out.DelegatorAddress = x.newDelegator.String() + } + if x.oldValidator != nil && old.ValidatorAddress == x.oldValidator.String() { + out.ValidatorAddress = x.newValidator.String() + } + return out +} + +func (x stakingAddressTransform) red(old stakingtypes.Redelegation) stakingtypes.Redelegation { + out := old + if x.oldDelegator != nil && old.DelegatorAddress == x.oldDelegator.String() { + out.DelegatorAddress = x.newDelegator.String() + } + if x.oldValidator != nil { + if old.ValidatorSrcAddress == x.oldValidator.String() { + out.ValidatorSrcAddress = x.newValidator.String() + } + if old.ValidatorDstAddress == x.oldValidator.String() { + out.ValidatorDstAddress = x.newValidator.String() + } + } + return out +} + +func (k Keeper) accountStakingRecords(ctx sdk.Context, delegator sdk.AccAddress) ( + []stakingtypes.Delegation, []stakingtypes.UnbondingDelegation, []stakingtypes.Redelegation, error, +) { + // Query the keeper's full uint16 range, then enforce our smaller static cap. + // This preserves exact cap/cap+1 detection across the combined record kinds. + limit := ^uint16(0) + delegations, err := k.stakingKeeper.GetDelegatorDelegations(ctx, delegator, limit) + if err != nil { + return nil, nil, nil, err + } + ubds, err := k.stakingKeeper.GetUnbondingDelegations(ctx, delegator, limit) + if err != nil { + return nil, nil, nil, err + } + reds, err := k.stakingKeeper.GetRedelegations(ctx, delegator, limit) + if err != nil { + return nil, nil, nil, err + } + count := len(delegations) + len(ubds) + len(reds) + if count > int(MaxAccountStakingMigrationRecords) { + return nil, nil, nil, fmt.Errorf("account staking records %d exceed migration cap %d", count, MaxAccountStakingMigrationRecords) + } + return delegations, ubds, reds, nil +} + +func (k Keeper) buildStakingMigrationPlan( + ctx sdk.Context, + delegations []stakingtypes.Delegation, + ubds []stakingtypes.UnbondingDelegation, + reds []stakingtypes.Redelegation, + x stakingAddressTransform, +) (stakingMigrationPlan, error) { + var plan stakingMigrationPlan + if len(delegations) == 0 && len(ubds) == 0 && len(reds) == 0 { + return plan, nil + } + if k.stakingStoreHandle == nil || k.stakingStoreHandle.svc == nil { + return plan, fmt.Errorf("staking migration plan: staking store service not wired") + } + store := k.stakingStoreHandle.svc.OpenKVStore(ctx) + + delegationSeen := make(map[string][]byte, len(delegations)) + delegationDestinations := make(map[string]struct{}, len(delegations)) + ubdSeen := make(map[string]struct{}, len(ubds)) + redSeen := make(map[string]struct{}, len(reds)) + ubdSnapshots := make(map[string][]byte, len(ubds)) + redSnapshots := make(map[string][]byte, len(reds)) + ubdDestinations := make(map[string]struct{}, len(ubds)) + redDestinations := make(map[string]struct{}, len(reds)) + ubdSubs := make(map[string][]pairSubstitution) + redSubs := make(map[string][]tripletSubstitution) + + for _, old := range delegations { + newRecord := x.delegation(old) + if old.DelegatorAddress == newRecord.DelegatorAddress && old.ValidatorAddress == newRecord.ValidatorAddress { + continue + } + oldDel, err := sdk.AccAddressFromBech32(old.DelegatorAddress) + if err != nil { + return plan, fmt.Errorf("malformed delegation delegator: %w", err) + } + oldVal, err := sdk.ValAddressFromBech32(old.ValidatorAddress) + if err != nil { + return plan, fmt.Errorf("malformed delegation validator: %w", err) + } + newDel, err := sdk.AccAddressFromBech32(newRecord.DelegatorAddress) + if err != nil { + return plan, fmt.Errorf("malformed destination delegation delegator: %w", err) + } + newVal, err := sdk.ValAddressFromBech32(newRecord.ValidatorAddress) + if err != nil { + return plan, fmt.Errorf("malformed destination delegation validator: %w", err) + } + oldKey := stakingtypes.GetDelegationKey(oldDel, oldVal) + oldBytes := stakingtypes.MustMarshalDelegation(k.cdc, old) + if snapshot, duplicate := delegationSeen[string(oldKey)]; duplicate { + if !bytes.Equal(snapshot, oldBytes) { + return plan, fmt.Errorf("conflicting duplicate delegation snapshot for primary %X", oldKey) + } + continue + } + delegationSeen[string(oldKey)] = append([]byte(nil), oldBytes...) + if err := requirePrimaryMatches(store, oldKey, oldBytes, "delegation"); err != nil { + return plan, err + } + newKey := stakingtypes.GetDelegationKey(newDel, newVal) + if bytes.Equal(oldKey, newKey) { + return plan, fmt.Errorf("delegation transformation did not change canonical key %X", oldKey) + } + if err := requireAbsent(store, newKey, "delegation"); err != nil { + return plan, err + } + if _, duplicate := delegationDestinations[string(newKey)]; duplicate { + return plan, fmt.Errorf("multiple delegations map to destination primary %X", newKey) + } + delegationDestinations[string(newKey)] = struct{}{} + hasStartingInfo, err := k.distributionKeeper.HasDelegatorStartingInfo(ctx, newVal, newDel) + if err != nil { + return plan, fmt.Errorf("check destination delegation starting info: %w", err) + } + if hasStartingInfo { + return plan, fmt.Errorf("destination delegation starting info already exists for %s/%s", newVal, newDel) + } + newBytes := stakingtypes.MustMarshalDelegation(k.cdc, newRecord) + plan.preconditions = append(plan.preconditions, + stakingStorePrecondition{key: append([]byte(nil), oldKey...), expected: append([]byte(nil), oldBytes...), description: "source delegation primary"}, + stakingStorePrecondition{key: append([]byte(nil), newKey...), description: "destination delegation primary"}, + ) + plan.delegations = append(plan.delegations, delegationMigration{ + old: append([]byte(nil), oldBytes...), new: append([]byte(nil), newBytes...), + oldDelegator: append(sdk.AccAddress(nil), oldDel...), newDelegator: append(sdk.AccAddress(nil), newDel...), + oldValidator: append(sdk.ValAddress(nil), oldVal...), newValidator: append(sdk.ValAddress(nil), newVal...), + }) + } + + for _, old := range ubds { + newRecord := x.ubd(old) + if old.DelegatorAddress == newRecord.DelegatorAddress && old.ValidatorAddress == newRecord.ValidatorAddress { + continue + } + oldDel, oldVal, newDel, newVal, err := parseUBDAddresses(old, newRecord) + if err != nil { + return plan, err + } + oldKey := stakingtypes.GetUBDKey(oldDel, oldVal) + oldBytes := stakingtypes.MustMarshalUBD(k.cdc, old) + if _, duplicate := ubdSeen[string(oldKey)]; duplicate { + if !bytes.Equal(ubdSnapshots[string(oldKey)], oldBytes) { + return plan, fmt.Errorf("conflicting duplicate UBD snapshot for primary %X", oldKey) + } + continue // exact union duplicate from account- and validator-scoped enumerations + } + ubdSeen[string(oldKey)] = struct{}{} + ubdSnapshots[string(oldKey)] = oldBytes + if err := requirePrimaryMatches(store, oldKey, oldBytes, "unbonding delegation"); err != nil { + return plan, err + } + newKey := stakingtypes.GetUBDKey(newDel, newVal) + if bytes.Equal(oldKey, newKey) { + return plan, fmt.Errorf("UBD transformation did not change canonical key %X", oldKey) + } + if err := requireAbsent(store, newKey, "unbonding delegation"); err != nil { + return plan, err + } + plan.preconditions = append(plan.preconditions, + stakingStorePrecondition{key: append([]byte(nil), oldKey...), expected: append([]byte(nil), oldBytes...), description: "source unbonding delegation primary"}, + stakingStorePrecondition{key: append([]byte(nil), newKey...), description: "destination unbonding delegation primary"}, + ) + if _, duplicate := ubdDestinations[string(newKey)]; duplicate { + return plan, fmt.Errorf("multiple UBD records map to destination primary %X", newKey) + } + ubdDestinations[string(newKey)] = struct{}{} + newBytes := stakingtypes.MustMarshalUBD(k.cdc, newRecord) + plan.ubds = append(plan.ubds, ubdMigration{ + old: append([]byte(nil), oldBytes...), new: append([]byte(nil), newBytes...), + }) + oldPair := stakingtypes.DVPair{DelegatorAddress: old.DelegatorAddress, ValidatorAddress: old.ValidatorAddress} + newPair := stakingtypes.DVPair{DelegatorAddress: newRecord.DelegatorAddress, ValidatorAddress: newRecord.ValidatorAddress} + for _, completion := range uniqueUBDCompletionTimes(old.Entries) { + key := stakingtypes.GetUnbondingDelegationTimeKey(completion) + ubdSubs[string(key)] = append(ubdSubs[string(key)], pairSubstitution{old: oldPair, new: newPair}) + } + } + + for _, old := range reds { + newRecord := x.red(old) + if old.DelegatorAddress == newRecord.DelegatorAddress && old.ValidatorSrcAddress == newRecord.ValidatorSrcAddress && old.ValidatorDstAddress == newRecord.ValidatorDstAddress { + continue + } + oldDel, oldSrc, oldDst, newDel, newSrc, newDst, err := parseREDAddresses(old, newRecord) + if err != nil { + return plan, err + } + oldKey := stakingtypes.GetREDKey(oldDel, oldSrc, oldDst) + oldBytes := stakingtypes.MustMarshalRED(k.cdc, old) + if _, duplicate := redSeen[string(oldKey)]; duplicate { + if !bytes.Equal(redSnapshots[string(oldKey)], oldBytes) { + return plan, fmt.Errorf("conflicting duplicate RED snapshot for primary %X", oldKey) + } + continue + } + redSeen[string(oldKey)] = struct{}{} + redSnapshots[string(oldKey)] = oldBytes + if err := requirePrimaryMatches(store, oldKey, oldBytes, "redelegation"); err != nil { + return plan, err + } + newKey := stakingtypes.GetREDKey(newDel, newSrc, newDst) + if bytes.Equal(oldKey, newKey) { + return plan, fmt.Errorf("redelegation transformation did not change canonical key %X", oldKey) + } + if err := requireAbsent(store, newKey, "redelegation"); err != nil { + return plan, err + } + plan.preconditions = append(plan.preconditions, + stakingStorePrecondition{key: append([]byte(nil), oldKey...), expected: append([]byte(nil), oldBytes...), description: "source redelegation primary"}, + stakingStorePrecondition{key: append([]byte(nil), newKey...), description: "destination redelegation primary"}, + ) + if _, duplicate := redDestinations[string(newKey)]; duplicate { + return plan, fmt.Errorf("multiple RED records map to destination primary %X", newKey) + } + redDestinations[string(newKey)] = struct{}{} + newBytes := stakingtypes.MustMarshalRED(k.cdc, newRecord) + plan.reds = append(plan.reds, redMigration{ + old: append([]byte(nil), oldBytes...), new: append([]byte(nil), newBytes...), + }) + oldTriplet := stakingtypes.DVVTriplet{DelegatorAddress: old.DelegatorAddress, ValidatorSrcAddress: old.ValidatorSrcAddress, ValidatorDstAddress: old.ValidatorDstAddress} + newTriplet := stakingtypes.DVVTriplet{DelegatorAddress: newRecord.DelegatorAddress, ValidatorSrcAddress: newRecord.ValidatorSrcAddress, ValidatorDstAddress: newRecord.ValidatorDstAddress} + for _, completion := range uniqueREDCompletionTimes(old.Entries) { + key := stakingtypes.GetRedelegationTimeKey(completion) + redSubs[string(key)] = append(redSubs[string(key)], tripletSubstitution{old: oldTriplet, new: newTriplet}) + } + } + + writes, err := k.buildQueueWrites(store, ubdSubs, redSubs) + if err != nil { + return stakingMigrationPlan{}, err + } + plan.queueWrites = writes + return plan, nil +} + +func requireAbsent(store corestore.KVStore, key []byte, kind string) error { + bz, err := store.Get(key) + if err != nil { + return err + } + if bz != nil { + return fmt.Errorf("destination %s primary already exists at %X", kind, key) + } + return nil +} + +func requirePrimaryMatches(store corestore.KVStore, key, expected []byte, kind string) error { + bz, err := store.Get(key) + if err != nil { + return err + } + if bz == nil { + return fmt.Errorf("source %s primary missing at %X", kind, key) + } + if !bytes.Equal(bz, expected) { + return fmt.Errorf("source %s primary at %X does not match preloaded snapshot", kind, key) + } + return nil +} + +func parseUBDAddresses(old, newRecord stakingtypes.UnbondingDelegation) (sdk.AccAddress, sdk.ValAddress, sdk.AccAddress, sdk.ValAddress, error) { + od, err := sdk.AccAddressFromBech32(old.DelegatorAddress) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("malformed UBD delegator: %w", err) + } + ov, err := sdk.ValAddressFromBech32(old.ValidatorAddress) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("malformed UBD validator: %w", err) + } + nd, err := sdk.AccAddressFromBech32(newRecord.DelegatorAddress) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("malformed destination UBD delegator: %w", err) + } + nv, err := sdk.ValAddressFromBech32(newRecord.ValidatorAddress) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("malformed destination UBD validator: %w", err) + } + return od, ov, nd, nv, nil +} + +func parseREDAddresses(old, newRecord stakingtypes.Redelegation) (sdk.AccAddress, sdk.ValAddress, sdk.ValAddress, sdk.AccAddress, sdk.ValAddress, sdk.ValAddress, error) { + od, err := sdk.AccAddressFromBech32(old.DelegatorAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, fmt.Errorf("malformed RED delegator: %w", err) + } + os, err := sdk.ValAddressFromBech32(old.ValidatorSrcAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, fmt.Errorf("malformed RED source: %w", err) + } + oz, err := sdk.ValAddressFromBech32(old.ValidatorDstAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, fmt.Errorf("malformed RED destination: %w", err) + } + nd, err := sdk.AccAddressFromBech32(newRecord.DelegatorAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + ns, err := sdk.ValAddressFromBech32(newRecord.ValidatorSrcAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + nz, err := sdk.ValAddressFromBech32(newRecord.ValidatorDstAddress) + if err != nil { + return nil, nil, nil, nil, nil, nil, err + } + return od, os, oz, nd, ns, nz, nil +} + +func uniqueUBDCompletionTimes(entries []stakingtypes.UnbondingDelegationEntry) []time.Time { + seen := map[int64]bool{} + out := make([]time.Time, 0, len(entries)) + for _, e := range entries { + n := e.CompletionTime.UnixNano() + if !seen[n] { + seen[n] = true + out = append(out, e.CompletionTime) + } + } + return out +} +func uniqueREDCompletionTimes(entries []stakingtypes.RedelegationEntry) []time.Time { + seen := map[int64]bool{} + out := make([]time.Time, 0, len(entries)) + for _, e := range entries { + n := e.CompletionTime.UnixNano() + if !seen[n] { + seen[n] = true + out = append(out, e.CompletionTime) + } + } + return out +} + +type pairSubstitution struct{ old, new stakingtypes.DVPair } +type tripletSubstitution struct{ old, new stakingtypes.DVVTriplet } + +func (k Keeper) buildQueueWrites(store corestore.KVStore, pairs map[string][]pairSubstitution, triplets map[string][]tripletSubstitution) ([]stakingQueueWrite, error) { + writes := make([]stakingQueueWrite, 0, len(pairs)+len(triplets)) + pairKeys := make([]string, 0, len(pairs)) + for key := range pairs { + pairKeys = append(pairKeys, key) + } + sort.Strings(pairKeys) + for _, keyString := range pairKeys { + substitutions := pairs[keyString] + key := []byte(keyString) + bz, err := store.Get(key) + if err != nil { + return nil, err + } + if bz == nil { + return nil, fmt.Errorf("missing UBD queue timeslice %X", key) + } + var slice stakingtypes.DVPairs + if err := k.cdc.Unmarshal(bz, &slice); err != nil { + return nil, fmt.Errorf("malformed UBD queue timeslice %X: %w", key, err) + } + for _, sub := range substitutions { + oldCount, newCount := 0, 0 + for i := range slice.Pairs { + if equalDVPair(slice.Pairs[i], sub.old) { + oldCount++ + } + if equalDVPair(slice.Pairs[i], sub.new) { + newCount++ + } + } + if oldCount != 1 || newCount != 0 { + return nil, fmt.Errorf("unsafe UBD queue timeslice %X: old tuple count %d, new tuple count %d", key, oldCount, newCount) + } + for i := range slice.Pairs { + if equalDVPair(slice.Pairs[i], sub.old) { + slice.Pairs[i] = sub.new + } + } + } + out, err := k.cdc.Marshal(&slice) + if err != nil { + return nil, err + } + writes = append(writes, stakingQueueWrite{key: append([]byte(nil), key...), expected: append([]byte(nil), bz...), value: out}) + } + tripletKeys := make([]string, 0, len(triplets)) + for key := range triplets { + tripletKeys = append(tripletKeys, key) + } + sort.Strings(tripletKeys) + for _, keyString := range tripletKeys { + substitutions := triplets[keyString] + key := []byte(keyString) + bz, err := store.Get(key) + if err != nil { + return nil, err + } + if bz == nil { + return nil, fmt.Errorf("missing RED queue timeslice %X", key) + } + var slice stakingtypes.DVVTriplets + if err := k.cdc.Unmarshal(bz, &slice); err != nil { + return nil, fmt.Errorf("malformed RED queue timeslice %X: %w", key, err) + } + for _, sub := range substitutions { + oldCount, newCount := 0, 0 + for i := range slice.Triplets { + if equalDVVTriplet(slice.Triplets[i], sub.old) { + oldCount++ + } + if equalDVVTriplet(slice.Triplets[i], sub.new) { + newCount++ + } + } + if oldCount != 1 || newCount != 0 { + return nil, fmt.Errorf("unsafe RED queue timeslice %X: old tuple count %d, new tuple count %d", key, oldCount, newCount) + } + for i := range slice.Triplets { + if equalDVVTriplet(slice.Triplets[i], sub.old) { + slice.Triplets[i] = sub.new + } + } + } + out, err := k.cdc.Marshal(&slice) + if err != nil { + return nil, err + } + writes = append(writes, stakingQueueWrite{key: append([]byte(nil), key...), expected: append([]byte(nil), bz...), value: out}) + } + return writes, nil +} + +func equalDVPair(a, b stakingtypes.DVPair) bool { + return a.DelegatorAddress == b.DelegatorAddress && a.ValidatorAddress == b.ValidatorAddress +} + +func equalDVVTriplet(a, b stakingtypes.DVVTriplet) bool { + return a.DelegatorAddress == b.DelegatorAddress && + a.ValidatorSrcAddress == b.ValidatorSrcAddress && + a.ValidatorDstAddress == b.ValidatorDstAddress +} + +func (k Keeper) applyStakingMigrationPlan(ctx sdk.Context, plan stakingMigrationPlan, applyDelegations bool) error { + if len(plan.queueWrites) == 0 && len(plan.delegations) == 0 && len(plan.ubds) == 0 && len(plan.reds) == 0 { + return nil + } + if k.stakingStoreHandle == nil || k.stakingStoreHandle.svc == nil { + return fmt.Errorf("apply staking migration plan: staking store service not wired") + } + store := k.stakingStoreHandle.svc.OpenKVStore(ctx) + // Revalidate every raw-store and cross-module assumption before the first + // write. Reward withdrawal may legitimately replace source starting info, + // but destination starting info must remain absent. + for _, condition := range plan.preconditions { + bz, err := store.Get(condition.key) + if err != nil { + return err + } + if !bytes.Equal(bz, condition.expected) { + return fmt.Errorf("%s at %X changed after preflight", condition.description, condition.key) + } + } + for _, write := range plan.queueWrites { + bz, err := store.Get(write.key) + if err != nil { + return err + } + if !bytes.Equal(bz, write.expected) { + return fmt.Errorf("staking queue timeslice %X changed after preflight", write.key) + } + } + for _, change := range plan.delegations { + has, err := k.distributionKeeper.HasDelegatorStartingInfo(ctx, change.newValidator, change.newDelegator) + if err != nil { + return fmt.Errorf("recheck destination delegation starting info: %w", err) + } + if has { + return fmt.Errorf("destination delegation starting info for %s/%s changed after preflight", change.newValidator, change.newDelegator) + } + } + + for _, write := range plan.queueWrites { + if err := store.Set(write.key, write.value); err != nil { + return err + } + } + if applyDelegations { + for _, change := range plan.delegations { + old, err := stakingtypes.UnmarshalDelegation(k.cdc, change.old) + if err != nil { + return fmt.Errorf("decode source delegation snapshot: %w", err) + } + newRecord, err := stakingtypes.UnmarshalDelegation(k.cdc, change.new) + if err != nil { + return fmt.Errorf("decode destination delegation snapshot: %w", err) + } + if err := k.distributionKeeper.DeleteDelegatorStartingInfo(ctx, change.oldValidator, change.oldDelegator); err != nil { + return err + } + if err := k.stakingKeeper.RemoveDelegation(ctx, old); err != nil { + return err + } + if err := k.stakingKeeper.SetDelegation(ctx, newRecord); err != nil { + return err + } + currentRewards, err := k.distributionKeeper.GetValidatorCurrentRewards(ctx, change.newValidator) + if err != nil { + return err + } + if currentRewards.Period == 0 { + return fmt.Errorf("validator current rewards period is zero for %s", change.newValidator) + } + previousPeriod := currentRewards.Period - 1 + val, err := k.stakingKeeper.GetValidator(ctx, change.newValidator) + if err != nil { + return err + } + if err := k.incrementHistoricalRewardsReferenceCount(ctx, change.newValidator, previousPeriod); err != nil { + return err + } + startingInfo := distrtypes.DelegatorStartingInfo{ + Height: uint64(ctx.BlockHeight()), + PreviousPeriod: previousPeriod, + Stake: val.TokensFromSharesTruncated(newRecord.Shares), + } + if err := k.distributionKeeper.SetDelegatorStartingInfo(ctx, change.newValidator, change.newDelegator, startingInfo); err != nil { + return err + } + } + } + for _, change := range plan.ubds { + old, err := stakingtypes.UnmarshalUBD(k.cdc, change.old) + if err != nil { + return fmt.Errorf("decode source UBD snapshot: %w", err) + } + newRecord, err := stakingtypes.UnmarshalUBD(k.cdc, change.new) + if err != nil { + return fmt.Errorf("decode destination UBD snapshot: %w", err) + } + if err := k.stakingKeeper.RemoveUnbondingDelegation(ctx, old); err != nil { + return err + } + if err := k.stakingKeeper.SetUnbondingDelegation(ctx, newRecord); err != nil { + return err + } + for _, entry := range newRecord.Entries { + if entry.UnbondingId > 0 { + if err := k.stakingKeeper.SetUnbondingDelegationByUnbondingID(ctx, newRecord, entry.UnbondingId); err != nil { + return err + } + } + } + } + for _, change := range plan.reds { + old, err := stakingtypes.UnmarshalRED(k.cdc, change.old) + if err != nil { + return fmt.Errorf("decode source RED snapshot: %w", err) + } + newRecord, err := stakingtypes.UnmarshalRED(k.cdc, change.new) + if err != nil { + return fmt.Errorf("decode destination RED snapshot: %w", err) + } + if err := k.stakingKeeper.RemoveRedelegation(ctx, old); err != nil { + return err + } + if err := k.stakingKeeper.SetRedelegation(ctx, newRecord); err != nil { + return err + } + for _, entry := range newRecord.Entries { + if entry.UnbondingId > 0 { + if err := k.stakingKeeper.SetRedelegationByUnbondingID(ctx, newRecord, entry.UnbondingId); err != nil { + return err + } + } + } + } + return nil +} + +// ApplyStakingMigrationPlan atomically applies a preflighted plan. Keeping the +// cache boundary here also makes direct/exported-helper callers safe outside a +// BaseApp transaction. +func (k Keeper) ApplyStakingMigrationPlan(ctx sdk.Context, plan stakingMigrationPlan) error { + cacheCtx, commit := ctx.CacheContext() + if err := k.applyStakingMigrationPlan(cacheCtx, plan, true); err != nil { + return err + } + commit() + return nil +} diff --git a/x/evmigration/keeper/staking_migration_plan_test.go b/x/evmigration/keeper/staking_migration_plan_test.go new file mode 100644 index 00000000..cd3acfe5 --- /dev/null +++ b/x/evmigration/keeper/staking_migration_plan_test.go @@ -0,0 +1,131 @@ +package keeper_test + +import ( + "testing" + "time" + + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func TestStakingMigrationPlanUsesCanonicalRawUBDQueueKey(t *testing.T) { + f := initMockFixture(t) + legacy, replacement := testAccAddr(), testAccAddr() + validator := sdk.ValAddress(testAccAddr()) + completion := f.ctx.BlockTime().Add(48 * time.Hour) + ubd := stakingtypes.UnbondingDelegation{ + DelegatorAddress: legacy.String(), + ValidatorAddress: validator.String(), + Entries: []stakingtypes.UnbondingDelegationEntry{{ + CreationHeight: 4, + CompletionTime: completion, + InitialBalance: math.NewInt(25), + Balance: math.NewInt(25), + UnbondingId: 17, + }}, + } + f.writeUnbondingDelegation(ubd) + + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.UnbondingDelegation{ubd}, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().RemoveUnbondingDelegation(gomock.Any(), ubd).Return(nil) + f.stakingKeeper.EXPECT().SetUnbondingDelegation(gomock.Any(), gomock.Any()).Return(nil) + f.stakingKeeper.EXPECT().SetUnbondingDelegationByUnbondingID(gomock.Any(), gomock.Any(), uint64(17)).Return(nil) + f.distributionKeeper.EXPECT().SetDelegatorWithdrawAddr(gomock.Any(), replacement, replacement).Return(nil) + require.NoError(t, f.keeper.MigrateStaking(f.ctx, legacy, replacement, nil)) + + // The migration must update the SDK's exact raw maturity-key row rather than + // appending through InsertUBDQueue (which can leave the old tuple behind). + key := stakingtypes.GetUnbondingDelegationTimeKey(completion) + raw, err := f.stakingStore.OpenKVStore(f.ctx).Get(key) + require.NoError(t, err) + var pairs stakingtypes.DVPairs + require.NoError(t, f.cdc.Unmarshal(raw, &pairs)) + require.Equal(t, []stakingtypes.DVPair{{ + DelegatorAddress: replacement.String(), + ValidatorAddress: validator.String(), + }}, pairs.Pairs) +} + +func TestStakingMigrationPlanApplyFailureRollsBackRawQueueWrite(t *testing.T) { + f := initMockFixture(t) + legacy, replacement := testAccAddr(), testAccAddr() + validator := sdk.ValAddress(testAccAddr()) + completion := f.ctx.BlockTime().Add(72 * time.Hour) + ubd := stakingtypes.UnbondingDelegation{ + DelegatorAddress: legacy.String(), + ValidatorAddress: validator.String(), + Entries: []stakingtypes.UnbondingDelegationEntry{{CompletionTime: completion}}, + } + f.writeUnbondingDelegation(ubd) + key := stakingtypes.GetUnbondingDelegationTimeKey(completion) + before, err := f.stakingStore.OpenKVStore(f.ctx).Get(key) + require.NoError(t, err) + + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.UnbondingDelegation{ubd}, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().RemoveUnbondingDelegation(gomock.Any(), ubd).Return(assertionError("injected apply failure")) + + err = f.keeper.MigrateStaking(f.ctx, legacy, replacement, nil) + require.ErrorContains(t, err, "injected apply failure") + after, getErr := f.stakingStore.OpenKVStore(f.ctx).Get(key) + require.NoError(t, getErr) + require.Equal(t, before, after) +} + +func TestStakingMigrationPlanRejectsExistingDestinationPrimary(t *testing.T) { + f := initMockFixture(t) + legacy, replacement := testAccAddr(), testAccAddr() + validator := sdk.ValAddress(testAccAddr()) + old := stakingtypes.UnbondingDelegation{DelegatorAddress: legacy.String(), ValidatorAddress: validator.String()} + destination := stakingtypes.UnbondingDelegation{DelegatorAddress: replacement.String(), ValidatorAddress: validator.String()} + f.writeUnbondingDelegation(old) + f.writeUnbondingDelegation(destination) + + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.UnbondingDelegation{old}, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + + err := f.keeper.MigrateStaking(f.ctx, legacy, replacement, nil) + require.ErrorContains(t, err, "destination unbonding delegation primary already exists") +} + +func TestStakingMigrationPlanRejectsMissingDelegationSourcePrimary(t *testing.T) { + f := initMockFixture(t) + legacy, replacement := testAccAddr(), testAccAddr() + validator := sdk.ValAddress(testAccAddr()) + delegation := stakingtypes.NewDelegation(legacy.String(), validator.String(), math.LegacyNewDec(25)) + + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.Delegation{delegation}, nil) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + + err := f.keeper.MigrateStaking(f.ctx, legacy, replacement, nil) + require.ErrorContains(t, err, "source delegation primary missing") +} + +func TestStakingMigrationPlanRejectsExistingDelegationDestinationPrimary(t *testing.T) { + f := initMockFixture(t) + legacy, replacement := testAccAddr(), testAccAddr() + validator := sdk.ValAddress(testAccAddr()) + source := stakingtypes.NewDelegation(legacy.String(), validator.String(), math.LegacyNewDec(25)) + seedDelegationPrimary(t, f, source) + destination := stakingtypes.NewDelegation(replacement.String(), validator.String(), source.Shares) + seedDelegationPrimary(t, f, destination) + + f.stakingKeeper.EXPECT().GetDelegatorDelegations(gomock.Any(), legacy, ^uint16(0)).Return([]stakingtypes.Delegation{source}, nil) + f.stakingKeeper.EXPECT().GetUnbondingDelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + f.stakingKeeper.EXPECT().GetRedelegations(gomock.Any(), legacy, ^uint16(0)).Return(nil, nil) + + err := f.keeper.MigrateStaking(f.ctx, legacy, replacement, nil) + require.ErrorContains(t, err, "destination delegation primary already exists") +} + +type assertionError string + +func (e assertionError) Error() string { return string(e) } diff --git a/x/evmigration/mocks/expected_keepers_mock.go b/x/evmigration/mocks/expected_keepers_mock.go index fb6f679c..5ed52ad0 100644 --- a/x/evmigration/mocks/expected_keepers_mock.go +++ b/x/evmigration/mocks/expected_keepers_mock.go @@ -803,6 +803,21 @@ func (mr *MockDistributionKeeperMockRecorder) GetValidatorOutstandingRewards(ctx return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorOutstandingRewards", reflect.TypeOf((*MockDistributionKeeper)(nil).GetValidatorOutstandingRewards), ctx, val) } +// HasDelegatorStartingInfo mocks base method. +func (m *MockDistributionKeeper) HasDelegatorStartingInfo(ctx context.Context, val types1.ValAddress, del types1.AccAddress) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "HasDelegatorStartingInfo", ctx, val, del) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// HasDelegatorStartingInfo indicates an expected call of HasDelegatorStartingInfo. +func (mr *MockDistributionKeeperMockRecorder) HasDelegatorStartingInfo(ctx, val, del any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasDelegatorStartingInfo", reflect.TypeOf((*MockDistributionKeeper)(nil).HasDelegatorStartingInfo), ctx, val, del) +} + // IterateValidatorHistoricalRewards mocks base method. func (m *MockDistributionKeeper) IterateValidatorHistoricalRewards(ctx context.Context, handler func(types1.ValAddress, uint64, types2.ValidatorHistoricalRewards) bool) { m.ctrl.T.Helper() diff --git a/x/evmigration/types/expected_keepers.go b/x/evmigration/types/expected_keepers.go index 9099704c..bcbc8415 100644 --- a/x/evmigration/types/expected_keepers.go +++ b/x/evmigration/types/expected_keepers.go @@ -81,6 +81,7 @@ type DistributionKeeper interface { SetDelegatorWithdrawAddr(ctx context.Context, delAddr, withdrawAddr sdk.AccAddress) error GetDelegatorStartingInfo(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) (distrtypes.DelegatorStartingInfo, error) + HasDelegatorStartingInfo(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) (bool, error) SetDelegatorStartingInfo(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress, period distrtypes.DelegatorStartingInfo) error DeleteDelegatorStartingInfo(ctx context.Context, val sdk.ValAddress, del sdk.AccAddress) error From 267fcba22bb8f9ad2133c1409cd1175c999a780a Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 12:10:08 +0000 Subject: [PATCH 07/18] fix(evmigration): preserve retained sdk identities --- proto/lumera/evmigration/params.proto | 5 + x/evmigration/keeper/keeper.go | 4 + x/evmigration/keeper/keeper_test.go | 1 + x/evmigration/keeper/migrate_authz.go | 63 +-- x/evmigration/keeper/migrate_retained.go | 503 ++++++++++++++++++ x/evmigration/keeper/migrate_retained_test.go | 76 +++ x/evmigration/keeper/migrate_test.go | 43 +- .../keeper/msg_server_claim_legacy.go | 13 +- .../keeper/msg_server_claim_legacy_test.go | 45 +- .../keeper/msg_server_migrate_validator.go | 10 +- .../msg_server_migrate_validator_test.go | 57 +- x/evmigration/module/depinject.go | 3 + x/evmigration/types/params.go | 16 + x/evmigration/types/params.pb.go | 92 +++- x/evmigration/types/params_test.go | 12 +- 15 files changed, 790 insertions(+), 153 deletions(-) create mode 100644 x/evmigration/keeper/migrate_retained.go create mode 100644 x/evmigration/keeper/migrate_retained_test.go diff --git a/proto/lumera/evmigration/params.proto b/proto/lumera/evmigration/params.proto index 459b6aa0..e8596df3 100644 --- a/proto/lumera/evmigration/params.proto +++ b/proto/lumera/evmigration/params.proto @@ -52,4 +52,9 @@ message Params { // migration open when enable_migration is true. Entries must be unique and // sorted lexicographically; at most 64 entries are permitted. repeated string canary_legacy_addresses = 6; + + // max_retained_state_entries bounds deterministic discovery plans for + // retained SDK references. Discovery rejects at cap+1 before any write. + // Default: 10000. + uint64 max_retained_state_entries = 7; } diff --git a/x/evmigration/keeper/keeper.go b/x/evmigration/keeper/keeper.go index e8f81f47..8befa442 100644 --- a/x/evmigration/keeper/keeper.go +++ b/x/evmigration/keeper/keeper.go @@ -7,6 +7,7 @@ import ( "cosmossdk.io/core/address" corestore "cosmossdk.io/core/store" "github.com/cosmos/cosmos-sdk/codec" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" "github.com/LumeraProtocol/lumera/x/evmigration/types" ) @@ -66,6 +67,7 @@ type Keeper struct { stakingKeeper types.StakingKeeper distributionKeeper types.DistributionKeeper authzKeeper types.AuthzKeeper + govKeeper *govkeeper.Keeper feegrantKeeper types.FeegrantKeeper supernodeKeeper types.SupernodeKeeper auditKeeper types.AuditKeeper @@ -82,6 +84,7 @@ func NewKeeper( stakingKeeper types.StakingKeeper, distributionKeeper types.DistributionKeeper, authzKeeper types.AuthzKeeper, + govKeeper *govkeeper.Keeper, feegrantKeeper types.FeegrantKeeper, supernodeKeeper types.SupernodeKeeper, auditKeeper types.AuditKeeper, @@ -111,6 +114,7 @@ func NewKeeper( stakingKeeper: stakingKeeper, distributionKeeper: distributionKeeper, authzKeeper: authzKeeper, + govKeeper: govKeeper, feegrantKeeper: feegrantKeeper, supernodeKeeper: supernodeKeeper, auditKeeper: auditKeeper, diff --git a/x/evmigration/keeper/keeper_test.go b/x/evmigration/keeper/keeper_test.go index c0f6dcea..9fd8ec5c 100644 --- a/x/evmigration/keeper/keeper_test.go +++ b/x/evmigration/keeper/keeper_test.go @@ -46,6 +46,7 @@ func initFixture(t *testing.T) *fixture { nil, // stakingKeeper nil, // distributionKeeper nil, // authzKeeper + nil, // govKeeper nil, // feegrantKeeper nil, // supernodeKeeper nil, // auditKeeper diff --git a/x/evmigration/keeper/migrate_authz.go b/x/evmigration/keeper/migrate_authz.go index 2e791de0..274c3c2d 100644 --- a/x/evmigration/keeper/migrate_authz.go +++ b/x/evmigration/keeper/migrate_authz.go @@ -2,58 +2,25 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/authz" ) -// MigrateAuthz re-keys all authz grants where legacyAddr is granter or grantee. +// MigrateAuthz atomically re-keys grants where the account is granter/grantee +// and rewrites embedded StakeAuthorization validator allow/deny references. +// Production handlers use the same prebuilt fragment through retainedStatePlan. func (k Keeper) MigrateAuthz(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress) error { - type grantToMigrate struct { - granter sdk.AccAddress - grantee sdk.AccAddress - grant authz.Grant + params, err := k.Params.Get(ctx) + if err != nil { + return err } - - var toMigrate []grantToMigrate - - // Collect all grants involving legacyAddr. - k.authzKeeper.IterateGrants(ctx, func(granterAddr, granteeAddr sdk.AccAddress, grant authz.Grant) bool { - if granterAddr.Equals(legacyAddr) || granteeAddr.Equals(legacyAddr) { - toMigrate = append(toMigrate, grantToMigrate{ - granter: granterAddr, - grantee: granteeAddr, - grant: grant, - }) - } - return false - }) - - for _, g := range toMigrate { - auth, err := g.grant.GetAuthorization() - if err != nil { - return err - } - msgType := auth.MsgTypeURL() - - // Delete old grant. - if err := k.authzKeeper.DeleteGrant(ctx, g.grantee, g.granter, msgType); err != nil { - return err - } - - // Compute new granter/grantee. - newGranter := g.granter - if newGranter.Equals(legacyAddr) { - newGranter = newAddr - } - newGrantee := g.grantee - if newGrantee.Equals(legacyAddr) { - newGrantee = newAddr - } - - // Re-create grant with new addresses. - if err := k.authzKeeper.SaveGrant(ctx, newGrantee, newGranter, auth, g.grant.Expiration); err != nil { - return err - } + moves, err := k.buildAuthzPlan(ctx, legacyAddr, newAddr, params.MaxRetainedStateEntries) + if err != nil { + return err } - + cacheCtx, commit := ctx.CacheContext() + plan := retainedStatePlan{authz: moves, legacyAddr: legacyAddr, newAddr: newAddr} + if err := k.applyRetainedStatePlan(cacheCtx, plan); err != nil { + return err + } + commit() return nil } diff --git a/x/evmigration/keeper/migrate_retained.go b/x/evmigration/keeper/migrate_retained.go new file mode 100644 index 00000000..3f8cd5c3 --- /dev/null +++ b/x/evmigration/keeper/migrate_retained.go @@ -0,0 +1,503 @@ +package keeper + +import ( + "bytes" + "errors" + "fmt" + "time" + + "cosmossdk.io/collections" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/gogoproto/proto" +) + +type retainedStatePlan struct { + authz []authzGrantMove + votes []govVoteMove + deposits []govDepositMove + withdraw []withdrawAddressMove + legacyAddr sdk.AccAddress + newAddr sdk.AccAddress +} + +type authzGrantMove struct { + oldGranter, oldGrantee sdk.AccAddress + newGranter, newGrantee sdk.AccAddress + msgType string + source authz.Grant + authorization authz.Authorization + expiration *time.Time +} + +type govVoteMove struct { + proposalID uint64 + source govv1.Vote + destination *govv1.Vote + result govv1.Vote + collapse bool +} + +type govDepositMove struct { + proposalID uint64 + source govv1.Deposit + destination *govv1.Deposit + result govv1.Deposit +} + +type withdrawAddressMove struct { + key []byte + oldValue []byte +} + +// buildRetainedStatePlan discovers every retained SDK reference before a +// production migration performs its first write. Missing dependencies are a +// configuration error: continuity must never silently degrade. +func (k Keeper) buildRetainedStatePlan(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress, cap uint64) (retainedStatePlan, error) { + plan := retainedStatePlan{legacyAddr: bytes.Clone(legacyAddr), newAddr: bytes.Clone(newAddr)} + if legacyAddr.Equals(newAddr) { + return plan, fmt.Errorf("retained state source and destination must differ") + } + if cap == 0 { + return plan, fmt.Errorf("retained state scan cap must be positive") + } + if k.authzKeeper == nil { + return plan, fmt.Errorf("retained state authz keeper is not configured") + } + if k.govKeeper == nil { + return plan, fmt.Errorf("retained state governance keeper is not configured") + } + if k.distributionStoreHandle == nil || k.distributionStoreHandle.svc == nil { + return plan, fmt.Errorf("retained state distribution store is not configured") + } + + var err error + plan.authz, err = k.buildAuthzPlan(ctx, legacyAddr, newAddr, cap) + if err != nil { + return plan, fmt.Errorf("build retained authz plan: %w", err) + } + plan.votes, plan.deposits, err = k.buildGovernancePlan(ctx, legacyAddr, newAddr, cap) + if err != nil { + return plan, fmt.Errorf("build retained governance plan: %w", err) + } + plan.withdraw, err = k.buildWithdrawAddressPlan(ctx, legacyAddr, cap) + if err != nil { + return plan, fmt.Errorf("build retained distribution plan: %w", err) + } + return plan, nil +} + +func (k Keeper) applyRetainedStatePlan(ctx sdk.Context, plan retainedStatePlan) error { + // Verify every snapshot before the first retained-state write. This catches + // stale plans deterministically and avoids partially applying direct calls. + if err := k.verifyRetainedStatePlan(ctx, plan); err != nil { + return err + } + for _, move := range plan.authz { + if err := k.authzKeeper.DeleteGrant(ctx, move.oldGrantee, move.oldGranter, move.msgType); err != nil { + return err + } + if err := k.authzKeeper.SaveGrant(ctx, move.newGrantee, move.newGranter, move.authorization, move.expiration); err != nil { + return err + } + } + for _, move := range plan.votes { + if err := k.govKeeper.Votes.Remove(ctx, collections.Join(move.proposalID, plan.legacyAddr)); err != nil { + return err + } + if !move.collapse { + if err := k.govKeeper.Votes.Set(ctx, collections.Join(move.proposalID, plan.newAddr), move.result); err != nil { + return err + } + } + } + for _, move := range plan.deposits { + if err := k.govKeeper.Deposits.Remove(ctx, collections.Join(move.proposalID, plan.legacyAddr)); err != nil { + return err + } + if err := k.govKeeper.Deposits.Set(ctx, collections.Join(move.proposalID, plan.newAddr), move.result); err != nil { + return err + } + } + if len(plan.withdraw) > 0 { + store := k.distributionStoreHandle.svc.OpenKVStore(ctx) + for _, move := range plan.withdraw { + if err := store.Set(move.key, plan.newAddr.Bytes()); err != nil { + return err + } + } + } + return nil +} + +func (k Keeper) verifyRetainedStatePlan(ctx sdk.Context, plan retainedStatePlan) error { + if len(plan.authz) > 0 { + if k.authzKeeper == nil { + return fmt.Errorf("retained state authz keeper is not configured") + } + current := make(map[string]authz.Grant) + k.authzKeeper.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool { + a, err := grant.GetAuthorization() + if err == nil { + current[authzIdentity(granter, grantee, a.MsgTypeURL())] = grant + } + return false + }) + for _, move := range plan.authz { + sourceID := authzIdentity(move.oldGranter, move.oldGrantee, move.msgType) + grant, ok := current[sourceID] + if !ok || !proto.Equal(&grant, &move.source) { + return fmt.Errorf("stale authz source grant %s", sourceID) + } + targetID := authzIdentity(move.newGranter, move.newGrantee, move.msgType) + if targetID != sourceID { + if _, exists := current[targetID]; exists { + return fmt.Errorf("stale authz destination grant %s", targetID) + } + } + } + } + if len(plan.votes) > 0 || len(plan.deposits) > 0 { + if k.govKeeper == nil { + return fmt.Errorf("retained state governance keeper is not configured") + } + } + for _, move := range plan.votes { + if err := verifyCollectionValue(ctx, k.govKeeper.Votes, collections.Join(move.proposalID, plan.legacyAddr), &move.source); err != nil { + return fmt.Errorf("stale governance vote source for proposal %d: %w", move.proposalID, err) + } + if err := verifyOptionalCollectionValue(ctx, k.govKeeper.Votes, collections.Join(move.proposalID, plan.newAddr), move.destination); err != nil { + return fmt.Errorf("stale governance vote destination for proposal %d: %w", move.proposalID, err) + } + } + for _, move := range plan.deposits { + if err := verifyCollectionValue(ctx, k.govKeeper.Deposits, collections.Join(move.proposalID, plan.legacyAddr), &move.source); err != nil { + return fmt.Errorf("stale governance deposit source for proposal %d: %w", move.proposalID, err) + } + if err := verifyOptionalCollectionValue(ctx, k.govKeeper.Deposits, collections.Join(move.proposalID, plan.newAddr), move.destination); err != nil { + return fmt.Errorf("stale governance deposit destination for proposal %d: %w", move.proposalID, err) + } + } + if len(plan.withdraw) > 0 { + if k.distributionStoreHandle == nil || k.distributionStoreHandle.svc == nil { + return fmt.Errorf("retained state distribution store is not configured") + } + store := k.distributionStoreHandle.svc.OpenKVStore(ctx) + for _, move := range plan.withdraw { + value, err := store.Get(move.key) + if err != nil { + return err + } + if !bytes.Equal(value, move.oldValue) { + return fmt.Errorf("stale distribution withdraw address at %X", move.key) + } + } + } + return nil +} + +// MigrateRetainedState is atomic for direct keeper callers. +func (k Keeper) MigrateRetainedState(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress, cap uint64) error { + plan, err := k.buildRetainedStatePlan(ctx, legacyAddr, newAddr, cap) + if err != nil { + return err + } + cacheCtx, commit := ctx.CacheContext() + if err := k.applyRetainedStatePlan(cacheCtx, plan); err != nil { + return err + } + commit() + return nil +} + +func (k Keeper) buildAuthzPlan(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress, cap uint64) ([]authzGrantMove, error) { + if k.authzKeeper == nil { + return nil, fmt.Errorf("authz keeper is not configured") + } + type observed struct { + granter, grantee sdk.AccAddress + grant authz.Grant + authorization authz.Authorization + msgType string + embedded bool + } + var all []observed + var buildErr error + var count uint64 + k.authzKeeper.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool { + count++ + if count > cap { + buildErr = fmt.Errorf("authz grant scan exceeds max %d", cap) + return true + } + authorization, err := grant.GetAuthorization() + if err != nil { + buildErr = fmt.Errorf("decode grant %s/%s: %w", granter, grantee, err) + return true + } + // Keeper implementations may return pointers backed by caches. Clone before + // rewriting so planning remains read-only. + cloned, ok := proto.Clone(authorization).(authz.Authorization) + if !ok { + buildErr = fmt.Errorf("clone unsupported authorization %T", authorization) + return true + } + msgType := cloned.MsgTypeURL() + embedded, err := rewriteStakeAuthorization(cloned, sdk.ValAddress(legacyAddr), sdk.ValAddress(newAddr)) + if err != nil { + buildErr = fmt.Errorf("validate stake authorization %s/%s/%s: %w", granter, grantee, msgType, err) + return true + } + grantCopy := proto.Clone(&grant).(*authz.Grant) + all = append(all, observed{bytes.Clone(granter), bytes.Clone(grantee), *grantCopy, cloned, msgType, embedded}) + return false + }) + if buildErr != nil { + return nil, buildErr + } + + existing := make(map[string]struct{}, len(all)) + for _, row := range all { + existing[authzIdentity(row.granter, row.grantee, row.msgType)] = struct{}{} + } + targets := make(map[string]struct{}) + moves := make([]authzGrantMove, 0) + for _, row := range all { + if !row.granter.Equals(legacyAddr) && !row.grantee.Equals(legacyAddr) && !row.embedded { + continue + } + newGranter, newGrantee := row.granter, row.grantee + if newGranter.Equals(legacyAddr) { + newGranter = newAddr + } + if newGrantee.Equals(legacyAddr) { + newGrantee = newAddr + } + sourceID := authzIdentity(row.granter, row.grantee, row.msgType) + targetID := authzIdentity(newGranter, newGrantee, row.msgType) + if targetID != sourceID { + if _, found := existing[targetID]; found { + return nil, fmt.Errorf("authz destination grant already exists for %s", targetID) + } + } + if _, duplicate := targets[targetID]; duplicate { + return nil, fmt.Errorf("duplicate authz destination semantics for %s", targetID) + } + targets[targetID] = struct{}{} + moves = append(moves, authzGrantMove{ + oldGranter: row.granter, oldGrantee: row.grantee, + newGranter: bytes.Clone(newGranter), newGrantee: bytes.Clone(newGrantee), + msgType: row.msgType, source: row.grant, authorization: row.authorization, + expiration: cloneTime(row.grant.Expiration), + }) + } + return moves, nil +} + +func cloneTime(t *time.Time) *time.Time { + if t == nil { + return nil + } + copy := *t + return © +} + +func authzIdentity(granter, grantee sdk.AccAddress, msgType string) string { + return granter.String() + "\x00" + grantee.String() + "\x00" + msgType +} + +func rewriteStakeAuthorization(authorization authz.Authorization, oldVal, newVal sdk.ValAddress) (bool, error) { + stakeAuth, ok := authorization.(*stakingtypes.StakeAuthorization) + if !ok { + return false, nil + } + var addresses *[]string + switch validators := stakeAuth.Validators.(type) { + case nil: + return false, nil + case *stakingtypes.StakeAuthorization_AllowList: + if validators.AllowList == nil { + return false, fmt.Errorf("nil allow list") + } + addresses = &validators.AllowList.Address + case *stakingtypes.StakeAuthorization_DenyList: + if validators.DenyList == nil { + return false, fmt.Errorf("nil deny list") + } + addresses = &validators.DenyList.Address + default: + return false, fmt.Errorf("unknown validator list type %T", validators) + } + seen := make(map[string]struct{}, len(*addresses)) + result := make([]string, 0, len(*addresses)) + foundOld := false + for _, encoded := range *addresses { + addr, err := sdk.ValAddressFromBech32(encoded) + if err != nil || encoded != addr.String() { + return false, fmt.Errorf("malformed validator address %q", encoded) + } + canonical := addr.String() + if addr.Equals(oldVal) { + foundOld = true + canonical = newVal.String() + } + if _, duplicate := seen[canonical]; duplicate { + // An old+new pair intentionally collapses to one destination entry. + if foundOld && canonical == newVal.String() { + continue + } + return false, fmt.Errorf("duplicate validator address %q", canonical) + } + seen[canonical] = struct{}{} + result = append(result, canonical) + } + if foundOld { + *addresses = result + } + return foundOld, nil +} + +func (k Keeper) buildGovernancePlan(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddress, cap uint64) ([]govVoteMove, []govDepositMove, error) { + if k.govKeeper == nil { + return nil, nil, fmt.Errorf("governance keeper is not configured") + } + votes := make([]govVoteMove, 0) + var scanned uint64 + err := k.govKeeper.Votes.Walk(ctx, nil, func(key collections.Pair[uint64, sdk.AccAddress], vote govv1.Vote) (bool, error) { + scanned++ + if scanned > cap { + return true, fmt.Errorf("governance row scan exceeds max %d", cap) + } + if !key.K2().Equals(legacyAddr) { + return false, nil + } + if vote.Voter != legacyAddr.String() || vote.ProposalId != key.K1() { + return true, fmt.Errorf("vote key and embedded value differ for proposal %d", key.K1()) + } + result := *proto.Clone(&vote).(*govv1.Vote) + result.Voter = newAddr.String() + move := govVoteMove{proposalID: key.K1(), source: vote, result: result} + destination, getErr := k.govKeeper.Votes.Get(ctx, collections.Join(key.K1(), newAddr)) + if getErr == nil { + destCopy := *proto.Clone(&destination).(*govv1.Vote) + move.destination = &destCopy + if !proto.Equal(&result, &destination) { + return true, fmt.Errorf("conflicting destination vote for proposal %d", key.K1()) + } + move.collapse = true + } else if !errors.Is(getErr, collections.ErrNotFound) { + return true, getErr + } + votes = append(votes, move) + return false, nil + }) + if err != nil { + return nil, nil, err + } + + deposits := make([]govDepositMove, 0) + err = k.govKeeper.Deposits.Walk(ctx, nil, func(key collections.Pair[uint64, sdk.AccAddress], deposit govv1.Deposit) (bool, error) { + scanned++ // governance cap is total rows across votes and deposits + if scanned > cap { + return true, fmt.Errorf("governance row scan exceeds max %d", cap) + } + if !key.K2().Equals(legacyAddr) { + return false, nil + } + if deposit.Depositor != legacyAddr.String() || deposit.ProposalId != key.K1() { + return true, fmt.Errorf("deposit key and embedded value differ for proposal %d", key.K1()) + } + if !sdk.Coins(deposit.Amount).IsValid() { + return true, fmt.Errorf("source deposit has invalid coins for proposal %d", key.K1()) + } + result := *proto.Clone(&deposit).(*govv1.Deposit) + result.Depositor = newAddr.String() + move := govDepositMove{proposalID: key.K1(), source: deposit, result: result} + if destination, getErr := k.govKeeper.Deposits.Get(ctx, collections.Join(key.K1(), newAddr)); getErr == nil { + if destination.Depositor != newAddr.String() || destination.ProposalId != key.K1() || !sdk.Coins(destination.Amount).IsValid() { + return true, fmt.Errorf("destination deposit is malformed for proposal %d", key.K1()) + } + destCopy := *proto.Clone(&destination).(*govv1.Deposit) + move.destination = &destCopy + // Valid sdk.Coins are sorted and unique, so Add preserves canonical order + // without changing deposit semantics. + move.result.Amount = sdk.Coins(destination.Amount).Add(deposit.Amount...) + } + deposits = append(deposits, move) + return false, nil + }) + return votes, deposits, err +} + +func (k Keeper) buildWithdrawAddressPlan(ctx sdk.Context, legacyAddr sdk.AccAddress, cap uint64) ([]withdrawAddressMove, error) { + if k.distributionStoreHandle == nil || k.distributionStoreHandle.svc == nil { + return nil, fmt.Errorf("distribution store is not configured") + } + store := k.distributionStoreHandle.svc.OpenKVStore(ctx) + prefix := distrtypes.DelegatorWithdrawAddrPrefix + iterator, err := store.Iterator(prefix, storetypes.PrefixEndBytes(prefix)) + if err != nil { + return nil, err + } + defer func() { _ = iterator.Close() }() + moves := make([]withdrawAddressMove, 0) + var scanned uint64 + for ; iterator.Valid(); iterator.Next() { + scanned++ + if scanned > cap { + return nil, fmt.Errorf("distribution withdraw-address scan exceeds max %d", cap) + } + key := iterator.Key() + // Exact SDK key: 0x03 | one-byte address length | delegator bytes. + if len(key) < 3 || key[0] != distrtypes.DelegatorWithdrawAddrPrefix[0] || int(key[1]) != len(key)-2 { + return nil, fmt.Errorf("malformed distribution withdraw-address key %X", key) + } + value := iterator.Value() + if len(value) == 0 { + return nil, fmt.Errorf("malformed distribution withdraw-address value at %X", key) + } + if bytes.Equal(value, legacyAddr.Bytes()) { + moves = append(moves, withdrawAddressMove{key: bytes.Clone(key), oldValue: bytes.Clone(value)}) + } + } + return moves, nil +} + +// These helpers keep stale checks exact while allowing the concrete collection +// value type to remain inferred from the SDK keeper fields. +func verifyCollectionValue[K, V any](ctx sdk.Context, m collections.Map[K, V], key K, expected proto.Message) error { + value, err := m.Get(ctx, key) + if err != nil { + return err + } + actual, ok := any(&value).(proto.Message) + if !ok || !proto.Equal(actual, expected) { + return fmt.Errorf("value changed") + } + return nil +} + +func verifyOptionalCollectionValue[K, V any](ctx sdk.Context, m collections.Map[K, V], key K, expected proto.Message) error { + value, err := m.Get(ctx, key) + if expected == nil { + if errors.Is(err, collections.ErrNotFound) { + return nil + } + if err != nil { + return err + } + return fmt.Errorf("destination appeared") + } + if err != nil { + return err + } + actual, ok := any(&value).(proto.Message) + if !ok || !proto.Equal(actual, expected) { + return fmt.Errorf("value changed") + } + return nil +} diff --git a/x/evmigration/keeper/migrate_retained_test.go b/x/evmigration/keeper/migrate_retained_test.go new file mode 100644 index 00000000..6db738aa --- /dev/null +++ b/x/evmigration/keeper/migrate_retained_test.go @@ -0,0 +1,76 @@ +package keeper + +import ( + "testing" + "time" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/runtime" + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" +) + +func TestRewriteStakeAuthorization_CloneDoesNotMutateSourceAndCollapsesDestination(t *testing.T) { + oldVal := sdk.ValAddress(bytesOf(1)) + newVal := sdk.ValAddress(bytesOf(2)) + otherVal := sdk.ValAddress(bytesOf(3)) + source := &stakingtypes.StakeAuthorization{ + Validators: &stakingtypes.StakeAuthorization_AllowList{AllowList: &stakingtypes.StakeAuthorization_Validators{ + Address: []string{oldVal.String(), newVal.String(), otherVal.String()}, + }}, + } + + cloned := proto.Clone(source).(*stakingtypes.StakeAuthorization) + changed, err := rewriteStakeAuthorization(cloned, oldVal, newVal) + require.NoError(t, err) + require.True(t, changed) + require.Equal(t, []string{oldVal.String(), newVal.String(), otherVal.String()}, source.GetAllowList().Address) + require.Equal(t, []string{newVal.String(), otherVal.String()}, cloned.GetAllowList().Address) +} + +func TestBuildWithdrawAddressPlan_CapPlusOneIsReadOnly(t *testing.T) { + key := storetypes.NewKVStoreKey(distrtypes.StoreKey) + ctx := testutil.DefaultContextWithKeys(map[string]*storetypes.KVStoreKey{distrtypes.StoreKey: key}, nil, nil) + svc := runtime.NewKVStoreService(key) + k := Keeper{distributionStoreHandle: &distributionStoreHandle{svc: svc}} + legacy := sdk.AccAddress(bytesOf(4)) + store := svc.OpenKVStore(ctx) + + for _, delegatorByte := range []byte{6, 7} { + delegator := sdk.AccAddress(bytesOf(delegatorByte)) + withdrawKey := append(append([]byte{}, distrtypes.DelegatorWithdrawAddrPrefix...), byte(len(delegator))) + withdrawKey = append(withdrawKey, delegator...) + require.NoError(t, store.Set(withdrawKey, legacy.Bytes())) + } + + _, err := k.buildWithdrawAddressPlan(ctx, legacy, 1) + require.ErrorContains(t, err, "exceeds max 1") + for _, delegatorByte := range []byte{6, 7} { + delegator := sdk.AccAddress(bytesOf(delegatorByte)) + withdrawKey := append(append([]byte{}, distrtypes.DelegatorWithdrawAddrPrefix...), byte(len(delegator))) + withdrawKey = append(withdrawKey, delegator...) + value, getErr := store.Get(withdrawKey) + require.NoError(t, getErr) + require.Equal(t, legacy.Bytes(), value) + } +} + +func TestCloneTime_DeepCopy(t *testing.T) { + original := time.Unix(123, 456) + cloned := cloneTime(&original) + require.NotSame(t, &original, cloned) + *cloned = cloned.Add(time.Hour) + require.Equal(t, time.Unix(123, 456), original) +} + +func bytesOf(value byte) []byte { + result := make([]byte, 20) + for i := range result { + result[i] = value + } + return result +} diff --git a/x/evmigration/keeper/migrate_test.go b/x/evmigration/keeper/migrate_test.go index 39d66df4..aaf63a11 100644 --- a/x/evmigration/keeper/migrate_test.go +++ b/x/evmigration/keeper/migrate_test.go @@ -1,11 +1,13 @@ package keeper_test import ( + "context" "errors" "sort" "strings" "testing" + coreaddress "cosmossdk.io/core/address" corestore "cosmossdk.io/core/store" "cosmossdk.io/math" storetypes "cosmossdk.io/store/types" @@ -23,6 +25,8 @@ import ( vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" "github.com/cosmos/cosmos-sdk/x/authz" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ethsecp256k1 "github.com/cosmos/evm/crypto/ethsecp256k1" "github.com/stretchr/testify/require" @@ -55,6 +59,19 @@ type mockFixture struct { actionKeeper *evmigrationmocks.MockActionKeeper } +// govAccountStub supplies the constructor-only account surface needed by the +// real governance keeper used in retained-state tests. +type govAccountStub struct { + codec coreaddress.Codec + addr sdk.AccAddress +} + +func (s govAccountStub) AddressCodec() coreaddress.Codec { return s.codec } +func (s govAccountStub) GetAccount(context.Context, sdk.AccAddress) sdk.AccountI { return nil } +func (s govAccountStub) GetModuleAddress(string) sdk.AccAddress { return s.addr } +func (s govAccountStub) GetModuleAccount(context.Context, string) sdk.ModuleAccountI { return nil } +func (s govAccountStub) SetModuleAccount(context.Context, sdk.ModuleAccountI) {} + func initMockFixture(t *testing.T) *mockFixture { t.Helper() @@ -70,6 +87,7 @@ func initMockFixture(t *testing.T) *mockFixture { supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) auditKeeper := evmigrationmocks.NewMockAuditKeeper(ctrl) actionKeeper := evmigrationmocks.NewMockActionKeeper(ctrl) + supernodeKeeper.EXPECT().BuildIdentityMigrationPlan(gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(func(_ sdk.Context, source, destination sdk.ValAddress) (sntypes.IdentityMigrationPlan, error) { return sntypes.NewIdentityMigrationPlan(source, destination, nil, nil, nil, nil, nil), nil @@ -83,20 +101,27 @@ func initMockFixture(t *testing.T) *mockFixture { storeKey := storetypes.NewKVStoreKey(types.StoreKey) stakingStoreKey := storetypes.NewKVStoreKey(stakingtypes.StoreKey) distributionStoreKey := storetypes.NewKVStoreKey(distrtypes.StoreKey) + govStoreKey := storetypes.NewKVStoreKey(govtypes.StoreKey) storeService := runtime.NewKVStoreService(storeKey) stakingStoreService := runtime.NewKVStoreService(stakingStoreKey) distributionStoreService := runtime.NewKVStoreService(distributionStoreKey) + govStoreService := runtime.NewKVStoreService(govStoreKey) ctx := testutil.DefaultContextWithKeys( map[string]*storetypes.KVStoreKey{ types.StoreKey: storeKey, stakingtypes.StoreKey: stakingStoreKey, distrtypes.StoreKey: distributionStoreKey, + govtypes.StoreKey: govStoreKey, }, map[string]*storetypes.TransientStoreKey{"transient_test": storetypes.NewTransientStoreKey("transient_test")}, nil, ) authority := authtypes.NewModuleAddress(types.GovModuleName) + govKeeper := govkeeper.NewKeeper( + encCfg.Codec, govStoreService, govAccountStub{codec: addrCodec, addr: authority}, nil, nil, nil, nil, + govtypes.DefaultConfig(), authority.String(), + ) k := keeper.NewKeeper( storeService, @@ -108,6 +133,7 @@ func initMockFixture(t *testing.T) *mockFixture { stakingKeeper, distributionKeeper, authzKeeper, + govKeeper, feegrantKeeper, supernodeKeeper, auditKeeper, @@ -312,19 +338,6 @@ func expectHistoricalRewardsIncrement( mock.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), val, period, gomock.Any()).Return(nil) } -// expectHistoricalRewardsSet sets up mock expectations for -// setHistoricalRewardsReferenceCount: look up the (val, period) row, then write -// its refcount back in a single set. -func expectHistoricalRewardsSet( - mock *evmigrationmocks.MockDistributionKeeper, - val sdk.ValAddress, - period uint64, - refCount uint32, -) { - expectHistoricalRewardsLookup(mock, val, period, refCount) - mock.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), val, period, gomock.Any()).Return(nil) -} - // --- MigrateAuth tests --- // TestMigrateAuth_BaseAccount verifies that a plain BaseAccount is removed @@ -1108,7 +1121,7 @@ func TestMigrateAuthz_AsGranter(t *testing.T) { f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()). Do(func(_ any, cb func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) { cb(legacy, grantee, grant) - }) + }).Times(2) f.authzKeeper.EXPECT().DeleteGrant(gomock.Any(), grantee, legacy, "/cosmos.bank.v1beta1.MsgSend").Return(nil) f.authzKeeper.EXPECT().SaveGrant(gomock.Any(), grantee, newAddr, genericAuth, grant.Expiration).Return(nil) @@ -1131,7 +1144,7 @@ func TestMigrateAuthz_AsGrantee(t *testing.T) { f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()). Do(func(_ any, cb func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) { cb(granter, legacy, grant) - }) + }).Times(2) f.authzKeeper.EXPECT().DeleteGrant(gomock.Any(), legacy, granter, "/cosmos.bank.v1beta1.MsgSend").Return(nil) f.authzKeeper.EXPECT().SaveGrant(gomock.Any(), newAddr, granter, genericAuth, grant.Expiration).Return(nil) diff --git a/x/evmigration/keeper/msg_server_claim_legacy.go b/x/evmigration/keeper/msg_server_claim_legacy.go index 93876cf3..33b2754d 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy.go +++ b/x/evmigration/keeper/msg_server_claim_legacy.go @@ -103,6 +103,10 @@ func (ms msgServer) ClaimLegacyAccount(goCtx context.Context, msg *types.MsgClai return nil, fmt.Errorf("build audit account transition: %w", err) } } + retainedPlan, err := ms.buildRetainedStatePlan(ctx, legacyAddr, newAddr, params.EffectiveMaxRetainedStateEntries()) + if err != nil { + return nil, err + } // Build the complete staking plan against pristine state. This validates // destination primaries and every touched maturity timeslice before reward @@ -120,7 +124,7 @@ func (ms msgServer) ClaimLegacyAccount(goCtx context.Context, msg *types.MsgClai } // --- Execute migration steps --- - if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode, auditPlan, delegations, stakingPlan); err != nil { + if err := ms.migrateAccount(ctx, legacyAddr, newAddr, &msg.NewProof, supernode, hasSupernode, auditPlan, retainedPlan, delegations, stakingPlan); err != nil { return nil, err } @@ -228,6 +232,7 @@ func (ms msgServer) migrateAccount( supernode sntypes.SuperNode, hasSupernode bool, auditPlan auditkeeper.AccountTransitionPlan, + retainedPlan retainedStatePlan, delegations []stakingtypes.Delegation, stakingPlan stakingMigrationPlan, ) error { @@ -263,9 +268,9 @@ func (ms msgServer) migrateAccount( } } - // Step 4: Re-key authz grants. - if err := ms.MigrateAuthz(ctx, legacyAddr, newAddr); err != nil { - return fmt.Errorf("migrate authz: %w", err) + // Step 4: Apply retained state discovered before Step 1's first write. + if err := ms.applyRetainedStatePlan(ctx, retainedPlan); err != nil { + return fmt.Errorf("migrate authz/retained SDK state: %w", err) } // Step 5: Re-key feegrant allowances. diff --git a/x/evmigration/keeper/msg_server_claim_legacy_test.go b/x/evmigration/keeper/msg_server_claim_legacy_test.go index ead8274b..c72df1e6 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy_test.go +++ b/x/evmigration/keeper/msg_server_claim_legacy_test.go @@ -16,6 +16,8 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/authz" distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" evmcryptotypes "github.com/cosmos/evm/crypto/ethsecp256k1" "github.com/stretchr/testify/require" @@ -41,6 +43,7 @@ type msgServerFixture struct { identityApplyCalls *int identityBuildErr *error continuityEvents *[]string + authzIterate *func(func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) } // newSingleKeyProofNew builds a valid new-side MigrationProof (eth_secp256k1, @@ -116,6 +119,11 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { supernodeKeeper := evmigrationmocks.NewMockSupernodeKeeper(ctrl) auditKeeper := evmigrationmocks.NewMockAuditKeeper(ctrl) actionKeeper := evmigrationmocks.NewMockActionKeeper(ctrl) + authzIterate := func(func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) {} + // Retained-state preflight always scans authz before a production handler's + // first write. Focused tests replace authzIterate to supply rows. + authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()). + Do(func(_ any, cb func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) { authzIterate(cb) }).AnyTimes() auditBuildCalls := 0 auditApplyCalls := 0 var auditBuildErr error @@ -150,18 +158,28 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { addrCodec := addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()) storeKey := storetypes.NewKVStoreKey(types.StoreKey) stakingStoreKey := storetypes.NewKVStoreKey(stakingtypes.StoreKey) + distributionStoreKey := storetypes.NewKVStoreKey(distrtypes.StoreKey) + govStoreKey := storetypes.NewKVStoreKey(govtypes.StoreKey) storeService := runtime.NewKVStoreService(storeKey) stakingStoreService := runtime.NewKVStoreService(stakingStoreKey) + distributionStoreService := runtime.NewKVStoreService(distributionStoreKey) + govStoreService := runtime.NewKVStoreService(govStoreKey) ctx := testutil.DefaultContextWithKeys( map[string]*storetypes.KVStoreKey{ types.StoreKey: storeKey, stakingtypes.StoreKey: stakingStoreKey, + distrtypes.StoreKey: distributionStoreKey, + govtypes.StoreKey: govStoreKey, }, map[string]*storetypes.TransientStoreKey{"transient_test": storetypes.NewTransientStoreKey("transient_test")}, nil, ).WithChainID(testChainID).WithBlockTime(time.Now()) authority := authtypes.NewModuleAddress(types.GovModuleName) + govKeeper := govkeeper.NewKeeper( + encCfg.Codec, govStoreService, govAccountStub{codec: addrCodec, addr: authority}, nil, nil, nil, nil, + govtypes.DefaultConfig(), authority.String(), + ) k := keeper.NewKeeper( storeService, @@ -173,6 +191,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { stakingKeeper, distributionKeeper, authzKeeper, + govKeeper, feegrantKeeper, supernodeKeeper, auditKeeper, @@ -183,6 +202,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { // and DeleteValidatorRecordNoHooks can run in unit tests. Production wiring // happens in app.go. k.SetStakingStoreService(stakingStoreService) + k.SetDistributionStoreService(distributionStoreService) // Initialize params with migration enabled. params := types.NewParams(true, 0, 50, 2000, 20) @@ -195,6 +215,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { keeper: k, cdc: encCfg.Codec, stakingStore: stakingStoreService, + distributionStore: distributionStoreService, accountKeeper: accountKeeper, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, @@ -216,6 +237,7 @@ func initMsgServerFixture(t *testing.T) *msgServerFixture { identityApplyCalls: &identityApplyCalls, identityBuildErr: &identityBuildErr, continuityEvents: &continuityEvents, + authzIterate: &authzIterate, } } @@ -565,7 +587,6 @@ func TestClaimLegacyAccount_Success(t *testing.T) { f.bankKeeper.EXPECT().SendCoins(gomock.Any(), legacyAddr, newAddr, balances).Return(nil) // Step 4: MigrateAuthz — no grants. - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) // Step 5: MigrateFeegrant — no allowances. f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) @@ -669,7 +690,6 @@ func TestClaimLegacyAccount_MigratedThirdPartyWithdrawAddress(t *testing.T) { f.bankKeeper.EXPECT().GetAllBalances(gomock.Any(), legacyAddr).Return(sdk.Coins{}) // Steps 4-7: no authz/feegrant/supernode/action to migrate. - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return( sntypes.SuperNode{}, false, nil, @@ -876,10 +896,10 @@ func TestClaimLegacyAccount_FailAtAuthz(t *testing.T) { genericAuth := authz.NewGenericAuthorization("/cosmos.bank.v1beta1.MsgSend") grant, err := authz.NewGrant(f.ctx.BlockTime(), genericAuth, nil) require.NoError(t, err) - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()). - Do(func(_ any, cb func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) { - cb(legacyAddr, testAccAddr(), grant) - }) + grantee := testAccAddr() + *f.authzIterate = func(cb func(sdk.AccAddress, sdk.AccAddress, authz.Grant) bool) { + cb(legacyAddr, grantee, grant) + } f.authzKeeper.EXPECT().DeleteGrant(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return( fmt.Errorf("authz store corrupted"), ) @@ -912,7 +932,6 @@ func TestClaimLegacyAccount_FailAtFeegrant(t *testing.T) { f.accountKeeper.EXPECT().SetAccount(gomock.Any(), newAcc) f.bankKeeper.EXPECT().GetAllBalances(gomock.Any(), legacyAddr).Return(sdk.Coins{}) - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) // Step 5: MigrateFeegrant fails. f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return( @@ -961,7 +980,6 @@ func TestClaimLegacyAccount_FailAtActions(t *testing.T) { f.accountKeeper.EXPECT().SetAccount(gomock.Any(), newAcc) f.bankKeeper.EXPECT().GetAllBalances(gomock.Any(), legacyAddr).Return(sdk.Coins{}) - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) // Step 7: MigrateActions fails. f.actionKeeper.EXPECT().GetActionsByCreator(gomock.Any(), gomock.Any()).Return( @@ -1092,9 +1110,7 @@ func setupV1toV4(f *mockFixture, oldValAddr, newValAddr sdk.ValAddress) { f.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return( distrtypes.ValidatorOutstandingRewards{}, fmt.Errorf("not found"), ) - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()) f.distributionKeeper.EXPECT().DeleteValidatorHistoricalRewards(gomock.Any(), oldValAddr) - f.distributionKeeper.EXPECT().IterateValidatorSlashEvents(gomock.Any(), gomock.Any()) f.distributionKeeper.EXPECT().DeleteValidatorSlashEvents(gomock.Any(), oldValAddr) // V4: no delegations. The pre-check already read delegations/ubds and V4 @@ -1251,9 +1267,7 @@ func TestMigrateValidator_FailAtValidatorDelegations(t *testing.T) { f.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return( distrtypes.ValidatorOutstandingRewards{}, fmt.Errorf("not found"), ) - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()) f.distributionKeeper.EXPECT().DeleteValidatorHistoricalRewards(gomock.Any(), oldValAddr) - f.distributionKeeper.EXPECT().IterateValidatorSlashEvents(gomock.Any(), gomock.Any()) f.distributionKeeper.EXPECT().DeleteValidatorSlashEvents(gomock.Any(), oldValAddr) // Step V4: unbonding-delegation re-key fails on its first store op. @@ -1410,7 +1424,7 @@ func TestClaimLegacyAccount_WithDelegations(t *testing.T) { f.distributionKeeper.EXPECT().GetDelegatorStartingInfo(gomock.Any(), valAddr, legacyAddr).Return( distrtypes.DelegatorStartingInfo{PreviousPeriod: 4}, nil, ) - expectHistoricalRewardsLookup(f.distributionKeeper, valAddr, 4, 1) + f.writeValidatorHistoricalRewards(valAddr, 4, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) f.distributionKeeper.EXPECT().WithdrawDelegationRewards(gomock.Any(), legacyAddr, valAddr).Return(sdk.Coins{}, nil) // Step 2: MigrateStaking — re-key delegation. @@ -1424,7 +1438,9 @@ func TestClaimLegacyAccount_WithDelegations(t *testing.T) { f.distributionKeeper.EXPECT().GetValidatorCurrentRewards(gomock.Any(), valAddr).Return( distrtypes.ValidatorCurrentRewards{Period: 5}, nil, ) - expectHistoricalRewardsIncrement(f.distributionKeeper, valAddr, 4, 1) + f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards( + gomock.Any(), valAddr, uint64(4), gomock.Any(), + ).Return(nil).Times(1) // migrateActiveDelegations fetches the validator to convert shares → tokens (rate 1.0). f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), valAddr).Return( stakingtypes.Validator{OperatorAddress: valAddr.String(), Tokens: math.NewInt(100), DelegatorShares: math.LegacyNewDec(100)}, nil, @@ -1453,7 +1469,6 @@ func TestClaimLegacyAccount_WithDelegations(t *testing.T) { f.bankKeeper.EXPECT().GetAllBalances(gomock.Any(), legacyAddr).Return(sdk.Coins{}) // Steps 4-7: no authz/feegrant/supernode/action to migrate. - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) f.supernodeKeeper.EXPECT().StrictGetSuperNodeByAccount(gomock.Any(), legacyAddr.String()).Return( sntypes.SuperNode{}, false, nil, diff --git a/x/evmigration/keeper/msg_server_migrate_validator.go b/x/evmigration/keeper/msg_server_migrate_validator.go index f51007ff..eb2a196d 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator.go +++ b/x/evmigration/keeper/msg_server_migrate_validator.go @@ -164,6 +164,10 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat return nil, fmt.Errorf("build audit account transition: %w", err) } } + retainedPlan, err := ms.buildRetainedStatePlan(ctx, legacyAddr, newAddr, params.EffectiveMaxRetainedStateEntries()) + if err != nil { + return nil, err + } // Account-owned staking positions are a separate bounded dimension. Build // their snapshot only after all proof/ownership checks, but before any write. @@ -318,9 +322,9 @@ func (ms msgServer) MigrateValidator(goCtx context.Context, msg *types.MsgMigrat } } - // Re-key authz grants (both granter and grantee roles). - if err := ms.MigrateAuthz(ctx, legacyAddr, newAddr); err != nil { - return nil, fmt.Errorf("migrate authz: %w", err) + // Apply authz/governance/distribution continuity discovered before V1. + if err := ms.applyRetainedStatePlan(ctx, retainedPlan); err != nil { + return nil, fmt.Errorf("migrate authz/retained SDK state: %w", err) } // Re-key feegrant allowances (both granter and grantee roles). diff --git a/x/evmigration/keeper/msg_server_migrate_validator_test.go b/x/evmigration/keeper/msg_server_migrate_validator_test.go index c7d267a1..c643c8da 100644 --- a/x/evmigration/keeper/msg_server_migrate_validator_test.go +++ b/x/evmigration/keeper/msg_server_migrate_validator_test.go @@ -277,15 +277,14 @@ func TestMigrateValidator_Success(t *testing.T) { f.distributionKeeper.EXPECT().SetValidatorOutstandingRewards(gomock.Any(), newValAddr, gomock.Any()).Return(nil) // HistoricalRewards — one entry carried over to the new validator. - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()). - Do(func(_ any, cb func(sdk.ValAddress, uint64, distrtypes.ValidatorHistoricalRewards) bool) { - cb(oldValAddr, 2, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) - }) + f.writeValidatorHistoricalRewards(oldValAddr, 2, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) + // The mocked setter does not mutate the raw scoped store used by the later + // O(1) target-period lookup, so seed that destination row as well. + f.writeValidatorHistoricalRewards(newValAddr, 2, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) f.distributionKeeper.EXPECT().DeleteValidatorHistoricalRewards(gomock.Any(), oldValAddr) - f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, uint64(2), gomock.Any()).Return(nil) + f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, uint64(2), gomock.Any()).Return(nil).Times(2) // SlashEvents — none. - f.distributionKeeper.EXPECT().IterateValidatorSlashEvents(gomock.Any(), gomock.Any()) f.distributionKeeper.EXPECT().DeleteValidatorSlashEvents(gomock.Any(), oldValAddr) // Step V4: MigrateValidatorDelegations — re-key the one delegation. @@ -295,7 +294,6 @@ func TestMigrateValidator_Success(t *testing.T) { f.distributionKeeper.EXPECT().GetValidatorCurrentRewards(gomock.Any(), newValAddr).Return( distrtypes.ValidatorCurrentRewards{Period: 3}, nil, ) - expectHistoricalRewardsSet(f.distributionKeeper, newValAddr, 2, 2) // V4 fetches the re-keyed validator to convert shares → tokens; a rate-1.0 // validator (tokens == shares) keeps Stake == shares for the assertion below. f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), newValAddr).Return( @@ -342,7 +340,6 @@ func TestMigrateValidator_Success(t *testing.T) { f.bankKeeper.EXPECT().SendCoins(gomock.Any(), legacyAddr, newAddr, balances).Return(nil) // MigrateAuthz — no grants. - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) // MigrateFeegrant — no allowances. f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) @@ -445,23 +442,17 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { f.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return(distrtypes.ValidatorOutstandingRewards{}, nil) f.distributionKeeper.EXPECT().DeleteValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return(nil) f.distributionKeeper.EXPECT().SetValidatorOutstandingRewards(gomock.Any(), newValAddr, gomock.Any()).Return(nil) - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()) + f.writeValidatorHistoricalRewards(oldValAddr, 2, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) + f.writeValidatorHistoricalRewards(newValAddr, 2, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1}) f.distributionKeeper.EXPECT().DeleteValidatorHistoricalRewards(gomock.Any(), oldValAddr) - f.distributionKeeper.EXPECT().IterateValidatorSlashEvents(gomock.Any(), gomock.Any()) + f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, uint64(2), gomock.Any()).Return(nil).Times(2) f.distributionKeeper.EXPECT().DeleteValidatorSlashEvents(gomock.Any(), oldValAddr) // V4: MigrateValidatorDelegations — re-key self-delegation. // Delegations/unbondings are supplied from the pre-check fetch; V4 no longer re-reads. f.distributionKeeper.EXPECT().GetValidatorCurrentRewards(gomock.Any(), newValAddr).Return(currentRewards, nil) - targetPeriod := currentRewards.Period - 1 - histRewards := distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1} - // Single write: set target period refcount to base(1) + N delegations. - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ sdk.Context, fn func(sdk.ValAddress, uint64, distrtypes.ValidatorHistoricalRewards) bool) { - fn(newValAddr, targetPeriod, histRewards) - }, - ) - f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, targetPeriod, gomock.Any()).Return(nil) + // The destination historical row is seeded above because mocked setters do + // not mutate the scoped store used by the O(1) period lookup. // V4 fetches the re-keyed validator to convert shares → tokens (rate 1.0). f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), newValAddr).Return( stakingtypes.Validator{OperatorAddress: newValAddr.String(), Tokens: math.NewInt(100), DelegatorShares: math.LegacyNewDec(100)}, nil, @@ -496,11 +487,7 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { ) // adjustHistoricalRewardsReferenceCount — ref count > 0, repairZero=true → no-op. otherHistRewards := distrtypes.ValidatorHistoricalRewards{ReferenceCount: 1} - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ sdk.Context, fn func(sdk.ValAddress, uint64, distrtypes.ValidatorHistoricalRewards) bool) { - fn(otherValAddr, 1, otherHistRewards) - }, - ) + f.writeValidatorHistoricalRewards(otherValAddr, 1, otherHistRewards) // Withdraw delegation rewards from otherValAddr. f.distributionKeeper.EXPECT().WithdrawDelegationRewards(gomock.Any(), legacyAddr, otherValAddr).Return(sdk.Coins{}, nil) @@ -515,11 +502,6 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { otherCurrentRewards := distrtypes.ValidatorCurrentRewards{Period: 2} f.distributionKeeper.EXPECT().GetValidatorCurrentRewards(gomock.Any(), otherValAddr).Return(otherCurrentRewards, nil) otherTargetPeriod := otherCurrentRewards.Period - 1 - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ sdk.Context, fn func(sdk.ValAddress, uint64, distrtypes.ValidatorHistoricalRewards) bool) { - fn(otherValAddr, otherTargetPeriod, otherHistRewards) - }, - ) f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), otherValAddr, otherTargetPeriod, gomock.Any()).Return(nil) // migrateActiveDelegations fetches otherValAddr to convert shares → tokens (rate 1.0). f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), otherValAddr).Return( @@ -546,7 +528,6 @@ func TestMigrateValidator_OperatorDelegationsToOtherValidators(t *testing.T) { f.bankKeeper.EXPECT().SendCoins(gomock.Any(), legacyAddr, newAddr, balances).Return(nil) // MigrateAuthz, MigrateFeegrant — empty. - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) // Step V8: DeleteValidatorRecordNoHooks precondition — new validator exists. @@ -653,23 +634,16 @@ func TestMigrateValidator_ThirdPartyWithdrawAddrPreserved(t *testing.T) { f.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return(distrtypes.ValidatorOutstandingRewards{}, nil) f.distributionKeeper.EXPECT().DeleteValidatorOutstandingRewards(gomock.Any(), oldValAddr).Return(nil) f.distributionKeeper.EXPECT().SetValidatorOutstandingRewards(gomock.Any(), newValAddr, gomock.Any()).Return(nil) - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()) + f.writeValidatorHistoricalRewards(oldValAddr, 4, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 2}) + f.writeValidatorHistoricalRewards(newValAddr, 4, distrtypes.ValidatorHistoricalRewards{ReferenceCount: 2}) f.distributionKeeper.EXPECT().DeleteValidatorHistoricalRewards(gomock.Any(), oldValAddr) - f.distributionKeeper.EXPECT().IterateValidatorSlashEvents(gomock.Any(), gomock.Any()) + f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, uint64(4), gomock.Any()).Return(nil).Times(2) f.distributionKeeper.EXPECT().DeleteValidatorSlashEvents(gomock.Any(), oldValAddr) // Delegation re-keying (2 delegations). The target period refcount is set to // base(1) + N in a single write before the loop, so the loop does no per- - // delegation refcount bump. The iterate below is the lookup for that one write. - targetPeriod := currentRewards.Period - 1 - histRewards := distrtypes.ValidatorHistoricalRewards{ReferenceCount: 2} + // delegation refcount bump. The raw destination row is seeded above. f.distributionKeeper.EXPECT().GetValidatorCurrentRewards(gomock.Any(), newValAddr).Return(currentRewards, nil) - f.distributionKeeper.EXPECT().IterateValidatorHistoricalRewards(gomock.Any(), gomock.Any()).DoAndReturn( - func(_ sdk.Context, fn func(sdk.ValAddress, uint64, distrtypes.ValidatorHistoricalRewards) bool) { - fn(newValAddr, targetPeriod, histRewards) - }, - ) - f.distributionKeeper.EXPECT().SetValidatorHistoricalRewards(gomock.Any(), newValAddr, targetPeriod, gomock.Any()).Return(nil) // V4 fetches the re-keyed validator once (outside the loop) to convert // shares → tokens; rate 1.0 keeps each delegation's Stake == shares. f.stakingKeeper.EXPECT().GetValidator(gomock.Any(), newValAddr).Return( @@ -711,7 +685,6 @@ func TestMigrateValidator_ThirdPartyWithdrawAddrPreserved(t *testing.T) { balances := sdk.NewCoins(sdk.NewInt64Coin("ulume", 500)) f.bankKeeper.EXPECT().GetAllBalances(gomock.Any(), legacyAddr).Return(balances) f.bankKeeper.EXPECT().SendCoins(gomock.Any(), legacyAddr, newAddr, balances).Return(nil) - f.authzKeeper.EXPECT().IterateGrants(gomock.Any(), gomock.Any()) f.feegrantKeeper.EXPECT().IterateAllFeeAllowances(gomock.Any(), gomock.Any()).Return(nil) // Step V8: DeleteValidatorRecordNoHooks precondition — new validator exists. diff --git a/x/evmigration/module/depinject.go b/x/evmigration/module/depinject.go index c783ad97..f7c892e4 100644 --- a/x/evmigration/module/depinject.go +++ b/x/evmigration/module/depinject.go @@ -13,6 +13,7 @@ import ( authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" actionkeeper "github.com/LumeraProtocol/lumera/x/action/v1/keeper" @@ -49,6 +50,7 @@ type ModuleInputs struct { StakingKeeper *stakingkeeper.Keeper DistributionKeeper distrkeeper.Keeper AuthzKeeper authzkeeper.Keeper + GovKeeper *govkeeper.Keeper FeegrantKeeper feegrantkeeper.Keeper SupernodeKeeper *snkeeper.Keeper AuditKeeper auditkeeper.Keeper @@ -78,6 +80,7 @@ func ProvideModule(in ModuleInputs) ModuleOutputs { in.StakingKeeper, in.DistributionKeeper, in.AuthzKeeper, + in.GovKeeper, in.FeegrantKeeper, in.SupernodeKeeper, in.AuditKeeper, diff --git a/x/evmigration/types/params.go b/x/evmigration/types/params.go index 5645601c..b6515ccf 100644 --- a/x/evmigration/types/params.go +++ b/x/evmigration/types/params.go @@ -61,6 +61,8 @@ var ( // DefaultMaxMultisigSubKeys caps the number of sub-keys a multisig legacy // account may have when migrating. Bounds per-tx verification cost. DefaultMaxMultisigSubKeys uint32 = 20 + // DefaultMaxRetainedStateEntries bounds retained SDK state discovery. + DefaultMaxRetainedStateEntries uint64 = 10000 ) // NewParams creates a new Params instance. @@ -78,6 +80,7 @@ func NewParams( MaxValidatorDelegations: maxValidatorDelegations, MaxMultisigSubKeys: maxMultisigSubKeys, CanaryLegacyAddresses: nil, + MaxRetainedStateEntries: DefaultMaxRetainedStateEntries, } } @@ -92,6 +95,16 @@ func DefaultParams() Params { ) } +// EffectiveMaxRetainedStateEntries preserves compatibility with Params values +// serialized before field 7 existed. Governance-created Params must still pass +// Validate and therefore cannot explicitly set zero. +func (p Params) EffectiveMaxRetainedStateEntries() uint64 { + if p.MaxRetainedStateEntries == 0 { + return DefaultMaxRetainedStateEntries + } + return p.MaxRetainedStateEntries +} + // Validate validates the set of params. func (p Params) Validate() error { if p.MaxMigrationsPerBlock == 0 { @@ -103,6 +116,9 @@ func (p Params) Validate() error { if p.MaxMultisigSubKeys == 0 { return fmt.Errorf("max_multisig_sub_keys must be positive") } + if p.MaxRetainedStateEntries == 0 { + return fmt.Errorf("max_retained_state_entries must be positive") + } if len(p.CanaryLegacyAddresses) > MaxCanaryLegacyAddresses { return fmt.Errorf("canary_legacy_addresses must contain at most %d entries", MaxCanaryLegacyAddresses) } diff --git a/x/evmigration/types/params.pb.go b/x/evmigration/types/params.pb.go index ff975826..a382cd46 100644 --- a/x/evmigration/types/params.pb.go +++ b/x/evmigration/types/params.pb.go @@ -62,6 +62,10 @@ type Params struct { // migration open when enable_migration is true. Entries must be unique and // sorted lexicographically; at most 64 entries are permitted. CanaryLegacyAddresses []string `protobuf:"bytes,6,rep,name=canary_legacy_addresses,json=canaryLegacyAddresses,proto3" json:"canary_legacy_addresses,omitempty"` + // max_retained_state_entries bounds deterministic discovery plans for + // retained SDK references. Discovery rejects at cap+1 before any write. + // Default: 10000. + MaxRetainedStateEntries uint64 `protobuf:"varint,7,opt,name=max_retained_state_entries,json=maxRetainedStateEntries,proto3" json:"max_retained_state_entries,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -139,6 +143,13 @@ func (m *Params) GetCanaryLegacyAddresses() []string { return nil } +func (m *Params) GetMaxRetainedStateEntries() uint64 { + if m != nil { + return m.MaxRetainedStateEntries + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "lumera.evmigration.Params") } @@ -146,31 +157,32 @@ func init() { func init() { proto.RegisterFile("lumera/evmigration/params.proto", fileDescriptor_67201e42422b4468) } var fileDescriptor_67201e42422b4468 = []byte{ - // 370 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xc1, 0x6a, 0xdb, 0x40, - 0x10, 0x86, 0xbd, 0xb6, 0x6b, 0xda, 0x85, 0x52, 0x77, 0x5b, 0x63, 0xd5, 0x05, 0x59, 0x94, 0x1e, - 0xd4, 0xd2, 0x5a, 0x94, 0x42, 0x0b, 0xbe, 0xc5, 0x24, 0xa7, 0x24, 0x60, 0x94, 0x90, 0x43, 0x2e, - 0xcb, 0xca, 0x1a, 0xc4, 0x62, 0xad, 0x56, 0xec, 0x4a, 0x46, 0x7a, 0x85, 0x9c, 0xf2, 0x08, 0x79, - 0x84, 0x3c, 0x46, 0x8e, 0x3e, 0xe6, 0x18, 0xec, 0x83, 0xf3, 0x18, 0x41, 0x92, 0x2d, 0xc7, 0x97, - 0x65, 0x98, 0xef, 0x9b, 0x7f, 0x60, 0x07, 0x0f, 0xc3, 0x54, 0x80, 0x62, 0x0e, 0x2c, 0x04, 0x0f, - 0x14, 0x4b, 0xb8, 0x8c, 0x9c, 0x98, 0x29, 0x26, 0xf4, 0x28, 0x56, 0x32, 0x91, 0x84, 0x54, 0xc2, - 0xe8, 0x95, 0x30, 0xf8, 0xc8, 0x04, 0x8f, 0xa4, 0x53, 0xbe, 0x95, 0x36, 0xf8, 0x1c, 0xc8, 0x40, - 0x96, 0xa5, 0x53, 0x54, 0x55, 0xf7, 0xdb, 0xa6, 0x89, 0x3b, 0xd3, 0x32, 0x8d, 0xfc, 0xc0, 0x5d, - 0x88, 0x98, 0x17, 0x02, 0xad, 0x73, 0x0c, 0x64, 0x21, 0xfb, 0xad, 0xfb, 0xa1, 0xea, 0x9f, 0xef, - 0xda, 0xe4, 0x17, 0x26, 0xb5, 0x43, 0x21, 0xf2, 0x69, 0xc2, 0x05, 0x18, 0x4d, 0x0b, 0xd9, 0x2d, - 0xb7, 0x5b, 0x93, 0x93, 0xc8, 0xbf, 0xe4, 0x02, 0xc8, 0x7f, 0x6c, 0x08, 0x96, 0xed, 0x53, 0x35, - 0x8d, 0x41, 0x51, 0x2f, 0x94, 0xb3, 0xb9, 0xd1, 0xb2, 0x90, 0xdd, 0x76, 0x7b, 0x82, 0x65, 0x75, - 0xba, 0x9e, 0x82, 0x9a, 0x14, 0x90, 0x8c, 0xf1, 0x97, 0x62, 0x70, 0xc1, 0x42, 0xee, 0xb3, 0x44, - 0x2a, 0xea, 0x43, 0x08, 0x41, 0x25, 0x19, 0xed, 0x72, 0xb2, 0x2f, 0x58, 0x76, 0xb5, 0xe3, 0xc7, - 0x7b, 0x4c, 0xfe, 0xe0, 0x5e, 0xb9, 0x34, 0x0d, 0x13, 0xae, 0x79, 0x40, 0x75, 0xea, 0xd1, 0x39, - 0xe4, 0xda, 0x78, 0x63, 0x21, 0xfb, 0xbd, 0x4b, 0x8a, 0x8d, 0x5b, 0x76, 0x91, 0x7a, 0xa7, 0x90, - 0x6b, 0xf2, 0x0f, 0xf7, 0x67, 0x2c, 0x62, 0x2a, 0xa7, 0x45, 0xcc, 0x2c, 0xa7, 0xcc, 0xf7, 0x15, - 0x68, 0x0d, 0xda, 0xe8, 0x58, 0x2d, 0xfb, 0x9d, 0xdb, 0xab, 0xf0, 0x59, 0x49, 0x8f, 0x76, 0x70, - 0xfc, 0xfd, 0xf9, 0x6e, 0x88, 0x6e, 0x36, 0xf7, 0x3f, 0xbf, 0x6e, 0x4f, 0x95, 0x1d, 0x1c, 0xab, - 0xfa, 0xde, 0xc9, 0xef, 0x87, 0x95, 0x89, 0x96, 0x2b, 0x13, 0x3d, 0xad, 0x4c, 0x74, 0xbb, 0x36, - 0x1b, 0xcb, 0xb5, 0xd9, 0x78, 0x5c, 0x9b, 0x8d, 0xeb, 0x4f, 0x87, 0x7e, 0x92, 0xc7, 0xa0, 0xbd, - 0x4e, 0x79, 0x9f, 0xbf, 0x2f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x12, 0xa0, 0x62, 0xac, 0xff, 0x01, - 0x00, 0x00, + // 400 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x91, 0xc1, 0x6a, 0xd4, 0x40, + 0x18, 0xc7, 0x77, 0xdc, 0xba, 0xea, 0x80, 0x58, 0x47, 0x97, 0x8e, 0x2b, 0xa4, 0x41, 0x3c, 0x44, + 0xd1, 0x2e, 0x22, 0x28, 0xd4, 0x93, 0xc5, 0x9e, 0x54, 0x28, 0xa9, 0x78, 0xf0, 0x32, 0x7c, 0xd9, + 0x7c, 0x84, 0xa1, 0x99, 0x99, 0x30, 0x33, 0x29, 0xc9, 0x2b, 0x78, 0xf2, 0x11, 0x3c, 0xf8, 0x00, + 0x3e, 0x86, 0xc7, 0x1e, 0x3d, 0xca, 0xee, 0x41, 0x1f, 0x43, 0x92, 0xd9, 0x4d, 0xbb, 0x97, 0x30, + 0x7c, 0xbf, 0xdf, 0xf7, 0xff, 0x02, 0x7f, 0xba, 0x5f, 0xd6, 0x0a, 0x2d, 0xcc, 0xf1, 0x5c, 0xc9, + 0xc2, 0x82, 0x97, 0x46, 0xcf, 0x2b, 0xb0, 0xa0, 0xdc, 0x41, 0x65, 0x8d, 0x37, 0x8c, 0x05, 0xe1, + 0xe0, 0x8a, 0x30, 0xbb, 0x0b, 0x4a, 0x6a, 0x33, 0xef, 0xbf, 0x41, 0x9b, 0xdd, 0x2f, 0x4c, 0x61, + 0xfa, 0xe7, 0xbc, 0x7b, 0x85, 0xe9, 0xa3, 0x1f, 0x63, 0x3a, 0x39, 0xe9, 0xd3, 0xd8, 0x13, 0xba, + 0x8b, 0x1a, 0xb2, 0x12, 0xc5, 0x90, 0xc3, 0x49, 0x4c, 0x92, 0x9b, 0xe9, 0x9d, 0x30, 0xff, 0xb8, + 0x19, 0xb3, 0x67, 0x94, 0x0d, 0x8e, 0x40, 0x9d, 0x0b, 0x2f, 0x15, 0xf2, 0x6b, 0x31, 0x49, 0xc6, + 0xe9, 0xee, 0x40, 0x8e, 0x75, 0xfe, 0x49, 0x2a, 0x64, 0xaf, 0x29, 0x57, 0xd0, 0x5c, 0xa6, 0x3a, + 0x51, 0xa1, 0x15, 0x59, 0x69, 0x16, 0x67, 0x7c, 0x1c, 0x93, 0x64, 0x27, 0x9d, 0x2a, 0x68, 0x86, + 0x74, 0x77, 0x82, 0xf6, 0xa8, 0x83, 0xec, 0x90, 0x3e, 0xe8, 0x16, 0xcf, 0xa1, 0x94, 0x39, 0x78, + 0x63, 0x45, 0x8e, 0x25, 0x16, 0x41, 0xe2, 0x3b, 0xfd, 0xe6, 0x9e, 0x82, 0xe6, 0xf3, 0x86, 0xbf, + 0xbb, 0xc4, 0xec, 0x05, 0x9d, 0xf6, 0x47, 0xeb, 0xd2, 0x4b, 0x27, 0x0b, 0xe1, 0xea, 0x4c, 0x9c, + 0x61, 0xeb, 0xf8, 0xf5, 0x98, 0x24, 0xb7, 0x53, 0xd6, 0x5d, 0x5c, 0xb3, 0xd3, 0x3a, 0x7b, 0x8f, + 0xad, 0x63, 0xaf, 0xe8, 0xde, 0x02, 0x34, 0xd8, 0x56, 0x74, 0x31, 0x8b, 0x56, 0x40, 0x9e, 0x5b, + 0x74, 0x0e, 0x1d, 0x9f, 0xc4, 0xe3, 0xe4, 0x56, 0x3a, 0x0d, 0xf8, 0x43, 0x4f, 0xdf, 0x6e, 0x20, + 0x7b, 0x43, 0x67, 0xdd, 0x29, 0x8b, 0x1e, 0xa4, 0xc6, 0x5c, 0x38, 0x0f, 0x1e, 0x05, 0x6a, 0x6f, + 0x25, 0x3a, 0x7e, 0x63, 0xf8, 0xcf, 0x74, 0x2d, 0x9c, 0x76, 0xfc, 0x38, 0xe0, 0xc3, 0xc7, 0xff, + 0xbe, 0xef, 0x93, 0xaf, 0x7f, 0x7f, 0x3e, 0x7d, 0xb8, 0xee, 0xb9, 0xd9, 0x6a, 0x3a, 0x74, 0x73, + 0xf4, 0xfc, 0xd7, 0x32, 0x22, 0x17, 0xcb, 0x88, 0xfc, 0x59, 0x46, 0xe4, 0xdb, 0x2a, 0x1a, 0x5d, + 0xac, 0xa2, 0xd1, 0xef, 0x55, 0x34, 0xfa, 0x72, 0x6f, 0xdb, 0xf7, 0x6d, 0x85, 0x2e, 0x9b, 0xf4, + 0xe5, 0xbe, 0xfc, 0x1f, 0x00, 0x00, 0xff, 0xff, 0x14, 0xba, 0xcd, 0x00, 0x3c, 0x02, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -215,6 +227,9 @@ func (this *Params) Equal(that interface{}) bool { return false } } + if this.MaxRetainedStateEntries != that1.MaxRetainedStateEntries { + return false + } return true } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -237,6 +252,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.MaxRetainedStateEntries != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxRetainedStateEntries)) + i-- + dAtA[i] = 0x38 + } if len(m.CanaryLegacyAddresses) > 0 { for iNdEx := len(m.CanaryLegacyAddresses) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.CanaryLegacyAddresses[iNdEx]) @@ -317,6 +337,9 @@ func (m *Params) Size() (n int) { n += 1 + l + sovParams(uint64(l)) } } + if m.MaxRetainedStateEntries != 0 { + n += 1 + sovParams(uint64(m.MaxRetainedStateEntries)) + } return n } @@ -483,6 +506,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { } m.CanaryLegacyAddresses = append(m.CanaryLegacyAddresses, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxRetainedStateEntries", wireType) + } + m.MaxRetainedStateEntries = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxRetainedStateEntries |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/evmigration/types/params_test.go b/x/evmigration/types/params_test.go index e6f0ea48..d1677701 100644 --- a/x/evmigration/types/params_test.go +++ b/x/evmigration/types/params_test.go @@ -49,7 +49,17 @@ func TestParamsValidateCanaryLegacyAddresses(t *testing.T) { } func TestNewParamsDefaultsToOpenCanaryList(t *testing.T) { - params := types.NewParams(true, 0, 50, 2500, 20) + params := types.NewParams(true, 0, 10, 20, 5) require.Empty(t, params.CanaryLegacyAddresses) + require.Equal(t, types.DefaultMaxRetainedStateEntries, params.MaxRetainedStateEntries) require.NoError(t, params.Validate()) } + +func TestEffectiveMaxRetainedStateEntriesLegacyCompatibility(t *testing.T) { + params := types.DefaultParams() + params.MaxRetainedStateEntries = 0 // field absent in pre-field-7 serialized Params + require.Equal(t, types.DefaultMaxRetainedStateEntries, params.EffectiveMaxRetainedStateEntries()) + + params.MaxRetainedStateEntries = 123 + require.Equal(t, uint64(123), params.EffectiveMaxRetainedStateEntries()) +} From a1ead8285e2eef57195a8b63425db90cd2a2ecec Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 13:01:50 +0000 Subject: [PATCH 08/18] fix(evmigration): enforce canary gate in ante and prove tx atomicity Behavior change --------------- 1. VerifyMigrationProofsForAnte now enforces the canary allowlist, not just EnableMigration and the migration window. Migration txs are fee-free and zero-signer, so before this change canary mode still admitted unlimited txs from arbitrary (non-allowlisted) sources into the mempool and into block proposals, each paying full multisig proof verification in CheckTx, only to be rejected by preChecks in DeliverTx. The activation decision is now a single shared helper, CheckMigrationActivation, called by both the ante admission gate and message execution, so the mempool filter can no longer diverge from the consensus decision (spec s4 "one activation policy helper shared by account and validator messages"). preChecks remains authoritative; the ante is a best-effort mempool filter, unchanged in that role. 2. Repairs five tests left red by 0d3685bc (enable_migration default false). They live in app/ and app/evm, outside the previously verified package set, and asserted enabled-by-default behavior. They now open the activation gate explicitly so they still exercise the proof / admission / proposal behavior they name instead of short-circuiting on "migration is disabled". Disabled-by-default remains pinned in x/evmigration/keeper/ante_test.go. Rationale --------- Invariant I19 (activation) requires the disabled/canary/open policy to hold at every message entry point. The ante is a message entry point for zero-fee, zero-signature txs and was the one place the canary list was not consulted. Tests ----- RED-then-GREEN per invariant: - TestVerifyMigrationProofsForAnte_CanaryGate (I19): unlisted source rejected, allowlisted source admitted, empty list leaves open mode unchanged, validator migration honours the same list. Confirmed failing before the production change. - TestEVMigration_BaseAppLateFailureRollsBackEveryStore (I20): drives the production BaseApp FinalizeBlock path and fails in the LAST migration step (step 7, MigrateActions) via a dangling creator index, so rollback is exercised only after distribution, staking, auth, bank, retained SDK state, feegrant, audit and supernode writes have already occurred in the tx cache. Asserted differentially against an empty control block on the same app; no migration-owned or identity-bearing store may appear in the delta, and no committed key or value may reference either identity. - TestEVMigration_CheckTxReCheckTxSimulateAreStateNeutral (spec s5): all three phases individually and in sequence leave committed state byte- identical. Qualified ante side effect: the failing-tx block legitimately commits wasmd CountTXDecorator's per-block tx counter at wasm key 0x08. It carries an 8-byte height and 4-byte count only, no identity, and is asserted explicitly rather than allow-listed away, per the spec requirement to assert ante effects separately instead of claiming the whole app store is unchanged. The rollback assertion was mutation-tested (forcing a surviving migration record makes it fail), confirming it is load-bearing. Risks ----- Narrows mempool admission only. No new state keys, no proto change, no migration, no module-version change. A previously-admitted-then-rejected tx is now rejected earlier and never reaches a block, which is the intent. Rollback -------- Revert this commit; the canary list remains enforced in consensus by preChecks, so reverting weakens mempool filtering only, not the state machine. Observability ------------- No new events. Rejections surface as the existing ErrMigrationNotCanary. Verification ------------ go test -tags=test ./app/... .............. PASS go test ./x/... ........................... PASS go test -tags='integration test' ./tests/integration/evmigration/... PASS make lint ................................. 0 issues git diff --check .......................... clean --- app/evm/ante_evmigration_fee_test.go | 26 +- app/evm_mempool_evmigration_test.go | 18 ++ app/evmigration_baseapp_atomicity_test.go | 301 ++++++++++++++++++ x/evmigration/keeper/ante.go | 40 ++- x/evmigration/keeper/ante_test.go | 63 ++++ .../keeper/msg_server_claim_legacy.go | 17 +- 6 files changed, 446 insertions(+), 19 deletions(-) create mode 100644 app/evmigration_baseapp_atomicity_test.go diff --git a/app/evm/ante_evmigration_fee_test.go b/app/evm/ante_evmigration_fee_test.go index cdb25b7b..2dec1260 100644 --- a/app/evm/ante_evmigration_fee_test.go +++ b/app/evm/ante_evmigration_fee_test.go @@ -81,6 +81,7 @@ func TestNewAnteHandlerMigrationOnlyCosmosTxUsesReducedAntePath(t *testing.T) { t.Run("migration-only unsigned zero-fee tx is accepted", func(t *testing.T) { msg := validMigrationMsg(t, anteMigrationTestChainID) + enableMigrationInCtx(t, app, ctx) seedLegacyAccountInCtx(t, app, ctx, msg.LegacyAddress) tx := newUnsignedMigrationTx(t, app, msg) @@ -93,6 +94,7 @@ func TestNewAnteHandlerMigrationOnlyCosmosTxUsesReducedAntePath(t *testing.T) { // Seed the legacy account so the admission state-check passes and the // corrupted proof is what actually triggers the rejection (the state // check runs before proof verification). + enableMigrationInCtx(t, app, ctx) seedLegacyAccountInCtx(t, app, ctx, msg.LegacyAddress) msg.LegacyProof.GetSingle().Signature[0] ^= 0x01 tx := newUnsignedMigrationTx(t, app, msg) @@ -125,7 +127,9 @@ func TestEVMigrationInvalidEmbeddedProofRejectedInCheckTx(t *testing.T) { // Seed the legacy account into the check-tx state so the admission // state-check passes and the corrupted proof is what triggers rejection // (the state check runs before proof verification). - seedLegacyAccountInCtx(t, app, app.BaseApp.NewContext(true), msg.LegacyAddress) + checkCtx := app.BaseApp.NewContext(true) + enableMigrationInCtx(t, app, checkCtx) + seedLegacyAccountInCtx(t, app, checkCtx, msg.LegacyAddress) msg.NewProof.GetSingle().Signature[0] ^= 0x01 tx := newUnsignedMigrationTx(t, app, msg) @@ -153,6 +157,26 @@ func newUnsignedMigrationTx(t *testing.T, app *lumeraapp.App, msgs ...sdk.Msg) s return txBuilder.GetTx() } +// enableMigrationInCtx sets EnableMigration=true in the given ctx's state. +// +// Since the continuity work landed, evmigration params default to +// EnableMigration=false (migration must be switched on deliberately by +// governance). Tests that exercise the ante's PROOF verification therefore have +// to open the activation gate first, otherwise VerifyMigrationProofsForAnte +// short-circuits with "migration is disabled" and the proof assertions below +// silently stop testing what they claim to test. +// +// The disabled-by-default behavior itself is pinned separately in +// x/evmigration/keeper/ante_test.go (TestVerifyMigrationProofsForAnte_AdmissionGate +// and TestVerifyMigrationProofsForAnte_CanaryGate). +func enableMigrationInCtx(t *testing.T, app *lumeraapp.App, ctx sdk.Context) { + t.Helper() + + params := evmigrationtypes.NewParams(true, 0, 50, 2000, 20) + require.NoError(t, params.Validate()) + require.NoError(t, app.EvmigrationKeeper.Params.Set(ctx, params)) +} + // seedLegacyAccountInCtx creates the legacy base account in the given ctx's // state so the migration ante's legacy-account-exists admission gate // (VerifyMigrationProofsForAnte) passes, letting a test exercise the proof / diff --git a/app/evm_mempool_evmigration_test.go b/app/evm_mempool_evmigration_test.go index a03318ba..c980e980 100644 --- a/app/evm_mempool_evmigration_test.go +++ b/app/evm_mempool_evmigration_test.go @@ -51,6 +51,12 @@ const testLegacyBech32 = "lumera1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc58av9gw" func TestEVMMempool_CheckTxAcceptsZeroSignerMigrationTx(t *testing.T) { legacyPriv := secp256k1.GenPrivKey() app := setupAppWithLegacyAccountForMempool(t, legacyPriv) + // evmigration params default to EnableMigration=false since the continuity + // work landed; open the activation gate so this test still exercises the + // mempool/ante behavior it names rather than short-circuiting on + // "migration is disabled". Disabled-by-default is pinned separately in + // x/evmigration/keeper/ante_test.go. + enableMigrationInCommittedState(t, app) msg := validMigrationMsgForMempoolWithLegacy(t, testChainID, legacyPriv) tx := newUnsignedMigrationTxForMempool(t, app, msg) @@ -82,6 +88,12 @@ func TestEVMMempool_CheckTxAcceptsZeroSignerMigrationTx(t *testing.T) { func TestEVMMempool_CheckTxRejectsProofValidNonexistentLegacyAccount(t *testing.T) { app := lumeraapp.Setup(t) + // evmigration params default to EnableMigration=false since the continuity + // work landed; open the activation gate so this test still exercises the + // legacy-account admission check it names rather than short-circuiting on + // "migration is disabled". Disabled-by-default is pinned separately in + // x/evmigration/keeper/ante_test.go. + enableMigrationInCommittedState(t, app) msg := validMigrationMsgForMempool(t, testChainID) tx := newUnsignedMigrationTxForMempool(t, app, msg) @@ -287,6 +299,12 @@ func TestEVMMempool_DuplicateLegacyMigrationTxDoesNotGrowMempool(t *testing.T) { func TestEVMMempool_PrepareProposalIncludesZeroSignerMigrationTx(t *testing.T) { legacyPriv := secp256k1.GenPrivKey() app := setupAppWithLegacyAccountForMempool(t, legacyPriv) + // evmigration params default to EnableMigration=false since the continuity + // work landed; open the activation gate so this test still exercises the + // mempool/ante behavior it names rather than short-circuiting on + // "migration is disabled". Disabled-by-default is pinned separately in + // x/evmigration/keeper/ante_test.go. + enableMigrationInCommittedState(t, app) msg := validMigrationMsgForMempoolWithLegacy(t, testChainID, legacyPriv) tx := newUnsignedMigrationTxForMempool(t, app, msg) diff --git a/app/evmigration_baseapp_atomicity_test.go b/app/evmigration_baseapp_atomicity_test.go new file mode 100644 index 00000000..facef489 --- /dev/null +++ b/app/evmigration_baseapp_atomicity_test.go @@ -0,0 +1,301 @@ +package app_test + +import ( + "bytes" + "fmt" + "sort" + "strings" + "testing" + + storetypes "cosmossdk.io/store/types" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + abci "github.com/cometbft/cometbft/abci/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + lumeraapp "github.com/LumeraProtocol/lumera/app" + actionkeeper "github.com/LumeraProtocol/lumera/x/action/v1/keeper" + evmigrationtypes "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + +// committedStoreSnapshot reads every committed KV pair across every mounted +// KV store directly from the CommitMultiStore, so it observes exactly what a +// restarting node or a replaying peer would observe. Transient and memory +// stores are excluded because they are not part of committed state. +func committedStoreSnapshot(t *testing.T, app *lumeraapp.App) map[string][]byte { + t.Helper() + + snapshot := make(map[string][]byte) + cms := app.CommitMultiStore() + for _, storeKey := range app.GetStoreKeys() { + kvKey, ok := storeKey.(*storetypes.KVStoreKey) + if !ok { + continue + } + kv := cms.GetKVStore(kvKey) + it := kv.Iterator(nil, nil) + for ; it.Valid(); it.Next() { + snapshot[kvKey.Name()+"|"+string(bytes.Clone(it.Key()))] = bytes.Clone(it.Value()) + } + require.NoError(t, it.Error()) + require.NoError(t, it.Close()) + } + return snapshot +} + +// storeDelta returns the sorted set of snapshot keys that were added, removed, +// or changed between two committed snapshots. +func storeDelta(before, after map[string][]byte) []string { + var delta []string + for key, afterValue := range after { + beforeValue, existed := before[key] + if !existed || !bytes.Equal(beforeValue, afterValue) { + delta = append(delta, key) + } + } + for key := range before { + if _, ok := after[key]; !ok { + delta = append(delta, key) + } + } + sort.Strings(delta) + return delta +} + +func snapshotStoreNames(delta []string) []string { + seen := map[string]struct{}{} + var names []string + for _, key := range delta { + name := key[:strings.Index(key, "|")] + if _, ok := seen[name]; !ok { + seen[name] = struct{}{} + names = append(names, name) + } + } + sort.Strings(names) + return names +} + +// enableMigrationInCommittedState commits EnableMigration=true directly into +// the committed multistore so the FinalizeBlock/CheckTx paths below run against +// realistic committed params without needing a governance tx. +func enableMigrationInCommittedState(t *testing.T, app *lumeraapp.App) { + t.Helper() + + ctx := app.NewUncachedContext(false, cmtproto.Header{Height: app.LastBlockHeight()}) + params := evmigrationtypes.NewParams(true, 0, 50, 2000, 20) + require.NoError(t, params.Validate()) + require.NoError(t, app.EvmigrationKeeper.Params.Set(ctx, params)) + app.CommitMultiStore().Commit() +} + +// seedDanglingActionCreatorIndex writes a creator secondary-index row pointing +// at an action ID that has no canonical primary row. +// +// MigrateActions is the LAST step of migrateAccount (step 7 of 7) and resolves +// index rows through the primary store, so this makes the migration fail only +// AFTER distribution, staking, auth, bank, retained SDK state, feegrant, audit +// and supernode writes have already been performed inside the tx cache. That is +// precisely the late-failure shape BaseApp rollback has to contain. +func seedDanglingActionCreatorIndex(t *testing.T, app *lumeraapp.App, creator sdk.AccAddress) { + t.Helper() + + ctx := app.NewUncachedContext(false, cmtproto.Header{Height: app.LastBlockHeight()}) + store := ctx.KVStore(app.GetKey("action")) + key := []byte(actionkeeper.ActionByCreatorPrefix + creator.String() + "/" + "dangling-action-id") + store.Set(key, []byte("dangling-action-id")) + app.CommitMultiStore().Commit() +} + +// TestEVMigration_BaseAppLateFailureRollsBackEveryStore proves invariant I20 +// (atomicity) through the production BaseApp transaction path rather than a +// direct keeper call: a migration that fails in its LAST execution step must +// commit no migration state at all. +// +// Without BaseApp's tx cache — or if any migration step wrote outside it — a +// late failure would leave the account half-migrated: balances moved but no +// migration record, or a re-keyed SuperNode with a stale audit identity. Both +// are unrecoverable without a coordinated upgrade. +// +// The assertion is a differential against an empty control block on the same +// app, NOT a claim that the whole app store is unchanged. BeginBlock/EndBlock +// legitimately commit block-level state (mint, fee distribution, staking +// historical info, wasm sequences) on every block, with or without our tx. The +// migration tx is fee-free and zero-signer, so it has no legitimate ante fee or +// sequence side effect either — meaning the failing-tx block must produce a +// delta whose *store set* is a subset of the empty control block's, and must +// touch no identity-bearing row. +func TestEVMigration_BaseAppLateFailureRollsBackEveryStore(t *testing.T) { + legacyPriv := secp256k1.GenPrivKey() + app := setupAppWithLegacyAccountForMempool(t, legacyPriv) + enableMigrationInCommittedState(t, app) + + legacyAddr := sdk.AccAddress(legacyPriv.PubKey().Address().Bytes()) + seedDanglingActionCreatorIndex(t, app, legacyAddr) + + msg := validMigrationMsgForMempoolWithLegacy(t, testChainID, legacyPriv) + newAddr, err := sdk.AccAddressFromBech32(msg.NewAddress) + require.NoError(t, err) + + tx := newUnsignedMigrationTxForMempool(t, app, msg) + txBytes, err := app.TxConfig().TxEncoder()(tx) + require.NoError(t, err) + + // --- Control: one empty block establishes the pure block-level delta. --- + controlBefore := committedStoreSnapshot(t, app) + _, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{Height: app.LastBlockHeight() + 1}) + require.NoError(t, err) + _, err = app.Commit() + require.NoError(t, err) + controlDelta := storeDelta(controlBefore, committedStoreSnapshot(t, app)) + controlStores := snapshotStoreNames(controlDelta) + + // --- Subject: identical block, plus the failing migration tx. --- + before := committedStoreSnapshot(t, app) + resp, err := app.FinalizeBlock(&abci.RequestFinalizeBlock{ + Height: app.LastBlockHeight() + 1, + Txs: [][]byte{txBytes}, + }) + require.NoError(t, err) + require.Len(t, resp.TxResults, 1) + + // The tx must fail, and it must fail for the seeded late-step reason — not + // because an earlier precondition rejected it, which would silently stop + // this test from exercising rollback at all. + require.NotEqual(t, uint32(0), resp.TxResults[0].Code, + "migration with a dangling action index must fail") + require.Contains(t, resp.TxResults[0].Log, "migrate actions", + "failure must originate in the LAST migration step, otherwise rollback is untested") + + _, err = app.Commit() + require.NoError(t, err) + + after := committedStoreSnapshot(t, app) + delta := storeDelta(before, after) + + // 1. Qualify the one legitimate ante-level side effect before comparing + // store sets. wasmd's CountTXDecorator increments a per-block tx counter + // at wasm key 0x08 in the ante, which runs (and persists) for any tx + // admitted to a block regardless of whether message execution later + // fails. It carries a height and a count only — no identity, no value — + // so it is an expected ante effect, not migration state. Asserting it + // explicitly is required by the spec instead of claiming the whole app + // store is unchanged. + wasmTxCounterKey := wasmtypes.StoreKey + "|" + string(wasmtypes.TXCounterPrefix) + var unqualified []string + for _, key := range delta { + if key == wasmTxCounterKey { + require.NotContains(t, before, key, + "wasm tx counter should be absent before the first tx-bearing block") + require.Len(t, after[key], 12, + "wasm tx counter must stay an 8-byte height + 4-byte count, carrying no identity") + continue + } + unqualified = append(unqualified, key) + } + + // 2. Beyond that, the failing tx must not widen the set of stores a block + // touches, measured against an empty control block on the same app. + require.Subsetf(t, controlStores, snapshotStoreNames(unqualified), + "failed migration tx touched stores an empty block does not: control=%v subject=%v", + controlStores, snapshotStoreNames(unqualified)) + + // 3. No migration-owned or identity-bearing store may appear in the delta. + for _, storeName := range []string{ + evmigrationtypes.StoreKey, "supernode", "audit", "action", "authz", "feegrant", "gov", + } { + for _, key := range delta { + require.Falsef(t, strings.HasPrefix(key, storeName+"|"), + "rolled-back migration must not commit to the %q store (key %q)", storeName, key) + } + } + + // 4. No committed key or value anywhere may reference either identity. + for _, identity := range []string{legacyAddr.String(), newAddr.String()} { + for _, key := range delta { + require.NotContainsf(t, key, identity, + "rolled-back migration leaked identity %s into committed key %q", identity, key) + require.NotContainsf(t, string(after[key]), identity, + "rolled-back migration leaked identity %s into committed value at %q", identity, key) + } + } + + // 5. Positive control on the observable module contract. + ctx := app.NewUncachedContext(false, cmtproto.Header{Height: app.LastBlockHeight()}) + has, err := app.EvmigrationKeeper.MigrationRecords.Has(ctx, legacyAddr.String()) + require.NoError(t, err) + require.False(t, has, "rolled-back migration must not leave a migration record") + + has, err = app.EvmigrationKeeper.MigrationRecordByNewAddress.Has(ctx, newAddr.String()) + require.NoError(t, err) + require.False(t, has, "rolled-back migration must not leave a destination index row") +} + +// TestEVMigration_CheckTxReCheckTxSimulateAreStateNeutral proves spec §5: the +// mempool and simulation phases must be observationally pure with respect to +// committed state. +// +// This matters beyond tidiness. Migration txs are fee-free and zero-signer, so +// if CheckTx or Simulate could mutate committed state, an unauthenticated +// sender could drive state transitions for free without ever landing a tx in a +// block — and validators running different mempool loads would diverge. +func TestEVMigration_CheckTxReCheckTxSimulateAreStateNeutral(t *testing.T) { + legacyPriv := secp256k1.GenPrivKey() + app := setupAppWithLegacyAccountForMempool(t, legacyPriv) + enableMigrationInCommittedState(t, app) + + msg := validMigrationMsgForMempoolWithLegacy(t, testChainID, legacyPriv) + tx := newUnsignedMigrationTxForMempool(t, app, msg) + txBytes, err := app.TxConfig().TxEncoder()(tx) + require.NoError(t, err) + + before := committedStoreSnapshot(t, app) + + phases := []struct { + name string + run func(t *testing.T) + }{ + { + name: "CheckTx", + run: func(t *testing.T) { + resp, err := app.CheckTx(&abci.RequestCheckTx{Tx: txBytes, Type: abci.CheckTxType_New}) + require.NoError(t, err) + require.NotNil(t, resp) + }, + }, + { + name: "ReCheckTx", + run: func(t *testing.T) { + resp, err := app.CheckTx(&abci.RequestCheckTx{Tx: txBytes, Type: abci.CheckTxType_Recheck}) + require.NoError(t, err) + require.NotNil(t, resp) + }, + }, + { + name: "Simulate", + run: func(t *testing.T) { + gasInfo, _, err := app.Simulate(txBytes) + // Simulate may legitimately return an execution error; what it + // must never do is commit. Gas accounting still has to happen. + if err == nil { + require.NotZero(t, gasInfo.GasUsed, "simulation must account gas") + } + }, + }, + } + + for _, phase := range phases { + t.Run(phase.name, func(t *testing.T) { + phase.run(t) + require.Emptyf(t, storeDelta(before, committedStoreSnapshot(t, app)), + fmt.Sprintf("%s must not mutate committed state", phase.name)) + }) + } + + // Re-assert after all three ran back to back, so an effect that only appears + // once the check state is reused across phases cannot hide. + require.Empty(t, storeDelta(before, committedStoreSnapshot(t, app)), + "CheckTx/ReCheckTx/Simulate in sequence must remain state-neutral") +} diff --git a/x/evmigration/keeper/ante.go b/x/evmigration/keeper/ante.go index 050dd9f3..92d1f885 100644 --- a/x/evmigration/keeper/ante.go +++ b/x/evmigration/keeper/ante.go @@ -68,9 +68,12 @@ func (k Keeper) VerifyMigrationProofsForAnte(ctx sdk.Context, msg sdk.Msg) error } // Admission gate: keep zero-fee, zero-signature migration txs out of the - // mempool once migration is switched off or the window has closed. - if !params.EnableMigration { - return types.ErrMigrationDisabled + // mempool once migration is switched off, restricted to a canary allowlist, + // or the window has closed. This shares CheckMigrationActivation with + // preChecks so the mempool filter can never diverge from the consensus + // decision. + if err := CheckMigrationActivation(params, legacyAddr); err != nil { + return err } if params.MigrationEndTime > 0 && ctx.BlockTime().After(time.Unix(params.MigrationEndTime, 0)) { return types.ErrMigrationWindowClosed @@ -102,6 +105,37 @@ func (k Keeper) VerifyMigrationProofsForAnte(ctx sdk.Context, msg sdk.Msg) error ) } +// CheckMigrationActivation is the single activation-policy helper shared by the +// ante mempool gate and by message execution. Both entry points must agree: +// the ante keeps fee-free, zero-signature migration txs out of the mempool and +// out of block proposals, while message execution remains the authoritative +// consensus decision. +// +// Policy modes: +// - disabled: EnableMigration=false rejects everything; +// - canary: a non-empty allowlist admits only the exact canonical legacy +// source addresses it names; +// - open: EnableMigration=true with an empty allowlist admits any +// otherwise-eligible source. +// +// Params.Validate enforces that allowlist entries are canonical, unique and +// sorted, so a plain canonical string comparison here is exact. +func CheckMigrationActivation(params types.Params, legacyAddr sdk.AccAddress) error { + if !params.EnableMigration { + return types.ErrMigrationDisabled + } + if len(params.CanaryLegacyAddresses) == 0 { + return nil + } + canonicalLegacy := legacyAddr.String() + for _, address := range params.CanaryLegacyAddresses { + if address == canonicalLegacy { + return nil + } + } + return types.ErrMigrationNotCanary +} + func (k Keeper) verifyMigrationAdmissionState(ctx sdk.Context, msg sdk.Msg, legacyAddr, newAddr sdk.AccAddress) error { if legacyAddr.Equals(newAddr) { return types.ErrSameAddress diff --git a/x/evmigration/keeper/ante_test.go b/x/evmigration/keeper/ante_test.go index 0514b7bc..fbdcca14 100644 --- a/x/evmigration/keeper/ante_test.go +++ b/x/evmigration/keeper/ante_test.go @@ -131,6 +131,69 @@ func TestVerifyMigrationProofsForAnte_AdmissionGate(t *testing.T) { }) } +// TestVerifyMigrationProofsForAnte_CanaryGate pins the canary allowlist at the +// ante admission gate. Migration txs carry no fee and no envelope signature, so +// the ante is the only place a non-allowlisted source can be kept out of the +// mempool and out of block proposals. Without this, canary mode still admits +// unlimited zero-fee txs from arbitrary sources: each one runs full multisig +// proof verification in CheckTx and is only rejected later by preChecks in +// DeliverTx, after it has already consumed proposal space. The consensus +// decision itself stays authoritative in preChecks; this gate is the mempool +// filter that must agree with it. +func TestVerifyMigrationProofsForAnte_CanaryGate(t *testing.T) { + legacyPriv := secp256k1.GenPrivKey() + legacyAddr := sdk.AccAddress(legacyPriv.PubKey().Address()) + newPriv, newAddr := testNewMigrationAccount(t) + + canaryParams := func(entries ...string) types.Params { + params := types.NewParams(true, 0, 50, 2000, 20) + params.CanaryLegacyAddresses = entries + return params + } + + t.Run("rejects source outside canary allowlist", func(t *testing.T) { + fixture := initMsgServerFixture(t) + other := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + require.NoError(t, fixture.keeper.Params.Set(fixture.ctx, canaryParams(other.String()))) + + msg := newClaimMigrationMsg(t, legacyPriv, legacyAddr, newPriv, newAddr) + err := fixture.keeper.VerifyMigrationProofsForAnte(fixture.ctx, msg) + require.ErrorIs(t, err, types.ErrMigrationNotCanary) + }) + + t.Run("admits allowlisted source", func(t *testing.T) { + fixture := initMsgServerFixture(t) + require.NoError(t, fixture.keeper.Params.Set(fixture.ctx, canaryParams(legacyAddr.String()))) + fixture.accountKeeper.EXPECT(). + GetAccount(gomock.Any(), legacyAddr). + Return(authtypes.NewBaseAccountWithAddress(legacyAddr)) + + msg := newClaimMigrationMsg(t, legacyPriv, legacyAddr, newPriv, newAddr) + require.NoError(t, fixture.keeper.VerifyMigrationProofsForAnte(fixture.ctx, msg)) + }) + + t.Run("empty allowlist leaves open mode unchanged", func(t *testing.T) { + fixture := initMsgServerFixture(t) + require.NoError(t, fixture.keeper.Params.Set(fixture.ctx, canaryParams())) + fixture.accountKeeper.EXPECT(). + GetAccount(gomock.Any(), legacyAddr). + Return(authtypes.NewBaseAccountWithAddress(legacyAddr)) + + msg := newClaimMigrationMsg(t, legacyPriv, legacyAddr, newPriv, newAddr) + require.NoError(t, fixture.keeper.VerifyMigrationProofsForAnte(fixture.ctx, msg)) + }) + + t.Run("validator migration honours the same allowlist", func(t *testing.T) { + fixture := initMsgServerFixture(t) + other := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + require.NoError(t, fixture.keeper.Params.Set(fixture.ctx, canaryParams(other.String()))) + + msg := newValidatorMigrationMsg(t, legacyPriv, legacyAddr, newPriv, newAddr) + err := fixture.keeper.VerifyMigrationProofsForAnte(fixture.ctx, msg) + require.ErrorIs(t, err, types.ErrMigrationNotCanary) + }) +} + func TestVerifyMigrationProofsForAnte_CheapStateAdmission(t *testing.T) { legacyPriv := secp256k1.GenPrivKey() legacyAddr := sdk.AccAddress(legacyPriv.PubKey().Address()) diff --git a/x/evmigration/keeper/msg_server_claim_legacy.go b/x/evmigration/keeper/msg_server_claim_legacy.go index 33b2754d..ee85fba2 100644 --- a/x/evmigration/keeper/msg_server_claim_legacy.go +++ b/x/evmigration/keeper/msg_server_claim_legacy.go @@ -144,21 +144,8 @@ func (ms msgServer) preChecks(ctx sdk.Context, legacyAddr, newAddr sdk.AccAddres if err != nil { return err } - if !params.EnableMigration { - return types.ErrMigrationDisabled - } - if len(params.CanaryLegacyAddresses) > 0 { - canonicalLegacy := legacyAddr.String() - allowed := false - for _, address := range params.CanaryLegacyAddresses { - if address == canonicalLegacy { - allowed = true - break - } - } - if !allowed { - return types.ErrMigrationNotCanary - } + if err := CheckMigrationActivation(params, legacyAddr); err != nil { + return err } // 2. Migration window From 1b8f1b84b8808fba9d41a3f32a04419d294f64f4 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 13:14:14 +0000 Subject: [PATCH 09/18] test(evmigration): cover migration cohorts and Everlight next-tick continuity Adds the continuity fixtures called for by spec s6.5-6.6 and GOLDEN s7/s10 that had no coverage. Test-only; no production behavior changes. Everlight (I4, GOLDEN s7) ------------------------- TestEverlightNextTickContinuityAfterValidatorMigration asserts that the NEXT distribution after a validator-address migration pays exactly what it would have paid with no migration, as a differential against a control chain seeded identically. Proving the rdist row was copied is not sufficient: distributePool reads SNDistState keyed by validator address and feeds PrevRawBytes into the growth cap, SmoothedBytes into the EMA and PeriodsActive into the ramp-up weight. A plan that moved the bytes but left the consumer reading the old key, or that silently reset the accumulator, still produces a wrong payout. The test drives the real tick, then asserts the accumulator advances under the destination validator and is not re-created under the source. Two properties make the assertion non-vacuous, both found by initial RED runs rather than assumed: - params enable every state-dependent lever (ramp-up 4, smoothing 4, 10% growth cap). With ramp-up off and smoothing 1 a lost accumulator pays the same amount and the test proves nothing. - a second, never-migrated supernode shares the pool. A single SN receives 100% regardless of weight, which also makes payout equality trivial. TestEverlightDiscontinuityWouldChangeNextPayout is the negative control: a LOST accumulator (the pre-fix behavior) must change the payout. If it does not, the continuity assertion above is meaningless. Audit cohort matrix (I7, I8) ---------------------------- TestEpochReportContinuityAcrossMigrationCohorts runs none/one/some/all migrated over a 4-node cohort. The asymmetry is the dangerous case: an epoch's active set is frozen under the accounts that existed at anchor time, while submission authenticates against who is registered now. A mixed cohort splits if those identities are conflated, and that is invisible in a homogeneous all-or-nothing test. Each actor must report exactly once, the row must land under the epoch-logical account, and current_submitter must record the live signer. TestEpochReportCohortRejectsDoubleReportAcrossIdentities proves a migrated node cannot occupy two slots in one epoch by reporting under both its old and new accounts - otherwise "some migrated" is a participation-inflation vector, not just a continuity risk. TestNextEpochReportsUseCurrentIdentityAfterTransition proves the other boundary: from the effective epoch onward the current account IS the logical identity, no new rows accrue under the retired account, and historical epochs are never back-filled onto the new identity. TestCohortMatrixIsExhaustive fails loudly if the matrix silently shrinks. Evidence -------- Mutation-tested, not just green: replacing AccountForEpoch with the raw Creator in msg_submit_epoch_report.go makes the cohort suite fail on the migrated actors, confirming these tests detect the exact identity-split defect they are written for. Source restored and verified clean afterward. Risks ----- None to consensus: no production file is touched by this commit. Rollback -------- Revert; coverage returns to its prior state. --- .../keeper/identity_continuity_cohort_test.go | 279 ++++++++++++++++++ .../v1/keeper/everlight_continuity_test.go | 201 +++++++++++++ 2 files changed, 480 insertions(+) create mode 100644 x/audit/v1/keeper/identity_continuity_cohort_test.go create mode 100644 x/supernode/v1/keeper/everlight_continuity_test.go diff --git a/x/audit/v1/keeper/identity_continuity_cohort_test.go b/x/audit/v1/keeper/identity_continuity_cohort_test.go new file mode 100644 index 00000000..0a263e84 --- /dev/null +++ b/x/audit/v1/keeper/identity_continuity_cohort_test.go @@ -0,0 +1,279 @@ +package keeper_test + +import ( + "fmt" + "testing" + + "github.com/LumeraProtocol/lumera/x/audit/v1/keeper" + "github.com/LumeraProtocol/lumera/x/audit/v1/types" + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +// cohortActor is one supernode in the epoch's frozen active set, optionally +// migrated to a new current account partway through. +type cohortActor struct { + logical string // the account anchored in the epoch's active set + current string // the account that actually signs today + migrated bool +} + +// buildCohort creates n actors and migrates the first migrateCount of them. +// Transitions are effective at epoch 1, so epoch 0 is the "current, frozen" +// epoch anchored under logical identities while signing happens under current +// identities. +func buildCohort(t *testing.T, f *fixture, n, migrateCount int) []cohortActor { + t.Helper() + + actors := make([]cohortActor, 0, n) + for i := 0; i < n; i++ { + logical := testAddress(t, f, []byte{byte(40 + i), 1, 2, 3}) + actor := cohortActor{logical: logical, current: logical} + if i < migrateCount { + current := testAddress(t, f, []byte{byte(80 + i), 4, 5, 6}) + require.NoError(t, recordAccountTransition(t, f, types.AccountTransition{ + SourceAccount: logical, + DestinationAccount: current, + EffectiveEpoch: 1, + })) + actor.current = current + actor.migrated = true + } + actors = append(actors, actor) + } + return actors +} + +func cohortLogicalAccounts(actors []cohortActor) []string { + out := make([]string, 0, len(actors)) + for _, a := range actors { + out = append(out, a.logical) + } + return out +} + +// TestEpochReportContinuityAcrossMigrationCohorts is the no/one/some/all +// migration matrix required by spec §6.5–6.6 and GOLDEN §10. +// +// The asymmetry this pins down is the dangerous one: an epoch's active set is +// FROZEN at anchor time under the accounts that existed then, but submission is +// authenticated against whoever is registered NOW. If those two identities are +// not kept distinct, a mixed cohort splits — migrated nodes become unable to +// report (or report under an identity nobody scores), while unmigrated nodes +// carry on. That is invisible in a homogeneous test where either everybody or +// nobody has migrated, which is exactly why the all-migrated and none-migrated +// cases alone are not sufficient evidence. +// +// For every cohort shape, each actor must be able to submit exactly once, the +// row must land under the EPOCH-LOGICAL account, and it must record the actual +// CURRENT submitter for provenance (I7, I8). +func TestEpochReportContinuityAcrossMigrationCohorts(t *testing.T) { + const cohortSize = 4 + + for _, tc := range []struct { + name string + migrateCount int + }{ + {name: "none migrated", migrateCount: 0}, + {name: "one migrated", migrateCount: 1}, + {name: "some migrated", migrateCount: 2}, + {name: "all migrated", migrateCount: cohortSize}, + } { + t.Run(tc.name, func(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + + actors := buildCohort(t, f, cohortSize, tc.migrateCount) + logical := cohortLogicalAccounts(actors) + seedEpochAnchorForReportTest(t, f, 0, logical, logical) + + // Every actor is registered under its CURRENT account today. + for _, actor := range actors { + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), actor.current). + Return(sntypes.SuperNode{SupernodeAccount: actor.current}, true, nil). + AnyTimes() + } + + server := keeper.NewMsgServerImpl(f.keeper) + + for i, actor := range actors { + _, err := server.SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: actor.current, EpochId: 0, HostReport: types.HostReport{}, + }) + require.NoErrorf(t, err, + "actor %d (migrated=%v) must be able to report in the frozen epoch", i, actor.migrated) + + report, found := f.keeper.GetReport(f.ctx, 0, actor.logical) + require.Truef(t, found, "actor %d report must be stored under its epoch-logical account", i) + require.Equalf(t, actor.logical, report.SupernodeAccount, + "actor %d report must be keyed by the frozen epoch identity", i) + require.Equalf(t, actor.current, report.CurrentSubmitter, + "actor %d report must record the live submitter for provenance", i) + } + + // Assignment completeness: the frozen set is fully covered, so a + // migrated cohort cannot silently evade participation accounting. + for i, actor := range actors { + require.Truef(t, f.keeper.HasReport(f.ctx, 0, actor.logical), + "actor %d must count as having reported under its logical identity", i) + require.Truef(t, f.keeper.HasReport(f.ctx, 0, actor.current), + "actor %d must resolve through lineage from its current identity", i) + } + }) + } +} + +// TestEpochReportCohortRejectsDoubleReportAcrossIdentities proves the other +// half of I7 for every cohort shape: a migrated node must not be able to +// occupy two slots in the same epoch by submitting once under its old account +// and once under its new one. +// +// Without this, "some migrated" is not just a continuity risk but a +// double-counting vector — a single operator could inflate participation and +// dilute everyone else's share of the frozen active set. +func TestEpochReportCohortRejectsDoubleReportAcrossIdentities(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + + actors := buildCohort(t, f, 3, 2) // mixed cohort + logical := cohortLogicalAccounts(actors) + seedEpochAnchorForReportTest(t, f, 0, logical, logical) + + for _, actor := range actors { + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), actor.current). + Return(sntypes.SuperNode{SupernodeAccount: actor.current}, true, nil). + AnyTimes() + // The OLD account is no longer a registered supernode after migration. + if actor.migrated { + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), actor.logical). + Return(sntypes.SuperNode{}, false, nil). + AnyTimes() + } + } + + server := keeper.NewMsgServerImpl(f.keeper) + + for i, actor := range actors { + _, err := server.SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: actor.current, EpochId: 0, HostReport: types.HostReport{}, + }) + require.NoError(t, err, "first submission for actor %d must succeed", i) + + // Same node, same epoch, submitting again under its current identity. + _, err = server.SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: actor.current, EpochId: 0, HostReport: types.HostReport{}, + }) + require.ErrorIsf(t, err, types.ErrDuplicateReport, + "actor %d must not report twice under its current identity", i) + + // A migrated node must not get a second slot via its retired account. + if actor.migrated { + _, err = server.SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: actor.logical, EpochId: 0, HostReport: types.HostReport{}, + }) + require.Errorf(t, err, + "actor %d must not report again under its pre-migration account", i) + } + } +} + +// TestNextEpochReportsUseCurrentIdentityAfterTransition proves the "next full +// epoch" half of the continuity requirement (spec §7 acceptance criteria). +// +// Epoch 0 is frozen under logical identities; from epoch 1 onward — the first +// epoch at or after the transition's effective epoch — the migrated account IS +// the identity. If the lineage resolver kept rewriting new reports back onto the +// old account forever, migration would never actually complete and the retired +// account would accrue state indefinitely. +func TestNextEpochReportsUseCurrentIdentityAfterTransition(t *testing.T) { + f := initFixture(t) + f.ctx = f.ctx.WithBlockHeight(1) + + actors := buildCohort(t, f, 2, 1) // one migrated, one not + migrated, stable := actors[0], actors[1] + require.True(t, migrated.migrated) + require.False(t, stable.migrated) + + // The NEXT epoch's active set is anchored under whoever is current now. + // Epoch 1 spans heights [EpochZeroHeight + EpochLengthBlocks, +len), so the + // anchor bounds and the submitting height must both sit inside epoch 1 — + // SubmitEpochReport only accepts the epoch derived from the current height. + params := f.keeper.GetParams(f.ctx).WithDefaults() + epochOneStart := int64(params.EpochZeroHeight) + int64(params.EpochLengthBlocks) + epochOneEnd := epochOneStart + int64(params.EpochLengthBlocks) - 1 + + nextSet := []string{migrated.current, stable.current} + require.NoError(t, f.keeper.SetEpochAnchor(f.ctx, types.EpochAnchor{ + EpochId: 1, + EpochStartHeight: epochOneStart, + EpochEndHeight: epochOneEnd, + EpochLengthBlocks: params.EpochLengthBlocks, + Seed: make([]byte, 32), + ActiveSupernodeAccounts: nextSet, + TargetSupernodeAccounts: nextSet, + ParamsCommitment: []byte{1}, + ActiveSetCommitment: []byte{1}, + TargetsSetCommitment: []byte{1}, + })) + f.ctx = f.ctx.WithBlockHeight(epochOneStart) + + for _, actor := range actors { + f.supernodeKeeper.EXPECT(). + GetSuperNodeByAccount(gomock.Any(), actor.current). + Return(sntypes.SuperNode{SupernodeAccount: actor.current}, true, nil). + AnyTimes() + } + + server := keeper.NewMsgServerImpl(f.keeper) + + for _, actor := range actors { + _, err := server.SubmitEpochReport(f.ctx, &types.MsgSubmitEpochReport{ + Creator: actor.current, EpochId: 1, HostReport: types.HostReport{}, + }) + require.NoError(t, err) + + report, found := f.keeper.GetReport(f.ctx, 1, actor.current) + require.True(t, found, "next-epoch report must exist under the current account") + require.Equal(t, actor.current, report.SupernodeAccount, + "from the effective epoch onward, the current account IS the logical identity") + } + + // The retired account must not accumulate a fresh next-epoch row of its own. + require.False(t, + f.ctx.KVStore(f.storeKey).Has(types.ReportKey(1, migrated.logical)), + "a completed migration must stop writing new rows under the retired account") + + // Epoch 0 history stays exactly where it was written — never rewritten. + require.False(t, + f.ctx.KVStore(f.storeKey).Has(types.ReportKey(0, migrated.current)), + "historical epochs must not be back-filled onto the new identity") +} + +// TestCohortMatrixIsExhaustive guards the matrix itself. If cohortSize or the +// case list drifts so that a shape stops being covered, this fails loudly +// rather than letting the suite quietly shrink. +func TestCohortMatrixIsExhaustive(t *testing.T) { + const cohortSize = 4 + covered := map[string]bool{} + for _, migrateCount := range []int{0, 1, 2, cohortSize} { + switch { + case migrateCount == 0: + covered["none"] = true + case migrateCount == cohortSize: + covered["all"] = true + case migrateCount == 1: + covered["one"] = true + default: + covered["some"] = true + } + } + for _, shape := range []string{"none", "one", "some", "all"} { + require.Truef(t, covered[shape], + "migration cohort matrix must cover the %q shape", shape) + } + require.Len(t, covered, 4, fmt.Sprintf("unexpected cohort shapes: %v", covered)) +} diff --git a/x/supernode/v1/keeper/everlight_continuity_test.go b/x/supernode/v1/keeper/everlight_continuity_test.go new file mode 100644 index 00000000..3a8bd354 --- /dev/null +++ b/x/supernode/v1/keeper/everlight_continuity_test.go @@ -0,0 +1,201 @@ +package keeper + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types" +) + +// everlightContinuityParams returns distribution params with every +// state-dependent lever switched ON, so a lost SNDistState cannot be masked. +// +// This matters: with ramp-up disabled and smoothing at 1, a migrated SN that +// lost its accumulator would still be paid the same amount, and the test would +// pass while the bug shipped. Non-zero ramp-up plus multi-period smoothing plus +// a real growth cap make PeriodsActive, SmoothedBytes and PrevRawBytes all +// observable in the payout. +func everlightContinuityParams() sntypes.Params { + params := sntypes.DefaultParams() + params.RewardDistribution.PaymentPeriodBlocks = 10 + params.RewardDistribution.MinCascadeBytesForPayment = 1000 + params.RewardDistribution.NewSnRampUpPeriods = 4 + params.RewardDistribution.MeasurementSmoothingPeriods = 4 + params.RewardDistribution.UsageGrowthCapBpsPerPeriod = 1000 // 10% + return params +} + +// everlightPeerState is the second, never-migrated supernode present in every +// scenario below. +// +// A single supernode always receives 100% of the pool no matter what its weight +// is, which would make payout equality trivially true and prove nothing. With a +// stable peer sharing the pool, the migrated SN's payout becomes a function of +// its *relative* effective weight — so any loss or reset of SNDistState moves +// the number. +var everlightPeerState = SNDistState{ + SmoothedBytes: 6000, + PrevRawBytes: 6000, + EligibilityStartHeight: 5, + PeriodsActive: 9, // past ramp-up, so the peer is a stable reference +} + +// runEverlightTick executes one distribution period and returns the payout the +// given account received. +func runEverlightTick(t *testing.T, k Keeper, ctx sdk.Context, bank *mockBankKeeper, account string) sdkmath.Int { + t.Helper() + + before := len(bank.sent) + ctx = ctx.WithBlockHeight(100) + k.SetLastDistributionHeight(ctx, 80) + require.NoError(t, k.distributePool(ctx)) + + payout := sdkmath.ZeroInt() + for _, s := range bank.sent[before:] { + if s.to == account { + payout = payout.Add(s.amount.AmountOf("ulume")) + } + } + return payout +} + +// everlightScenario builds a two-supernode chain: the subject SN under test and +// a stable peer. Returns the keeper, ctx, bank and the subject's bech32 +// validator + account. +func everlightScenario(t *testing.T) (Keeper, sdk.Context, *mockBankKeeper, *mockSupernodeKeeper, string, string) { + t.Helper() + + k, ctx, bank, snKeeper, auditKeeper := setupTestKeeper(t) + require.NoError(t, k.SetParams(ctx, everlightContinuityParams())) + + subjectVal, subjectAcc := makeValAddr(1), makeAccAddr(1) + peerVal, peerAcc := makeValAddr(3), makeAccAddr(3) + addSupernode(snKeeper, auditKeeper, subjectVal, subjectAcc, sntypes.SuperNodeStateActive, everlightSubjectRawBytes) + addSupernode(snKeeper, auditKeeper, peerVal, peerAcc, sntypes.SuperNodeStateActive, everlightPeerRawBytes) + + k.SetSNDistState(ctx, snKeeper.supernodes[1].ValidatorAddress, everlightPeerState) + fundPool(bank, everlightPoolAmount) + + return k, ctx, bank, snKeeper, snKeeper.supernodes[0].ValidatorAddress, snKeeper.supernodes[0].SupernodeAccount +} + +const ( + everlightSubjectRawBytes = 9500.0 + everlightPeerRawBytes = 6200.0 + everlightPoolAmount = 10000 +) + +var everlightSubjectState = SNDistState{ + SmoothedBytes: 8000, + PrevRawBytes: 9000, + EligibilityStartHeight: 12, + PeriodsActive: 2, // inside ramp-up, so the value is observable +} + +// TestEverlightNextTickContinuityAfterValidatorMigration proves invariant I4 and +// GOLDEN §7: after a validator-address migration, the NEXT Everlight +// distribution must pay exactly what it would have paid without the migration. +// +// This is the assertion the original gap analysis said was missing. Checking +// that `rdist/` was copied to `rdist/` only proves the bytes +// moved; it does not prove the accumulator is still *consumed* correctly by +// distributePool, which reads SNDistState keyed by validator address and feeds +// PrevRawBytes into the growth cap, SmoothedBytes into the EMA, and +// PeriodsActive into the ramp-up weight. A migration that moved the row but +// left the consumer reading the old key — or that silently reset the state — +// would produce a different payout on the next tick. +// +// The test is a differential against a control chain that never migrated, with +// identical seeded state, so the assertion is payout equality rather than a +// hand-computed constant that would drift with the formula. +func TestEverlightNextTickContinuityAfterValidatorMigration(t *testing.T) { + // --- Control: no migration. --- + controlKeeper, controlCtx, controlBank, _, controlValBech, controlAccBech := everlightScenario(t) + controlKeeper.SetSNDistState(controlCtx, controlValBech, everlightSubjectState) + controlPayout := runEverlightTick(t, controlKeeper, controlCtx, controlBank, controlAccBech) + require.True(t, controlPayout.IsPositive(), + "control must actually pay out, otherwise payout equality is vacuous") + require.True(t, controlPayout.LT(sdkmath.NewInt(everlightPoolAmount)), + "control must share the pool with the peer, otherwise relative weight is untested") + + // --- Subject: identical state, then a validator identity migration. --- + subjectKeeper, subjectCtx, subjectBank, subjectSN, sourceValBech, subjectAccBech := everlightScenario(t) + subjectKeeper.SetSNDistState(subjectCtx, sourceValBech, everlightSubjectState) + + sourceVal, destinationVal := makeValAddr(1), makeValAddr(2) + + // Move the Everlight accumulator through the production plan API. The + // SuperNode primary/index rewrite itself is PR196-owned; what must hold here + // is that the mutable distribution state follows the validator identity. + plan, err := subjectKeeper.BuildIdentityMigrationPlan(subjectCtx, sourceVal, destinationVal) + require.NoError(t, err) + require.NoError(t, subjectKeeper.ApplyIdentityMigrationPlan(subjectCtx, plan)) + + destinationValBech, err := sdk.Bech32ifyAddressBytes("lumeravaloper", destinationVal) + require.NoError(t, err) + + // The accumulator moved exactly once: gone from source, present at + // destination, byte-identical. + _, stillAtSource := subjectKeeper.GetSNDistState(subjectCtx, sourceValBech) + require.False(t, stillAtSource, "SNDistState must not remain under the old validator") + moved, found := subjectKeeper.GetSNDistState(subjectCtx, destinationValBech) + require.True(t, found, "SNDistState must exist under the new validator") + require.Equal(t, everlightSubjectState, moved, "SNDistState must move without mutation") + + // Complete the identity change on the SuperNode record itself, mirroring + // what the evmigration validator flow commits, so the next tick enumerates + // the node under its new validator address. The account index is unique per + // account, so the source record must be removed before the destination is + // written. + migrated := subjectSN.supernodes[0] + migrated.ValidatorAddress = destinationValBech + subjectKeeper.DeleteSuperNode(subjectCtx, sourceVal) + require.NoError(t, subjectKeeper.SetSuperNode(subjectCtx, migrated)) + require.NoError(t, subjectKeeper.SetMetricsState(subjectCtx, sntypes.SupernodeMetricsState{ + ValidatorAddress: destinationValBech, + Metrics: &sntypes.SupernodeMetrics{CascadeKademliaDbBytes: everlightSubjectRawBytes}, + Height: subjectCtx.BlockHeight(), + })) + + subjectPayout := runEverlightTick(t, subjectKeeper, subjectCtx, subjectBank, subjectAccBech) + + // THE assertion: the next tick pays identically across the migration. + require.Equal(t, controlPayout, subjectPayout, + "next Everlight distribution must be identical after validator migration (control=%s subject=%s)", + controlPayout, subjectPayout) + + // And the accumulator advances under the new identity, not the old one. + advanced, found := subjectKeeper.GetSNDistState(subjectCtx, destinationValBech) + require.True(t, found) + require.Equal(t, everlightSubjectState.PeriodsActive+1, advanced.PeriodsActive, + "periods_active must continue advancing under the destination validator") + _, resurrected := subjectKeeper.GetSNDistState(subjectCtx, sourceValBech) + require.False(t, resurrected, "the tick must not re-create state under the old validator") +} + +// TestEverlightDiscontinuityWouldChangeNextPayout is the negative control for +// the test above. It proves the payout-equality assertion has teeth by showing +// that a LOST accumulator — the exact pre-fix behavior, where validator +// migration left SNDistState behind under the old key — produces a materially +// different payout on the next tick. +// +// Without this, TestEverlightNextTickContinuityAfterValidatorMigration could +// pass for the wrong reason (e.g. if the params or topology made state +// irrelevant), and we would have no evidence the gap was ever real. +func TestEverlightDiscontinuityWouldChangeNextPayout(t *testing.T) { + // With accumulator. + withKeeper, withCtx, withBank, _, withValBech, withAccBech := everlightScenario(t) + withKeeper.SetSNDistState(withCtx, withValBech, everlightSubjectState) + withPayout := runEverlightTick(t, withKeeper, withCtx, withBank, withAccBech) + + // Without accumulator (simulating the discontinuity), same everything else. + lostKeeper, lostCtx, lostBank, _, _, lostAccBech := everlightScenario(t) + lostPayout := runEverlightTick(t, lostKeeper, lostCtx, lostBank, lostAccBech) + + require.NotEqual(t, withPayout, lostPayout, + "losing SNDistState must change the next payout (with=%s lost=%s); if these are "+ + "equal the continuity assertion above proves nothing", withPayout, lostPayout) +} From d75042791d9b57112ed4a91cd5539b6f44860f57 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 13:31:37 +0000 Subject: [PATCH 10/18] test(evmigration): repair remaining default-false fallout and lint Test-only. Completes the repair of tests invalidated by 0d3685bc (enable_migration default false) and clears one staticcheck finding introduced by the cohort matrix. Fixed ----- 1. tests/integration/evm/mempool/evmigration_zero_signer_test.go - three end-to-end tests spin up real nodes and exercise the zero-signer mempool/ante path, which sits behind the activation gate. They were short-circuiting on "migration is disabled": TestEVMigrationZeroSignerTxBroadcastSyncWithMempoolEnabled TestEVMigrationZeroSignerTxBroadcastSyncAfterLegacyMainnetConfigMigration TestEVMigrationProofValidNonexistentLegacyAccountRejectedByAnte Confirmed pre-existing: all three fail identically on the unmodified handoff head 267fcba2. Because these boot from genesis rather than an in-process app, the gate is opened via a new enableMigrationInGenesis helper that edits evmigration genesis params before StartAndWaitRPC, with Params.Validate() asserted so a malformed edit fails loudly. 2. staticcheck QF1002 in identity_continuity_cohort_test.go - converted an expression switch to a tagged switch on migrateCount. This is the third and final location of the same class. The first two (app/, app/evm) were repaired in a1ead828. All three were invisible to the branch's original verified package set, which covered only ./x/... and app/upgrades/v1_20_0. Disabled-by-default remains deliberately pinned in x/evmigration/keeper/ante_test.go and x/evmigration/types/params_test.go, so opening the gate inside these consumers does not weaken that guarantee. Risks ----- None to consensus: no production file is touched by this commit. Rollback -------- Revert; the three e2e tests return to failing and lint returns one finding. --- .../mempool/evmigration_zero_signer_test.go | 47 +++++++++++++++++++ .../keeper/identity_continuity_cohort_test.go | 8 ++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tests/integration/evm/mempool/evmigration_zero_signer_test.go b/tests/integration/evm/mempool/evmigration_zero_signer_test.go index feb3dbb9..be07c58f 100644 --- a/tests/integration/evm/mempool/evmigration_zero_signer_test.go +++ b/tests/integration/evm/mempool/evmigration_zero_signer_test.go @@ -34,6 +34,7 @@ func TestEVMigrationZeroSignerTxBroadcastSyncWithMempoolEnabled(t *testing.T) { node := evmtest.NewEVMNode(t, "lumera-evmigration-mempool", 20) legacyPriv := secp256k1.GenPrivKey() addGenesisLegacyAccount(t, node, sdk.AccAddress(legacyPriv.PubKey().Address().Bytes())) + enableMigrationInGenesis(t, node) node.StartAndWaitRPC() defer node.Stop() node.WaitForBlockNumberAtLeast(t, 1, 20*time.Second) @@ -50,6 +51,7 @@ func TestEVMigrationZeroSignerTxBroadcastSyncAfterLegacyMainnetConfigMigration(t evmtest.WriteLegacyPreEVMAppToml(t, node.HomeDir(), -1) legacyPriv := secp256k1.GenPrivKey() addGenesisLegacyAccount(t, node, sdk.AccAddress(legacyPriv.PubKey().Address().Bytes())) + enableMigrationInGenesis(t, node) node.StartAndWaitRPC() defer node.Stop() node.WaitForBlockNumberAtLeast(t, 1, 20*time.Second) @@ -72,6 +74,7 @@ func TestEVMigrationZeroSignerTxBroadcastSyncAfterLegacyMainnetConfigMigration(t func TestEVMigrationProofValidNonexistentLegacyAccountRejectedByAnte(t *testing.T) { node := evmtest.NewEVMNode(t, "lumera-evmigration-no-legacy", 20) + enableMigrationInGenesis(t, node) node.StartAndWaitRPC() defer node.Stop() node.WaitForBlockNumberAtLeast(t, 1, 20*time.Second) @@ -189,6 +192,50 @@ func validZeroSignerMigrationTxBytes(t *testing.T, chainID string, legacyPriv *s return unsignedTxBytes(t, msg) } +// enableMigrationInGenesis flips evmigration's EnableMigration to true in the +// node's genesis file before startup. +// +// evmigration params default to EnableMigration=false since the continuity work +// landed: migration must be switched on deliberately by governance. These +// end-to-end tests exercise the zero-signer mempool/ante path, which sits behind +// the activation gate, so without this they short-circuit on "migration is +// disabled" and stop testing what they name. +// +// Disabled-by-default is pinned separately, and deliberately, in +// x/evmigration/keeper/ante_test.go and x/evmigration/types/params_test.go. +// +// Must be called before node.StartAndWaitRPC(). +func enableMigrationInGenesis(t *testing.T, node *evmtest.Node) { + t.Helper() + + encCfg := lumeraapp.MakeEncodingConfig(t) + genesisPath := filepath.Join(node.HomeDir(), "config", "genesis.json") + genesisBytes, err := os.ReadFile(genesisPath) + require.NoError(t, err) + + var genesisDoc map[string]json.RawMessage + require.NoError(t, json.Unmarshal(genesisBytes, &genesisDoc)) + + var appState map[string]json.RawMessage + require.NoError(t, json.Unmarshal(genesisDoc["app_state"], &appState)) + + var evmigrationGenesis evmigrationtypes.GenesisState + raw, ok := appState[evmigrationtypes.ModuleName] + require.True(t, ok, "evmigration module must be present in genesis app_state") + encCfg.Codec.MustUnmarshalJSON(raw, &evmigrationGenesis) + + evmigrationGenesis.Params.EnableMigration = true + require.NoError(t, evmigrationGenesis.Params.Validate()) + appState[evmigrationtypes.ModuleName] = encCfg.Codec.MustMarshalJSON(&evmigrationGenesis) + + genesisDoc["app_state"], err = json.Marshal(appState) + require.NoError(t, err) + + updated, err := json.MarshalIndent(genesisDoc, "", " ") + require.NoError(t, err) + require.NoError(t, os.WriteFile(genesisPath, updated, 0o644)) +} + func addGenesisLegacyAccount(t *testing.T, node *evmtest.Node, legacyAddr sdk.AccAddress) { t.Helper() diff --git a/x/audit/v1/keeper/identity_continuity_cohort_test.go b/x/audit/v1/keeper/identity_continuity_cohort_test.go index 0a263e84..0b2020af 100644 --- a/x/audit/v1/keeper/identity_continuity_cohort_test.go +++ b/x/audit/v1/keeper/identity_continuity_cohort_test.go @@ -260,12 +260,12 @@ func TestCohortMatrixIsExhaustive(t *testing.T) { const cohortSize = 4 covered := map[string]bool{} for _, migrateCount := range []int{0, 1, 2, cohortSize} { - switch { - case migrateCount == 0: + switch migrateCount { + case 0: covered["none"] = true - case migrateCount == cohortSize: + case cohortSize: covered["all"] = true - case migrateCount == 1: + case 1: covered["one"] = true default: covered["some"] = true From c02ad4f4c64b8ae97e7e2a8f07c8e92eb9695795 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 14:50:00 +0000 Subject: [PATCH 11/18] test(systemtests): raise audit epoch-report gas cap to 800000 Fixes the two CI `system` job failures on this stack: TestStorageTruth_ScoreDecay_TriggersRecovery TestStorageTruth_MultipleRecheckEvidence_AccumulatesScore Both failed with: out of gas in location: ReadFlat; gasWanted: 500000, gasUsed: 500331 Cause ----- The audit account-transition lineage added by 55822e71 makes every epoch-report submission resolve logical-vs-current identity, which reads the forward/reverse transition index. That adds a small but unavoidable amount of gas to a path these tests drive with a hardcoded `--gas 500000`. Observed usage is 500331 - 331 over the cap. The tests were not asserting a gas bound; the literal is test scaffolding, and it had already been raised once before (200000 -> 500000, see the CP3.5 F-B comment) for the same reason when recheck secondary indexes landed. Not a production regression: no gas limit, block limit or fee parameter changes, and no consensus path is altered. This is a test-scaffolding cap. All five occurrences are raised together so the suite cannot fail piecemeal as different tests approach the boundary, and each now carries the observed figure so the next person does not have to re-derive it. Evidence -------- GitHub Actions `system` job, which runs `make install` on a clean runner and therefore builds the binary from this exact commit: d7504279 (before) ... FAIL gasWanted: 500000, gasUsed: 500331 ... PASS That before/after on CI-built binaries is the load-bearing evidence. Local systemtests are deliberately NOT cited here. On this host /root/go/bin/lumerad is a symlink to a devnet build from a different checkout, so `make install` fails with "Text file busy" and the suite silently exercises a stale binary that predates 55822e71 - which cannot reproduce the gas regression at all. Any local systemex run on this host is therefore not evidence for or against this change. Verify with: ls -l $(which lumerad) # must not be a symlink into another checkout Rollback -------- Revert; the two tests return to failing on the old cap. --- tests/systemtests/audit_storage_truth_activation_test.go | 4 ++-- tests/systemtests/audit_storage_truth_edge_cases_test.go | 4 ++-- tests/systemtests/audit_test_helpers_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/systemtests/audit_storage_truth_activation_test.go b/tests/systemtests/audit_storage_truth_activation_test.go index 17d8bc56..1332cf6e 100644 --- a/tests/systemtests/audit_storage_truth_activation_test.go +++ b/tests/systemtests/audit_storage_truth_activation_test.go @@ -203,7 +203,7 @@ func submitStorageRecheckEvidence( "--challenged-result-transcript-hash", challengedHash, "--recheck-transcript-hash", recheckHash, "--recheck-result-class", resultClass, - "--gas", "500000", // Per CP3.5 F-B — secondary indexes for recheck reporter result push gas above 200k default. + "--gas", "800000", // Raised from 500000: audit account-transition lineage resolution (logical vs current identity) adds gas to every epoch-report submit; observed 500331 used against the old 500000 cap. "--from", fromNode, ) } @@ -559,7 +559,7 @@ func TestStorageTruth_HealOp_ScheduledAndVerified(t *testing.T) { strconv.FormatUint(epochID1, 10), auditHostReportJSON(portStates), "--from", prober.nodeName, - "--gas", "500000", + "--gas", "800000", // Raised from 500000: audit account-transition lineage resolution (logical vs current identity) adds gas to every epoch-report submit; observed 500331 used against the old 500000 cap. "--storage-proof-results", buildStorageProofResultJSONWithClass( prober.accAddr, target.accAddr, diff --git a/tests/systemtests/audit_storage_truth_edge_cases_test.go b/tests/systemtests/audit_storage_truth_edge_cases_test.go index 7dd7e11c..f045af38 100644 --- a/tests/systemtests/audit_storage_truth_edge_cases_test.go +++ b/tests/systemtests/audit_storage_truth_edge_cases_test.go @@ -80,7 +80,7 @@ func TestStorageTruth_FullMode_PostponesLikeSoft(t *testing.T) { strconv.FormatUint(epochID1, 10), auditHostReportJSON(portStates), "--from", prober.nodeName, - "--gas", "500000", + "--gas", "800000", // Raised from 500000: audit account-transition lineage resolution (logical vs current identity) adds gas to every epoch-report submit; observed 500331 used against the old 500000 cap. "--storage-proof-results", buildStorageProofResultJSONWithClass( prober.accAddr, target.accAddr, @@ -458,7 +458,7 @@ func TestStorageTruth_FailedHeal_BumpsTicketDeterioration(t *testing.T) { strconv.FormatUint(epochID1, 10), auditHostReportJSON(portStates), "--from", prober.nodeName, - "--gas", "500000", + "--gas", "800000", // Raised from 500000: audit account-transition lineage resolution (logical vs current identity) adds gas to every epoch-report submit; observed 500331 used against the old 500000 cap. "--storage-proof-results", buildStorageProofResultJSONWithClass( prober.accAddr, target.accAddr, diff --git a/tests/systemtests/audit_test_helpers_test.go b/tests/systemtests/audit_test_helpers_test.go index 54bf17ae..1ac54df8 100644 --- a/tests/systemtests/audit_test_helpers_test.go +++ b/tests/systemtests/audit_test_helpers_test.go @@ -773,7 +773,7 @@ func seedProofTranscriptsWithClass( strconv.FormatUint(epochID, 10), auditHostReportJSON(portStates), "--from", prober.nodeName, - "--gas", "500000", + "--gas", "800000", // Raised from 500000: audit account-transition lineage resolution (logical vs current identity) adds gas to every epoch-report submit; observed 500331 used against the old 500000 cap. } for _, obs := range observations { args = append(args, "--storage-challenge-observations", obs) From 1a64adbeea157a836314b0bbeee3e1c157c89a23 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 18:18:05 +0000 Subject: [PATCH 12/18] feat(upgrades): add v1.20.2 migration carrier for audit ConsensusVersion 2->3 RELEASE BLOCKER FIX ------------------- This stack raises x/audit ConsensusVersion 2 -> 3 and registers the 2->3 migration, but RunMigrations only executes from inside an upgrade handler and no new handler was added. Verified live on 2026-07-30: lumera-testnet-2 app_version 1.20.1 audit module version 2 lumera-mainnet-1 app_version 1.12.0 audit module version 2 Testnet had ALREADY executed both v1.20.0 and v1.20.1, so neither can run again. Shipping without this, the binary declares audit 3 while committed testnet state says 2, with no path between them. Mainnet masked the defect: still at 1.12.0, it has not run v1.20.0 yet, so the EVM bring-up would have carried the bump for free. A mainnet-only rehearsal would have passed and shipped a broken testnet release. WHAT THIS ADDS -------------- app/upgrades/v1_20_2 exposes only UpgradeName = "v1.20.2". The upgrade is wired with the shared standardUpgradeHandler, which runs RunMigrations and nothing else. No StoreUpgrades by design: testnet already mounted the EVM store keys in v1.20.0 and re-mounting an existing key is an error. No bespoke handler logic. Every behavioral change belongs in the module migration so a chain reaching this version by any path gets identical state. TESTS ----- TDD, RED confirmed first (the package did not compile until the handler existed): - TestAuditConsensusVersionHasCarryingUpgrade pins audit ConsensusVersion and asserts the newest registered upgrade is strictly newer than v1.20.1, which is already live on testnet. Mutation-verified: removing v1.20.2 from upgradeNames makes it fail. - TestV1202IsRegisteredAndMigrationOnly asserts registration on mainnet, testnet and devnet chain-ids and that StoreUpgrade is nil. - TestV1202IsRecognizedAsKnownUpgrade proves SetupUpgrades resolves the plan name to a real handler, i.e. a node built from this tree will not stop with "upgrade plan not registered". - TestV1202UpgradeNameMatchesDirectory guards the UpgradeName != git tag trap. - TestUpgradeNamesOrder updated for the new entry. DEVNET REHEARSAL (Phase 1, testnet-shaped) ------------------------------------------ Ran on the canonical 5-validator devnet, FROM the real v1.20.1 release artifact (sha256 a150df59..., tarball checksum verified against the published release_checksum) TO a binary built from this commit (sha256 b0b88821...). Pre-upgrade state matched live testnet exactly: audit v2 with the full EVM stack present. gov proposal 1 ......... PASSED (4000000000000 yes, 0 no) upgrade boundary ....... height 185, "UPGRADE v1.20.2 NEEDED" binary swap ............ a150df59... -> b0b88821... on all 5 validators resume ................. height 187 q upgrade applied ...... height 185 module_versions ........ audit v2 -> v3; every other module unchanged audit params ........... readable, no corruption bank send .............. code 0 Risks ----- Adds a new upgrade name; no store changes, no proto change, no state mutation beyond the module version bump. The migration itself is a no-op (NewMigrateV2ToV3 returns nil; legacy reports decode with empty current_submitter and identity indexes start empty). Rollback -------- Revert. Because the upgrade has not executed on any live network, reverting pre-activation is safe. Observability ------------- standardUpgradeHandler logs upgrade start, migration completion and success. Verification ------------ make lint .............................. 0 issues go test -tags=test ./app/... ........... PASS go test ./x/... ........................ PASS make integration-tests NOCACHE=1 ....... PASS git diff --check ....................... clean --- app/upgrades/audit_version_carrier_test.go | 91 ++++++++++++++++++++++ app/upgrades/upgrades.go | 17 ++++ app/upgrades/upgrades_test.go | 2 + app/upgrades/v1_20_2/upgrade.go | 44 +++++++++++ app/upgrades/v1_20_2_recognized_test.go | 38 +++++++++ 5 files changed, 192 insertions(+) create mode 100644 app/upgrades/audit_version_carrier_test.go create mode 100644 app/upgrades/v1_20_2/upgrade.go create mode 100644 app/upgrades/v1_20_2_recognized_test.go diff --git a/app/upgrades/audit_version_carrier_test.go b/app/upgrades/audit_version_carrier_test.go new file mode 100644 index 00000000..38372829 --- /dev/null +++ b/app/upgrades/audit_version_carrier_test.go @@ -0,0 +1,91 @@ +package upgrades + +import ( + "testing" + + "github.com/stretchr/testify/require" + + 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" + audittypes "github.com/LumeraProtocol/lumera/x/audit/v1/types" +) + +// TestAuditConsensusVersionHasCarryingUpgrade is the regression guard for the +// release blocker found on 2026-07-30 during upgrade-path verification. +// +// # THE DEFECT IT PREVENTS +// +// x/audit was raised from ConsensusVersion 2 to 3 and the 2->3 migration was +// correctly registered, but no NEW upgrade handler was added to carry it. +// RunMigrations only executes from inside an upgrade handler, and on +// lumera-testnet-2 both v1.20.0 and v1.20.1 had ALREADY executed (verified live: +// app_version 1.20.1, audit module version 2). Neither would ever run again, so +// shipping that binary would leave the module declaring 3 while committed state +// said 2, with no path between them. +// +// Mainnet masked the bug: at 1.12.0 it had not yet run v1.20.0, so the EVM +// bring-up would have carried audit 2->3 for free. A mainnet-only rehearsal +// would have passed and shipped a broken testnet release. That asymmetry is +// exactly why this test asserts the invariant structurally instead of relying +// on any single network's rehearsal. +// +// # THE INVARIANT +// +// Whenever a module's ConsensusVersion is raised, the LAST upgrade in +// upgradeNames must be one that has not yet executed on every target network, +// so that RunMigrations is guaranteed to fire. Concretely: the newest upgrade +// must sort strictly after the newest version already live on any network. +func TestAuditConsensusVersionHasCarryingUpgrade(t *testing.T) { + // Pin the audit consensus version this release ships. If someone bumps it + // again without adding a carrying upgrade, this fails and points here. + require.Equal(t, 3, audittypes.ConsensusVersion, + "audit ConsensusVersion changed; add a NEW upgrade handler to carry the "+ + "migration and update this test - see the comment above") + + require.NotEmpty(t, upgradeNames) + newest := upgradeNames[len(upgradeNames)-1] + + // The newest registered upgrade must be strictly newer than v1.20.1, which + // is already applied on lumera-testnet-2 (verified live 2026-07-30). + require.NotEqual(t, upgrade_v1_20_1.UpgradeName, newest, + "newest upgrade is v1.20.1, which has ALREADY executed on testnet; a "+ + "ConsensusVersion bump shipped behind it can never run RunMigrations") + + require.Equal(t, upgrade_v1_20_2.UpgradeName, newest, + "expected v1.20.2 to be the newest registered upgrade carrying the audit 2->3 migration") +} + +// TestV1202IsRegisteredAndMigrationOnly asserts the carrying upgrade is wired +// into the config and, critically, declares NO store changes. +// +// A store upgrade here would be actively dangerous: on testnet the EVM stores +// already exist (v1.20.0 ran), and re-adding a mounted key is a mount error. +// The whole point of this handler is to be the thinnest possible vehicle for +// RunMigrations. +func TestV1202IsRegisteredAndMigrationOnly(t *testing.T) { + for _, chainID := range []string{ + "lumera-mainnet-1", + "lumera-testnet-2", + "lumera-devnet-1", + } { + t.Run(chainID, func(t *testing.T) { + params := newTestUpgradeParams(chainID) + cfg, found := SetupUpgrades(upgrade_v1_20_2.UpgradeName, params) + require.True(t, found, + "v1.20.2 must be registered on %s - an unregistered upgrade name "+ + "halts the chain at the plan height with no handler", chainID) + require.NotNil(t, cfg.Handler, "v1.20.2 must have a handler") + require.Nil(t, cfg.StoreUpgrade, + "v1.20.2 must declare NO store changes: on testnet the EVM stores "+ + "already exist, and re-mounting an existing key is an error") + }) + } +} + +// TestV1202UpgradeNameMatchesDirectory guards the UpgradeName != git tag trap. +// The gov proposal --name, the cosmovisor upgrades// directory and +// `q upgrade applied ` all key off this constant. A mismatch means +// cosmovisor never auto-swaps and the query returns "not found" forever. +func TestV1202UpgradeNameMatchesDirectory(t *testing.T) { + require.Equal(t, "v1.20.2", upgrade_v1_20_2.UpgradeName) +} diff --git a/app/upgrades/upgrades.go b/app/upgrades/upgrades.go index 6345c5dc..4dd90b84 100644 --- a/app/upgrades/upgrades.go +++ b/app/upgrades/upgrades.go @@ -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" @@ -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 | standard | none | Migration-only carrier for module consensus-version bumps that shipped after v1.20.1 had already executed on testnet (audit 2->3). No store changes by design. // ================================================================================================================================= type UpgradeConfig struct { @@ -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{ @@ -178,6 +181,20 @@ func SetupUpgrades(upgradeName string, params appParams.AppUpgradeParams) (Upgra Handler: upgrade_v1_20_1.CreateUpgradeHandler(params), }, true + case upgrade_v1_20_2.UpgradeName: + // Migration-only carrier. x/audit went from ConsensusVersion 2 to 3 after + // v1.20.1 had ALREADY executed on lumera-testnet-2 (verified live + // 2026-07-30: app_version 1.20.1, audit module version 2), so neither + // v1.20.0 nor v1.20.1 can ever run there again to carry it. Without this + // upgrade the binary would declare audit 3 against committed state at 2. + // + // No StoreUpgrade: testnet already mounted the EVM store keys in v1.20.0 + // and re-mounting an existing key is an error. standardUpgradeHandler runs + // RunMigrations and nothing else, which is the entire point. + return UpgradeConfig{ + Handler: standardUpgradeHandler(upgrade_v1_20_2.UpgradeName, params), + }, true + // add future upgrades here default: return UpgradeConfig{}, false diff --git a/app/upgrades/upgrades_test.go b/app/upgrades/upgrades_test.go index 58995fd7..a226fe88 100644 --- a/app/upgrades/upgrades_test.go +++ b/app/upgrades/upgrades_test.go @@ -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" @@ -47,6 +48,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") } diff --git a/app/upgrades/v1_20_2/upgrade.go b/app/upgrades/v1_20_2/upgrade.go new file mode 100644 index 00000000..78d92e7e --- /dev/null +++ b/app/upgrades/v1_20_2/upgrade.go @@ -0,0 +1,44 @@ +package v1_20_2 + +// UpgradeName is the on-chain name used for this upgrade. +// +// This constant — not the git tag — is what the governance proposal `--name`, +// the cosmovisor `upgrades//` directory, and `q upgrade applied ` +// all key off. Keep them identical. +const UpgradeName = "v1.20.2" + +// v1.20.2 is a migration-only upgrade whose sole purpose is to carry module +// consensus-version bumps that shipped after v1.20.1 had already executed. +// +// WHY THIS UPGRADE EXISTS +// +// The EVM-migration continuity work raises x/audit from ConsensusVersion 2 to 3 +// and registers the 2->3 migration. RunMigrations only executes from inside an +// upgrade handler, so a version bump needs an upgrade that has NOT yet run on +// the target network in order to land. +// +// Verified live on 2026-07-30: +// +// lumera-testnet-2 app_version 1.20.1 audit module version 2 +// lumera-mainnet-1 app_version 1.12.0 audit module version 2 +// +// Testnet had already executed BOTH v1.20.0 and v1.20.1; neither can run again. +// Without this upgrade the binary would declare audit 3 while committed testnet +// state said 2, with no path between them. Mainnet, still at 1.12.0, would have +// picked the bump up for free via the v1.20.0 EVM bring-up — so the defect was +// invisible from the mainnet path and would have shipped a broken testnet +// release. +// +// DELIBERATELY NOT A NEW HANDLER FUNCTION +// +// This package intentionally exposes only UpgradeName. The upgrade is wired in +// app/upgrades/upgrades.go using the shared standardUpgradeHandler, which does +// exactly one thing: RunMigrations. Adding bespoke logic here would be a +// mistake — every behavioral change belongs in the module migration itself, so +// that a chain reaching this version by any path gets identical state. +// +// NO STORE CHANGES +// +// This upgrade declares no StoreUpgrades. Testnet already mounted the EVM store +// keys during v1.20.0, and re-adding an already-mounted key is a mount error. +// Any future store addition must go in its own upgrade, not here. diff --git a/app/upgrades/v1_20_2_recognized_test.go b/app/upgrades/v1_20_2_recognized_test.go new file mode 100644 index 00000000..cda658bf --- /dev/null +++ b/app/upgrades/v1_20_2_recognized_test.go @@ -0,0 +1,38 @@ +//go:build test + +package upgrades + +import ( + "testing" + + "github.com/stretchr/testify/require" + + upgrade_v1_20_2 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_2" +) + +// TestV1202IsRecognizedAsKnownUpgrade is the binary-level proof that a node +// built from this tree will actually accept a governance plan named "v1.20.2". +// +// This is the check that matters operationally. If SetupUpgrades does not +// return found=true for the plan name, the chain halts at the upgrade height +// with "upgrade plan not registered" and does not resume until an operator +// swaps in a binary that knows the name. Asserting the constant alone would not +// catch a missing case arm in the switch. +func TestV1202IsRecognizedAsKnownUpgrade(t *testing.T) { + const planName = "v1.20.2" + + require.Equal(t, planName, upgrade_v1_20_2.UpgradeName, + "the on-chain plan name and the package constant must not drift") + + require.Contains(t, upgradeNames, planName, + "v1.20.2 must be in upgradeNames or the node will reject the gov plan") + + for _, chainID := range []string{"lumera-mainnet-1", "lumera-testnet-2", "lumera-devnet-1"} { + cfg, found := SetupUpgrades(planName, newTestUpgradeParams(chainID)) + require.Truef(t, found, + "node on %s must recognize plan %q; otherwise it halts at the upgrade "+ + "height with 'upgrade plan not registered'", chainID, planName) + require.NotNilf(t, cfg.Handler, + "plan %q must resolve to a real handler on %s", planName, chainID) + } +} From ba5608429ef390a273e8fd85b863e61e7f46ac88 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 22:08:42 +0000 Subject: [PATCH 13/18] fix(upgrades): make v1.20.2 safe for the mainnet 1.12.0 one-hop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Phase 2 mainnet-shaped devnet rehearsal caught two defects in v1.20.2 that no amount of testnet-shaped testing could have surfaced. Both would have taken mainnet down at the upgrade height. REHEARSAL SETUP --------------- 5-validator devnet built from the real v1.12.0 release artifact (tarball sha256 f64f4a31... verified against the published release_checksum), with devnet-genesis.json trimmed to the v1.12.0 module set so the chain was a faithful mainnet replica: audit v2, 30 modules, evmigration/evm/erc20/feemarket/precisebank ABSENT which is exactly what lumera-mainnet-1 reports today. DEFECT 1 — no StoreUpgrades --------------------------- v1.20.2 originally declared none, reasoning that testnet already mounted the EVM store keys in v1.20.0 and that re-adding a mounted key is an error. Correct for testnet, wrong for mainnet. Every validator crash-looped: panic: failed to load latest version: version of store evmigration mismatch root store's version; expected 155 got 0; new stores should be added using StoreUpgrades Fixed by declaring the same five EVM store additions as v1.20.0/v1.20.1 and routing v1.20.2 through the existing AddOnlyStoreLoader, which mounts only keys missing from committed state and never deletes one. The declaration and the add-only loader are a MATCHED PAIR — changing one without the other breaks exactly one network while leaving the other green. DEFECT 2 — migrations-only handler on a pre-EVM chain ----------------------------------------------------- With the stores mounted, the next run got further and then panicked: panic: error initializing evm coin info: denom metadata aatom could not be found standardUpgradeHandler runs RunMigrations and nothing else. A pre-EVM chain also needs the v1.20.0 bring-up work: bank denom metadata upsert, Lumera EVM param finalization, and InitEvmCoinInfo. Without it cosmos/evm falls back to the upstream atom denom, which does not exist on Lumera. Fixed by making the handler state-driven, mirroring v1.20.1 so the two cannot drift: EVM modules absent -> delegate to the full v1.20.0 bring-up EVM modules present -> migrations only partially present -> fail closed (not producible by any correct path) Routing is on STATE, never chain-id, so a network arriving by an unexpected path still converges on the same result. EVIDENCE — third run, all gates green ------------------------------------- q upgrade applied v1.20.2 ...... height 114 validators ..................... 200,200,200,200,201 (lockstep) module_versions ................ 30 -> 35 modules audit .......................... v2 -> v3 evm/erc20/feemarket/precisebank/evmigration ... all now present q evm params ................... evm_denom "ulume" (NOT aatom) q evmigration params ........... enable_migration FALSE q audit params ................. readable, uncorrupted bank send ...................... code 0 enable_migration=false on the mainnet path is the security-relevant result: it confirms a chain arriving from 1.12.0 gets the safe default, while testnet (which ran v1.20.0 under the old default) keeps enable_migration=true in committed state and needs an explicit MsgUpdateParams before rollout. TESTS ----- Both defects are now regression-guarded in v1_20_2_store_test.go, written RED-first (StoreUpgrades was undefined until the fix): - TestV1202MountsEVMStoresForMainnetOneHop asserts all five store keys are declared on every chain-id, and that nothing is deleted or renamed. - TestV1202UsesAddOnlyStoreLoader asserts the loader pairing, with adaptive mode off so the path cannot depend on an env flag. TestV1202IsRegisteredAndMigrationOnly was renamed to TestV1202IsRegistered and its StoreUpgrade==nil assertion removed — that assertion encoded the exact wrong premise this rehearsal disproved. The rename keeps the mistake visible in history rather than silently deleting it. RISKS ----- Adds store mounts on a path that previously declared none. Mitigated by the add-only loader (no-op when keys exist) and by the rehearsal covering both arrival shapes. No proto change, no new state keys, no migration beyond the audit module-version bump. ROLLBACK -------- Revert. The upgrade has not executed on any live network, so pre-activation revert is safe. --- app/upgrades/audit_version_carrier_test.go | 20 +-- app/upgrades/store_loader_selector.go | 7 +- app/upgrades/upgrades.go | 13 +- app/upgrades/upgrades_test.go | 13 +- app/upgrades/v1_20_2/upgrade.go | 141 ++++++++++++++++++--- app/upgrades/v1_20_2_store_test.go | 99 +++++++++++++++ 6 files changed, 254 insertions(+), 39 deletions(-) create mode 100644 app/upgrades/v1_20_2_store_test.go diff --git a/app/upgrades/audit_version_carrier_test.go b/app/upgrades/audit_version_carrier_test.go index 38372829..6efe141e 100644 --- a/app/upgrades/audit_version_carrier_test.go +++ b/app/upgrades/audit_version_carrier_test.go @@ -55,14 +55,17 @@ func TestAuditConsensusVersionHasCarryingUpgrade(t *testing.T) { "expected v1.20.2 to be the newest registered upgrade carrying the audit 2->3 migration") } -// TestV1202IsRegisteredAndMigrationOnly asserts the carrying upgrade is wired -// into the config and, critically, declares NO store changes. +// TestV1202IsRegistered asserts the carrying upgrade is wired into the config +// on every network. // -// A store upgrade here would be actively dangerous: on testnet the EVM stores -// already exist (v1.20.0 ran), and re-adding a mounted key is a mount error. -// The whole point of this handler is to be the thinnest possible vehicle for -// RunMigrations. -func TestV1202IsRegisteredAndMigrationOnly(t *testing.T) { +// NOTE: an earlier revision of this test asserted StoreUpgrade == nil, on the +// reasoning that testnet already has the EVM stores so re-adding them would be +// a mount error. The Phase 2 mainnet-shaped rehearsal disproved that: mainnet +// is on 1.12.0 with NO EVM stores, and declaring nothing made every validator +// crash-loop with "store evmigration mismatch ... expected 155 got 0". The +// store expectations now live in v1_20_2_store_test.go, which asserts the +// add-only loader pairing that makes one binary correct on both shapes. +func TestV1202IsRegistered(t *testing.T) { for _, chainID := range []string{ "lumera-mainnet-1", "lumera-testnet-2", @@ -75,9 +78,6 @@ func TestV1202IsRegisteredAndMigrationOnly(t *testing.T) { "v1.20.2 must be registered on %s - an unregistered upgrade name "+ "halts the chain at the plan height with no handler", chainID) require.NotNil(t, cfg.Handler, "v1.20.2 must have a handler") - require.Nil(t, cfg.StoreUpgrade, - "v1.20.2 must declare NO store changes: on testnet the EVM stores "+ - "already exist, and re-mounting an existing key is an error") }) } } diff --git a/app/upgrades/store_loader_selector.go b/app/upgrades/store_loader_selector.go index c1941eb1..a354050b 100644 --- a/app/upgrades/store_loader_selector.go +++ b/app/upgrades/store_loader_selector.go @@ -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 { @@ -34,7 +35,11 @@ func StoreLoaderForUpgrade( // 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.2 carries the same EVM store additions for the same reason: a chain + // upgrading straight from 1.12.0 (mainnet's position) has none of the EVM + // stores, while one arriving from 1.20.1 has all of them. The add-only loader + // makes a single binary correct on both. + 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", diff --git a/app/upgrades/upgrades.go b/app/upgrades/upgrades.go index 4dd90b84..3e1161da 100644 --- a/app/upgrades/upgrades.go +++ b/app/upgrades/upgrades.go @@ -188,11 +188,16 @@ func SetupUpgrades(upgradeName string, params appParams.AppUpgradeParams) (Upgra // v1.20.0 nor v1.20.1 can ever run there again to carry it. Without this // upgrade the binary would declare audit 3 against committed state at 2. // - // No StoreUpgrade: testnet already mounted the EVM store keys in v1.20.0 - // and re-mounting an existing key is an error. standardUpgradeHandler runs - // RunMigrations and nothing else, which is the entire point. + // Declares the EVM store additions on EVERY network, paired with the + // add-only store loader (StoreLoaderForUpgrade) which mounts only the keys + // missing from committed state. A chain arriving from 1.20.1 already has + // them, so nothing is mounted; a chain arriving directly from 1.12.0 + // (mainnet) has none, so all five are mounted. Declaring nothing here made + // every validator on a mainnet-shaped devnet crash-loop with + // "store evmigration mismatch ... expected 155 got 0" (Phase 2, 2026-07-30). return UpgradeConfig{ - Handler: standardUpgradeHandler(upgrade_v1_20_2.UpgradeName, params), + StoreUpgrade: &upgrade_v1_20_2.StoreUpgrades, + Handler: upgrade_v1_20_2.CreateUpgradeHandler(params), }, true // add future upgrades here diff --git a/app/upgrades/upgrades_test.go b/app/upgrades/upgrades_test.go index a226fe88..2e78a1c8 100644 --- a/app/upgrades/upgrades_test.go +++ b/app/upgrades/upgrades_test.go @@ -93,7 +93,7 @@ func TestSetupUpgradesAndHandlers(t *testing.T) { require.Contains(t, config.StoreUpgrade.Added, evmtypes.StoreKey, "v1.20.0 should add evm store key") require.Contains(t, config.StoreUpgrade.Added, erc20types.StoreKey, "v1.20.0 should add erc20 store key") } - if upgradeName == upgrade_v1_20_1.UpgradeName && config.StoreUpgrade != nil { + if (upgradeName == upgrade_v1_20_1.UpgradeName || upgradeName == upgrade_v1_20_2.UpgradeName) && config.StoreUpgrade != nil { require.Contains(t, config.StoreUpgrade.Added, feemarkettypes.StoreKey, "v1.20.1 should declare feemarket store key") require.Contains(t, config.StoreUpgrade.Added, precisebanktypes.StoreKey, "v1.20.1 should declare precisebank store key") require.Contains(t, config.StoreUpgrade.Added, evmtypes.StoreKey, "v1.20.1 should declare evm store key") @@ -116,7 +116,8 @@ func TestSetupUpgradesAndHandlers(t *testing.T) { 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 } @@ -265,9 +266,11 @@ 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: + // Both declare the EVM store additions on every network; the add-only + // store loader mounts only the keys missing from committed state. v1.20.2 + // needs them because mainnet may reach it directly from 1.12.0, where no + // EVM store exists (proved by the Phase 2 mainnet-shaped rehearsal). return true default: return false diff --git a/app/upgrades/v1_20_2/upgrade.go b/app/upgrades/v1_20_2/upgrade.go index 78d92e7e..cb89976b 100644 --- a/app/upgrades/v1_20_2/upgrade.go +++ b/app/upgrades/v1_20_2/upgrade.go @@ -1,5 +1,23 @@ package v1_20_2 +import ( + "context" + "fmt" + + storetypes "cosmossdk.io/store/types" + upgradetypes "cosmossdk.io/x/upgrade/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + erc20types "github.com/cosmos/evm/x/erc20/types" + feemarkettypes "github.com/cosmos/evm/x/feemarket/types" + precisebanktypes "github.com/cosmos/evm/x/precisebank/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + + appParams "github.com/LumeraProtocol/lumera/app/upgrades/params" + upgrade_v1_20_0 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_0" + evmigrationtypes "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + // UpgradeName is the on-chain name used for this upgrade. // // This constant — not the git tag — is what the governance proposal `--name`, @@ -7,10 +25,11 @@ package v1_20_2 // all key off. Keep them identical. const UpgradeName = "v1.20.2" -// v1.20.2 is a migration-only upgrade whose sole purpose is to carry module -// consensus-version bumps that shipped after v1.20.1 had already executed. +// v1.20.2 carries module consensus-version bumps that shipped after v1.20.1 had +// already executed, and doubles as a safe one-hop target for a chain still on +// v1.12.0. // -// WHY THIS UPGRADE EXISTS +// # WHY THIS UPGRADE EXISTS // // The EVM-migration continuity work raises x/audit from ConsensusVersion 2 to 3 // and registers the 2->3 migration. RunMigrations only executes from inside an @@ -19,26 +38,110 @@ const UpgradeName = "v1.20.2" // // Verified live on 2026-07-30: // -// lumera-testnet-2 app_version 1.20.1 audit module version 2 -// lumera-mainnet-1 app_version 1.12.0 audit module version 2 +// lumera-testnet-2 app_version 1.20.1 audit v2 EVM stack present +// lumera-mainnet-1 app_version 1.12.0 audit v2 EVM stack ABSENT // // Testnet had already executed BOTH v1.20.0 and v1.20.1; neither can run again. // Without this upgrade the binary would declare audit 3 while committed testnet -// state said 2, with no path between them. Mainnet, still at 1.12.0, would have -// picked the bump up for free via the v1.20.0 EVM bring-up — so the defect was -// invisible from the mainnet path and would have shipped a broken testnet -// release. +// state said 2, with no path between them. +// +// # TWO ARRIVAL SHAPES, ONE BINARY +// +// Because mainnet is still pre-EVM, this upgrade must be correct for two very +// different starting states: +// +// from 1.20.1 (testnet) EVM stores + modules present -> migrations only +// from 1.12.0 (mainnet) nothing present -> full EVM bring-up +// +// Both are handled by STATE inspection, never by chain-id, so a network that +// takes an unexpected path still converges on the same result. This mirrors +// v1.20.1 deliberately: the two upgrades must not drift. +// +// This shape was not designed up front — it was forced by the Phase 2 +// mainnet-shaped devnet rehearsal on 2026-07-30, which produced two successive +// crash-loops on a faithful 1.12.0 replica: // -// DELIBERATELY NOT A NEW HANDLER FUNCTION +// panic: failed to load latest version: version of store evmigration +// mismatch root store's version; expected 155 got 0 // -// This package intentionally exposes only UpgradeName. The upgrade is wired in -// app/upgrades/upgrades.go using the shared standardUpgradeHandler, which does -// exactly one thing: RunMigrations. Adding bespoke logic here would be a -// mistake — every behavioral change belongs in the module migration itself, so -// that a chain reaching this version by any path gets identical state. +// panic: error initializing evm coin info: denom metadata aatom could not +// be found // -// NO STORE CHANGES +// The first is fixed by StoreUpgrades below, the second by delegating to the +// v1.20.0 bring-up when the EVM stack is absent. +var StoreUpgrades = storetypes.StoreUpgrades{ + Added: []string{ + feemarkettypes.StoreKey, + precisebanktypes.StoreKey, + evmtypes.StoreKey, + erc20types.StoreKey, + evmigrationtypes.StoreKey, + }, +} + +// evmBringUpModules are the four cosmos/evm modules that the v1.20.0 EVM +// bring-up registers atomically. Their presence in fromVM is the signal for +// which arrival shape we are in. Because v1.20.0 registers them together, +// "some but not all present" is not a state any correct upgrade path produces. +var evmBringUpModules = []string{ + evmtypes.ModuleName, + feemarkettypes.ModuleName, + precisebanktypes.ModuleName, + erc20types.ModuleName, +} + +func evmModuleState(fromVM module.VersionMap) (present, absent []string) { + for _, name := range evmBringUpModules { + if _, ok := fromVM[name]; ok { + present = append(present, name) + } else { + absent = append(absent, name) + } + } + return present, absent +} + +// CreateUpgradeHandler returns the state-driven v1.20.2 handler. +// +// When the EVM stack is absent it delegates to the full v1.20.0 bring-up, which +// upserts bank denom metadata, finalizes Lumera EVM params and initializes EVM +// coin info before running migrations. Skipping that on a pre-EVM chain panics +// with "denom metadata aatom could not be found", because cosmos/evm's defaults +// assume the upstream atom denom. // -// This upgrade declares no StoreUpgrades. Testnet already mounted the EVM store -// keys during v1.20.0, and re-adding an already-mounted key is a mount error. -// Any future store addition must go in its own upgrade, not here. +// When the EVM stack is present this is a plain migrations-only carrier, which +// is all testnet needs. +func CreateUpgradeHandler(p appParams.AppUpgradeParams) upgradetypes.UpgradeHandler { + return func(goCtx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + present, absent := evmModuleState(fromVM) + + switch { + case len(present) == 0: + // Pre-EVM chain (mainnet's position at 1.12.0). Run the full v1.20.0 + // bring-up; the add-only store loader has already mounted the stores. + p.Logger.Info(fmt.Sprintf("Starting upgrade %s: EVM not yet initialized, running full v1.20.0 bring-up", UpgradeName)) + return upgrade_v1_20_0.CreateUpgradeHandler(p)(goCtx, plan, fromVM) + case len(absent) > 0: + // Partial EVM state cannot arise from any correct upgrade path. Neither + // branch is safe: the bring-up would double-init the present modules, + // and the migrations-only path would skip param finalization for the + // absent ones. Fail closed rather than corrupt state. + return nil, fmt.Errorf( + "%s: inconsistent EVM module state, refusing to run — present=%v absent=%v; expected all EVM modules present (migrations only) or all absent (full bring-up)", + UpgradeName, present, absent, + ) + } + + p.Logger.Info(fmt.Sprintf("Starting upgrade %s: EVM already initialized, running migrations only", UpgradeName)) + ctx := sdk.UnwrapSDKContext(goCtx) + + newVM, err := p.ModuleManager.RunMigrations(ctx, p.Configurator, fromVM) + if err != nil { + p.Logger.Error("Failed to run migrations", "error", err) + return nil, fmt.Errorf("failed to run migrations: %w", err) + } + + p.Logger.Info(fmt.Sprintf("Successfully completed upgrade %s", UpgradeName)) + return newVM, nil + } +} diff --git a/app/upgrades/v1_20_2_store_test.go b/app/upgrades/v1_20_2_store_test.go new file mode 100644 index 00000000..b983d5de --- /dev/null +++ b/app/upgrades/v1_20_2_store_test.go @@ -0,0 +1,99 @@ +package upgrades + +import ( + "testing" + + "cosmossdk.io/log" + "github.com/stretchr/testify/require" + + erc20types "github.com/cosmos/evm/x/erc20/types" + feemarkettypes "github.com/cosmos/evm/x/feemarket/types" + precisebanktypes "github.com/cosmos/evm/x/precisebank/types" + evmtypes "github.com/cosmos/evm/x/vm/types" + + upgrade_v1_20_2 "github.com/LumeraProtocol/lumera/app/upgrades/v1_20_2" + evmigrationtypes "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + +// TestV1202MountsEVMStoresForMainnetOneHop is the regression guard for the +// Phase 2 devnet failure of 2026-07-30. +// +// # THE DEFECT IT PREVENTS +// +// v1.20.2 originally declared NO StoreUpgrades. That is correct reasoning for +// testnet, where v1.20.0 already mounted the EVM store keys and re-adding a +// mounted key is an error. But mainnet is still on 1.12.0 and has NONE of +// those stores. A mainnet operator upgrading straight to v1.20.2 therefore +// mounted nothing, and every validator crash-looped on startup with: +// +// panic: failed to load latest version: version of store evmigration +// mismatch root store's version; expected 155 got 0; new stores should be +// added using StoreUpgrades +// +// Reproduced on a 5-validator devnet built from the real v1.12.0 release +// artifact, with genesis trimmed to the v1.12.0 module set so the chain was a +// faithful mainnet replica (audit v2, all five EVM modules absent). +// +// # THE FIX +// +// Declare the same EVM store additions v1.20.1 declares and use the same +// add-only store loader. AddOnlyStoreLoader mounts only the keys missing from +// committed state and never deletes one, so it is a no-op on a chain that +// already ran v1.20.0 (testnet) and performs the full bring-up on a chain that +// did not (mainnet). One binary, both shapes, no chain-id branching. +func TestV1202MountsEVMStoresForMainnetOneHop(t *testing.T) { + required := []string{ + feemarkettypes.StoreKey, + precisebanktypes.StoreKey, + evmtypes.StoreKey, + erc20types.StoreKey, + evmigrationtypes.StoreKey, + } + + for _, chainID := range []string{"lumera-mainnet-1", "lumera-testnet-2", "lumera-devnet-1"} { + t.Run(chainID, func(t *testing.T) { + cfg, found := SetupUpgrades(upgrade_v1_20_2.UpgradeName, newTestUpgradeParams(chainID)) + require.True(t, found, "v1.20.2 must be registered on %s", chainID) + + require.NotNil(t, cfg.StoreUpgrade, + "v1.20.2 MUST declare EVM store additions: a chain arriving from "+ + "1.12.0 has none of them, and without StoreUpgrades every "+ + "validator panics with 'store evmigration mismatch ... expected N got 0'") + + for _, key := range required { + require.Containsf(t, cfg.StoreUpgrade.Added, key, + "v1.20.2 must add store key %q on %s so a direct 1.12.0 -> 1.20.2 "+ + "upgrade mounts it", key, chainID) + } + + require.Empty(t, cfg.StoreUpgrade.Deleted, + "v1.20.2 must never delete a store; deletion is unsafe on a chain "+ + "that already ran v1.20.0") + require.Empty(t, cfg.StoreUpgrade.Renamed, + "v1.20.2 must never rename a store") + }) + } +} + +// TestV1202UsesAddOnlyStoreLoader asserts the loader selection, which is what +// makes declaring the store keys safe on a chain that already has them. +// +// Without the add-only loader, declaring an already-mounted key is a mount +// error on testnet — so the store declaration above and this loader are a +// matched pair. Changing one without the other breaks exactly one network, +// which is the failure mode this whole rehearsal series exists to catch. +func TestV1202UsesAddOnlyStoreLoader(t *testing.T) { + sel := StoreLoaderForUpgrade( + upgrade_v1_20_2.UpgradeName, + 100, + &upgrade_v1_20_2.StoreUpgrades, + map[string]struct{}{}, + log.NewNopLogger(), + false, // adaptive off: the add-only path must not depend on the env flag + ) + require.NotNil(t, sel.Loader, + "v1.20.2 must select a store loader even with adaptive mode off") + require.Equal(t, "add-only EVM bring-up", sel.LogLabel, + "v1.20.2 must use the add-only loader so mounting is safe whether or not "+ + "the EVM stores already exist") +} From 55c404ca8e08eec6aaea6e743b9c68a6fe4b48b7 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Thu, 30 Jul 2026 23:46:54 +0000 Subject: [PATCH 14/18] =?UTF-8?q?test(evmigration):=20pin=20canary=20activ?= =?UTF-8?q?ation=20semantics=20=E2=80=94=20empty=20allowlist=20is=20ALLOW?= =?UTF-8?q?=20ALL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Locks down the three-state model that decides whether a live network keeps migrating across this upgrade. WHY --- CheckMigrationActivation (keeper/ante.go:123) reads: if !params.EnableMigration { return ErrMigrationDisabled } if len(params.CanaryLegacyAddresses) == 0 { return nil } // ALLOW ALL An empty allowlist means allow-all, NOT deny-all. Combined with EnableMigration=true that is "migration fully open", which is exactly the state lumera-testnet-2 is in today (verified live 2026-07-30: enable_migration=true, migration_end_time 1790940497 = 2026-10-02, no canary addresses set). CORRECTION ---------- I had recommended flipping testnet to enable_migration=false before rollout. That recommendation was wrong and is withdrawn. Migration being open is the POINT of this release; the continuity work exists to make migration safe, not to switch it off. Closing a working testnet migration would have been a self-inflicted regression with no upside. The resulting three states: EnableMigration=false -> closed (mainnet post-upgrade default) EnableMigration=true, canary empty -> open (testnet today, unchanged) EnableMigration=true, canary non-empty -> canary (opt-in narrowing) Canary is a NARROWING of an already-open network, entered by ADDING addresses. It is not a stage every network must pass through, and an open network does not need to be closed first to reach it. The testnet/mainnet asymmetry is therefore real but benign, and is the correct outcome for each: testnet stays open and keeps migrating while this upgrade lands the audit 2->3 migration, the ante-level canary enforcement and the identity-continuity fixes underneath it; mainnet arrives closed because it runs v1.20.0's bring-up for the first time under the new default, which is the right posture for a chain that has never had migration enabled. TESTS ----- - TestCheckMigrationActivationEmptyCanaryAllowsEveryone: empty canary admits any address and does not discriminate between addresses; EnableMigration=false dominates even a listed address; a non-empty canary admits listed and rejects unlisted with ErrMigrationNotCanary. - TestCanaryIsOptIn: documents closed/open/canary as a three-state model so the operational meaning of each params combination is unambiguous. These exist so nobody "hardens" the empty-list case into a deny and silently breaks every open network at the next upgrade. RISKS ----- Test-only. No production code touched. VERIFICATION ------------ make lint ......................... 0 issues go test ./x/evmigration/... ....... PASS go test ./x/... ................... PASS go test -tags=test ./app/... ...... PASS git diff --check .................. clean --- x/evmigration/keeper/canary_semantics_test.go | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 x/evmigration/keeper/canary_semantics_test.go diff --git a/x/evmigration/keeper/canary_semantics_test.go b/x/evmigration/keeper/canary_semantics_test.go new file mode 100644 index 00000000..61ca396c --- /dev/null +++ b/x/evmigration/keeper/canary_semantics_test.go @@ -0,0 +1,85 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/LumeraProtocol/lumera/x/evmigration/keeper" + "github.com/LumeraProtocol/lumera/x/evmigration/types" +) + +// TestCheckMigrationActivationEmptyCanaryAllowsEveryone pins the semantics that +// determine whether a live network keeps working across this upgrade. +// +// # WHY THIS MATTERS +// +// An empty CanaryLegacyAddresses list means ALLOW ALL, not deny all. Combined +// with EnableMigration=true that is "migration fully open", which is exactly +// the state lumera-testnet-2 is in today (verified live 2026-07-30: +// enable_migration=true, no canary field set). +// +// I previously recommended flipping testnet to enable_migration=false before +// rollout. That recommendation was WRONG: it would have switched off a working +// testnet migration for no benefit. Migration being open is the point of the +// feature; the continuity work exists to make it SAFE, not to switch it off. +// +// This test exists so nobody "hardens" the empty-list case into a deny and +// silently breaks every open network at the next upgrade. +func TestCheckMigrationActivationEmptyCanaryAllowsEveryone(t *testing.T) { + legacy := sdk.AccAddress([]byte("legacy______________")) + other := sdk.AccAddress([]byte("other_______________")) + + t.Run("enabled with empty canary allows any address", func(t *testing.T) { + params := types.Params{EnableMigration: true} + require.NoError(t, keeper.CheckMigrationActivation(params, legacy), + "empty canary list must ALLOW ALL - this is live testnet's state") + require.NoError(t, keeper.CheckMigrationActivation(params, other), + "empty canary list must not discriminate between addresses") + }) + + t.Run("disabled blocks even a listed address", func(t *testing.T) { + params := types.Params{ + EnableMigration: false, + CanaryLegacyAddresses: []string{legacy.String()}, + } + err := keeper.CheckMigrationActivation(params, legacy) + require.ErrorIs(t, err, types.ErrMigrationDisabled, + "EnableMigration=false must dominate the allowlist") + }) + + t.Run("non-empty canary restricts to listed addresses", func(t *testing.T) { + params := types.Params{ + EnableMigration: true, + CanaryLegacyAddresses: []string{legacy.String()}, + } + require.NoError(t, keeper.CheckMigrationActivation(params, legacy), + "listed address must be admitted during canary") + require.ErrorIs(t, keeper.CheckMigrationActivation(params, other), + types.ErrMigrationNotCanary, + "unlisted address must be rejected during canary") + }) +} + +// TestCanaryIsOptIn documents the resulting three-state model so the operational +// meaning of each params combination is unambiguous: +// +// EnableMigration=false -> closed (mainnet's post-upgrade default) +// EnableMigration=true, canary empty -> open (testnet today, unchanged by this upgrade) +// EnableMigration=true, canary non-empty -> canary (opt-in narrowing) +// +// Canary is a NARROWING of an open network, entered by ADDING addresses. It is +// not a stage every network must pass through, and a network already running +// open does not need to be closed first. +func TestCanaryIsOptIn(t *testing.T) { + addr := sdk.AccAddress([]byte("someaddress_________")) + + closed := types.Params{EnableMigration: false} + open := types.Params{EnableMigration: true} + canary := types.Params{EnableMigration: true, CanaryLegacyAddresses: []string{"lumera1someoneelse"}} + + require.Error(t, keeper.CheckMigrationActivation(closed, addr), "closed rejects") + require.NoError(t, keeper.CheckMigrationActivation(open, addr), "open admits") + require.Error(t, keeper.CheckMigrationActivation(canary, addr), "canary excludes unlisted") +} From 4cfdc45871a12f16bee3190a1ba0328c7918a55a Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Fri, 31 Jul 2026 14:38:27 +0000 Subject: [PATCH 15/18] fix(evmigration): don't proto.Clone gov Deposit - it panics on big.Word Found on a mainnet-shaped devnet while migrating a real fixture cohort: any legacy account holding an ACTIVE governance deposit could not migrate. buildGovernancePlan called proto.Clone on a govv1.Deposit. Deposit.Amount is []sdk.Coin, whose Amount is an sdkmath.Int wrapping *big.Int. gogoproto's reflective table-merge descends into big.Int's unexported 'abs []big.Word', finds no registered merger for big.Word, and panics: ERR panic recovered in runTx err="recovered: merger not found for type:big.Word gogoproto/proto.(*mergeInfo).computeMergeInfo table_merge.go:662 x/gov/types/v1.(*Deposit).XXX_Merge gov.pb.go:212 gogoproto/proto.Clone clone.go:52 keeper.buildGovernancePlan migrate_retained.go:417 msgServer.ClaimLegacyAccount msg_server_claim_legacy.go:106 The tx aborts so no state is corrupted, but the account stays unmigratable while the deposit exists, and the operator-facing error names neither governance nor deposits - undiagnosable in the field. Likelihood is high: any account that has submitted a proposal whose deposit is still held is affected, which includes the governance participants most likely to migrate first. Replaced both proto.Clone sites with an explicit cloneGovDeposit deep copy. Coin.Amount is an immutable sdkmath.Int, so element-wise copy is a correct deep copy and avoids reflection entirely. Tests (x/evmigration/keeper/migrate_retained_clone_test.go): - TestProtoCloneOnGovDepositPanics: RED test pinning that proto.Clone still panics upstream, so the helper is not 'simplified' back into it later. - TestCloneGovDepositIsCorrectDeepCopy: value equality AND independence - a shallow copy would let later mutation leak into the plan's source record, which is what rollback/verification compares against. - TestCloneGovDepositEdgeCases: nil Amount, empty slice, and a multi-word big.Int (2^200) - the exact shape that makes the reflective walk touch big.Word at all. Verified live: rebuilt binary sha256 019e427ed4cbc433..., hot-swapped into the running mainnet-shaped devnet, restarted, re-ran migration -> zero 'merger not found' occurrences in validator logs. ./x/evmigration/... and -tags=test ./app/... both green. Known follow-up (NOT fixed here): with the panic gone the same account now fails with 'stale governance deposit source for proposal 2: value changed' from the plan's staleness guard. Fails closed, so it is safe, but it needs its own investigation - tracked in BUGS-AND-FINDINGS.md as BUG-17. --- x/evmigration/keeper/migrate_retained.go | 39 +++++- .../keeper/migrate_retained_clone_test.go | 124 ++++++++++++++++++ 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 x/evmigration/keeper/migrate_retained_clone_test.go diff --git a/x/evmigration/keeper/migrate_retained.go b/x/evmigration/keeper/migrate_retained.go index 3f8cd5c3..06f546be 100644 --- a/x/evmigration/keeper/migrate_retained.go +++ b/x/evmigration/keeper/migrate_retained.go @@ -414,14 +414,14 @@ func (k Keeper) buildGovernancePlan(ctx sdk.Context, legacyAddr, newAddr sdk.Acc if !sdk.Coins(deposit.Amount).IsValid() { return true, fmt.Errorf("source deposit has invalid coins for proposal %d", key.K1()) } - result := *proto.Clone(&deposit).(*govv1.Deposit) + result := cloneGovDeposit(deposit) result.Depositor = newAddr.String() move := govDepositMove{proposalID: key.K1(), source: deposit, result: result} if destination, getErr := k.govKeeper.Deposits.Get(ctx, collections.Join(key.K1(), newAddr)); getErr == nil { if destination.Depositor != newAddr.String() || destination.ProposalId != key.K1() || !sdk.Coins(destination.Amount).IsValid() { return true, fmt.Errorf("destination deposit is malformed for proposal %d", key.K1()) } - destCopy := *proto.Clone(&destination).(*govv1.Deposit) + destCopy := cloneGovDeposit(destination) move.destination = &destCopy // Valid sdk.Coins are sorted and unique, so Add preserves canonical order // without changing deposit semantics. @@ -501,3 +501,38 @@ func verifyOptionalCollectionValue[K, V any](ctx sdk.Context, m collections.Map[ } return nil } + +// cloneGovDeposit deep-copies a gov Deposit without going through proto.Clone. +// +// WHY NOT proto.Clone: govv1.Deposit.Amount is []sdk.Coin, whose Amount is an +// sdkmath.Int wrapping *big.Int. gogoproto's reflection-based table merge walks +// into big.Int's unexported `abs []big.Word` slice, finds no registered merger +// for big.Word, and PANICS: +// +// panic: recovered: merger not found for type:big.Word +// gogoproto/proto.(*mergeInfo).computeMergeInfo +// x/gov/types/v1.(*Deposit).XXX_Merge +// gogoproto/proto.Clone +// keeper.buildGovernancePlan (migrate_retained.go) +// +// Observed on a mainnet-shaped devnet: any legacy account holding an ACTIVE +// governance deposit failed to migrate with an opaque "merger not found" error. +// The tx aborts so no state is corrupted, but that account can never migrate +// while the deposit exists, and the operator-facing error explains nothing. +// +// Coins are value types holding an immutable Int, so an explicit element copy is +// a correct deep copy and avoids reflection entirely. +func cloneGovDeposit(src govv1.Deposit) govv1.Deposit { + out := govv1.Deposit{ + ProposalId: src.ProposalId, + Depositor: src.Depositor, + } + if src.Amount != nil { + out.Amount = make([]sdk.Coin, len(src.Amount)) + for i, c := range src.Amount { + // Coin.Amount is an immutable sdkmath.Int; copying the struct is safe. + out.Amount[i] = sdk.NewCoin(c.Denom, c.Amount) + } + } + return out +} diff --git a/x/evmigration/keeper/migrate_retained_clone_test.go b/x/evmigration/keeper/migrate_retained_clone_test.go new file mode 100644 index 00000000..80474729 --- /dev/null +++ b/x/evmigration/keeper/migrate_retained_clone_test.go @@ -0,0 +1,124 @@ +package keeper + +import ( + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" +) + +// TestProtoCloneOnGovDepositPanics is the RED test for the defect this file's +// cloneGovDeposit helper exists to fix. +// +// DEFECT (found on a mainnet-shaped devnet, 2026-07-31): +// buildGovernancePlan called proto.Clone on a govv1.Deposit. Deposit.Amount is +// []sdk.Coin, whose Amount is an sdkmath.Int wrapping *big.Int. gogoproto's +// reflection-based table merge descends into big.Int's unexported +// `abs []big.Word` slice, finds no registered merger for big.Word, and panics: +// +// panic: recovered: merger not found for type:big.Word +// gogoproto/proto.(*mergeInfo).computeMergeInfo +// x/gov/types/v1.(*Deposit).XXX_Merge +// gogoproto/proto.Clone +// keeper.buildGovernancePlan +// +// Operator-visible symptom: a legacy account holding an ACTIVE governance +// deposit could not migrate, failing with an opaque "merger not found" message +// that names neither governance nor the deposit. The tx aborts so no state is +// corrupted, but the account stays unmigratable while the deposit exists. +// +// This test pins the upstream behaviour so nobody "simplifies" cloneGovDeposit +// back into proto.Clone. +func TestProtoCloneOnGovDepositPanics(t *testing.T) { + dep := govv1.Deposit{ + ProposalId: 3, + Depositor: "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", + Amount: sdk.NewCoins(sdk.NewCoin("ulume", sdkmath.NewInt(2000000000))), + } + + require.Panics(t, func() { + _ = proto.Clone(&dep) + }, "proto.Clone on a gov Deposit with non-zero Coins must still panic; "+ + "if this ever stops panicking upstream, cloneGovDeposit may be simplified") +} + +// TestCloneGovDepositIsCorrectDeepCopy proves the replacement both avoids the +// panic AND produces an independent copy — a shallow copy would let a later +// mutation of the result leak back into the source deposit, silently corrupting +// the migration plan's "source" record used for rollback/verification. +func TestCloneGovDepositIsCorrectDeepCopy(t *testing.T) { + src := govv1.Deposit{ + ProposalId: 7, + Depositor: "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", + Amount: sdk.NewCoins( + sdk.NewCoin("ulume", sdkmath.NewInt(2000000000)), + sdk.NewCoin("uatom", sdkmath.NewInt(42)), + ), + } + + var out govv1.Deposit + require.NotPanics(t, func() { + out = cloneGovDeposit(src) + }, "cloneGovDeposit must not panic where proto.Clone does") + + require.Equal(t, src.ProposalId, out.ProposalId) + require.Equal(t, src.Depositor, out.Depositor) + require.True(t, sdk.Coins(out.Amount).Equal(sdk.Coins(src.Amount)), + "cloned coins must be value-equal") + require.True(t, sdk.Coins(out.Amount).IsValid(), + "cloned coins must remain a valid, sorted Coins set") + + // Independence: mutating the clone must not touch the source. + out.Depositor = "lumera1changed" + out.Amount[0] = sdk.NewCoin(out.Amount[0].Denom, sdkmath.NewInt(1)) + require.Equal(t, "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", src.Depositor, + "mutating the clone must not change the source depositor") + require.True(t, sdk.Coins(src.Amount).Equal(sdk.NewCoins( + sdk.NewCoin("ulume", sdkmath.NewInt(2000000000)), + sdk.NewCoin("uatom", sdkmath.NewInt(42)), + )), "mutating the clone must not change the source coins") + + // The backing arrays must be distinct. + if len(src.Amount) > 0 && len(out.Amount) > 0 { + require.NotSame(t, &src.Amount[0], &out.Amount[0], + "clone must not share the Coins backing array with the source") + } +} + +// TestCloneGovDepositEdgeCases covers the shapes a real chain will hand us: +// an empty deposit, a nil Amount, and a very large Int (multi-word big.Int, +// which is precisely what makes the reflective merge walk big.Word at all). +func TestCloneGovDepositEdgeCases(t *testing.T) { + t.Run("nil amount", func(t *testing.T) { + out := cloneGovDeposit(govv1.Deposit{ProposalId: 1, Depositor: "a"}) + require.Nil(t, out.Amount) + require.EqualValues(t, 1, out.ProposalId) + }) + + t.Run("empty amount slice", func(t *testing.T) { + out := cloneGovDeposit(govv1.Deposit{ + ProposalId: 2, Depositor: "b", Amount: []sdk.Coin{}, + }) + require.NotNil(t, out.Amount) + require.Len(t, out.Amount, 0) + }) + + t.Run("multi-word big.Int amount", func(t *testing.T) { + // 2^200 needs several big.Words — the exact case that trips the + // reflective merge path. + huge := sdkmath.NewIntFromBigInt(new(big.Int).Lsh(big.NewInt(1), 200)) + src := govv1.Deposit{ + ProposalId: 9, + Depositor: "c", + Amount: []sdk.Coin{{Denom: "ulume", Amount: huge}}, + } + var out govv1.Deposit + require.NotPanics(t, func() { out = cloneGovDeposit(src) }) + require.True(t, out.Amount[0].Amount.Equal(huge), + "a multi-word big.Int amount must survive the copy exactly") + }) +} From 299f964192f1656a5e45083258aeb5a8ae456f23 Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Fri, 31 Jul 2026 15:40:20 +0000 Subject: [PATCH 16/18] fix(evmigration): retained-state guard blocked legitimate migrations Two defects in the retained-state staleness guard, both found on a mainnet-shaped devnet while migrating a real fixture cohort. Each one independently prevented a legacy account holding a governance deposit from ever migrating. 1) proto.Equal is unreliable for Coin-bearing messages verifyCollectionValue compared the re-read on-chain value against the plan's expectation with proto.Equal. gogoproto cannot compare sdkmath.Int: proto: don't know how to compare 2000000000 so proto.Equal returns FALSE for two byte-identical gov Deposits. Migration failed with stale governance deposit source for proposal 2: value changed on a deposit nothing had touched. Same reflection family that panics outright in proto.Clone. Replaced with marshalled-bytes comparison (protoBytesEqual), which is the deterministic, consensus-relevant notion of "unchanged" - it is what the store actually holds - and avoids reflection entirely. Applied to the authz-grant and destination-vote comparisons too, which had the same exposure. 2) typed-nil interface defeated the optional-destination check verifyOptionalCollectionValue took `expected proto.Message` and tested `expected == nil` to mean "the plan saw no destination entry". Callers pass a concrete typed pointer, and a nil *govv1.Deposit boxed in an interface is NOT == nil, so the "absent is fine" branch never ran. The guard fell through to the comparison path and failed on ErrNotFound: stale governance deposit destination for proposal 2: collections: not found: key '("2","lumera1k7del...")' of type ...gov.v1.Deposit It demanded the destination exist, then failed because it did not - blocking every account whose target address had no pre-existing deposit, i.e. the normal case. Added isNilMessage, which detects typed-nil via reflection. Both fail closed, so no state was corrupted. But they permanently blocked legitimate migrations and the errors pointed at concurrent mutation that never happened. Tests (migrate_retained_staleness_test.go): - TestProtoEqualIsBrokenForGovDeposits: characterization test pinning the upstream defect, so the byte comparison is not "simplified" back later. - TestProtoBytesEqualFixesTheFalseStaleness: identical deposits compare equal, including via cloneGovDeposit. - TestProtoBytesEqualDetectsRealChanges: NEGATIVE CONTROL - a comparator that always returned true would also "fix" the bug while destroying the guard. Covers changed amount, depositor, proposal id, extra denom, emptied amount. - TestProtoBytesEqualNilVsEmptyCoins: nil and empty Coins marshal identically and must not trip the guard. - TestIsNilMessageCatchesTypedNil: the typed-nil trap for both Deposit and Vote, asserting the raw `== nil` is false while isNilMessage is true. Verified live on the mainnet-shaped devnet (v1.12.0 -> v1.20.2 single-hop, 5 validators, 5 supernodes, 19-account fixture cohort): before: 16/19 migrated, blocked by the panic then: 16/19, blocked by "source: value changed" then: 16/19, blocked by "destination: not found" now: 17/19 - 17 migration records on chain The 2 remaining failures are validator operators correctly rejected with "use MsgMigrateValidator instead". Gates: ./x/evmigration/... green, -tags=test ./app/... green, -tags='integration test' ./tests/integration/evmigration/... green, make lint 0 issues. --- x/evmigration/keeper/migrate_retained.go | 108 ++++++++- .../keeper/migrate_retained_staleness_test.go | 213 ++++++++++++++++++ 2 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 x/evmigration/keeper/migrate_retained_staleness_test.go diff --git a/x/evmigration/keeper/migrate_retained.go b/x/evmigration/keeper/migrate_retained.go index 06f546be..cc53fb0d 100644 --- a/x/evmigration/keeper/migrate_retained.go +++ b/x/evmigration/keeper/migrate_retained.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "reflect" "time" "cosmossdk.io/collections" @@ -150,7 +151,15 @@ func (k Keeper) verifyRetainedStatePlan(ctx sdk.Context, plan retainedStatePlan) for _, move := range plan.authz { sourceID := authzIdentity(move.oldGranter, move.oldGrantee, move.msgType) grant, ok := current[sourceID] - if !ok || !proto.Equal(&grant, &move.source) { + if !ok { + return fmt.Errorf("stale authz source grant %s", sourceID) + } + // Bytes comparison, not proto.Equal - see verifyCollectionValue. + same, cmpErr := protoBytesEqual(&grant, &move.source) + if cmpErr != nil { + return cmpErr + } + if !same { return fmt.Errorf("stale authz source grant %s", sourceID) } targetID := authzIdentity(move.newGranter, move.newGrantee, move.msgType) @@ -385,7 +394,12 @@ func (k Keeper) buildGovernancePlan(ctx sdk.Context, legacyAddr, newAddr sdk.Acc if getErr == nil { destCopy := *proto.Clone(&destination).(*govv1.Vote) move.destination = &destCopy - if !proto.Equal(&result, &destination) { + // Bytes comparison, not proto.Equal - see verifyCollectionValue. + sameVote, cmpErr := protoBytesEqual(&result, &destination) + if cmpErr != nil { + return true, cmpErr + } + if !sameVote { return true, fmt.Errorf("conflicting destination vote for proposal %d", key.K1()) } move.collapse = true @@ -469,21 +483,82 @@ func (k Keeper) buildWithdrawAddressPlan(ctx sdk.Context, legacyAddr sdk.AccAddr // These helpers keep stale checks exact while allowing the concrete collection // value type to remain inferred from the SDK keeper fields. +// verifyCollectionValue re-reads a value and asserts it still matches the +// expectation captured when the plan was built. +// +// WHY NOT proto.Equal: gogoproto's reflection-based comparison is unreliable for +// messages carrying sdk.Coin, because Coin.Amount is an sdkmath.Int wrapping +// *big.Int with an unexported `abs []big.Word`. proto.Equal returns FALSE for two +// byte-identical gov Deposits (proved by TestProtoEqualOnIdenticalGovDeposits) -- +// the same reflection family that outright panics in proto.Clone. +// +// Using it here produced a FALSE staleness positive: migrating a legacy account +// holding a governance deposit failed with +// +// stale governance deposit source for proposal 2: value changed +// +// on a deposit nothing had touched. Fail-closed, so it was safe, but it blocked +// a legitimate migration and the message pointed at concurrent mutation that +// never happened. +// +// Marshalled-bytes comparison is the deterministic, consensus-relevant notion of +// "unchanged" -- it is exactly what the store holds -- and it sidesteps +// reflection entirely. func verifyCollectionValue[K, V any](ctx sdk.Context, m collections.Map[K, V], key K, expected proto.Message) error { value, err := m.Get(ctx, key) if err != nil { return err } actual, ok := any(&value).(proto.Message) - if !ok || !proto.Equal(actual, expected) { + if !ok { + return fmt.Errorf("value is not a proto.Message") + } + same, err := protoBytesEqual(actual, expected) + if err != nil { + return err + } + if !same { return fmt.Errorf("value changed") } return nil } +// protoBytesEqual compares two proto messages by their marshalled bytes. +// See verifyCollectionValue for why proto.Equal is not used. +func protoBytesEqual(a, b proto.Message) (bool, error) { + ab, err := proto.Marshal(a) + if err != nil { + return false, fmt.Errorf("marshal actual: %w", err) + } + bb, err := proto.Marshal(b) + if err != nil { + return false, fmt.Errorf("marshal expected: %w", err) + } + return bytes.Equal(ab, bb), nil +} + +// verifyOptionalCollectionValue asserts that an optional destination entry is +// still in the state the plan expected: absent if the plan saw it absent, or +// byte-identical if the plan captured a value. +// +// TYPED-NIL TRAP: callers pass a concrete typed pointer (e.g. *govv1.Deposit) +// for `expected`. When that pointer is nil it is wrapped in a NON-nil +// proto.Message interface, so a plain `expected == nil` check is FALSE and the +// "absent is fine" branch never runs. The function then treated a legitimately +// absent destination as an error and surfaced: +// +// stale governance deposit destination for proposal 2: +// collections: not found: key '("2","lumera1k7del...")' of type ...gov.v1.Deposit +// +// i.e. it demanded the destination exist and then failed because it did not. +// isNilMessage uses reflection on the interface's underlying value to detect the +// typed-nil case correctly. +// +// Comparison is by marshalled bytes, not proto.Equal - see verifyCollectionValue +// for why gogoproto's reflective equality is unreliable for Coin-bearing values. func verifyOptionalCollectionValue[K, V any](ctx sdk.Context, m collections.Map[K, V], key K, expected proto.Message) error { value, err := m.Get(ctx, key) - if expected == nil { + if isNilMessage(expected) { if errors.Is(err, collections.ErrNotFound) { return nil } @@ -496,12 +571,35 @@ func verifyOptionalCollectionValue[K, V any](ctx sdk.Context, m collections.Map[ return err } actual, ok := any(&value).(proto.Message) - if !ok || !proto.Equal(actual, expected) { + if !ok { + return fmt.Errorf("value is not a proto.Message") + } + same, cmpErr := protoBytesEqual(actual, expected) + if cmpErr != nil { + return cmpErr + } + if !same { return fmt.Errorf("value changed") } return nil } +// isNilMessage reports whether a proto.Message interface is nil OR holds a nil +// typed pointer. A bare `m == nil` misses the second case, which is how an +// absent optional destination was misread as a hard error. +func isNilMessage(m proto.Message) bool { + if m == nil { + return true + } + v := reflect.ValueOf(m) + switch v.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Map, reflect.Slice: + return v.IsNil() + default: + return false + } +} + // cloneGovDeposit deep-copies a gov Deposit without going through proto.Clone. // // WHY NOT proto.Clone: govv1.Deposit.Amount is []sdk.Coin, whose Amount is an diff --git a/x/evmigration/keeper/migrate_retained_staleness_test.go b/x/evmigration/keeper/migrate_retained_staleness_test.go new file mode 100644 index 00000000..098ea204 --- /dev/null +++ b/x/evmigration/keeper/migrate_retained_staleness_test.go @@ -0,0 +1,213 @@ +package keeper + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" +) + +// BUG-17: after fixing the proto.Clone panic (BUG-16), migrating a legacy account +// holding a governance deposit failed with +// +// stale governance deposit source for proposal 2: value changed +// +// on a deposit that nothing had modified. The message came from +// verifyCollectionValue, which used proto.Equal to compare the freshly-read +// on-chain value against the plan's expectation. +// +// ROOT CAUSE (proved below): gogoproto's proto.Equal returns FALSE for two +// byte-identical gov Deposits. Deposit.Amount is []sdk.Coin whose Amount is an +// sdkmath.Int wrapping *big.Int; the same reflection machinery that outright +// panics in proto.Clone silently misreports equality here. Fail-closed, so no +// state was corrupted -- but a legitimate migration was permanently blocked and +// the error pointed at concurrent mutation that never occurred. +// +// FIX: verifyCollectionValue (and the authz/vote staleness checks) now compare +// marshalled bytes, which is the deterministic, consensus-relevant notion of +// "unchanged" and avoids reflection entirely. + +// TestProtoEqualIsBrokenForGovDeposits is a CHARACTERIZATION test: it documents +// the upstream defect that motivated the fix. If gogoproto ever repairs this, +// this test fails and the byte-comparison helper can be reconsidered. +func TestProtoEqualIsBrokenForGovDeposits(t *testing.T) { + mk := func() *govv1.Deposit { + return &govv1.Deposit{ + ProposalId: 2, + Depositor: "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", + Amount: sdk.NewCoins(sdk.NewCoin("ulume", sdkmath.NewInt(2000000000))), + } + } + a, b := mk(), mk() + + require.False(t, proto.Equal(a, b), + "CHARACTERIZATION: gogoproto proto.Equal is expected to WRONGLY report two "+ + "identical Coin-bearing gov Deposits as unequal. If this now passes, "+ + "upstream fixed it and protoBytesEqual may be revisited.") +} + +// TestProtoBytesEqualFixesTheFalseStaleness is the real regression test: the +// replacement comparator must report identical deposits as identical, which is +// what unblocks the migration. +func TestProtoBytesEqualFixesTheFalseStaleness(t *testing.T) { + mk := func() *govv1.Deposit { + return &govv1.Deposit{ + ProposalId: 2, + Depositor: "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", + Amount: sdk.NewCoins(sdk.NewCoin("ulume", sdkmath.NewInt(2000000000))), + } + } + + same, err := protoBytesEqual(mk(), mk()) + require.NoError(t, err) + require.True(t, same, + "two identical deposits must compare equal, or the staleness guard fires "+ + "on unchanged state and blocks a legitimate migration (BUG-17)") + + // The plan's real shape: expectation built via cloneGovDeposit. + src := *mk() + cloned := cloneGovDeposit(src) + same, err = protoBytesEqual(&src, &cloned) + require.NoError(t, err) + require.True(t, same, + "a cloneGovDeposit result must compare equal to its source") +} + +// TestProtoBytesEqualDetectsRealChanges is the NEGATIVE CONTROL. A comparator +// that always returns true would also "fix" the bug while destroying the +// guard's entire purpose -- silently permitting migration over genuinely +// mutated state. Each field must be detected. +func TestProtoBytesEqualDetectsRealChanges(t *testing.T) { + base := &govv1.Deposit{ + ProposalId: 2, + Depositor: "lumera1gm0f4jhgygj9j4w685x4plfgkckevsqps08s3w", + Amount: sdk.NewCoins(sdk.NewCoin("ulume", sdkmath.NewInt(2000000000))), + } + + cases := map[string]*govv1.Deposit{ + "different amount": { + ProposalId: 2, Depositor: base.Depositor, + Amount: sdk.NewCoins(sdk.NewCoin("ulume", sdkmath.NewInt(1))), + }, + "different depositor": { + ProposalId: 2, Depositor: "lumera1other", + Amount: base.Amount, + }, + "different proposal": { + ProposalId: 3, Depositor: base.Depositor, + Amount: base.Amount, + }, + "extra denom": { + ProposalId: 2, Depositor: base.Depositor, + Amount: sdk.NewCoins( + sdk.NewCoin("ulume", sdkmath.NewInt(2000000000)), + sdk.NewCoin("uatom", sdkmath.NewInt(1)), + ), + }, + "emptied amount": { + ProposalId: 2, Depositor: base.Depositor, Amount: nil, + }, + } + + for name, mutated := range cases { + t.Run(name, func(t *testing.T) { + same, err := protoBytesEqual(base, mutated) + require.NoError(t, err) + require.False(t, same, + "a genuinely changed deposit MUST be detected as changed; the guard "+ + "must still fail closed on real mutation") + }) + } +} + +// TestIsNilMessageCatchesTypedNil is the regression test for the third defect in +// this guard family (BUG-19). +// +// verifyOptionalCollectionValue took `expected proto.Message` and checked +// `expected == nil` to mean "the plan saw no destination entry". But callers pass +// a CONCRETE typed pointer: +// +// var destination *govv1.Deposit // nil +// verifyOptionalCollectionValue(ctx, m, key, destination) +// +// A nil *govv1.Deposit wrapped in a proto.Message interface is NOT == nil, so the +// "absent is fine" branch never executed. The guard fell through to the +// value-comparison path, the store lookup returned ErrNotFound, and migration +// failed with: +// +// stale governance deposit destination for proposal 2: +// collections: not found: key '("2","lumera1k7del...")' of type ...gov.v1.Deposit +// +// It demanded the destination exist, then failed because it did not — blocking +// every legacy account whose target address had no pre-existing deposit, i.e. the +// normal case. +func TestIsNilMessageCatchesTypedNil(t *testing.T) { + t.Run("bare nil interface", func(t *testing.T) { + require.True(t, isNilMessage(nil)) + }) + + t.Run("typed nil pointer - the actual bug", func(t *testing.T) { + // The trap: a nil typed pointer boxed in an interface. Go's `== nil` is + // FALSE here because the interface carries a type — which is exactly what + // the old `expected == nil` guard tested, and why it never fired. + // + // nilInterface() returns through a proto.Message return type so staticcheck + // cannot statically resolve the concrete type (SA4023) — the dynamic + // behaviour is the whole point of the test. + asMessage := nilDepositMessage() + + require.False(t, asMessage == nil, + "CHARACTERIZATION: a nil *Deposit boxed in proto.Message is NOT == nil; "+ + "this is precisely why the old `expected == nil` check never fired") + + require.True(t, isNilMessage(asMessage), + "isNilMessage must detect a typed-nil pointer, or an absent optional "+ + "destination is misread as a hard error (BUG-19)") + }) + + t.Run("non-nil message is not nil", func(t *testing.T) { + require.False(t, isNilMessage(&govv1.Deposit{ProposalId: 1}), + "a real message must not be treated as absent, or the guard would skip "+ + "verification entirely") + }) + + t.Run("typed nil vote pointer", func(t *testing.T) { + require.True(t, isNilMessage(nilVoteMessage()), + "the same trap applies to the vote destination check") + }) +} + +// nilDepositMessage returns a nil *govv1.Deposit as a proto.Message — the exact +// shape callers pass for an absent optional destination. +func nilDepositMessage() proto.Message { + var d *govv1.Deposit + return d +} + +// nilVoteMessage is the vote equivalent of nilDepositMessage. +func nilVoteMessage() proto.Message { + var v *govv1.Vote + return v +} + +// TestProtoBytesEqualNilVsEmptyCoins pins the nil-vs-empty decision. sdk.NewCoins() +// yields an empty non-nil slice while a decoded Deposit with no coins may carry +// nil. Both marshal to the same bytes (an absent repeated field), so they compare +// equal -- which is correct: they are semantically identical, and treating them as +// different would reintroduce a false staleness positive. +func TestProtoBytesEqualNilVsEmptyCoins(t *testing.T) { + nilCoins := &govv1.Deposit{ProposalId: 2, Depositor: "a", Amount: nil} + emptyCoins := &govv1.Deposit{ProposalId: 2, Depositor: "a", Amount: []sdk.Coin{}} + + same, err := protoBytesEqual(nilCoins, emptyCoins) + require.NoError(t, err) + require.True(t, same, + "nil and empty Coins are semantically identical and must not trip the guard") + + // cloneGovDeposit must not silently convert one into the other either. + require.Nil(t, cloneGovDeposit(*nilCoins).Amount) + require.NotNil(t, cloneGovDeposit(*emptyCoins).Amount) +} From e901b8c7b89eb20a45cae03748b6bab10a9836ba Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Sat, 1 Aug 2026 00:09:51 +0000 Subject: [PATCH 17/18] test(supernode): pin evidence + metrics preservation across identity migration Closes a coverage hole where nothing - unit or devnet - proved that a supernode's evidence history and metrics survive an identity migration. The gap was vacuous-green in both places: - devnet fixtures report evidence=0 and has_metrics=false on every supernode, so "evidence preserved" passed because there was nothing to preserve; - the keeper fixture rawTestSuperNode() carries no evidence either. This test supplies the non-empty state both lack: three evidence entries with distinct reporters, types and heights, plus metrics with a non-zero ReportCount. Contract note (corrected after reading the existing test): the primary supernode record is VALIDATION-ONLY in ApplyIdentityMigrationPlan and is deliberately NOT moved - // Primary/account/history are owned by PR196 and are validation-only here. require.Equal(t, sourcePrimaryRaw, store.Get(types.GetSupernodeKey(source))) require.Nil(t, store.Get(types.GetSupernodeKey(destination))) so the correct invariant at this layer is not "evidence moves" but "evidence is left byte-identical at the source, untouched" while the continuity state the plan does own (metrics, distribution, payout history) relocates. The test also asserts no record is conjured at the destination. Asserted: - evidence count, reporter, type and height unchanged, in order - metrics follow the identity with exact values, ReportCount included - metrics do NOT remain at the source (duplicate state would double-count in audit/payout aggregation) Proven non-vacuous by mutation testing (evidence/g4_mutation_evidence.sh), 3/3 mutants detected: - metrics move dropped -> "metrics must follow the migrated identity" - metrics ReportCount zeroed -> "report count must be preserved" - one evidence entry silently dropped -> "should have 3 item(s), but has 2" Baseline passes before and after; the script restores the source tree and re-verifies. Note a first attempt at the third mutant injected into ApplyIdentityMigrationPlan where sourceSN is out of scope: it failed to compile, which is INCONCLUSIVE rather than a pass, and was fixed to inject into BuildIdentityMigrationPlan. Gates: ./x/supernode/... green, ./x/evmigration/... green, golangci-lint ./x/supernode/... 0 issues. --- .../identity_migration_evidence_test.go | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 x/supernode/v1/keeper/identity_migration_evidence_test.go diff --git a/x/supernode/v1/keeper/identity_migration_evidence_test.go b/x/supernode/v1/keeper/identity_migration_evidence_test.go new file mode 100644 index 00000000..83016ab3 --- /dev/null +++ b/x/supernode/v1/keeper/identity_migration_evidence_test.go @@ -0,0 +1,112 @@ +package keeper + +import ( + "bytes" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/LumeraProtocol/lumera/x/supernode/v1/types" +) + +// G2 de-vacuuming. +// +// The devnet fixture reports evidence=0 and has_metrics=false on every +// supernode, so a devnet-level "evidence preserved" assertion passes only +// because there is nothing to preserve. The existing keeper fixture +// (rawTestSuperNode) also carries NO evidence, so that gap exists at unit level +// too: nothing anywhere proved evidence survives an identity migration. +// +// Evidence is an EMBEDDED field on the SuperNode record +// (`repeated Evidence evidence = 3` in super_node.proto), NOT a separate store +// key. Critically, ApplyIdentityMigrationPlan treats the primary record as +// VALIDATION-ONLY and deliberately does not move it - the existing test states +// this explicitly: +// +// // Primary/account/history are owned by PR196 and are validation-only here. +// require.Equal(t, sourcePrimaryRaw, store.Get(types.GetSupernodeKey(source))) +// require.Nil(t, store.Get(types.GetSupernodeKey(destination))) +// +// So the correct invariant for THIS layer is not "evidence moves" but +// "evidence is left byte-identical at the source, untouched": the plan must +// never silently mutate or drop embedded evidence while relocating the +// continuity state it does own (metrics, distribution, payout history). +// +// That is the property this test pins, with the non-empty evidence + metrics +// fixture the devnet and rawTestSuperNode both lack. +func TestIdentityMigrationPreservesEvidenceAndMetrics(t *testing.T) { + k, ctx := setupKeeperForInternalTest(t) + source, destination, account := migrationValidators() + + // Real evidence history: multiple entries, distinct reporters/types/heights, + // so both ordering and content are observable. + evidence := []*types.Evidence{ + {ReporterAddress: sdk.AccAddress(bytes.Repeat([]byte{0x51}, 20)).String(), EvidenceType: "storage_challenge_fail", Height: 11}, + {ReporterAddress: sdk.AccAddress(bytes.Repeat([]byte{0x52}, 20)).String(), EvidenceType: "unavailable", Height: 22}, + {ReporterAddress: sdk.AccAddress(bytes.Repeat([]byte{0x53}, 20)).String(), EvidenceType: "bad_proof", Height: 33}, + } + + sn := rawTestSuperNode(source, account) + sn.Evidence = evidence + store := migrationRawStore(k, ctx) + store.Set(types.GetSupernodeKey(source), marshalRawSuperNode(t, k, sn)) + store.Set(append(bytes.Clone(types.SuperNodeByAccountKey), []byte(account)...), source) + + metrics := types.SupernodeMetricsState{ + ValidatorAddress: source.String(), + Metrics: &types.SupernodeMetrics{CascadeKademliaDbBytes: 424242.5, PeersCount: 9}, + ReportCount: 31, + Height: 789, + } + require.NoError(t, k.SetMetricsState(ctx, metrics)) + + // Sanity: the fixture is genuinely non-empty. Without this guard a passing + // assertion below would prove nothing - the exact devnet vacuity problem. + require.Len(t, sn.Evidence, 3, "fixture must carry real evidence") + preMetrics, found := k.GetMetricsState(ctx, source) + require.True(t, found) + require.NotNil(t, preMetrics.Metrics) + + plan, err := k.BuildIdentityMigrationPlan(ctx, source, destination) + require.NoError(t, err) + require.NoError(t, k.ApplyIdentityMigrationPlan(ctx, plan)) + + // EVIDENCE: the primary record is validation-only at this layer, so evidence + // must remain byte-identical AT THE SOURCE - never mutated, reordered or + // dropped as a side effect of relocating the continuity state. + srcRaw := store.Get(types.GetSupernodeKey(source)) + require.NotNil(t, srcRaw, "source supernode record must still exist") + var kept types.SuperNode + require.NoError(t, k.cdc.Unmarshal(srcRaw, &kept)) + + require.Len(t, kept.Evidence, len(evidence), + "evidence count must not change when continuity state is migrated") + for i, want := range evidence { + require.Equal(t, want.ReporterAddress, kept.Evidence[i].ReporterAddress, + "evidence[%d] reporter must be unchanged", i) + require.Equal(t, want.EvidenceType, kept.Evidence[i].EvidenceType, + "evidence[%d] type must be unchanged", i) + require.Equal(t, want.Height, kept.Evidence[i].Height, + "evidence[%d] height must not be rewritten", i) + } + + // No supernode record may be conjured at the destination by this layer. + require.Nil(t, store.Get(types.GetSupernodeKey(destination)), + "primary record is validation-only here and must not be written") + + // METRICS must follow the identity, exactly. + movedMetrics, found := k.GetMetricsState(ctx, destination) + require.True(t, found, "metrics must follow the migrated identity") + require.NotNil(t, movedMetrics.Metrics) + require.EqualValues(t, 424242.5, movedMetrics.Metrics.CascadeKademliaDbBytes) + require.EqualValues(t, 9, movedMetrics.Metrics.PeersCount) + require.EqualValues(t, 31, movedMetrics.ReportCount, + "report count must be preserved - it feeds audit/payout accounting") + + // And must NOT remain at the old identity: duplicated metrics would + // double-count in any downstream aggregation. + _, stillAtSource := k.GetMetricsState(ctx, source) + require.False(t, stillAtSource, + "metrics must not remain under the source identity") +} From 2781bd088f53a70fed9040d8963c71aa7326afaf Mon Sep 17 00:00:00 2001 From: Matee ullah Malik Date: Sat, 1 Aug 2026 03:32:21 +0000 Subject: [PATCH 18/18] test(audit): cover LEP-6 Class-A fault accounting (found by mutation testing) Class-A storage-truth fault accounting had NO test coverage. Mutation testing proved it: forcing `isClassA = false` in updateNodeSuspicionHistoryFields - so HASH_MISMATCH and RECHECK_CONFIRMED_FAIL are never counted - left the entire storage-truth suite GREEN. ClassACountWindow / LastClassAEpoch / CleanPassCount gate band escalation and recovery. Without these assertions, a regression could let a node with repeated hash mismatches (the strongest evidence of storage dishonesty) accumulate no suspicion and recover as if clean, with nothing failing. Adds two tests: TestUpdateNodeSuspicionHistoryFields_ClassAFaultAccounting HASH_MISMATCH and RECHECK_CONFIRMED_FAIL each must - increment ClassACountWindow (added to a pre-existing count, not replaced) - stamp LastClassAEpoch - reset CleanPassCount (recovery requires clean passes with no new Class A) - leave ClassBCountWindow untouched TestUpdateNodeSuspicionHistoryFields_ClassBDoesNotTouchClassAGates TIMEOUT_OR_NO_RESPONSE must increment ClassB counters ONLY and must not touch Class-A gates. The code comment states TIMEOUT-on-INDEX "must not reset Class-A recovery gates or increment ClassACountWindow"; that separation was previously unasserted in both directions. Verified the new coverage is real - with the mutant injected the suite now FAILS on both Class-A classes, and passes once restored: --- FAIL: ..._ClassAFaultAccounting/STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH --- FAIL: ..._ClassAFaultAccounting/STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL Two fixture requirements worth noting for future tests here: - the counters live inside an `isFailure` branch keyed on BucketType / ArtifactClass, so a bare {ResultClass} result does not exercise them; - WindowStartEpoch must be fresh, otherwise the stale-window reset zeroes ClassACountWindow before the increment and masks the assertion. Gates: ./x/audit/... green, golangci-lint ./x/audit/... 0 issues. --- .../v1/keeper/storage_truth_class_a_test.go | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 x/audit/v1/keeper/storage_truth_class_a_test.go diff --git a/x/audit/v1/keeper/storage_truth_class_a_test.go b/x/audit/v1/keeper/storage_truth_class_a_test.go new file mode 100644 index 00000000..ed7e9d06 --- /dev/null +++ b/x/audit/v1/keeper/storage_truth_class_a_test.go @@ -0,0 +1,114 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/LumeraProtocol/lumera/x/audit/v1/types" +) + +// FINDING-24: Class-A fault accounting had NO test coverage. +// +// Mutation testing proved it: setting `isClassA = false` in +// updateNodeSuspicionHistoryFields - so HASH_MISMATCH and RECHECK_CONFIRMED_FAIL +// are never counted - left the ENTIRE storage-truth suite green. +// +// ClassACountWindow / LastClassAEpoch / CleanPassCount gate band escalation and +// recovery. Without these assertions a regression could let a node with repeated +// hash mismatches (the strongest evidence of storage dishonesty) accumulate no +// suspicion and recover as if clean, with nothing failing. +// +// The code comment is explicit that TIMEOUT-on-INDEX is a liveness/Class-B +// failure that "must not reset Class-A recovery gates or increment +// ClassACountWindow" - that separation was unasserted in BOTH directions. + +func classAParams() types.Params { + p := types.DefaultParams() + if p.StorageTruthPatternEscalationWindow == 0 { + p.StorageTruthPatternEscalationWindow = 10 + } + return p +} + +func TestUpdateNodeSuspicionHistoryFields_ClassAFaultAccounting(t *testing.T) { + k := Keeper{} + params := classAParams() + const epochID = uint64(100) + + classA := []types.StorageProofResultClass{ + types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_HASH_MISMATCH, + types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_RECHECK_CONFIRMED_FAIL, + } + + for _, class := range classA { + t.Run(class.String(), func(t *testing.T) { + // WindowStartEpoch == epochID keeps the escalation window FRESH. + // If the window is stale, updateNodeSuspicionHistoryFields resets + // ClassACountWindow to 0 before incrementing, which masks the + // increment and lets a broken isClassA survive. + state := types.NodeSuspicionState{ + WindowStartEpoch: epochID, + CleanPassCount: 7, // must be reset by a Class-A fault + ClassACountWindow: 2, // pre-existing count must be ADDED to, not replaced + } + before := state.ClassACountWindow + + k.updateNodeSuspicionHistoryFields(&state, + &types.StorageProofResult{ + ResultClass: class, + BucketType: types.StorageProofBucketType_STORAGE_PROOF_BUCKET_TYPE_RECENT, + ArtifactClass: types.StorageProofArtifactClass_STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL, + }, epochID, params) + + require.EqualValues(t, before+1, state.ClassACountWindow, + "%s must increment ClassACountWindow - it gates band escalation", class) + require.Equal(t, epochID, state.LastClassAEpoch, + "%s must stamp LastClassAEpoch", class) + require.Zero(t, state.CleanPassCount, + "%s must reset CleanPassCount - recovery requires clean passes "+ + "with no new Class-A failures", class) + require.Zero(t, state.ClassBCountWindow, + "%s is Class A and must NOT increment the Class-B counter", class) + }) + } +} + +// The inverse direction: a Class-B (liveness) failure must NOT touch Class-A +// gates. Per the code comment, TIMEOUT-on-INDEX "must not reset Class-A recovery +// gates or increment ClassACountWindow". +func TestUpdateNodeSuspicionHistoryFields_ClassBDoesNotTouchClassAGates(t *testing.T) { + k := Keeper{} + params := classAParams() + const epochID = uint64(100) + + state := types.NodeSuspicionState{ + WindowStartEpoch: epochID, + CleanPassCount: 7, + ClassACountWindow: 3, + LastClassAEpoch: 42, + } + + // NOTE: the counters live inside an `isFailure` branch that also keys on + // BucketType / ArtifactClass. A bare {ResultClass} fixture does not exercise + // them - my first version of this test asserted against an under-specified + // result and failed for that reason, not because the code was wrong. + k.updateNodeSuspicionHistoryFields(&state, + &types.StorageProofResult{ + ResultClass: types.StorageProofResultClass_STORAGE_PROOF_RESULT_CLASS_TIMEOUT_OR_NO_RESPONSE, + BucketType: types.StorageProofBucketType_STORAGE_PROOF_BUCKET_TYPE_RECENT, + ArtifactClass: types.StorageProofArtifactClass_STORAGE_PROOF_ARTIFACT_CLASS_SYMBOL, + }, epochID, params) + + require.EqualValues(t, 1, state.ClassBCountWindow, + "TIMEOUT_OR_NO_RESPONSE must increment ClassBCountWindow") + require.Equal(t, epochID, state.LastClassBEpoch, + "TIMEOUT_OR_NO_RESPONSE must stamp LastClassBEpoch") + + require.EqualValues(t, 3, state.ClassACountWindow, + "a Class-B liveness failure must NOT increment ClassACountWindow") + require.EqualValues(t, 42, state.LastClassAEpoch, + "a Class-B liveness failure must NOT stamp LastClassAEpoch") + require.EqualValues(t, 7, state.CleanPassCount, + "a Class-B liveness failure must NOT reset Class-A recovery gates") +}