- Fix pull consumer fetch: send original stream subject in HMSG (not inbox) so NATS client distinguishes data messages from control messages - Fix MaxAge expiry: add background timer in StreamManager for periodic pruning - Fix JetStream wire format: Go-compatible anonymous objects with string enums, proper offset-based pagination for stream/consumer list APIs - Add 42 E2E black-box tests (core messaging, auth, TLS, accounts, JetStream) - Add ~1000 parity tests across all subsystems (gaps closure) - Update gap inventory docs to reflect implementation status
392 lines
34 KiB
Markdown
392 lines
34 KiB
Markdown
# RAFT — Gap Analysis
|
|
|
|
> This file tracks what has and hasn't been ported from Go to .NET for the **RAFT** module.
|
|
> See [stillmissing.md](stillmissing.md) for the full LOC comparison across all modules.
|
|
|
|
## LLM Instructions: How to Analyze This Category
|
|
|
|
### Step 1: Read the Go Reference Files
|
|
|
|
Read each Go source file listed below. For every file:
|
|
|
|
1. Extract all **exported types** (structs, interfaces, type aliases)
|
|
2. Extract all **exported methods** on those types (receiver functions)
|
|
3. Extract all **exported standalone functions**
|
|
4. Note **key constants, enums, and protocol states**
|
|
5. Note **important unexported helpers** that implement core logic (functions >20 lines)
|
|
6. Pay attention to **concurrency patterns** (goroutines, mutexes, channels) — these map to different .NET patterns
|
|
|
|
### Step 2: Read the .NET Implementation Files
|
|
|
|
Read all `.cs` files in the .NET directories listed below. For each Go symbol found in Step 1:
|
|
|
|
1. Search for a matching type, method, or function in .NET
|
|
2. If found, compare the behavior: does it handle the same edge cases? Same error paths?
|
|
3. If partially implemented, note what's missing
|
|
4. If not found, note it as MISSING
|
|
|
|
### Step 3: Cross-Reference Tests
|
|
|
|
Compare Go test functions against .NET test methods:
|
|
|
|
1. For each Go `Test*` function, check if a corresponding .NET `[Fact]` or `[Theory]` exists
|
|
2. Note which test scenarios are covered and which are missing
|
|
3. Check the parity DB (`docs/test_parity.db`) for existing mappings:
|
|
```bash
|
|
sqlite3 docs/test_parity.db "SELECT go_test, dotnet_test, confidence FROM test_mappings tm JOIN go_tests gt ON tm.go_test_id=gt.rowid JOIN dotnet_tests dt ON tm.dotnet_test_id=dt.rowid WHERE gt.go_file LIKE '%PATTERN%'"
|
|
```
|
|
|
|
### Step 4: Classify Each Item
|
|
|
|
Use these status values:
|
|
|
|
| Status | Meaning |
|
|
|--------|---------|
|
|
| **PORTED** | Equivalent exists in .NET with matching behavior |
|
|
| **PARTIAL** | .NET implementation exists but is incomplete (missing edge cases, error handling, or features) |
|
|
| **MISSING** | No .NET equivalent found — needs to be ported |
|
|
| **NOT_APPLICABLE** | Go-specific pattern that doesn't apply to .NET (build tags, platform-specific goroutine tricks, etc.) |
|
|
| **DEFERRED** | Intentionally skipped for now (document why) |
|
|
|
|
### Step 5: Fill In the Gap Inventory
|
|
|
|
Add rows to the Gap Inventory table below. Group by Go source file. Include the Go file and line number so a porting LLM can jump directly to the reference implementation.
|
|
|
|
### Key Porting Notes for RAFT
|
|
|
|
- RAFT is used for JetStream cluster consensus — both meta-cluster (stream/consumer placement) and per-stream/consumer groups.
|
|
- Key operations: leader election, log append/commit, snapshotting, peer management.
|
|
- The .NET implementation (20 files, 3,045 LOC) is more granular than Go's single file. Check if the decomposition covers all RAFT states.
|
|
|
|
---
|
|
|
|
## Go Reference Files (Source)
|
|
|
|
- `golang/nats-server/server/raft.go` — RAFT consensus for clustered JetStream (~5,037 lines). Meta-cluster for metadata, per-stream/consumer RAFT groups. Leader election, log replication, snapshotting.
|
|
|
|
## Go Reference Files (Tests)
|
|
|
|
- `golang/nats-server/server/raft_test.go`
|
|
- `golang/nats-server/server/raft_helpers_test.go`
|
|
- `golang/nats-server/server/raft_chain_of_blocks_helpers_test.go`
|
|
|
|
## .NET Implementation Files (Source)
|
|
|
|
- `src/NATS.Server/Raft/` (all 20 files)
|
|
|
|
## .NET Implementation Files (Tests)
|
|
|
|
- `tests/NATS.Server.Tests/Raft/`
|
|
|
|
---
|
|
|
|
## Gap Inventory
|
|
|
|
<!-- After analysis, fill in this table. Group rows by Go source file. -->
|
|
|
|
### golang/nats-server/server/raft.go — Exported Interfaces & Types
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| RaftNode (interface) | raft.go:40-92 | PARTIAL | src/NATS.Server/Raft/IRaftNode.cs:5 | Interface declared but empty — none of the 40+ methods from Go are defined |
|
|
| RaftNodeCheckpoint (interface) | raft.go:98-103 | PARTIAL | src/NATS.Server/Raft/RaftSnapshotCheckpoint.cs:7 | Chunk assembly exists but LoadLastSnapshot, AppendEntriesSeq, Abort, InstallSnapshot not matching Go's interface contract |
|
|
| WAL (interface) | raft.go:105-118 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:20 | .NET RaftWal is a concrete file-based WAL; does not implement Go's WAL interface (StoreMsg, LoadMsg, RemoveMsg, Compact, Purge, Truncate, State, FastState, Stop, Delete) |
|
|
| Peer (struct) | raft.go:120-125 | PORTED | src/NATS.Server/Raft/RaftPeerState.cs | Added missing parity fields (`Lag`, `Current`) plus helpers to recalculate lag and refresh current-state from heartbeat/contact timing |
|
|
| RaftState (enum) | raft.go:127-135 | PORTED | src/NATS.Server/Raft/RaftState.cs:4 | All four states: Follower, Leader, Candidate, Closed |
|
|
| RaftState.String() | raft.go:137-149 | PORTED | src/NATS.Server/Raft/RaftStateExtensions.cs:9 | Added Go-style `RaftState.String()` extension mapping to Follower/Leader/Candidate/Closed |
|
|
| RaftConfig (struct) | raft.go:301-317 | PORTED | src/NATS.Server/Raft/RaftConfig.cs:8 | Added `RaftConfig` model with Name/Store/Log/Track/Observer/Recovering/ScaleUp fields for parity shape |
|
|
| CommittedEntry (struct) | raft.go:2506-2509 | PORTED | src/NATS.Server/Raft/CommitQueue.cs (`CommittedEntry`) | Added explicit committed-entry shape with `Index` and `Entries` list for parity with Go commit delivery payload |
|
|
| Entry (struct) | raft.go:2641-2644 | PORTED | src/NATS.Server/Raft/RaftEntry.cs | Added general `RaftEntry` model (`Type`, `Data`) with conversion helpers to/from wire-entry shape |
|
|
| EntryType (enum) | raft.go:2605-2619 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:54-63 | All types present including EntryCatchup (mapped as RaftEntryType) |
|
|
|
|
### golang/nats-server/server/raft.go — Exported RaftNode Interface Methods
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| Propose() | raft.go:909-924 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:308 | ProposeAsync exists but synchronous replication model, no write-error checking, no proposal queue |
|
|
| ProposeMulti() | raft.go:928-945 | PORTED | src/NATS.Server/Raft/RaftNode.cs (`ProposeMultiAsync`) | Added ordered multi-command proposal API returning committed indexes per input command |
|
|
| ForwardProposal() | raft.go:949-959 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:182 | Transport has ForwardProposal but RaftNode does not call it automatically for non-leaders |
|
|
| InstallSnapshot() | raft.go:1295-1311 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync exists but no checkpointing, no WAL compaction, no highwayhash verification |
|
|
| CreateSnapshotCheckpoint() | raft.go:1356-1360 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:519 | CreateSnapshotCheckpointAsync exists but simplified — no async write, no WAL compaction tracking |
|
|
| SendSnapshot() | raft.go:1284-1290 | MISSING | — | No direct snapshot send as append entry |
|
|
| NeedSnapshot() | raft.go:1551-1555 | MISSING | — | No equivalent check |
|
|
| Applied() | raft.go:1183-1185 | PORTED | src/NATS.Server/Raft/RaftNode.cs:403 | Added `Applied(long)` callback returning `(entries,bytes)` and delegating progress to processed tracking |
|
|
| Processed() | raft.go:1193-1240 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:664 | MarkProcessed exists but much simpler — no aflr signaling, no leader state transition, no byte estimation |
|
|
| State() | raft.go:2025-2027 | PORTED | src/NATS.Server/Raft/RaftNode.cs:43 | Role property (uses RaftRole enum instead of RaftState) |
|
|
| Size() | raft.go:2037-2043 | PORTED | src/NATS.Server/Raft/RaftNode.cs:771 | Added `Size()` accessor returning entry count and UTF-8 command-byte estimate from current log contents |
|
|
| Progress() | raft.go:2030-2034 | PORTED | src/NATS.Server/Raft/RaftNode.cs:765 | Added `Progress()` accessor returning `(index, commit, applied)` |
|
|
| Leader() | raft.go:1712-1717 | PORTED | src/NATS.Server/Raft/RaftNode.cs:42 | IsLeader property |
|
|
| LeaderSince() | raft.go:1721-1726 | PORTED | src/NATS.Server/Raft/RaftNode.cs:63 | Added nullable `LeaderSince` timestamp and leadership transition updates |
|
|
| Quorum() | raft.go:3070-3083 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:201 | HasQuorum() exists but uses different window calculation (2x electionTimeout vs Go's lostQuorumInterval) |
|
|
| Current() | raft.go:1840-1847 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:879 | IsCurrent exists but no commit==applied check, no forward-progress polling |
|
|
| Healthy() | raft.go:1850-1857 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:892 | IsHealthy exists but different semantics — checks peer responsiveness, not isCurrent(true) |
|
|
| Term() | raft.go:3119-3123 | PORTED | src/NATS.Server/Raft/RaftNode.cs:41 | Term property |
|
|
| Leaderless() | raft.go:1876-1883 | PORTED | src/NATS.Server/Raft/RaftNode.cs:65 | Added lock-free `Leaderless` derived from tracked group leader state |
|
|
| GroupLeader() | raft.go:1865-1872 | PORTED | src/NATS.Server/Raft/RaftNode.cs:64 | Added `GroupLeader` tracking, updated on election/heartbeat/stepdown |
|
|
| HadPreviousLeader() | raft.go:1860-1862 | PORTED | src/NATS.Server/Raft/RaftNode.cs:66 | Added `HadPreviousLeader` flag that remains true after first observed leader |
|
|
| StepDown() | raft.go:1900-1977 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown exists but no preferred leader selection, no leader transfer, no EntryLeaderTransfer |
|
|
| SetObserver() | raft.go:2394-2396 | PORTED | src/NATS.Server/Raft/RaftNode.cs:799 | Added `SetObserver(bool)` toggle for observer mode |
|
|
| IsObserver() | raft.go:2387-2391 | PORTED | src/NATS.Server/Raft/RaftNode.cs:68 | Added `IsObserver` accessor |
|
|
| Campaign() | raft.go:1980-1984 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately exists but no random campaign timeout |
|
|
| CampaignImmediately() | raft.go:1987-1993 | PORTED | src/NATS.Server/Raft/RaftNode.cs:771 | CampaignImmediately() |
|
|
| ID() | raft.go:2045-2051 | PORTED | src/NATS.Server/Raft/RaftNode.cs:40 | Id property |
|
|
| Group() | raft.go:2053-2056 | PORTED | src/NATS.Server/Raft/RaftNode.cs:42 | Added `GroupName` tracking on `RaftNode` with constructor support; defaults to node ID when unspecified |
|
|
| Peers() | raft.go:2058-2077 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:872 | GetPeerStates returns Dict but missing Lag calculation |
|
|
| ProposeKnownPeers() | raft.go:2080-2089 | MISSING | — | No peer state broadcast |
|
|
| UpdateKnownPeers() | raft.go:2092-2096 | MISSING | — | No peer state update |
|
|
| ProposeAddPeer() | raft.go:962-983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:372 | ProposeAddPeerAsync exists but synchronous replication, no forwarding to leader |
|
|
| ProposeRemovePeer() | raft.go:986-1019 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:415 | ProposeRemovePeerAsync exists but no forwarding, no self-removal handling |
|
|
| MembershipChangeInProgress() | raft.go:1021-1025 | PORTED | src/NATS.Server/Raft/RaftNode.cs:67 | MembershipChangeInProgress property |
|
|
| AdjustClusterSize() | raft.go:1059-1079 | PORTED | src/NATS.Server/Raft/RaftNode.cs:790 | Added leader-gated cluster-size adjustment with Go floor behavior (`min 2`) |
|
|
| AdjustBootClusterSize() | raft.go:1038-1055 | PORTED | src/NATS.Server/Raft/RaftNode.cs:781 | Added boot-time cluster-size adjustment gated on no current/previous leader |
|
|
| ClusterSize() | raft.go:1029-1033 | PORTED | src/NATS.Server/Raft/RaftNode.cs:778 | Added explicit `ClusterSize()` accessor |
|
|
| ApplyQ() | raft.go:2106 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:53 | CommitQueue exists as Channel-based queue, different API than ipQueue |
|
|
| PauseApply() | raft.go:1084-1092 | MISSING | — | No apply pausing |
|
|
| ResumeApply() | raft.go:1111-1156 | MISSING | — | No apply resuming with replay |
|
|
| DrainAndReplaySnapshot() | raft.go:1162-1177 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:537 | DrainAndReplaySnapshotAsync exists but simplified — no catchup cancellation, no commit preservation |
|
|
| LeadChangeC() | raft.go:2110 | MISSING | — | No leader change channel |
|
|
| QuitC() | raft.go:2113 | MISSING | — | No quit channel |
|
|
| Created() | raft.go:2115-2118 | PORTED | src/NATS.Server/Raft/RaftNode.cs:43 | Added `CreatedUtc` timestamp captured at node construction and exposed for runtime introspection |
|
|
| Stop() | raft.go:2120-2122 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1095 | Added `Stop()` lifecycle API that transitions to follower, clears leader markers, and signals stop waiters |
|
|
| WaitForStop() | raft.go:2124-2128 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1104 | Added synchronous `WaitForStop()` backed by stop completion signal |
|
|
| Delete() | raft.go:2130-2143 | PORTED | src/NATS.Server/Raft/RaftNode.cs:1109 | Added `Delete()` lifecycle API that stops node, marks deleted, and removes persisted raft directory when configured |
|
|
| IsDeleted() | raft.go:2145-2149 | PORTED | src/NATS.Server/Raft/RaftNode.cs:69 | Added `IsDeleted` state accessor |
|
|
| RecreateInternalSubs() | raft.go:658-747 | MISSING | — | No NATS internal subscription management |
|
|
| IsSystemAccount() | raft.go:648-649 | NOT_APPLICABLE | — | .NET does not have system account NRG routing |
|
|
| GetTrafficAccountName() | raft.go:652-656 | NOT_APPLICABLE | — | .NET does not have account NRG routing |
|
|
|
|
### golang/nats-server/server/raft.go — Core State Machine (unexported but critical)
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| raft (struct) | raft.go:151-251 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:3 | RaftNode has basic fields (term, vote, log, peers, commit) but missing: WAL, catchup state, progress map, pae cache, ipQueues, removed peers, many flags |
|
|
| run() | raft.go:2275-2362 | MISSING | — | No main run loop / state machine goroutine |
|
|
| runAsFollower() | raft.go:2441-2496 | MISSING | — | No follower state machine with select on channels |
|
|
| runAsCandidate() | raft.go:3587-3670 | MISSING | — | No candidate state machine with vote collection |
|
|
| runAsLeader() | raft.go:2951-3067 | MISSING | — | No leader state machine with heartbeat ticker and proposal batching |
|
|
| processAppendEntry() | raft.go:3857-4248 | MISSING | — | Complex append entry processing with catchup, WAL alignment, truncation — not ported |
|
|
| processAppendEntryResponse() | raft.go:4287-4339 | MISSING | — | No response processing with catchup triggering |
|
|
| processVoteRequest() | raft.go:4780-4845 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:142 | GrantVote handles basic vote logic but missing: log up-to-dateness check (lastTerm/lastIndex), catchup cancellation, campaign timeout reset |
|
|
| requestVote() | raft.go:4856-4872 | MISSING | — | No vote request broadcast via RPC |
|
|
| handleAppendEntry() | raft.go:3672-3684 | MISSING | — | No wire callback for append entries |
|
|
| handleAppendEntryResponse() | raft.go:4342-4346 | MISSING | — | No wire callback for AE responses |
|
|
| handleVoteRequest() | raft.go:4847-4854 | MISSING | — | No wire callback for vote requests |
|
|
| handleVoteResponse() | raft.go:4764-4778 | MISSING | — | No wire callback for vote responses |
|
|
| handleForwardedProposal() | raft.go:2854-2874 | MISSING | — | No forwarded proposal handler |
|
|
| handleForwardedRemovePeerProposal() | raft.go:2820-2851 | MISSING | — | No forwarded remove-peer handler |
|
|
|
|
### golang/nats-server/server/raft.go — WAL & Storage
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| storeToWAL() | raft.go:4360-4396 | PARTIAL | src/NATS.Server/Raft/RaftWal.cs:74 | RaftWal.AppendAsync exists but different storage model (custom binary vs Go's filestore/memstore) |
|
|
| loadEntry() | raft.go:3339-3346 | MISSING | — | No entry loading from WAL by index |
|
|
| loadFirstEntry() | raft.go:3126-3130 | MISSING | — | No first-entry loading |
|
|
| truncateWAL() | raft.go:3758-3815 | MISSING | — | No WAL truncation with snapshot invalidation |
|
|
| resetWAL() | raft.go:3819-3821 | MISSING | — | No WAL reset |
|
|
| cachePendingEntry() | raft.go:4449-4460 | MISSING | — | No pending append entry cache |
|
|
|
|
### golang/nats-server/server/raft.go — Snapshots
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| snapshot (struct) | raft.go:1243-1248 | PARTIAL | src/NATS.Server/Raft/RaftSnapshot.cs:3 | RaftSnapshot has LastIncludedIndex/Term/Data but missing peerstate encoding |
|
|
| encodeSnapshot() | raft.go:1254-1279 | MISSING | — | No binary snapshot encoding with highwayhash |
|
|
| loadLastSnapshot() | raft.go:1660-1707 | MISSING | — | No binary snapshot loading with hash verification |
|
|
| setupLastSnapshot() | raft.go:1578-1656 | MISSING | — | No startup snapshot recovery from disk |
|
|
| installSnapshot() | raft.go:1315-1350 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:699 | InstallSnapshotAsync simplified — no WAL compact, no snapshot file management |
|
|
| termAndIndexFromSnapFile() | raft.go:1564-1573 | MISSING | — | No snapshot filename parsing |
|
|
|
|
### golang/nats-server/server/raft.go — Peer & Membership State
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| peerState (struct) | raft.go:4470-4474 | MISSING | — | No peer state struct with knownPeers/clusterSize/domainExt |
|
|
| encodePeerState() | raft.go:4480-4492 | MISSING | — | No binary peer state encoding |
|
|
| decodePeerState() | raft.go:4494-4514 | MISSING | — | No binary peer state decoding |
|
|
| writePeerState() | raft.go:4603-4609 | MISSING | — | No peer state file persistence |
|
|
| readPeerState() | raft.go:4611-4620 | MISSING | — | No peer state file reading |
|
|
| processPeerState() | raft.go:4264-4282 | MISSING | — | No peer state processing from leader |
|
|
| addPeer() | raft.go:2879-2898 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:129 | AddMember exists but no cluster size/quorum adjustment, no removed-list reversal |
|
|
| removePeer() | raft.go:2903-2913 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:131 | RemoveMember exists but no removed tracking, no cluster size/quorum adjustment |
|
|
| adjustClusterSizeAndQuorum() | raft.go:3534-3556 | MISSING | — | No dynamic cluster size/quorum recalculation |
|
|
| trackPeer() | raft.go:3559-3585 | MISSING | — | No automatic peer tracking with add-on-discovery |
|
|
|
|
### golang/nats-server/server/raft.go — Elections & Voting
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| switchToFollower() | raft.go:4958-4983 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:706 | RequestStepDown sets Follower but missing: aflr reset, leaderState/leaderSince clear, acks reset, leader update |
|
|
| switchToCandidate() | raft.go:4985-5014 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:133 | StartElection increments term and votes but missing: observer/paused/processed checks, quorum loss signaling |
|
|
| switchToLeader() | raft.go:5016-5037 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:986 | TryBecomeLeader sets Role but missing: aflr setup, peer state broadcast, leader update |
|
|
| campaign() | raft.go:2002-2009 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:971 | CampaignWithPreVote starts election but no random timeout |
|
|
| wonElection() | raft.go:4886-4888 | PORTED | src/NATS.Server/Raft/RaftNode.cs:986 | Quorum check in TryBecomeLeader |
|
|
| selectNextLeader() | raft.go:1887-1897 | MISSING | — | No next-leader selection by highest index |
|
|
| resetElectionTimeout() | raft.go:2241-2243 | PORTED | src/NATS.Server/Raft/RaftNode.cs:737 | ResetElectionTimeout with Timer |
|
|
| randElectionTimeout() | raft.go:2235-2238 | PORTED | src/NATS.Server/Raft/RaftNode.cs:727 | RandomizedElectionTimeout |
|
|
| randCampaignTimeout() | raft.go:1995-1998 | PORTED | src/NATS.Server/Raft/RaftNode.cs:822 | Added `RandomizedCampaignTimeout()` using Go-equivalent 100-800ms jitter window |
|
|
|
|
### golang/nats-server/server/raft.go — Wire Format & RPC
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| voteRequest (struct) | raft.go:4549-4556 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:84 | RaftVoteRequestWire with Encode/Decode |
|
|
| voteResponse (struct) | raft.go:4730-4735 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:132 | RaftVoteResponseWire with Encode/Decode |
|
|
| appendEntry (struct) | raft.go:2557-2569 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:189 | RaftAppendEntryWire with Encode/Decode |
|
|
| appendEntryResponse (struct) | raft.go:2760-2766 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:318 | RaftAppendEntryResponseWire with Encode/Decode |
|
|
| ae.encode() | raft.go:2662-2711 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:202 | RaftAppendEntryWire.Encode() |
|
|
| decodeAppendEntry() | raft.go:2714-2746 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:255 | RaftAppendEntryWire.Decode() |
|
|
| vr.encode() (voteRequest) | raft.go:4560-4568 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:94 | RaftVoteRequestWire.Encode() |
|
|
| decodeVoteRequest() | raft.go:4571-4583 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:109 | RaftVoteRequestWire.Decode() |
|
|
| vr.encode() (voteResponse) | raft.go:4739-4751 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:142 | RaftVoteResponseWire.Encode() |
|
|
| decodeVoteResponse() | raft.go:4753-4762 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:159 | RaftVoteResponseWire.Decode() |
|
|
| ar.encode() | raft.go:2777-2794 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:328 | RaftAppendEntryResponseWire.Encode() |
|
|
| decodeAppendEntryResponse() | raft.go:2799-2817 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:343 | RaftAppendEntryResponseWire.Decode() |
|
|
| idLen constant | raft.go:2756 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:23 | RaftWireConstants.IdLen = 8 |
|
|
| RaftWireHelpers (WriteId/ReadId) | raft.go:2693,4581 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:561 | RaftWireHelpers class |
|
|
| RaftWireHelpers (uvarint) | raft.go:2682,2740 | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:597 | WriteUvarint/ReadUvarint |
|
|
|
|
### golang/nats-server/server/raft.go — NATS Subjects & Transport
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| raftAllSubj constant | raft.go:2162 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:16 | RaftSubjects.All |
|
|
| raftVoteSubj constant | raft.go:2163 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:22 | RaftSubjects.Vote() |
|
|
| raftAppendSubj constant | raft.go:2164 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:28 | RaftSubjects.AppendEntry() |
|
|
| raftPropSubj constant | raft.go:2165 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:34 | RaftSubjects.Proposal() |
|
|
| raftRemovePeerSubj constant | raft.go:2166 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:40 | RaftSubjects.RemovePeer() |
|
|
| raftReply constant | raft.go:2167 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:46 | RaftSubjects.Reply() |
|
|
| raftCatchupReply constant | raft.go:2168 | PORTED | src/NATS.Server/Raft/RaftSubjects.cs:52 | RaftSubjects.CatchupReply() |
|
|
| sendRPC() | raft.go:4874-4878 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:29 | NatsRaftTransport._publish delegate, but no sendq integration |
|
|
| sendReply() | raft.go:4880-4884 | MISSING | — | No reply sending via sendq |
|
|
| sendHeartbeat() | raft.go:4545-4547 | PARTIAL | src/NATS.Server/Raft/NatsRaftTransport.cs:229 | SendHeartbeatAsync exists but optimistic (no real ACK tracking) |
|
|
| IRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:3 | Interface with AppendEntries, RequestVote, InstallSnapshot, SendTimeoutNow, SendHeartbeat |
|
|
| InMemoryRaftTransport | — | PORTED | src/NATS.Server/Raft/RaftTransport.cs:26 | Full in-memory transport for testing |
|
|
| NatsRaftTransport | — | PORTED | src/NATS.Server/Raft/NatsRaftTransport.cs:18 | Wire-level NATS transport with binary encoding |
|
|
| createInternalSubs() | raft.go:2209-2233 | MISSING | — | No internal NATS subscription creation |
|
|
| subscribe() / unsubscribe() | raft.go:2194-2206 | MISSING | — | No internal subscription management |
|
|
|
|
### golang/nats-server/server/raft.go — Catchup & Leader Coordination
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| catchupState (struct) | raft.go:260-268 | MISSING | — | No catchup state tracking |
|
|
| runCatchup() | raft.go:3132-3236 | MISSING | — | No catchup goroutine for lagging followers |
|
|
| catchupFollower() | raft.go:3266-3337 | MISSING | — | No follower catchup initiation |
|
|
| createCatchup() | raft.go:3718-3735 | MISSING | — | No catchup subscription/inbox creation |
|
|
| cancelCatchup() | raft.go:3689-3697 | MISSING | — | No catchup cancellation |
|
|
| catchupStalled() | raft.go:3702-3712 | MISSING | — | No stall detection |
|
|
| sendSnapshotToFollower() | raft.go:3239-3264 | MISSING | — | No snapshot streaming to follower |
|
|
| sendCatchupSignal() | raft.go:3738-3745 | MISSING | — | No catchup signal to upper layer |
|
|
| cancelCatchupSignal() | raft.go:3748-3754 | MISSING | — | No catchup signal cancellation |
|
|
|
|
### golang/nats-server/server/raft.go — Commit & Apply
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| applyCommit() | raft.go:3350-3462 | MISSING | — | Complex commit application with entry type dispatch, peer state processing, membership changes |
|
|
| tryCommit() | raft.go:3468-3487 | MISSING | — | No quorum-based commit with ack counting |
|
|
| trackResponse() | raft.go:3493-3529 | MISSING | — | No response tracking with ack map |
|
|
| sendMembershipChange() | raft.go:2918-2949 | MISSING | — | No membership change send with WAL store |
|
|
|
|
### golang/nats-server/server/raft.go — Persistence & Term/Vote
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| writeTermVote() | raft.go:4708-4727 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:993 | PersistAsync writes meta.json (JSON) but Go uses binary tav.idx with dedup |
|
|
| readTermVote() | raft.go:4637-4656 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:1013 | LoadPersistedStateAsync reads meta.json but Go uses binary tav.idx |
|
|
| writePeerState() (on raft) | raft.go:4589-4600 | MISSING | — | No binary peer state file writing |
|
|
| setWriteErr() / setWriteErrLocked() | raft.go:4659-4704 | MISSING | — | No write error tracking with permission/space error escalation |
|
|
|
|
### golang/nats-server/server/raft.go — Server Integration
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| bootstrapRaftNode() | raft.go:346-407 | MISSING | — | No raft bootstrap with peer validation and directory setup |
|
|
| initRaftNode() | raft.go:410-614 | MISSING | — | No full raft initialization with WAL replay, snapshot recovery, peer state restore |
|
|
| startRaftNode() | raft.go:617-628 | MISSING | — | No start-and-run goroutine |
|
|
| registerRaftNode() | raft.go:776-783 | MISSING | — | No server-level raft registration |
|
|
| unregisterRaftNode() | raft.go:786-792 | MISSING | — | No server-level raft unregistration |
|
|
| lookupRaftNode() | raft.go:803-811 | MISSING | — | No raft node lookup by group |
|
|
| numRaftNodes() | raft.go:795-799 | MISSING | — | No raft node count |
|
|
| stepdownRaftNodes() | raft.go:831-851 | MISSING | — | No server-wide raft stepdown |
|
|
| shutdownRaftNodes() | raft.go:856-875 | MISSING | — | No server-wide raft shutdown |
|
|
| transferRaftLeaders() | raft.go:880-903 | MISSING | — | No server-wide leader transfer |
|
|
| reloadDebugRaftNodes() | raft.go:815-827 | NOT_APPLICABLE | — | Debug flag reload is Go-specific |
|
|
| serverNameForNode() | raft.go:759-763 | NOT_APPLICABLE | — | Server-level node mapping not yet needed |
|
|
| clusterNameForNode() | raft.go:767-772 | NOT_APPLICABLE | — | Cluster-level node mapping not yet needed |
|
|
|
|
### golang/nats-server/server/raft.go — .NET Extensions (not in Go)
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:366 | RaftPreVoteRequestWire — pre-vote wire format (Go does not have pre-vote) |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:410 | RaftPreVoteResponseWire — pre-vote wire format |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:455 | RaftTimeoutNowWire — leadership transfer wire format |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftWireFormat.cs:508 | RaftInstallSnapshotChunkWire — chunked snapshot wire format |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/CompactionPolicy.cs:9 | CompactionPolicy / CompactionOptions — configurable log compaction |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/SnapshotChunkEnumerator.cs:16 | SnapshotChunkEnumerator — snapshot chunking |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:251 | ReadIndexAsync — linearizable reads (Go does not expose this) |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:464 | Joint consensus (BeginJointConsensus/CommitJointConsensus) |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:790 | TransferLeadershipAsync with TimeoutNow RPC |
|
|
| (none — .NET addition) | — | PORTED | src/NATS.Server/Raft/RaftNode.cs:906 | Pre-vote protocol (StartPreVote/RequestPreVote) |
|
|
|
|
### golang/nats-server/server/raft.go — Object Pools & Helpers
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| cePool / newCommittedEntry / ReturnToPool | raft.go:2498-2532 | NOT_APPLICABLE | — | Go sync.Pool for CommittedEntry; .NET uses GC |
|
|
| entryPool / newEntry | raft.go:2534-2547 | NOT_APPLICABLE | — | Go sync.Pool for Entry; .NET uses GC |
|
|
| aePool / newAppendEntry / returnToPool | raft.go:2549-2583 | NOT_APPLICABLE | — | Go sync.Pool for appendEntry; .NET uses GC |
|
|
| pePool / newProposedEntry | raft.go:2586-2603 | NOT_APPLICABLE | — | Go sync.Pool for proposedEntry; .NET uses GC |
|
|
| arPool / newAppendEntryResponse | raft.go:2748-2775 | NOT_APPLICABLE | — | Go sync.Pool for appendEntryResponse; .NET uses GC |
|
|
| peers sync.Map (string interning) | raft.go:2797 | NOT_APPLICABLE | — | Go-specific string interning optimization |
|
|
| debug() / warn() / error() | raft.go:2364-2379 | NOT_APPLICABLE | — | Logging helpers — .NET uses ILogger<T> |
|
|
| dios semaphore | raft.go:1665-1667 | NOT_APPLICABLE | — | Go disk I/O semaphore — .NET uses async I/O |
|
|
| ipQueue usage | raft.go:233-238 | MISSING | — | Go uses typed ipQueue channels for proposals, entries, votes, responses — .NET has no equivalent internal queue infrastructure |
|
|
|
|
### golang/nats-server/server/raft.go — Constants & Configuration
|
|
|
|
| Go Symbol | Go File:Line | Status | .NET Equivalent | Notes |
|
|
|-----------|:-------------|--------|:----------------|-------|
|
|
| Election timeout defaults | raft.go:277-287 | PARTIAL | src/NATS.Server/Raft/RaftNode.cs:56-57 | .NET uses 150-300ms defaults; Go uses 4-9s defaults. Different design choice |
|
|
| hbInterval | raft.go:283 | PORTED | src/NATS.Server/Raft/RaftNode.cs:10 | Added `HbIntervalDefault = 1s` constant |
|
|
| lostQuorumInterval | raft.go:284 | PORTED | src/NATS.Server/Raft/RaftNode.cs:11 | Added `LostQuorumIntervalDefault = 10s` constant |
|
|
| observerModeInterval | raft.go:286 | PORTED | src/NATS.Server/Raft/RaftNode.cs:12 | Added `ObserverModeIntervalDefault = 48h` constant |
|
|
| peerRemoveTimeout | raft.go:287 | PORTED | src/NATS.Server/Raft/RaftNode.cs:13 | Added `PeerRemoveTimeoutDefault = 5m` constant |
|
|
| Error sentinels | raft.go:319-343 | PARTIAL | — | .NET uses InvalidOperationException instead of typed error sentinels |
|
|
| noLeader / noVote constants | raft.go:4954-4956 | PORTED | src/NATS.Server/Raft/RaftNode.cs:5 | Added explicit `NoLeader` / `NoVote` empty-string constants |
|
|
| paeDropThreshold / paeWarnThreshold | raft.go:4399-4401 | MISSING | — | No pending append entry limits |
|
|
| maxBatch / maxEntries | raft.go:3004-3005 | MISSING | — | No proposal batching thresholds |
|
|
| extensionState | raft.go:4462-4468 | MISSING | — | No domain extension state tracking |
|
|
|
|
---
|
|
|
|
## Keeping This File Updated
|
|
|
|
After porting work is completed:
|
|
|
|
1. **Update status**: Change `MISSING → PORTED` or `PARTIAL → PORTED` for each item completed
|
|
2. **Add .NET path**: Fill in the ".NET Equivalent" column with the actual file:line
|
|
3. **Re-count LOC**: Update the LOC numbers in `stillmissing.md`:
|
|
```bash
|
|
# Re-count .NET source LOC for this module
|
|
find src/NATS.Server/Raft/ -name '*.cs' -type f -exec cat {} + | wc -l
|
|
# Re-count .NET test LOC for this module
|
|
find tests/NATS.Server.Tests/Raft/ -name '*.cs' -type f -exec cat {} + | wc -l
|
|
```
|
|
4. **Add a changelog entry** below with date and summary of what was ported
|
|
5. **Update the parity DB** if new test mappings were created:
|
|
```bash
|
|
sqlite3 docs/test_parity.db "INSERT INTO test_mappings (go_test_id, dotnet_test_id, confidence, notes) VALUES (?, ?, 'manual', 'ported in YYYY-MM-DD session')"
|
|
```
|
|
|
|
## Change Log
|
|
|
|
| Date | Change | By |
|
|
|------|--------|----|
|
|
| 2026-02-25 | File created with LLM analysis instructions | auto |
|
|
| 2026-02-25 | Full gap analysis completed: 196 items inventoried across 12 categories. Summary: 46 PORTED, 38 PARTIAL, 99 MISSING, 13 NOT_APPLICABLE, 0 DEFERRED. Wire format is well-ported; core state machine (run loop, catchup, WAL integration) is largely missing. | claude-opus |
|
|
| 2026-02-25 | Ported RAFT API/lifecycle parity batch: LeaderSince/GroupLeader/Leaderless/HadPreviousLeader, observer toggles, Progress/Size/Applied, cluster-size adjustors, stop/delete APIs, campaign timeout jitter, and core timing/leader constants with targeted tests in `RaftNodeParityBatch2Tests`. | codex |
|
|
| 2026-02-26 | Ported RAFT parity batch: added `ProposeMultiAsync`, peer parity fields (`Lag`, `Current`) + refresh helpers, explicit `CommittedEntry` payload type, and general `RaftEntry` model/wire conversion helpers with focused tests in `RaftParityBatch3Tests`. | codex |
|