Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ https://sysnode.info/ -> SPA
https://sysnode.info/auth/* -> backend
https://sysnode.info/vault/* -> backend
https://sysnode.info/gov/* -> backend
https://sysnode.info/mnStats -> backend
https://sysnode.info/mnCount -> backend
https://sysnode.info/mnstats -> backend
https://sysnode.info/mncount -> backend
https://sysnode.info/govlist -> backend
```

Expand Down
6 changes: 3 additions & 3 deletions src/lib/api.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios';

// Base URL for the anonymous public sysnode-backend endpoints
// (`/mnStats`, `/mnCount`, `/govlist`). Kept in lockstep with the
// (`/mnstats`, `/mncount`, `/govlist`). Kept in lockstep with the
// authenticated client in `./apiClient.js` so a single build-time
// override (`REACT_APP_API_BASE`) retargets BOTH surfaces at once.
//
Expand Down Expand Up @@ -33,12 +33,12 @@ const client = axios.create({
});

export async function fetchNetworkStats() {
const response = await client.get('/mnStats');
const response = await client.get('/mnstats');
return response.data;
}

export async function fetchNodeHistory() {
const response = await client.get('/mnCount');
const response = await client.get('/mncount');
return response.data;
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/governanceWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ export function computeProposalWindow({
return { startEpoch, endEpoch, anchor, padding };
}

// Extract the next-superblock epoch (seconds) from the /mnStats
// Extract the next-superblock epoch (seconds) from the /mnstats
// response. The backend exposes a numeric `superblock_next_epoch_sec`
// field (see sysnode-backend services/calculations.js). We never
// parse the human-readable `superblock_date` string — it's formatted
Expand Down
4 changes: 2 additions & 2 deletions src/lib/governanceWindow.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('SUPERBLOCK_CYCLE_SEC', () => {
});

// Codex PR20 round 4 P1: the prepare-time drift check must not
// treat sub-SB /mnStats re-estimates as a superblock rotation.
// treat sub-SB /mnstats re-estimates as a superblock rotation.
describe('anchorsAreSameSuperblock', () => {
const NOW = 1_800_000_000;
const ANCHOR = NOW + 10 * 86400;
Expand Down Expand Up @@ -516,7 +516,7 @@ describe('nextSuperblockEpochSecFromStats', () => {
).toBeNull();
});

// Codex PR20 P1: the backend's /mnStats feed can lag for a
// Codex PR20 P1: the backend's /mnstats feed can lag for a
// window between the real next superblock landing and
// sysMain.js refreshing its cache. If we were to accept that
// stale (past) timestamp as a valid anchor, the wizard would
Expand Down
14 changes: 7 additions & 7 deletions src/pages/NewProposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export default function NewProposal() {
// toast-like feedback.
const [draftSavedAt, setDraftSavedAt] = useState(0);

// Live next-superblock anchor. We fetch the backend /mnStats feed
// Live next-superblock anchor. We fetch the backend /mnstats feed
// on mount and extract `superblock_stats.superblock_next_epoch_sec`
// so that `computeProposalWindow` can align the derived start/end
// epochs to the real chain. Loading / error states gate the
Expand All @@ -199,7 +199,7 @@ export default function NewProposal() {
const [nextSuperblockSec, setNextSuperblockSec] = useState(null);
const [statsError, setStatsError] = useState(null);
const [statsLoading, setStatsLoading] = useState(true);
// Monotonic counter tracking the "latest issued /mnStats request".
// Monotonic counter tracking the "latest issued /mnstats request".
// Each refreshStats invocation takes a snapshot of its own id and
// only mutates anchor state if that id is still current when the
// response resolves. Prevents out-of-order responses from clobbering
Expand Down Expand Up @@ -272,7 +272,7 @@ export default function NewProposal() {
return () => clearInterval(id);
}, []);

// True only when /mnStats gave us a future superblock anchor.
// True only when /mnstats gave us a future superblock anchor.
// Used consistently by WindowPreview, the Prepare-button gate,
// and the ReviewStep schedule so all three flip together the
// instant the cached anchor goes stale. Mere truthiness is
Expand Down Expand Up @@ -712,7 +712,7 @@ export default function NewProposal() {
// the stats-unavailable banner, clear the cached anchor
// so Prepare stays disabled until refreshStats() recovers.
// (b) fetch returns a stale or missing anchor
// (next_SB epoch <= now) → same as (a). The /mnStats
// (next_SB epoch <= now) → same as (a). The /mnstats
// source occasionally lags a few blocks behind the tip
// and we refuse to submit against a backward-pointing
// anchor for the same reason.
Expand All @@ -735,7 +735,7 @@ export default function NewProposal() {
//
// Codex PR20 round 3 P2: the wall-clock cutoff used to validate
// the refreshed anchor must be read AFTER the fetch resolves,
// not before. /mnStats is a real network RTT (plus jsdom /
// not before. /mnstats is a real network RTT (plus jsdom /
// proxy / slow-node delays in practice) and can straddle the
// actual superblock transition; in that window an anchor that
// was strictly future at pre-await time can already be in the
Expand Down Expand Up @@ -776,7 +776,7 @@ export default function NewProposal() {
}
// Compare the refreshed anchor against the cached one the
// user just reviewed. We CANNOT use strict equality — the
// backend's /mnStats recomputes `superblock_next_epoch_sec`
// backend's /mnstats recomputes `superblock_next_epoch_sec`
// every sysMain tick (20 s) as `now + diffBlock *
// avgBlockTime`, so the value drifts by seconds/minutes
// between fetches even when the same upcoming superblock is
Expand Down Expand Up @@ -1632,7 +1632,7 @@ function ReviewStep({
form.paymentCount
);
// Only render the projected schedule when we have a live next-SB
// anchor from /mnStats. `derivedWindow` alone is insufficient:
// anchor from /mnstats. `derivedWindow` alone is insufficient:
// computeProposalWindow falls back to `now + cycle` when the anchor
// is missing/stale, which would paint a plausible-looking list of
// payout dates that don't match the chain. WindowPreview already
Expand Down
24 changes: 12 additions & 12 deletions src/pages/NewProposal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import { proposalService } from '../lib/proposalService';
/* eslint-enable import/first */

// Stable next-superblock anchor captured fresh per-test in beforeEach
// (see below). The wizard now fetches /mnStats BOTH on mount AND at
// (see below). The wizard now fetches /mnstats BOTH on mount AND at
// Prepare time and compares the two — if they differ it assumes the
// chain advanced a cycle while the wizard was open and forces a
// re-review instead of submitting. A mock that recomputes
Expand Down Expand Up @@ -170,7 +170,7 @@ describe('NewProposal wizard', () => {
jest.clearAllMocks();
// Snapshot a stable next-SB anchor at test start so both the
// mount-time and prepare-time fetchNetworkStats calls return
// the same value (same rationale as in production: /mnStats
// the same value (same rationale as in production: /mnstats
// reports the same pre-computed SB epoch across rapid calls).
currentStableNextSb = Math.floor(Date.now() / 1000) + 30 * 86400;
fetchNetworkStats.mockImplementation(defaultNetworkStatsResolver);
Expand Down Expand Up @@ -780,7 +780,7 @@ describe('NewProposal wizard', () => {
);

test(
'Prepare fails closed when the pre-submit /mnStats refresh throws (Codex round 2 P2)',
'Prepare fails closed when the pre-submit /mnstats refresh throws (Codex round 2 P2)',
async () => {
// The wizard refreshes the next-SB anchor right before
// submitting. If that fetch errors out, we must NOT fall
Expand All @@ -799,7 +799,7 @@ describe('NewProposal wizard', () => {
fireEvent.click(screen.getByTestId('wizard-next'));
expect(screen.getByTestId('wizard-panel-review')).toBeInTheDocument();

// Now break the /mnStats endpoint for the prepare-time
// Now break the /mnstats endpoint for the prepare-time
// refresh. The mount-time fetch already succeeded with the
// stable anchor from beforeEach.
fetchNetworkStats.mockRejectedValueOnce(
Expand Down Expand Up @@ -829,10 +829,10 @@ describe('NewProposal wizard', () => {
);

test(
'Prepare fails closed when the pre-submit /mnStats refresh returns a stale (past) anchor',
'Prepare fails closed when the pre-submit /mnstats refresh returns a stale (past) anchor',
async () => {
// Same fail-closed behaviour as the throw case: a lagging
// /mnStats feed that still returns a positive but past
// /mnstats feed that still returns a positive but past
// timestamp must not let us submit. The mount fetch used
// the stable future anchor from beforeEach; we corrupt only
// the prepare-time refresh.
Expand Down Expand Up @@ -869,12 +869,12 @@ describe('NewProposal wizard', () => {
);

test(
'Prepare fails closed when /mnStats resolves across the SB boundary (anchor future at pre-await, past at post-await) (Codex round 3 P2)',
'Prepare fails closed when /mnstats resolves across the SB boundary (anchor future at pre-await, past at post-await) (Codex round 3 P2)',
async () => {
// Codex PR20 round 3 P2: the previous implementation captured
// `nowSec = Math.floor(Date.now() / 1000)` BEFORE awaiting
// fetchNetworkStats() and reused it to validate the refreshed
// anchor. /mnStats is a real network RTT and can straddle
// anchor. /mnstats is a real network RTT and can straddle
// wall-clock boundaries — including, at a SB transition, the
// actual superblock. In that case an anchor that was strictly
// future at pre-await time is already in the past by the time
Expand Down Expand Up @@ -992,7 +992,7 @@ describe('NewProposal wizard', () => {
fireEvent.click(screen.getByTestId('wizard-next'));
expect(screen.getByTestId('wizard-panel-review')).toBeInTheDocument();

// Next /mnStats call returns a DIFFERENT future anchor (one
// Next /mnstats call returns a DIFFERENT future anchor (one
// superblock past the cached one). Subsequent calls return
// the same drifted value so the retry click sees a stable
// state.
Expand Down Expand Up @@ -1074,7 +1074,7 @@ describe('NewProposal wizard', () => {
fireEvent.click(screen.getByTestId('wizard-next'));
expect(screen.getByTestId('wizard-panel-review')).toBeInTheDocument();

// Next /mnStats call returns a slightly drifted anchor
// Next /mnstats call returns a slightly drifted anchor
// (60 s forward). Well within the cycle/2 tolerance, so
// the wizard must treat it as "same SB, just a fresher
// estimate" and proceed to prepare.
Expand Down Expand Up @@ -1117,7 +1117,7 @@ describe('NewProposal wizard', () => {
);

test(
'Review step suppresses the projected schedule when /mnStats anchor is unavailable (Codex round 5 P2)',
'Review step suppresses the projected schedule when /mnstats anchor is unavailable (Codex round 5 P2)',
async () => {
// Regression: computeProposalWindow has an internal
// "stale anchor" fallback (anchor = now + cycle) so that
Expand All @@ -1132,7 +1132,7 @@ describe('NewProposal wizard', () => {
// in lockstep with WindowPreview and only render when we
// have a real live anchor.
//
// Setup: stub /mnStats to return a response that
// Setup: stub /mnstats to return a response that
// nextSuperblockEpochSecFromStats rejects (missing
// superblock_next_epoch_sec field). Wizard state lands at
// `nextSuperblockSec = null, statsError != null`. 3-month
Expand Down
Loading