Cover revision conflicts Merkle updates stale ACKs and rollback

This commit is contained in:
evgeny committed 2026-10-26 22:46:08 +01:00
1 parent 82ef6f282f
commit e45f33e7b5
1 file changed
+51 -2
+51 -2
View File
@@ -212,6 +212,55 @@ static void test_delivery(void) {
destroy(&f);
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS exact same-timestamp ACK identity and idempotence");
}
/* Настоящие подписи и MESSAGES-страницы: версия меняет содержимое, сохраняя ID и локальный ряд. */
static void test_revisions(void) {
struct fixture a, b; init(&a); init(&b); uint8_t signature[64], key[32], root1[32], root5[32], other[32];
sign(&a, 100, "original", signature); struct db_message_revision revision = a.revision;
assert(insert(&a, 100, "original", signature) == 0 && db_sync_verify(a.si) == 0 && db_sync_verify(b.si) == 0);
assert(chat_message_id(10, 100, 1, revision.nonce, key) == 0 && db_sync_read_root(a.si, root1) == 0);
sql(&a, "UPDATE history SET local_attrs='saved',flags=1,delivered_peers=9,delivery_chain='old'");
revision.ver = 5; revision.edited_at = 500;
assert(db_sync_sign_message(10, 100, 1, &revision, "edited", 6, a.seed, signature) == 0);
fail_commit = 1;
assert(db_sync_insert_signed(a.si, "edited", 6, signature, 64, 100, NULL, &revision) == -1);
intact(&a, "original"); assert(a.si->next_id == 2);
assert(db_sync_read_root(a.si, other) == 0 && !memcmp(root1, other, 32));
assert(db_sync_insert_signed(a.si, "edited", 6, signature, 64, 100, NULL, &revision) == 0 && a.si->next_id == 2);
intact(&a, "edited"); assert(db_sync_count(a.si) == 1);
assert(db_sync_read_root(a.si, root5) == 0 && memcmp(root1, root5, 32));
assert(si_delivery_update(a.si, key, 1, 2) == 0);
sqlite3_stmt* st = NULL;
assert(sqlite3_prepare_v2(a.db.db, "SELECT id,ver,edited_at,local_attrs,delivered_peers FROM history", -1, &st, NULL) == SQLITE_OK);
assert(sqlite3_step(st) == SQLITE_ROW && sqlite3_column_int(st, 0) == 1 && sqlite3_column_int(st, 1) == 5 &&
sqlite3_column_int(st, 2) == 500 && !strcmp((const char*)sqlite3_column_text(st, 3), "saved") && !sqlite3_column_int(st, 4));
assert(sqlite3_finalize(st) == SQLITE_OK);
assert(si_delivery_update(a.si, key, 5, 2) == 0);
uint8_t wire[DB_WIRE_MAX]; size_t bytes = sizeof(wire); merkle_key_t after = {{0}}, next; int more;
uint64_t prefix = merkle_sync_level_prefix(db_read64(key), MT_MAX_LEVEL);
assert(db_message_page(&a.inst, "10", prefix, 0, &after, wire, &bytes, &next, &more) == 0 && !more);
assert(db_message_apply(&b.inst, "10", 2, prefix, 0, &after, &next, more, wire, bytes) == 0);
intact(&b, "edited"); assert(db_sync_read_root(b.si, other) == 0 && !memcmp(root5, other, 32));
assert(db_message_apply(&b.inst, "10", 2, prefix, 0, &after, &next, more, wire, bytes) == 0 && b.si->next_id == 2);
struct db_record parsed; const uint8_t* cursor = wire;
assert(db_parse_record(b.si, &cursor, wire + bytes, &parsed) == 0 && parsed.revision.ver == 5);
parsed.revision.ver = 2; parsed.revision.edited_at = 200; parsed.data = "older"; parsed.data_len = 5;
assert(db_sync_sign_message(10, 100, 1, &parsed.revision, parsed.data, parsed.data_len, a.seed, parsed.signature) == 0);
assert(db_insert_one(b.si, &parsed, NULL) == 1); intact(&b, "edited");
parsed.revision = revision; parsed.data = "conflict"; parsed.data_len = 8;
assert(db_sync_sign_message(10, 100, 1, &parsed.revision, parsed.data, parsed.data_len, a.seed, parsed.signature) == 0);
assert(db_insert_one(b.si, &parsed, NULL) == -2); intact(&b, "edited");
size_t conflict_bytes = (size_t)db_encode_record(&parsed, wire);
assert(db_message_apply(&b.inst, "10", 2, prefix, 0, &after, &next, 0, wire, conflict_bytes) == -1);
assert(db_sync_read_root(b.si, other) == 0 && !memcmp(root5, other, 32));
wire[40] ^= 1; cursor = wire;
assert(db_parse_record(b.si, &cursor, wire + conflict_bytes, &parsed) == -1);
parsed.author = 1; parsed.revision = revision; parsed.data = "edited"; parsed.data_len = 6; parsed.sig_len = 64;
assert(db_sync_sign_message(11, 100, 1, &revision, parsed.data, parsed.data_len, a.seed, parsed.signature) == 0);
assert(db_insert_one(b.si, &parsed, NULL) == -2); intact(&b, "edited");
destroy(&a); destroy(&b);
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS revision jump/first-seen v5, rollback, roots, stale ACK, duplicate, old version, conflict and author/channel binding");
}
static int first_calls, second_calls;
static void second_done(struct DB_SYNC_INSTANCE* si, uint64_t peer, void* arg) {
(void)si; (void)peer; (void)arg; second_calls++;
@@ -356,8 +405,8 @@ int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG);
size_t baseline = u_get_allocated_count();
test_preservation(); test_deletion(); test_index(); test_delivery(); test_callbacks();
test_pages(); test_deleted_author(); test_page_rollback(); test_late_author_key(); test_ttl();
test_pages(); test_deleted_author(); test_page_rollback(); test_late_author_key(); test_ttl(); test_revisions();
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "history injected failures=%u allocations=%zu baseline=%zu", fault_hits, u_get_allocated_count(), baseline);
assert(fault_hits == 11 && u_get_allocated_count() == baseline);
assert(fault_hits == 12 && u_get_allocated_count() == baseline);
return 0;
}