From 0fb3f8f8018896a075ff61bc989e5528edb38ce9 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 24 Feb 2026 09:34:38 +0100 Subject: [PATCH 1/9] security: add attestation height bounds validation The Attest handler previously accepted attestations for any height, allowing attackers to spam storage with attestations for arbitrarily far-future blocks (MaxInt64) or arbitrarily old blocks. This commit adds height bounds validation: - Upper bound: msg.Height <= ctx.BlockHeight() + 1 - Lower bound: msg.Height >= ctx.BlockHeight() - (PruneAfter * EpochLength) - Lower bound floor of 1 for early chain heights The retention window is derived from the existing PruneAfter and EpochLength params, keeping it consistent with the pruning configuration. Adds 7 table-driven test cases covering boundary conditions. --- modules/network/keeper/msg_server.go | 18 +++- modules/network/keeper/msg_server_test.go | 102 ++++++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 1ab62979..0aa1bd7a 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -47,8 +47,22 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M return nil, sdkerr.Wrapf(sdkerrors.ErrNotFound, "validator index not found for %s", msg.ConsensusAddress) } - // todo (Alex): we need to set a limit to not have validators attest old blocks. Also make sure that this relates with - // the retention period for pruning + // Enforce attestation height bounds to prevent storage exhaustion from + // future-height spam and stale attestations for ancient blocks + params := k.GetParams(ctx) + currentHeight := ctx.BlockHeight() + maxFutureHeight := currentHeight + 1 + retentionWindow := int64(params.PruneAfter * params.EpochLength) + minHeight := currentHeight - retentionWindow + if minHeight < 1 { + minHeight = 1 + } + if msg.Height > maxFutureHeight { + return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d exceeds max allowed height %d", msg.Height, maxFutureHeight) + } + if msg.Height < minHeight { + return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d is below minimum allowed height %d", msg.Height, minHeight) + } bitmap, err := k.GetAttestationBitmap(ctx, msg.Height) if err != nil && !errors.Is(err, collections.ErrNotFound) { return nil, sdkerr.Wrap(err, "get attestation bitmap") diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 1366379e..7a9ad990 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -124,6 +124,108 @@ func TestJoinAttesterSet(t *testing.T) { } } +func TestAttestHeightBounds(t *testing.T) { + myValAddr := sdk.ValAddress("validator1") + + specs := map[string]struct { + blockHeight int64 + attestH int64 + pruneAfter uint64 + epochLength uint64 + expErr error + }{ + "future height rejected": { + blockHeight: 100, + attestH: 200, + pruneAfter: 7, + epochLength: 1, + expErr: sdkerrors.ErrInvalidRequest, + }, + "stale height rejected": { + blockHeight: 100, + attestH: 1, + pruneAfter: 7, + epochLength: 1, + expErr: sdkerrors.ErrInvalidRequest, + }, + "current height accepted": { + blockHeight: 100, + attestH: 100, + pruneAfter: 7, + epochLength: 1, + }, + "next height accepted": { + blockHeight: 100, + attestH: 101, + pruneAfter: 7, + epochLength: 1, + }, + "min boundary accepted": { + blockHeight: 100, + attestH: 93, + pruneAfter: 7, + epochLength: 1, + }, + "below min boundary rejected": { + blockHeight: 100, + attestH: 92, + pruneAfter: 7, + epochLength: 1, + expErr: sdkerrors.ErrInvalidRequest, + }, + "early chain - low height accepted": { + blockHeight: 3, + attestH: 1, + pruneAfter: 7, + epochLength: 1, + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + sk := NewMockStakingKeeper() + cdc := moduletestutil.MakeTestEncodingConfig().Codec + keys := storetypes.NewKVStoreKeys(types.StoreKey) + logger := log.NewTestLogger(t) + cms := integration.CreateMultiStore(keys, logger) + authority := authtypes.NewModuleAddress("gov") + keeper := NewKeeper(cdc, runtime.NewKVStoreService(keys[types.StoreKey]), sk, nil, nil, authority.String()) + server := msgServer{Keeper: keeper} + ctx := sdk.NewContext(cms, cmtproto.Header{ + ChainID: "test-chain", + Time: time.Now().UTC(), + Height: spec.blockHeight, + }, false, logger).WithContext(t.Context()) + + // Set params with custom pruneAfter and epochLength + params := types.DefaultParams() + params.PruneAfter = spec.pruneAfter + params.EpochLength = spec.epochLength + keeper.SetParams(ctx, params) + + // Setup: add attester and build index map + require.NoError(t, keeper.SetAttesterSetMember(ctx, myValAddr.String())) + require.NoError(t, keeper.BuildValidatorIndexMap(ctx)) + + msg := &types.MsgAttest{ + Authority: myValAddr.String(), + ConsensusAddress: myValAddr.String(), + Height: spec.attestH, + Vote: []byte("vote"), + } + + rsp, err := server.Attest(ctx, msg) + if spec.expErr != nil { + require.ErrorIs(t, err, spec.expErr) + require.Nil(t, rsp) + return + } + require.NoError(t, err) + require.NotNil(t, rsp) + }) + } +} + var _ types.StakingKeeper = &MockStakingKeeper{} type MockStakingKeeper struct { From 0f4fe6224ed1feec48f6d21b725e76ff04647525 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 24 Feb 2026 09:48:03 +0100 Subject: [PATCH 2/9] fix: guard against integer overflow in retentionWindow calculation Cast PruneAfter and EpochLength to int64 before multiplication to avoid uint64 overflow followed by negative int64 wrapping. If the product overflows int64, cap at MaxInt64 to keep the lower bound permissive. --- modules/network/keeper/msg_server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 0aa1bd7a..71ac88b2 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -52,7 +52,12 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M params := k.GetParams(ctx) currentHeight := ctx.BlockHeight() maxFutureHeight := currentHeight + 1 - retentionWindow := int64(params.PruneAfter * params.EpochLength) + pruneAfter := int64(params.PruneAfter) + epochLen := int64(params.EpochLength) + retentionWindow := pruneAfter * epochLen + if retentionWindow < 0 { // guard against int64 overflow on extreme param values + retentionWindow = 1<<63 - 1 // math.MaxInt64 + } minHeight := currentHeight - retentionWindow if minHeight < 1 { minHeight = 1 From 110da3c4203a162f951e89c21ccadbc0980c3430 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 24 Feb 2026 11:03:55 +0100 Subject: [PATCH 3/9] fix: check SetParams error return in test (errcheck) --- modules/network/keeper/msg_server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 7a9ad990..99a2d360 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -201,7 +201,7 @@ func TestAttestHeightBounds(t *testing.T) { params := types.DefaultParams() params.PruneAfter = spec.pruneAfter params.EpochLength = spec.epochLength - keeper.SetParams(ctx, params) + require.NoError(t, keeper.SetParams(ctx, params)) // Setup: add attester and build index map require.NoError(t, keeper.SetAttesterSetMember(ctx, myValAddr.String())) From 892b29b55c832a77e81d869fceed7ce05c22baab Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 24 Feb 2026 11:22:17 +0100 Subject: [PATCH 4/9] fix: only enforce future-height upper bound, allow stale attestations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lower-bound check rejected stale attestations that the integration test legitimately submits (backward-attesting from height 1). Stale attestations are harmless — they just waste gas and get pruned by the existing pruning logic. The real security fix is preventing future-height spam that could exhaust state storage. --- modules/network/keeper/msg_server.go | 19 ++---------- modules/network/keeper/msg_server_test.go | 37 +++-------------------- 2 files changed, 7 insertions(+), 49 deletions(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 71ac88b2..fd8cf71f 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -47,27 +47,14 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M return nil, sdkerr.Wrapf(sdkerrors.ErrNotFound, "validator index not found for %s", msg.ConsensusAddress) } - // Enforce attestation height bounds to prevent storage exhaustion from - // future-height spam and stale attestations for ancient blocks - params := k.GetParams(ctx) + // Enforce attestation height upper bound to prevent storage exhaustion + // from future-height spam. Stale attestations are harmless (they get pruned) + // and are allowed for backward-attesting workflows. currentHeight := ctx.BlockHeight() maxFutureHeight := currentHeight + 1 - pruneAfter := int64(params.PruneAfter) - epochLen := int64(params.EpochLength) - retentionWindow := pruneAfter * epochLen - if retentionWindow < 0 { // guard against int64 overflow on extreme param values - retentionWindow = 1<<63 - 1 // math.MaxInt64 - } - minHeight := currentHeight - retentionWindow - if minHeight < 1 { - minHeight = 1 - } if msg.Height > maxFutureHeight { return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d exceeds max allowed height %d", msg.Height, maxFutureHeight) } - if msg.Height < minHeight { - return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d is below minimum allowed height %d", msg.Height, minHeight) - } bitmap, err := k.GetAttestationBitmap(ctx, msg.Height) if err != nil && !errors.Is(err, collections.ErrNotFound) { return nil, sdkerr.Wrap(err, "get attestation bitmap") diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 99a2d360..6c59a4b7 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -130,54 +130,29 @@ func TestAttestHeightBounds(t *testing.T) { specs := map[string]struct { blockHeight int64 attestH int64 - pruneAfter uint64 - epochLength uint64 expErr error }{ "future height rejected": { blockHeight: 100, attestH: 200, - pruneAfter: 7, - epochLength: 1, expErr: sdkerrors.ErrInvalidRequest, }, - "stale height rejected": { + "two-ahead rejected": { blockHeight: 100, - attestH: 1, - pruneAfter: 7, - epochLength: 1, + attestH: 102, expErr: sdkerrors.ErrInvalidRequest, }, "current height accepted": { blockHeight: 100, attestH: 100, - pruneAfter: 7, - epochLength: 1, }, "next height accepted": { blockHeight: 100, attestH: 101, - pruneAfter: 7, - epochLength: 1, - }, - "min boundary accepted": { - blockHeight: 100, - attestH: 93, - pruneAfter: 7, - epochLength: 1, }, - "below min boundary rejected": { + "old height accepted": { blockHeight: 100, - attestH: 92, - pruneAfter: 7, - epochLength: 1, - expErr: sdkerrors.ErrInvalidRequest, - }, - "early chain - low height accepted": { - blockHeight: 3, attestH: 1, - pruneAfter: 7, - epochLength: 1, }, } @@ -197,11 +172,7 @@ func TestAttestHeightBounds(t *testing.T) { Height: spec.blockHeight, }, false, logger).WithContext(t.Context()) - // Set params with custom pruneAfter and epochLength - params := types.DefaultParams() - params.PruneAfter = spec.pruneAfter - params.EpochLength = spec.epochLength - require.NoError(t, keeper.SetParams(ctx, params)) + require.NoError(t, keeper.SetParams(ctx, types.DefaultParams())) // Setup: add attester and build index map require.NoError(t, keeper.SetAttesterSetMember(ctx, myValAddr.String())) From 7ec9962b94e95ae3be1ab153ef0408e40e36fb23 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Tue, 24 Feb 2026 17:26:46 +0100 Subject: [PATCH 5/9] Add lower border for attestation height --- modules/network/keeper/msg_server.go | 19 +++++++++++++++++-- modules/network/keeper/msg_server_test.go | 21 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 94ffc582..3faee4aa 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -52,13 +52,28 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M } // Enforce attestation height upper bound to prevent storage exhaustion - // from future-height spam. Stale attestations are harmless (they get pruned) - // and are allowed for backward-attesting workflows. + // from future-height spam. currentHeight := ctx.BlockHeight() maxFutureHeight := currentHeight + 1 if msg.Height > maxFutureHeight { return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d exceeds max allowed height %d", msg.Height, maxFutureHeight) } + + // Enforce attestation height lower bound: reject heights that fall below + // the PruneAfter retention window. Attesting pruned/about-to-be-pruned + // heights wastes storage and serves no purpose. This uses the same epoch + // calculation as PruneOldBitmaps so the two stay aligned. + params := k.GetParams(ctx) + minHeight := int64(1) + if params.PruneAfter > 0 && params.EpochLength > 0 { + currentEpoch := uint64(currentHeight) / params.EpochLength + if currentEpoch > params.PruneAfter { + minHeight = int64((currentEpoch - params.PruneAfter) * params.EpochLength) + } + } + if msg.Height < minHeight { + return nil, sdkerr.Wrapf(sdkerrors.ErrInvalidRequest, "attestation height %d is below retention window (min %d)", msg.Height, minHeight) + } bitmap, err := k.GetAttestationBitmap(ctx, msg.Height) if err != nil && !errors.Is(err, collections.ErrNotFound) { return nil, sdkerr.Wrap(err, "get attestation bitmap") diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 3ffbdba9..085bb1b0 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -227,6 +227,8 @@ func TestAttestVotePayloadValidation(t *testing.T) { func TestAttestHeightBounds(t *testing.T) { myValAddr := sdk.ValAddress("validator1") + // With DefaultParams: EpochLength=1, PruneAfter=7 + // At blockHeight=100: currentEpoch=100, minHeight=(100-7)*1=93 specs := map[string]struct { blockHeight int64 @@ -251,9 +253,24 @@ func TestAttestHeightBounds(t *testing.T) { blockHeight: 100, attestH: 101, }, - "old height accepted": { + "stale height rejected": { blockHeight: 100, attestH: 1, + expErr: sdkerrors.ErrInvalidRequest, + }, + "below retention window rejected": { + blockHeight: 100, + attestH: 92, // minHeight = 93 + expErr: sdkerrors.ErrInvalidRequest, + }, + "at retention boundary accepted": { + blockHeight: 100, + attestH: 93, // exactly minHeight + }, + "early chain no stale rejection": { + // blockHeight=5, currentEpoch=5, PruneAfter=7 → currentEpoch <= PruneAfter → minHeight=1 + blockHeight: 5, + attestH: 1, }, } @@ -283,7 +300,7 @@ func TestAttestHeightBounds(t *testing.T) { Authority: myValAddr.String(), ConsensusAddress: myValAddr.String(), Height: spec.attestH, - Vote: []byte("vote"), + Vote: make([]byte, MinVoteLen), } rsp, err := server.Attest(ctx, msg) From abae484bd38824d03a27bb420401672a36310b8f Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 25 Feb 2026 08:27:37 +0100 Subject: [PATCH 6/9] refactor(network): use GetCurrentEpoch in Attest lower-bound check Replace the inline epoch computation (uint64(currentHeight)/params.EpochLength) with k.GetCurrentEpoch(ctx) so the Attest lower-bound and PruneOldBitmaps use the same formula and cannot silently diverge. Extend TestAttestHeightBounds with two sub-cases using EpochLength=10 and a blockHeight (35) that is not on an epoch boundary, asserting that the floor-to-epoch-start rounding is correct: attestH=0 rejected (below minHeight=10), attestH=10 accepted (at minHeight). --- modules/network/keeper/msg_server.go | 2 +- modules/network/keeper/msg_server_test.go | 32 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 3faee4aa..3cde26ec 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -66,7 +66,7 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M params := k.GetParams(ctx) minHeight := int64(1) if params.PruneAfter > 0 && params.EpochLength > 0 { - currentEpoch := uint64(currentHeight) / params.EpochLength + currentEpoch := k.GetCurrentEpoch(ctx) if currentEpoch > params.PruneAfter { minHeight = int64((currentEpoch - params.PruneAfter) * params.EpochLength) } diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 085bb1b0..678d214e 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -233,6 +233,7 @@ func TestAttestHeightBounds(t *testing.T) { specs := map[string]struct { blockHeight int64 attestH int64 + params *types.Params // nil → DefaultParams() expErr error }{ "future height rejected": { @@ -272,6 +273,31 @@ func TestAttestHeightBounds(t *testing.T) { blockHeight: 5, attestH: 1, }, + // Cases with EpochLength>1 and blockHeight NOT on an epoch boundary. + // EpochLength=10, PruneAfter=2, blockHeight=35 (mid epoch 3): + // GetCurrentEpoch = 35/10 = 3 (integer floor — not aligned to boundary) + // currentEpoch(3) > PruneAfter(2) → minHeight = (3-2)*10 = 10 + // attestH must be a checkpoint (multiple of EpochLength) to bypass the + // SIGN_MODE_CHECKPOINT guard and reach the lower-bound check. + // This asserts that the floor-to-epoch-start rounding from GetCurrentEpoch + // is preserved and consistent with PruneOldBitmaps. + "multi-epoch mid-block below retention rejected": { + blockHeight: 35, // epoch 3 (floor), mid-epoch + attestH: 0, // checkpoint (0%10==0), but < minHeight(10) → rejected + params: func() *types.Params { + p := types.NewParams(10, types.DefaultQuorumFraction, types.DefaultMinParticipation, 2, types.DefaultSignMode) + return &p + }(), + expErr: sdkerrors.ErrInvalidRequest, + }, + "multi-epoch mid-block at retention boundary accepted": { + blockHeight: 35, // epoch 3 (floor), mid-epoch + attestH: 10, // checkpoint (10%10==0) and == minHeight(10) → accepted + params: func() *types.Params { + p := types.NewParams(10, types.DefaultQuorumFraction, types.DefaultMinParticipation, 2, types.DefaultSignMode) + return &p + }(), + }, } for name, spec := range specs { @@ -290,7 +316,11 @@ func TestAttestHeightBounds(t *testing.T) { Height: spec.blockHeight, }, false, logger).WithContext(t.Context()) - require.NoError(t, keeper.SetParams(ctx, types.DefaultParams())) + p := types.DefaultParams() + if spec.params != nil { + p = *spec.params + } + require.NoError(t, keeper.SetParams(ctx, p)) // Setup: add attester and build index map require.NoError(t, keeper.SetAttesterSetMember(ctx, myValAddr.String())) From 76494ef172431cf34a335036d8d9ffe82286bc91 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 25 Feb 2026 08:31:01 +0100 Subject: [PATCH 7/9] Revert "refactor(network): use GetCurrentEpoch in Attest lower-bound check" This reverts commit abae484bd38824d03a27bb420401672a36310b8f. --- modules/network/keeper/msg_server.go | 2 +- modules/network/keeper/msg_server_test.go | 32 +---------------------- 2 files changed, 2 insertions(+), 32 deletions(-) diff --git a/modules/network/keeper/msg_server.go b/modules/network/keeper/msg_server.go index 3cde26ec..3faee4aa 100644 --- a/modules/network/keeper/msg_server.go +++ b/modules/network/keeper/msg_server.go @@ -66,7 +66,7 @@ func (k msgServer) Attest(goCtx context.Context, msg *types.MsgAttest) (*types.M params := k.GetParams(ctx) minHeight := int64(1) if params.PruneAfter > 0 && params.EpochLength > 0 { - currentEpoch := k.GetCurrentEpoch(ctx) + currentEpoch := uint64(currentHeight) / params.EpochLength if currentEpoch > params.PruneAfter { minHeight = int64((currentEpoch - params.PruneAfter) * params.EpochLength) } diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 678d214e..085bb1b0 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -233,7 +233,6 @@ func TestAttestHeightBounds(t *testing.T) { specs := map[string]struct { blockHeight int64 attestH int64 - params *types.Params // nil → DefaultParams() expErr error }{ "future height rejected": { @@ -273,31 +272,6 @@ func TestAttestHeightBounds(t *testing.T) { blockHeight: 5, attestH: 1, }, - // Cases with EpochLength>1 and blockHeight NOT on an epoch boundary. - // EpochLength=10, PruneAfter=2, blockHeight=35 (mid epoch 3): - // GetCurrentEpoch = 35/10 = 3 (integer floor — not aligned to boundary) - // currentEpoch(3) > PruneAfter(2) → minHeight = (3-2)*10 = 10 - // attestH must be a checkpoint (multiple of EpochLength) to bypass the - // SIGN_MODE_CHECKPOINT guard and reach the lower-bound check. - // This asserts that the floor-to-epoch-start rounding from GetCurrentEpoch - // is preserved and consistent with PruneOldBitmaps. - "multi-epoch mid-block below retention rejected": { - blockHeight: 35, // epoch 3 (floor), mid-epoch - attestH: 0, // checkpoint (0%10==0), but < minHeight(10) → rejected - params: func() *types.Params { - p := types.NewParams(10, types.DefaultQuorumFraction, types.DefaultMinParticipation, 2, types.DefaultSignMode) - return &p - }(), - expErr: sdkerrors.ErrInvalidRequest, - }, - "multi-epoch mid-block at retention boundary accepted": { - blockHeight: 35, // epoch 3 (floor), mid-epoch - attestH: 10, // checkpoint (10%10==0) and == minHeight(10) → accepted - params: func() *types.Params { - p := types.NewParams(10, types.DefaultQuorumFraction, types.DefaultMinParticipation, 2, types.DefaultSignMode) - return &p - }(), - }, } for name, spec := range specs { @@ -316,11 +290,7 @@ func TestAttestHeightBounds(t *testing.T) { Height: spec.blockHeight, }, false, logger).WithContext(t.Context()) - p := types.DefaultParams() - if spec.params != nil { - p = *spec.params - } - require.NoError(t, keeper.SetParams(ctx, p)) + require.NoError(t, keeper.SetParams(ctx, types.DefaultParams())) // Setup: add attester and build index map require.NoError(t, keeper.SetAttesterSetMember(ctx, myValAddr.String())) From c74874ee3e4a5254efae722964ba4fa8ae5c71a4 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 25 Feb 2026 14:47:19 +0100 Subject: [PATCH 8/9] Extend prune interval for bigger attestation window --- modules/network/keeper/msg_server_test.go | 5 ++--- modules/network/types/params.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index 085bb1b0..b6d6682d 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -227,7 +227,7 @@ func TestAttestVotePayloadValidation(t *testing.T) { func TestAttestHeightBounds(t *testing.T) { myValAddr := sdk.ValAddress("validator1") - // With DefaultParams: EpochLength=1, PruneAfter=7 + // With DefaultParams: EpochLength=1, PruneAfter=15 // At blockHeight=100: currentEpoch=100, minHeight=(100-7)*1=93 specs := map[string]struct { @@ -268,8 +268,7 @@ func TestAttestHeightBounds(t *testing.T) { attestH: 93, // exactly minHeight }, "early chain no stale rejection": { - // blockHeight=5, currentEpoch=5, PruneAfter=7 → currentEpoch <= PruneAfter → minHeight=1 - blockHeight: 5, + blockHeight: 16, attestH: 1, }, } diff --git a/modules/network/types/params.go b/modules/network/types/params.go index 811bf9e2..d97a34d7 100644 --- a/modules/network/types/params.go +++ b/modules/network/types/params.go @@ -11,7 +11,7 @@ var ( DefaultEpochLength = uint64(1) // Changed from 10 to 1 to allow attestations on every block DefaultQuorumFraction = math.LegacyNewDecWithPrec(667, 3) // 2/3 DefaultMinParticipation = math.LegacyNewDecWithPrec(5, 1) // 1/2 - DefaultPruneAfter = uint64(7) + DefaultPruneAfter = uint64(15) // also used as number of blocks for attestations to land DefaultSignMode = SignMode_SIGN_MODE_CHECKPOINT ) From 0d7822db8d2fd4caad6bc010dc7e591cb099e022 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Wed, 25 Feb 2026 15:21:23 +0100 Subject: [PATCH 9/9] Fix test --- modules/network/keeper/msg_server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/network/keeper/msg_server_test.go b/modules/network/keeper/msg_server_test.go index b6d6682d..4b9623ad 100644 --- a/modules/network/keeper/msg_server_test.go +++ b/modules/network/keeper/msg_server_test.go @@ -260,7 +260,7 @@ func TestAttestHeightBounds(t *testing.T) { }, "below retention window rejected": { blockHeight: 100, - attestH: 92, // minHeight = 93 + attestH: 84, // minHeight = 85 expErr: sdkerrors.ErrInvalidRequest, }, "at retention boundary accepted": {