Exercise offline and live edits over real Merkle and PUSH transport

This commit is contained in:
evgeny committed 2026-10-26 22:50:20 +01:00
1 parent ec8c6e9eb1
commit d998b390f3
1 file changed
+42
+42
View File
@@ -64,6 +64,7 @@ static char temp_dir[] = "/tmp/utun_dbsync_XXXXXX";
static char config_a[256], config_b[256], config_c[256];
static uint32_t ca_target, cb_target, cc_target;
static uint64_t delivery_timestamp;
static uint64_t expected_revision;
static int history_ready(struct DB_SYNC_INSTANCE* si);
/* Оба сообщения одного автора в одной ms получают отдельные реальные ACK. */
@@ -259,6 +260,18 @@ static int same_roots(struct DB_SYNC_INSTANCE* a, struct DB_SYNC_INSTANCE* b, st
return !c || (!db_sync_read_root(c, rc) && !memcmp(ra, rc, 32));
}
static int cond_revision(void) {
struct UTUN_INSTANCE* nodes[] = {inst_a, inst_b};
for (unsigned i = 0; i < 2; i++) {
sqlite3_stmt* st = NULL;
int rc = sqlite3_prepare_v2(nodes[i]->topo_sqlite_db, "SELECT max(ver) FROM push_t", -1, &st, NULL);
if (rc == SQLITE_OK) rc = sqlite3_step(st);
int matches = rc == SQLITE_ROW && (uint64_t)sqlite3_column_int64(st, 0) == expected_revision;
sqlite3_finalize(st); if (!matches) return 0;
}
return same_roots(si_a, si_b, NULL);
}
int main(void) {
printf("=== test_db_sync ===\n");
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR);
@@ -417,6 +430,35 @@ int main(void) {
!wait_for("two distinct delivery ACKs", cond_delivery, PHASE_TIMEOUT_TB)) { test_phase = 2; goto done; }
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS two same-timestamp PUSH records acknowledged separately");
/* Отключённый пир пропускает две правки: Merkle принимает сразу v3 при том же количестве строк. */
{
sqlite3_stmt* st = NULL; uint64_t ts; struct db_message_revision revision = {.ver = 1}; uint8_t signature[64];
if (sqlite3_prepare_v2(inst_a->topo_sqlite_db, "SELECT timestamp,nonce FROM push_t ORDER BY timestamp LIMIT 1", -1, &st, NULL) != SQLITE_OK ||
sqlite3_step(st) != SQLITE_ROW || sqlite3_column_bytes(st, 1) != 16) {
sqlite3_finalize(st); test_phase = 2; goto done;
}
ts = (uint64_t)sqlite3_column_int64(st, 0); memcpy(revision.nonce, sqlite3_column_blob(st, 1), 16); sqlite3_finalize(st);
db_sync_peer_set_paused(si_a, inst_b->node_id, 1); db_sync_peer_set_paused(si_b, inst_a->node_id, 1);
for (uint64_t ver = 2; ver <= 3; ver++) {
const char* text = ver == 2 ? "{\"ct\":\"text/plain\",\"d\":\"edit 2\"}" : "{\"ct\":\"text/plain\",\"d\":\"edit 3\"}";
revision.ver = ver; revision.edited_at = db_sync_next_timestamp(si_a);
if (db_sync_sign_message(40, ts, inst_a->node_id, &revision, text, strlen(text), inst_a->my_ed25519_privkey, signature) < 0 ||
db_sync_insert_signed(si_a, text, strlen(text), signature, 64, ts, NULL, &revision) != 0) {
test_phase = 2; goto done;
}
}
expected_revision = 3; reset_done_flags();
db_sync_reinitiate(si_a, inst_b->node_id); db_sync_reinitiate(si_b, inst_a->node_id);
if (!wait_for("offline edits converge by Merkle", cond_revision, PHASE_TIMEOUT_TB) ||
db_sync_count(si_a) != 6 || db_sync_count(si_b) != 6) { test_phase = 2; goto done; }
const char* text = "{\"ct\":\"text/plain\",\"d\":\"live edit 4\"}";
revision.ver = 4; revision.edited_at = db_sync_next_timestamp(si_a); expected_revision = 4;
if (db_sync_sign_message(40, ts, inst_a->node_id, &revision, text, strlen(text), inst_a->my_ed25519_privkey, signature) < 0 ||
db_sync_insert_signed(si_a, text, strlen(text), signature, 64, ts, NULL, &revision) != 0 ||
!wait_for("live edit over PUSH", cond_revision, PHASE_TIMEOUT_TB)) { test_phase = 2; goto done; }
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS offline v1->v3 and live v4 converge with stable IDs and unchanged record count");
}
/* Одинаковые count, отличие в первой записи длинной истории и разные локальные id. */
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "Phase 5d: long history with equal counts and divergent first record");
remove_si(&si_a); remove_si(&si_b);