feat(raft-commit-safety): (lastLogTerm,lastLogIndex) vote+prevote freshness (tasks 1.3, 4.1-4.2, 5.1-5.2, 6.2)
logOk (raft.tla:285, closes G6): local_last_log_term = log_len>0 ? on_get_entry_term(log_len):0; logOk = cand_last_log_term > local || (== && cand_last_log_index >= log_len). Lexicographic: term dominates, tie decided by index. Shared raft_log_ok helper. Vote (4.2): logOk deny inserted after stale-term/UpdateTerm/role-gate/one-vote-per-term and BEFORE on_vote_eligible (denies even when the app gate would grant); persist-before-grant intact. PreVote (5.2): identical logOk, fully side-effect-free (no term/vote/role/commit mutation, no on_persist). Signatures extended (1.3) on raft_on_vote_request/pre_vote_request; all 39 lib call sites + sim routing updated; sim captures the candidate's send-time last-log tuple (raft_sim_rpc_t). Empty-log call sites pass (0,0) → logOk true → existing outcomes preserved. 9 freshness tests. NOTE: controller cluster.c vote callers still use the old signatures and will NOT compile — an accepted, design-documented revalidation trigger; the controller adapter plumbing is owned by the downstream ha-term-safety spec (to be implemented next). Library suite ASAN green (239 tests).
This commit is contained in:
parent
195f660f9a
commit
56485e2fa3
5 changed files with 449 additions and 62 deletions
|
|
@ -244,12 +244,29 @@ int raft_start_election(raft_state_t *s);
|
|||
|
||||
/*
|
||||
* raft_on_vote_request: process an incoming RequestVote RPC.
|
||||
* Applies Raft invariants (term comparison, one-vote-per-term).
|
||||
* If invariants pass, calls on_vote_eligible to allow application policy.
|
||||
* Applies Raft invariants (term comparison, one-vote-per-term), then the
|
||||
* (lastLogTerm, lastLogIndex) freshness gate (raft.tla HandleRequestVoteRequest
|
||||
* logOk, line 285, closes G6), then the application policy gate.
|
||||
*
|
||||
* The library owns the logOk decision (design "Option B: library owns logOk"):
|
||||
* local_last_log_term = (log_len > 0) ? on_get_entry_term(log_len) : 0
|
||||
* logOk = (cand_last_log_term > local_last_log_term)
|
||||
* || (cand_last_log_term == local_last_log_term
|
||||
* && cand_last_log_index >= log_len)
|
||||
* The candidate advertises (cand_last_log_term, cand_last_log_index) in its wire
|
||||
* RequestVote; the caller decodes and passes them here.
|
||||
*
|
||||
* Step order: stale-term deny → UpdateTerm/step-down → role gate →
|
||||
* one-vote-per-term → logOk gate (NEW; precedes the application gate, so a
|
||||
* !logOk denies regardless of on_vote_eligible) → on_vote_eligible →
|
||||
* persist-before-grant.
|
||||
*
|
||||
* Fires on_persist before granting.
|
||||
* Returns RAFT_GRANT (1) or RAFT_DENY (0); sets *reply_term to current term.
|
||||
*/
|
||||
int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
||||
uint64_t cand_last_log_term,
|
||||
uint64_t cand_last_log_index,
|
||||
uint64_t *reply_term);
|
||||
|
||||
/*
|
||||
|
|
@ -301,13 +318,17 @@ int raft_begin_pre_vote(raft_state_t *s);
|
|||
|
||||
/*
|
||||
* raft_on_pre_vote_request: answer an incoming PreVote probe.
|
||||
* Denies if cand_term < s->term; otherwise returns on_vote_eligible(ctx,
|
||||
* from_idx, cand_term) (granting when the gate is NULL). Never touches term,
|
||||
* voted_for, role, or fires on_persist. Freshness is applied by the
|
||||
* dispatcher before this call (same contract as the real vote path).
|
||||
* Denies if cand_term < s->term; then applies the IDENTICAL (lastLogTerm,
|
||||
* lastLogIndex) logOk as raft_on_vote_request (the library owns the predicate —
|
||||
* it is NO LONGER applied by the dispatcher before this call), then consults
|
||||
* on_vote_eligible (granting when the gate is NULL). Mutates NOTHING — never
|
||||
* touches term, voted_for, voted_for_term, role, or commit_index, and never
|
||||
* fires on_persist — preserving the PreVote side-effect-free invariant.
|
||||
* Returns 1 = grant, 0 = deny.
|
||||
*/
|
||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term);
|
||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term,
|
||||
uint64_t cand_last_log_term,
|
||||
uint64_t cand_last_log_index);
|
||||
|
||||
/*
|
||||
* raft_on_pre_vote_reply: tally a reply to our PreVote.
|
||||
|
|
@ -423,11 +444,17 @@ void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);
|
|||
/* Upper bound on buffered AppendEntries awaiting delivery (mirrors pending[]). */
|
||||
#define RAFT_SIM_MAX_AE 64
|
||||
|
||||
/* Buffered RPC entry: one pending vote-request to route */
|
||||
/* Buffered RPC entry: one pending vote-request to route.
|
||||
* last_log_term/last_log_index capture the CANDIDATE's advertised last-log tuple
|
||||
* at SEND time (mirroring raft_sim_ae_t.leader_term) so the (lastLogTerm,
|
||||
* lastLogIndex) freshness gate (raft-commit-safety) is evaluated against the
|
||||
* candidate's send-epoch log, not a value re-read at delivery. */
|
||||
typedef struct {
|
||||
int src;
|
||||
int dst;
|
||||
uint64_t term;
|
||||
uint64_t last_log_term; /* candidate's last-log term at send (0 if empty) */
|
||||
uint64_t last_log_index; /* candidate's last-log index at send (== log_count) */
|
||||
} raft_sim_rpc_t;
|
||||
|
||||
/* One in-memory log entry: term stamp + fixed-size payload bytes.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue