Skip to content

Commit c48449e

Browse files
fix(audit): keep disk pressure out of postponed state (#146)
* fix(audit): keep disk pressure out of postponed state * test(systemtests): align bootstrap-recovery test with LEP-6 §17 disk-pressure invariant The PR removes disk from selfHostViolatesMinimums / selfHostCompliant so disk pressure is owned exclusively by the STORAGE_FULL transition path. TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed was written under the old invariant (disk-non-compliance → stays POSTPONED) and started observing POSTPONED → STORAGE_FULL after this PR landed in CI. Mirrors the unit-level refactor of TestEnforceEpochEnd_EmptyActiveSet_NonCompliantSelf_NoRecover already in this PR (disk → cpu swap). Changes: - Swap the violating metric in TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed from disk_usage_percent=95 / MinDiskFreePercent=20 to cpu_usage_percent=95 / MinCpuFreePercent=20. The intent of the test (self-compliance still gates the bootstrap exception) is preserved on a metric that still postpones. - Add TestAuditEmptyActiveSetBootstrap_DiskPressureGoesToStorageFull, the end-to-end mirror of unit-level TestEnforceEpochEnd_RecoversPostponedToStorageFullWhenDiskStillHigh: empty active set + POSTPONED self + disk > MaxStorageUsagePercent → recovers to STORAGE_FULL (not ACTIVE, not stuck POSTPONED). - Replace helper setAuditParamsForFastEpochsWithMinDiskFree with setAuditParamsForFastEpochsWithMinCpuFree (the old helper had a single caller and contradicted the new invariant). - Add auditHostReportWithCpuUsageJSON; keep auditHostReportWithDiskUsageJSON for the new STORAGE_FULL test. * fix(audit): align storage-full contract surfaces * fix(audit): address storage-full review comments * fix(supernode): remove stale storage-full nolint * fix(audit): preserve released v1.11.1 handler
1 parent e4b1619 commit c48449e

17 files changed

Lines changed: 420 additions & 63 deletions

tests/systemtests/audit_empty_active_set_bootstrap_test.go

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,29 @@ func TestAuditEmptyActiveSetBootstrap_HostOnlyReportsRecover(t *testing.T) {
115115

116116
// TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed verifies
117117
// the bootstrap-recovery exception still gates on self-compliance. A
118-
// POSTPONED supernode that submits a host report violating a min-free
119-
// threshold MUST remain POSTPONED even when the active set is empty.
118+
// POSTPONED supernode that submits a host report violating a non-storage
119+
// min-free threshold (CPU here) MUST remain POSTPONED even when the active
120+
// set is empty.
120121
//
121122
// This guards against the exception turning into a "free pass" for
122123
// misbehaving SNs and complements the unit-level tests in
123124
// x/audit/v1/keeper/enforcement_empty_active_set_test.go.
125+
//
126+
// Note: per LEP-6 §17 disk pressure is owned exclusively by the STORAGE_FULL
127+
// transition path (audit SetReport) and is no longer a postpone reason, so
128+
// this test exercises the non-storage CPU path. The disk-pressure bootstrap
129+
// case is covered by TestAuditEmptyActiveSetBootstrap_DiskPressureGoesToStorageFull.
124130
func TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed(t *testing.T) {
125131
const (
126132
epochLengthBlocks = uint64(10)
127133
originHeight = int64(1)
128134
)
129135

130-
// Set a non-zero MinDiskFreePercent so non-compliant disk usage in the host
136+
// Set a non-zero MinCpuFreePercent so non-compliant CPU usage in the host
131137
// report blocks self-compliance.
132138
sut.ModifyGenesisJSON(t,
133139
setSupernodeParamsForAuditTests(t),
134-
setAuditParamsForFastEpochsWithMinDiskFree(t, epochLengthBlocks, 1, 1, 1, []uint32{4444}, 20),
140+
setAuditParamsForFastEpochsWithMinCpuFree(t, epochLengthBlocks, 1, 1, 1, []uint32{4444}, 20),
135141
)
136142
sut.StartChain(t)
137143

@@ -153,10 +159,10 @@ func TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed(t *testing.
153159
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n0.valAddr))
154160
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n1.valAddr))
155161

156-
// Epoch 1: empty active set. Both submit host reports with disk usage 95%
157-
// (5% free, below the 20% MinDiskFreePercent). Self-compliance fails.
162+
// Epoch 1: empty active set. Both submit host reports with CPU usage 95%
163+
// (5% free, below the 20% MinCpuFreePercent). Self-compliance fails.
158164
epochID1 := uint64((epoch1Start - originHeight) / int64(epochLengthBlocks))
159-
hostNonCompliant := auditHostReportWithDiskUsageJSON([]string{"PORT_STATE_OPEN"}, 95.0)
165+
hostNonCompliant := auditHostReportWithCpuUsageJSON([]string{"PORT_STATE_OPEN"}, 95.0)
160166
RequireTxSuccess(t, submitEpochReport(t, cli, n0.nodeName, epochID1, hostNonCompliant, nil))
161167
RequireTxSuccess(t, submitEpochReport(t, cli, n1.nodeName, epochID1, hostNonCompliant, nil))
162168

@@ -168,3 +174,73 @@ func TestAuditEmptyActiveSetBootstrap_NonCompliantHostStaysPostponed(t *testing.
168174
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n1.valAddr),
169175
"node1 should remain POSTPONED — self-compliance gate blocks the bootstrap exception")
170176
}
177+
178+
// TestAuditEmptyActiveSetBootstrap_DiskPressureGoesToStorageFull verifies the
179+
// LEP-6 §17 invariant that disk pressure is owned exclusively by the
180+
// STORAGE_FULL transition path, not by audit_host_requirements POSTPONE.
181+
//
182+
// Scenario:
183+
// 1. Two SNs register and miss epoch 0 reports → both POSTPONED for
184+
// audit_missing_reports.
185+
// 2. Epoch 1: empty active set. Both submit host reports with disk usage
186+
// above the supernode module's MaxStorageUsagePercent (default 90).
187+
// 3. Epoch 1 end: the bootstrap-recovery exception fires because
188+
// selfHostCompliant ignores disk (only cpu/mem gate the bootstrap
189+
// exception). The new recoverSupernodeFromPostponed helper observes
190+
// disk > MaxStorageUsagePercent in the same epoch's report and steers
191+
// recovery to STORAGE_FULL instead of ACTIVE.
192+
//
193+
// Invariant locked in: disk pressure never produces ACTIVE in this branch,
194+
// never produces POSTPONED via audit_host_requirements, and produces
195+
// STORAGE_FULL exactly when disk > MaxStorageUsagePercent.
196+
func TestAuditEmptyActiveSetBootstrap_DiskPressureGoesToStorageFull(t *testing.T) {
197+
const (
198+
epochLengthBlocks = uint64(10)
199+
originHeight = int64(1)
200+
)
201+
202+
// No MinCpuFreePercent / MinMemFreePercent override → only disk pressure
203+
// is in play. The supernode module's default MaxStorageUsagePercent (90)
204+
// gates the STORAGE_FULL transition; we report 95% to cross it.
205+
sut.ModifyGenesisJSON(t,
206+
setSupernodeParamsForAuditTests(t),
207+
setAuditParamsForFastEpochs(t, epochLengthBlocks, 1, 1, 1, []uint32{4444}),
208+
)
209+
sut.StartChain(t)
210+
211+
cli := NewLumeradCLI(t, sut, true)
212+
n0 := getNodeIdentity(t, cli, "node0")
213+
n1 := getNodeIdentity(t, cli, "node1")
214+
215+
registerSupernode(t, cli, n0, "192.168.1.1")
216+
registerSupernode(t, cli, n1, "192.168.1.2")
217+
218+
// Epoch 0: no reports → both POSTPONED for audit_missing_reports.
219+
currentHeight := sut.AwaitNextBlock(t)
220+
_, epoch0Start := nextEpochAfterHeight(originHeight, epochLengthBlocks, currentHeight)
221+
epoch1Start := epoch0Start + int64(epochLengthBlocks)
222+
epoch2Start := epoch1Start + int64(epochLengthBlocks)
223+
224+
awaitAtLeastHeightWithSlack(t, epoch1Start)
225+
226+
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n0.valAddr))
227+
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n1.valAddr))
228+
229+
// Epoch 1: empty active set. Both submit host reports with disk usage
230+
// 95% (> MaxStorageUsagePercent=90). Self-compliance passes (no
231+
// cpu/mem floor configured), so the bootstrap exception fires; the
232+
// recovery helper observes the high disk and steers to STORAGE_FULL.
233+
epochID1 := uint64((epoch1Start - originHeight) / int64(epochLengthBlocks))
234+
hostHighDisk := auditHostReportWithDiskUsageJSON([]string{"PORT_STATE_OPEN"}, 95.0)
235+
RequireTxSuccess(t, submitEpochReport(t, cli, n0.nodeName, epochID1, hostHighDisk, nil))
236+
RequireTxSuccess(t, submitEpochReport(t, cli, n1.nodeName, epochID1, hostHighDisk, nil))
237+
238+
awaitAtLeastHeightWithSlack(t, epoch2Start)
239+
240+
// LEP-6 §17 invariant: disk pressure routes POSTPONED → STORAGE_FULL,
241+
// never POSTPONED → ACTIVE, never stuck POSTPONED on audit_host_requirements.
242+
require.Equal(t, "SUPERNODE_STATE_STORAGE_FULL", querySupernodeLatestState(t, cli, n0.valAddr),
243+
"node0 should transition POSTPONED → STORAGE_FULL via the audit recovery helper (disk > MaxStorageUsagePercent)")
244+
require.Equal(t, "SUPERNODE_STATE_STORAGE_FULL", querySupernodeLatestState(t, cli, n1.valAddr),
245+
"node1 should transition POSTPONED → STORAGE_FULL via the audit recovery helper (disk > MaxStorageUsagePercent)")
246+
}

tests/systemtests/audit_test_helpers_test.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,22 @@ func setAuditParamsForFastEpochs(t *testing.T, epochLengthBlocks uint64, peerQuo
6666
}
6767
}
6868

69-
// setAuditParamsForFastEpochsWithMinDiskFree is setAuditParamsForFastEpochs
70-
// plus an explicit MinDiskFreePercent override. Used by tests that need to
71-
// exercise the self-compliance gate against the host report's disk-usage
72-
// field (e.g. the empty-active-set bootstrap exception's self-compliance
73-
// guard).
74-
func setAuditParamsForFastEpochsWithMinDiskFree(t *testing.T, epochLengthBlocks uint64, peerQuorumReports, minTargets, maxTargets uint32, requiredOpenPorts []uint32, minDiskFreePercent uint32) GenesisMutator {
69+
// setAuditParamsForFastEpochsWithMinCpuFree is setAuditParamsForFastEpochs
70+
// plus an explicit MinCpuFreePercent override. Used by tests that need to
71+
// exercise the self-compliance gate against a non-storage host minimum
72+
// (e.g. the empty-active-set bootstrap exception's self-compliance guard).
73+
//
74+
// Note: disk pressure is intentionally NOT used for postpone/self-compliance
75+
// gating — per LEP-6 it is owned exclusively by the STORAGE_FULL transition
76+
// path in audit SetReport. Tests that want to keep an SN out of the recovery
77+
// path must drive non-compliance via a non-storage metric (cpu or mem).
78+
func setAuditParamsForFastEpochsWithMinCpuFree(t *testing.T, epochLengthBlocks uint64, peerQuorumReports, minTargets, maxTargets uint32, requiredOpenPorts []uint32, minCpuFreePercent uint32) GenesisMutator {
7579
base := setAuditParamsForFastEpochs(t, epochLengthBlocks, peerQuorumReports, minTargets, maxTargets, requiredOpenPorts)
7680
return func(genesis []byte) []byte {
7781
t.Helper()
7882
state := base(genesis)
7983
var err error
80-
state, err = sjson.SetRawBytes(state, "app_state.audit.params.min_disk_free_percent", []byte(strconv.FormatUint(uint64(minDiskFreePercent), 10)))
84+
state, err = sjson.SetRawBytes(state, "app_state.audit.params.min_cpu_free_percent", []byte(strconv.FormatUint(uint64(minCpuFreePercent), 10)))
8185
require.NoError(t, err)
8286
return state
8387
}
@@ -311,7 +315,7 @@ func auditHostReportJSON(inboundPortStates []string) string {
311315

312316
// auditHostReportWithDiskUsageJSON is like auditHostReportJSON but lets the
313317
// caller pin disk_usage_percent. Used by tests that exercise the
314-
// self-compliance gate (e.g. min-free thresholds).
318+
// STORAGE_FULL transition via the SetReport disk-pressure path.
315319
func auditHostReportWithDiskUsageJSON(inboundPortStates []string, diskUsagePercent float64) string {
316320
bz, _ := json.Marshal(map[string]any{
317321
"cpu_usage_percent": 1.0,
@@ -323,6 +327,20 @@ func auditHostReportWithDiskUsageJSON(inboundPortStates []string, diskUsagePerce
323327
return string(bz)
324328
}
325329

330+
// auditHostReportWithCpuUsageJSON is like auditHostReportJSON but lets the
331+
// caller pin cpu_usage_percent. Used by tests that exercise the
332+
// self-compliance gate against a non-storage host minimum.
333+
func auditHostReportWithCpuUsageJSON(inboundPortStates []string, cpuUsagePercent float64) string {
334+
bz, _ := json.Marshal(map[string]any{
335+
"cpu_usage_percent": cpuUsagePercent,
336+
"mem_usage_percent": 1.0,
337+
"disk_usage_percent": 1.0,
338+
"inbound_port_states": inboundPortStates,
339+
"failed_actions_count": 0,
340+
})
341+
return string(bz)
342+
}
343+
326344
// storageChallengeObservationJSON builds the JSON payload for --storage-challenge-observations flag.
327345
func storageChallengeObservationJSON(targetSupernodeAccount string, portStates []string) string {
328346
bz, _ := json.Marshal(map[string]any{

x/audit/v1/POSTPONEMENT_RULES.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Postponement and Recovery Rules (audit/v1)
22

3-
This document describes the on-chain rules implemented by the audit module (v1) for switching a supernode between `ACTIVE` and `POSTPONED`, and for recovering back to `ACTIVE`.
3+
This document describes the on-chain rules implemented by the audit module (v1) for switching a supernode between `ACTIVE` and `POSTPONED`, and for recovering out of `POSTPONED`.
44

55
## Definitions
66

@@ -33,10 +33,11 @@ This is evaluated by checking for a stored report in each of the last `N` epochs
3333

3434
### 2) Host Report requirements
3535

36-
If a submitted host report violates any enabled minimum free% threshold, the supernode is set to `POSTPONED`.
36+
If a submitted host report violates any enabled CPU or memory minimum free% threshold, the supernode is set to `POSTPONED`.
3737

38-
- Params: `min_cpu_free_percent`, `min_mem_free_percent`, `min_disk_free_percent` (`free% = 100 - usage%`).
38+
- Params: `min_cpu_free_percent`, `min_mem_free_percent` (`free% = 100 - usage%`).
3939
- Special case: if `*_usage_percent == 0`, that metric is treated as **unknown** and does not trigger postponement.
40+
- Disk pressure is not a postponement criterion; it is handled by the `STORAGE_FULL` state path.
4041

4142
The following host-report fields are currently ignored by postponement logic:
4243
- `failed_actions_count`
@@ -50,8 +51,12 @@ An epoch counts toward the consecutive requirement only if:
5051
- there is at least **1** peer reporter about the target in that epoch, and
5152
- the share of peer reporters about the target in that epoch that report `PORT_STATE_CLOSED` for port index `i` meets or exceeds `peer_port_postpone_threshold_percent`.
5253

53-
## Recovery rule (POSTPONED → ACTIVE)
54+
## Recovery rule (POSTPONED → ACTIVE or STORAGE_FULL)
5455

55-
In a single epoch, a `POSTPONED` supernode becomes `ACTIVE` if:
56+
In a single epoch, a `POSTPONED` supernode recovers if:
5657
- it submits one compliant host report (Host Report requirements), and
5758
- there exists at least **1** peer report about that supernode in the same epoch where **all** required ports are `PORT_STATE_OPEN`.
59+
60+
The recovery target is determined from the same-epoch self HostReport:
61+
- if `disk_usage_percent` is omitted/zero or is at or below `supernode.max_storage_usage_percent`, the supernode becomes `ACTIVE`;
62+
- if `disk_usage_percent` is above `supernode.max_storage_usage_percent`, the supernode becomes `STORAGE_FULL`.

x/audit/v1/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,18 @@ At epoch end, a supernode can be postponed for:
8787

8888
- **Action-finalization evidence thresholds** (per-epoch counts meeting consecutive-epoch windows),
8989
- **Missing reports** for `consecutive_epochs_to_postpone` consecutive epochs,
90-
- **Self Report minimum failures** (CPU/mem/disk free% thresholds),
90+
- **Self Report minimum failures** (CPU/mem free% thresholds),
9191
- **Peer port thresholds**: a required port is treated as CLOSED if peer observations meet `peer_port_postpone_threshold_percent`, and this happens for `consecutive_epochs_to_postpone` consecutive epochs.
9292

93-
### Recovery (`POSTPONED -> ACTIVE`)
93+
### Recovery (`POSTPONED -> ACTIVE` or `STORAGE_FULL`)
9494

9595
At epoch end, a supernode can recover:
9696

9797
- If postponed due to action-finalization evidence: by the action-finalization recovery window and total-bad-evidence constraint.
9898
- Otherwise: if it has a compliant self report and at least one peer observation in the epoch where all required ports are `OPEN`.
9999

100+
If the same-epoch self HostReport still has `disk_usage_percent` above `supernode.max_storage_usage_percent`, recovery routes to `STORAGE_FULL` instead of `ACTIVE`.
101+
100102
Detailed behavior is implemented in the module's epoch-end enforcement logic.
101103

102104
## Evidence
@@ -192,7 +194,7 @@ Params are initialized from genesis and may later be updated by governance via `
192194
- Enforcement:
193195
- `min_cpu_free_percent`: `0` (disabled)
194196
- `min_mem_free_percent`: `0` (disabled)
195-
- `min_disk_free_percent`: `0` (disabled)
197+
- `min_disk_free_percent`: `0` (legacy/no-op for epoch-end postponement; disk pressure is handled as `STORAGE_FULL`)
196198
- `consecutive_epochs_to_postpone`: `1`
197199
- `peer_port_postpone_threshold_percent`: `100`
198200
- `keep_last_epoch_entries`: `200`

x/audit/v1/keeper/enforcement.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (k Keeper) EnforceEpochEnd(ctx sdk.Context, epochID uint64, params types.Pa
8585
continue
8686
}
8787

88-
if err := k.recoverSupernodeActive(ctx, sn); err != nil {
88+
if err := k.recoverSupernodeFromPostponed(ctx, sn, epochID); err != nil {
8989
return err
9090
}
9191
k.clearActionFinalizationPostponedAtEpochID(ctx, sn.SupernodeAccount)
@@ -413,16 +413,14 @@ func (k Keeper) selfHostViolatesMinimums(ctx sdk.Context, supernodeAccount strin
413413
return false, nil
414414
}
415415

416-
// If any known metric is below minimum free%, postpone.
416+
// If any known non-storage metric is below minimum free%, postpone.
417+
// Disk pressure is modeled as STORAGE_FULL by audit SetReport, not POSTPONED.
417418
if violatesMinFree(r.HostReport.CpuUsagePercent, params.MinCpuFreePercent) {
418419
return true, nil
419420
}
420421
if violatesMinFree(r.HostReport.MemUsagePercent, params.MinMemFreePercent) {
421422
return true, nil
422423
}
423-
if violatesMinFree(r.HostReport.DiskUsagePercent, params.MinDiskFreePercent) {
424-
return true, nil
425-
}
426424

427425
return false, nil
428426
}
@@ -439,9 +437,6 @@ func (k Keeper) selfHostCompliant(ctx sdk.Context, supernodeAccount string, epoc
439437
if !compliesMinFree(r.HostReport.MemUsagePercent, params.MinMemFreePercent) {
440438
return false, nil
441439
}
442-
if !compliesMinFree(r.HostReport.DiskUsagePercent, params.MinDiskFreePercent) {
443-
return false, nil
444-
}
445440

446441
return true, nil
447442
}
@@ -567,6 +562,35 @@ func (k Keeper) recoverSupernodeActive(ctx sdk.Context, sn sntypes.SuperNode) er
567562
return k.supernodeKeeper.RecoverSuperNodeFromPostponed(ctx, valAddr)
568563
}
569564

565+
func (k Keeper) markSupernodeStorageFull(ctx sdk.Context, sn sntypes.SuperNode) error {
566+
if sn.ValidatorAddress == "" {
567+
return fmt.Errorf("missing validator address for supernode %q", sn.SupernodeAccount)
568+
}
569+
valAddr, err := sdk.ValAddressFromBech32(sn.ValidatorAddress)
570+
if err != nil {
571+
return err
572+
}
573+
return k.supernodeKeeper.MarkSuperNodeStorageFull(ctx, valAddr)
574+
}
575+
576+
func (k Keeper) recoverSupernodeFromPostponed(ctx sdk.Context, sn sntypes.SuperNode, epochID uint64) error {
577+
r, found := k.GetReport(ctx, epochID, sn.SupernodeAccount)
578+
if !found || r.HostReport.DiskUsagePercent == 0 {
579+
return k.recoverSupernodeActive(ctx, sn)
580+
}
581+
582+
if !isValidHostUsagePercent(r.HostReport.DiskUsagePercent) {
583+
return k.markSupernodeStorageFull(ctx, sn)
584+
}
585+
586+
maxStorage := float64(k.supernodeKeeper.GetParams(ctx).MaxStorageUsagePercent)
587+
if r.HostReport.DiskUsagePercent <= maxStorage {
588+
return k.recoverSupernodeActive(ctx, sn)
589+
}
590+
591+
return k.markSupernodeStorageFull(ctx, sn)
592+
}
593+
570594
// storageTruthBand represents a node suspicion severity level.
571595
type storageTruthBand int
572596

x/audit/v1/keeper/enforcement_empty_active_set_test.go

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func TestEnforceEpochEnd_EmptyActiveSet_NoSelfReport_NoRecover(t *testing.T) {
166166

167167
// TestEnforceEpochEnd_EmptyActiveSet_NonCompliantSelf_NoRecover verifies the
168168
// bootstrap exception does NOT bypass the self-compliance health checks.
169-
// A POSTPONED SN that submits a report violating the disk-usage minimum
169+
// A POSTPONED SN that submits a report violating a non-storage host minimum
170170
// stays POSTPONED even when the active set is empty.
171171
func TestEnforceEpochEnd_EmptyActiveSet_NonCompliantSelf_NoRecover(t *testing.T) {
172172
f := initFixture(t)
@@ -181,28 +181,19 @@ func TestEnforceEpochEnd_EmptyActiveSet_NonCompliantSelf_NoRecover(t *testing.T)
181181
params := types.DefaultParams()
182182
params.RequiredOpenPorts = []uint32{4444}
183183
params.ConsecutiveEpochsToPostpone = 1
184-
// Require at least 20% disk free; sn0 reports 95% usage → 5% free → not compliant.
185-
params.MinDiskFreePercent = 20
184+
// Require at least 20% CPU free; sn0 reports 95% usage → 5% free → not compliant.
185+
params.MinCpuFreePercent = 20
186186

187187
epochID := uint64(1)
188188

189189
writeEmptyActiveSetAnchor(t, f, epochID)
190190

191-
// SetReport with non-zero DiskUsagePercent invokes the STORAGE_FULL
192-
// transition source path, which queries supernodeKeeper. Stub these
193-
// dependencies so the call lands cleanly without triggering a
194-
// transition (we return "not found" → SetReport short-circuits).
195-
f.supernodeKeeper.EXPECT().
196-
GetSuperNodeByAccount(gomock.AssignableToTypeOf(f.ctx), sn0.SupernodeAccount).
197-
Return(sntypes.SuperNode{}, false, nil).
198-
Times(1)
199-
200191
if err := f.keeper.SetReport(f.ctx, types.EpochReport{
201192
SupernodeAccount: sn0.SupernodeAccount,
202193
EpochId: epochID,
203194
ReportHeight: f.ctx.BlockHeight(),
204195
HostReport: types.HostReport{
205-
DiskUsagePercent: 95.0, // 5% free, below the 20% minimum
196+
CpuUsagePercent: 95.0, // 5% free, below the 20% minimum
206197
},
207198
}); err != nil {
208199
t.Fatalf("failed to set report: %v", err)

0 commit comments

Comments
 (0)