feat(raft-sim-conformance): State Machine Safety invariant checker (task 3.4)

This commit is contained in:
Rongsong Shen 2026-06-24 12:51:58 +00:00
parent 63a9740c85
commit a38d9a7271
4 changed files with 119 additions and 1 deletions

View file

@ -813,6 +813,26 @@ int raft_sim_check_election_safety(const raft_sim_cluster_t *c);
*/
int raft_sim_check_leader_completeness(const raft_sim_cluster_t *c);
/*
* raft_sim_check_state_machine_safety: assert the Raft State Machine Safety property
* over the cluster's observable per-node applied state (Req 3.4, 3.6).
*
* State Machine Safety: no two nodes have applied DIFFERENT values at the same log
* index. Computed purely from observable state (Req 3.6): each node's applied extent
* is 1..commit_index (sim_conf_on_commit_advanced fills applied[k-1] with the value
* applied at 1-based committed index k). For every pair (a < b) and every index k in
* the shared applied prefix 1..min(a->commit_index, b->commit_index), the property is
* violated if applied[k-1] differs between the two nodes. Pairs are symmetric, so
* a < b suffices.
*
* Returns 1 if State Machine Safety holds across the whole cluster, 0 if any pair of
* nodes disagrees on the value applied at a commonly-applied index. Pure read-only
* over c (reads applied[] and commit_index only; no I/O, no Unity assertions) so the
* lib stays test-framework-agnostic; the caller asserts on the returned value (same
* convention as raft_sim_check_log_matching).
*/
int raft_sim_check_state_machine_safety(const raft_sim_cluster_t *c);
/*
* raft_sim_advance_commit: drive raft_advance_commit on node_idx (task 6.1).
*

View file

@ -882,6 +882,33 @@ int raft_sim_check_leader_completeness(const raft_sim_cluster_t *c)
return 1; /* holds */
}
int raft_sim_check_state_machine_safety(const raft_sim_cluster_t *c)
{
/* State Machine Safety: no two nodes have applied a different value at the same
* (1-based) log index. Observable-only (Req 3.6): each node's applied extent is
* 1..commit_index (on_commit_advanced fills applied[k-1] for committed index k).
* For every pair (a < b) and every index k in the shared applied prefix
* 1..min(commit_a, commit_b), fail if applied[k-1] differs. Pairs are symmetric,
* so a < b suffices. */
for (int a = 0; a < c->n; a++) {
const raft_sim_node_t *na = &c->nodes[a];
for (int b = a + 1; b < c->n; b++) {
const raft_sim_node_t *nb = &c->nodes[b];
uint64_t shared = na->commit_index < nb->commit_index
? na->commit_index : nb->commit_index;
if (shared > RAFT_SIM_MAX_LOG)
shared = RAFT_SIM_MAX_LOG;
for (uint64_t k = 1; k <= shared; k++) {
if (na->applied[k - 1] != nb->applied[k - 1])
return 0; /* divergent applied value — State Machine Safety violated */
}
}
}
return 1; /* holds */
}
int raft_sim_restart_node(raft_sim_cluster_t *c, int node_idx)
{
if (node_idx < 0 || node_idx >= c->n)