feat(ha-term-safety): restore controller build + wire vote/AE term wire fields (tasks 1.1, 1.2)

1.1: WalCatchupEntry gains term@4/prevLogIndex@5/prevLogTerm@6 (append-only).
1.2: RequestVote/PreVote gain lastLogTerm@4/lastLogIndex@5; cluster.c vote callers
fixed to the extended library arity (RESTORES xce_controller + controller_unit_test
compile+link, broken by the raft-commit-safety signature extension); ha.c emits the
candidate's last-log tuple on send.

5 minimal stub log/commit callbacks wired into s_real_cbs so raft_init's new
non-NULL requirement is satisfied — tasks 2.x replace them with real bodies; the
logOk freshness gate is inert until the term seam (1.3) + on_get_entry_term (2.2).

Pre-existing test_repl_unit.c wal_catchup -EAGAIN failures + ASAN leaks are an
overlayfs /tmp fsync env artifact (bug-850), not from this task (untouched paths;
append-only schema cannot affect the @3 entries-list decode). Build/baseline
isolation is blocked by a c-capnproto flake-hash drift — see Implementation Notes.
This commit is contained in:
Rongsong Shen 2026-06-22 03:41:49 +00:00
parent e8e41752c0
commit 004a40bb3b
9 changed files with 264 additions and 9 deletions

View file

@ -10,9 +10,11 @@
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include <capnp_c.h>
#include "config.h"
#include "controller.capnp.h"
@ -132,6 +134,115 @@ static void test_repl_envelope_capnp_fields(void)
printf("test_repl_envelope_capnp_fields: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Test 4: RequestVoteRequest carries the Raft (lastLogTerm, */
/* lastLogIndex) tuple through a real encode → wire → decode round-trip */
/* (ha-term-safety task 1.2, Req 2.3/2.4/2.6). */
/* ------------------------------------------------------------------ */
static void test_request_vote_last_log_tuple_round_trip(void)
{
request_vote_req_t src;
memset(&src, 0, sizeof(src));
src.term = 5ULL;
src.candidate_id = "node-1";
src.config_tag = 9ULL;
src.last_log_lsn = 100ULL;
src.last_log_term = 4ULL; /* NEW: candidate's last-log term */
src.last_log_index = 100ULL; /* NEW: candidate's last-log index */
/* Encode the typemap struct into a capnp message, serialize to a flat
* buffer, then decode it back — exercises BOTH the schema field and the
* typemap.h mirror mapping (encode_/decode_ go through the C struct). */
struct capn c;
capn_init_malloc(&c);
struct capn_segment *cs = capn_root(&c).seg;
/* encode_*_ptr allocates a struct in the segment and writes the C struct
* fields into it (via the $C.mapname mapping). */
RequestVoteRequest_ptr p;
encode_RequestVoteRequest_ptr(cs, &p, &src);
capn_setp(capn_root(&c), 0, p.p);
int64_t sz = capn_size(&c);
TEST_ASSERT_TRUE(sz > 0);
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, (size_t)sz, 0);
capn_free(&c);
struct capn d;
TEST_ASSERT_EQUAL_INT(0, capn_init_mem(&d, buf, (size_t)sz, 0));
RequestVoteRequest_ptr dp;
dp.p = capn_getp(capn_root(&d), 0, 1);
struct RequestVoteRequest wire;
read_RequestVoteRequest(&wire, dp);
request_vote_req_t dst;
memset(&dst, 0, sizeof(dst));
decode_RequestVoteRequest(&dst, &wire);
TEST_ASSERT_EQUAL_UINT64(5ULL, dst.term);
TEST_ASSERT_EQUAL_UINT64(9ULL, dst.config_tag);
TEST_ASSERT_EQUAL_UINT64(100ULL, dst.last_log_lsn);
TEST_ASSERT_EQUAL_UINT64(4ULL, dst.last_log_term); /* survives the wire */
TEST_ASSERT_EQUAL_UINT64(100ULL, dst.last_log_index); /* survives the wire */
capn_free(&d);
free(buf);
printf("test_request_vote_last_log_tuple_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Test 5: WalCatchupEntry (replication envelope) round-trips the */
/* AppendEntries term fields (prev_log_index, prev_log_term, term) */
/* (ha-term-safety task 1.1, Req 1.3/1.4). */
/* ------------------------------------------------------------------ */
static void test_wal_catchup_append_term_fields_round_trip(void)
{
wal_catchup_entry_t src;
memset(&src, 0, sizeof(src));
src.lsn = 42ULL;
src.entry_type = (uint8_t)WAL_JOB_SUBMIT;
src.term = 7ULL; /* NEW: this entry's term stamp */
src.prev_log_index = 41ULL; /* NEW: lsn - 1 */
src.prev_log_term = 6ULL; /* NEW: term at prev_log_index */
struct capn c;
capn_init_malloc(&c);
struct capn_segment *cs = capn_root(&c).seg;
WalCatchupEntry_ptr p;
encode_WalCatchupEntry_ptr(cs, &p, &src);
capn_setp(capn_root(&c), 0, p.p);
int64_t sz = capn_size(&c);
TEST_ASSERT_TRUE(sz > 0);
uint8_t *buf = (uint8_t *)malloc((size_t)sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, (size_t)sz, 0);
capn_free(&c);
struct capn d;
TEST_ASSERT_EQUAL_INT(0, capn_init_mem(&d, buf, (size_t)sz, 0));
WalCatchupEntry_ptr dp;
dp.p = capn_getp(capn_root(&d), 0, 1);
struct WalCatchupEntry wire;
read_WalCatchupEntry(&wire, dp);
wal_catchup_entry_t dst;
memset(&dst, 0, sizeof(dst));
decode_WalCatchupEntry(&dst, &wire);
TEST_ASSERT_EQUAL_UINT64(42ULL, dst.lsn);
TEST_ASSERT_EQUAL_UINT64(7ULL, dst.term); /* survives the wire */
TEST_ASSERT_EQUAL_UINT64(41ULL, dst.prev_log_index); /* survives the wire */
TEST_ASSERT_EQUAL_UINT64(6ULL, dst.prev_log_term); /* survives the wire */
capn_free(&d);
free(buf);
printf("test_wal_catchup_append_term_fields_round_trip: PASS\n");
}
/* ------------------------------------------------------------------ */
/* Suite entry point */
/* ------------------------------------------------------------------ */
@ -143,4 +254,6 @@ void test_e2e_capnp(void)
RUN_TEST(test_persist_config_capnp_round_trip);
RUN_TEST(test_snapshot_header_capnp_fields);
RUN_TEST(test_repl_envelope_capnp_fields);
RUN_TEST(test_request_vote_last_log_tuple_round_trip);
RUN_TEST(test_wal_catchup_append_term_fields_round_trip);
}