Skip to content

fix(dapi): truthful state sync status, health height field collision, client crash on absent sections - #4532

Draft
PastaPastaPasta wants to merge 3 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:fix/dapi-status-reporting
Draft

fix(dapi): truthful state sync status, health height field collision, client crash on absent sections#4532
PastaPastaPasta wants to merge 3 commits into
dashpay:v4.2-devfrom
PastaPastaPasta:fix/dapi-status-reporting

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Three status-reporting bugs found during state-sync QA (all observed live on a state-synced local node). Independent of the state-sync feature PRs — these affect today's v4.2-dev.

What was done?

  • rs-dapi: stop emitting misleading all-zero stateSync counters. Tenderdash 1.7 never populates the six state-sync counters in /statusenv.StateSyncMetricer has no assignment site anywhere in tenderdash (the reactor is dropped into node.services with no reference kept, and *statesync.Reactor doesn't even implement the Metricer interface), so the fields are always "0". rs-dapi's emit gate was additionally inverted (json:",string" means the fields are never absent, so the "is any data present" check always passed). getStatus now emits the stateSync section only when one of the six state-sync-specific counters is non-zero — absent rather than "state sync ran and did nothing" — and block-sync progress fields no longer leak into the state-sync section. The precise tenderdash-side fix (wire the reactor into rpcEnv, implement the two missing Metricer methods, snapshot syncer values before syncComplete() nils them, persist across restart) is documented in the code and being addressed separately.
  • rs-dapi: /health no longer reports a Core height under the name latestBlockHeight. getStatus's chain.latestBlockHeight was always correct (tenderdash platform height); the collision was checks.coreRpc.latestBlockHeight on the unversioned /health endpoint, sourced from Core getblockcount — the exact number QA observed. Renamed to coreBlockHeight (no in-repo consumers of the old key), with a regression test pinning that chain.latest_block_height is the platform height.
  • js-dapi-client: getStatus no longer throws on nodes missing optional sections. getStateSync(), getNode(), getChain(), getNetwork(), getTime(), and the nested version getters were all called unguarded; any of them absent (never-synced node, Drive or tenderdash unreachable) threw a TypeError. All optional sub-messages are now guarded, with unit tests for the absent-section shapes.

How Has This Been Tested?

rs-dapi: 307 unit tests passing including 7 new/changed (mid-sync, post-sync, never-synced, block-sync-only fixtures; height-collision regression). js-dapi-client: 320 passing including new absent-section cases. fmt/clippy clean. code-review-validator gate run; both valid findings fixed.

Breaking Changes

/health's checks.coreRpc.latestBlockHeight key is renamed to coreBlockHeight (unversioned diagnostics endpoint; no in-repo consumers — external scrapers of that exact key would need the one-word update). getStatus omits the stateSync section when there is no state-sync data instead of emitting zeros.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation if needed

🤖 Generated with Claude Code

PastaPastaPasta and others added 3 commits August 29, 2026 21:15
…are absent

Every message-typed field of GetStatusResponseV0 is optional on the wire. DAPI omits the state_sync section on nodes that are not state syncing, and omits node/chain/network when Tenderdash is unreachable, so createFromProto threw a TypeError on v0.getStateSync().getTotalSyncedTime() for any ordinary node.

Guard every optional sub-message read: absent node/chain/network/state_sync sections now yield null instead of throwing, and absent version sub-messages yield undefined. Also covers protocol.drive (absent when Drive is down) and protocol.tenderdash, which had the same unguarded chain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build_state_sync_info gated the state sync section on the Tenderdash strings being non-empty, but Tenderdash serialises every one of those fields as a quoted integer that is always present. The gate passed on every reachable node and getStatus emitted an all-zero StateSync message that asserts "state sync ran and did nothing" - indistinguishable from "this node never state synced", and identical on a node that had just restored a snapshot.

In Tenderdash 1.7 six of the eight values are hardcoded zero regardless: /status only copies the state sync counters when Environment.StateSyncMetricer is set (internal/rpc/core/status.go:91) and nothing ever assigns that field (internal/rpc/core/env.go:86 has no writer). total_synced_time and remaining_time are the only ones that can move, and they belong to the block sync reactor (internal/blocksync/reactor.go:308-328), not state sync. Emit the section only when Tenderdash actually reported a non-zero value, and omit it otherwise so clients can tell the difference.

Also rename the /health Core RPC height from latestBlockHeight to coreBlockHeight. It carries Dash Core's getblockcount, while latestBlockHeight in getStatus's chain section is the Platform height; a freshly joined node showing a Core height of ~8000 next to a Platform height of 28 was read as a broken Platform height. The getStatus mapping itself is correct - Tenderdash's /status exposes no Dash Core chain fields at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
total_synced_time and remaining_time are owned by Tenderdash's block sync reactor and are set on any node merely replaying blocks, so treating them as evidence of state sync raised a section named after state sync on nodes that never touched a snapshot. Gate the section on the six genuinely state-sync-specific counters instead.

Also make js-dapi-client's time section follow the same guard as the other optional sections rather than silently yielding undefined field values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit bdff957)

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final validation — Sol-only technical fallback

The status builder now omits stateSync unless state-sync-specific counters justify the section, while preserving the existing timing payload when genuine state-sync data is available. The proposed blocker conflates presence gating with payload preservation; the implementation, regression tests, and existing protobuf schema make that distinction explicit, so no actionable findings remain.

Source: reviewer 1: gpt-5.6-sol (agent: sol-fallback-reviewer, role: general); reviewer 2: gpt-5.6-sol (agent: sol-fallback-reviewer, role: rust-quality); final verifier: gpt-5.6-sol (agent: sol-verifier, role: final-verifier)

One or more required Phase-1 GLM Flash lanes remained technically unusable after the bounded exact-model retry. Their evidence was discarded as authoritative, and the complete selected role cohort was rerun fresh on exact gpt-5.6-sol before this fresh Sol verifier produced the final decision. No additional Phase-2 reviewer pass ran.

Review provenance

  • Phase 1 GLM evidence: technically unusable after bounded retry; discarded from the decision
  • GLM failure attempts: codex-general-259753a23f144b6392bd0f78eb193856 (failed), codex-general-55495ae61dd540818d15ac60e0dd5ff6 (failed), codex-rust-quality-478f1aae62394461b64986f1f9b6a58a (failed), codex-rust-quality-8d92ce1cc5b94fdebcf6375637ba6a5f (failed)
  • Sol-only fallback reasons: launch_transport_or_nonzero_exit, launch_transport_or_nonzero_exit
  • Sol-only fallback reviewers: gpt-5.6-sol — general (completed); agent sol-fallback-reviewer, gpt-5.6-sol — rust-quality (completed); agent sol-fallback-reviewer
  • Fresh verifier (Sol): gpt-5.6-sol — final-verifier; agent sol-verifier
  • Additional Phase 2 pass: not run; the Sol-only fallback is final

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants