Verify history migration results and checkpoint revision races

This commit is contained in:
evgeny committed 2026-10-26 18:27:27 +01:00
1 parent bf94b240c8
commit 750ccf638d
3 files changed
+105 -4

No files matched your search

+86 -1
View File
@@ -1,6 +1,9 @@
member_sync / merkle_sync — аудит контрактов, 26.10.2026
Текущий статус: дефекты удаления корректной истории и доставки db_sync исправлены
Текущий статус: согласованный перенос истории на MESSAGES Merkle реализован (раздел13).
ASAN/UBSan/LSan10/10 PASS; окончательный общий прогон после уточнения stream-фикстур идёт.
Результаты прежних этапов ниже не являются проверкой нового переноса.
Дефекты удаления корректной истории и доставки db_sync исправлены
после rebase на03b05094 (раздел11). Общий аудит db_sync остаётся частичным.
Новый check.sh:203/203, proxy9/9,burst1/1,load8/8,exit0,195с; STUN PASS отдельно и в общем наборе.
ASAN/UBSan/LSan текущего исправления:8/8 PASS с detect_leaks=1.
@@ -819,3 +822,85 @@ round7,stateSERVE,READY в очереди. Применение старых PAG
логическое обоснование требует п.0 и согласованного индекса, а не опирается на неизменённый старый движок.
новая модель и её тесты ещё не реализованы. Перенос нельзя объявлять проверенным текущими203/203
и8/8: эти результаты относятся к уже выполненному исправлению раздела11.
13. Реализация MESSAGES, свежий handshake и ограниченные транзакции
Пользователь одобрил вариантБ раздела12, уточнив два обязательных условия:
- Не ограничивать исторические сообщения по deleted автора: ограничение входа в группу отдельно.
- Не фиксировать всю новую историю одной транзакцией: короткая локальная вставка/страница,
сеть вне транзакции, первичная проверка и построение индекса ограниченными порциями.
Реализовано:
- db_sync оставляет общую SQLite, подписанные записи, публичное API и PUSH/ACK(message_id).
Собственный bulk-автомат, chain_hash, sparse-поиск и сетевой OFFSET удалены.
MT_DATA_MESSAGES=2 использует общий merkle_sync; namespace=group_id, ключ=message_id[32].
Канонический лист включает ключ/timestamp/author/длину/точное тело/signature и исключает local id,
attrs/flags/доставку. Одинаковая подписанная история не зависит от локального порядка получения.
- Wire v4:55-байтовый заголовок с двумя случайными128-битными round_id/accept_id.
Старые PAGE/DONE/ERROR после cancel/start не проходят новый challenge; дубликаты BEGIN/READY
не сбрасывают применение. Ошибка RNG обеих сторон даёт MT_ERR_IO до установки нового раунда.
Полные корни и revision подтверждаются общими CHECK/DONE; равный count не означает успех.
- Страница MESSAGES проверяет полный32-байтовый курсор, лист, строгий порядок и подписи до BEGIN.
До16 записей и ограничение размера wire; данные/индекс/next_id фиксируются атомарно.
Обычная вставка меняет один путь дерева в своей короткой транзакции.
Preflight проверяет до16 строк вне write-lock, затем сверяет собственные байты в коротком commit.
Второй этап строит до16 листьев/путей за commit; timer1 tick возвращает управление I/O между шагами.
Число строк ограничено, но жёсткий временной предел commit не обещается: диск/наполнение листа влияют.
- VALID/BAD_FORMAT/BAD_SIGNATURE/DEFERRED/ERROR сохранены. Удаляются только доказанно неверные
сохранённые байты; SQL/OpenSSL/NOMEM/неизвестный ключ не удаляют историю и не публикуют пустой корень.
Ошибка оставляет pending; поздний общий ключ, включая событие другого канала, возобновляет preflight.
Ключ автора читается независимо от deleted; допуск пересылающего READY-пира проверяется отдельно.
- Миграция схемы сохраняет подписанные строки и local_attrs/flags/счётчики доставки.
Производные message_id/bucket вычисляются заново; ошибка DDL/copy/COMMIT откатывает перестройку.
Подпись неправильной длины — доказанный неверный формат, корректной длины проверяется preflight.
- Новые data_ops apply_page/can_run/on_result; узкая отмена dataset/ns и dataset/peer.
MESSAGES сохраняет sound_on-разрешение спящего пира; остальные модели используют default sleep policy.
Отзыв/возврат допуска и sleep/sound переоценивают сессии на прежнем UP, не затрагивая чужие datasets.
Android retry сохраняет отменяемое ожидание активной фазы, inline callback отказа памяти не рекурсивен.
- on_insert bulk отложен за пределы data_ops и видит committed страницу; rollback отменяет уведомления.
Done получает только MT_OK, включая автоматические раунды; самоотписка, удаление следующей подписки
и канала безопасны. Remove сохраняет таблицу, отменяя только её MESSAGES/таймеры/уведомления.
- TTL исправлен на миллисекунды Unix и удаляет до16 собственных неподтверждённых строк за commit.
Удаление и очистка MESSAGES-индекса атомарны; далее корень gated/pending до восстановления.
Это уточнение реализации к плану: массовая очистка сбрасывает производный индекс вместо обновления
каждого удалённого пути. Не создаёт tombstone; другая сохранённая копия может вернуться при обмене.
Проверки новой реализации:
- test_db_sync:реальный ETCP/READY,2/3 узла, PUSH/ACK и случайные слияния;200 одинаковых записей,
затем отличие первой при равном count и сдвиг local id на900. Итог201 на обоих, полный корень одинаков.
- test_db_sync_integrity:11 введённых crypto/SQL/COMMIT/BLOB/timer отказов, реальные подписи/триггеры;
сохранность хорошей копии, доказанная порча/формат0/1/63/65/255/4001, два канала, индекс/next_id rollback.
Плотный лист40 записей, несколько страниц, вставка до курсора требует следующей сверки, replay без дублей.
Отказ второй записи и COMMIT страницы откатывает все строки/корень/уведомления; on_insert только после commit.
Подписи preflight проверяются при sqlite3_get_autocommit=1. Разные id/attrs/flags/доставка не меняют корень.
История deleted автора выдаётся; поздний ключ через событие другого канала сохраняет строку и запускает индекс.
TTL дренирует19 expired в двух коротких commit и оставляет fresh/ACKed. Tracked allocations восстановлены.
- test_merkle_protocol:старые токены/фазы/курсоры, reattach, дубликаты BEGIN/READY, RNG обеих сторон,
backpressure, timeout, apply/SQLite faults, модельный sleep-допуск и восстановление на том же соединении.
- test_db_sync_schema:сохранность полей, повторный restart, rollback DDL после copy, очистка только доказанной порчи.
- test_dm_invite:настоящий JOIN и UTUN_TEST_HISTORY_TIMEOUT=1; принудительно потерянный обмен30с
завершается timeout, повтор восстанавливает историю, тест exit0.
- Общий первый прогон199/203:две изолированные db_sync-фикстуры не создавали общий движок,
purge-фикстура использовала недопущенного peer2; исправлены setup/teardown и реальный допущенный peer.
Повтор202/203, STUN PASS:stream-сценарий зависел от standby предыдущих сценариев.
DEBUG и SQL-диагностика показали новые версии только у автора и sleep=1 на центральном пиру;
wake-only PASS. Тест явно восстанавливает activity центрального узла и пробуждает lifecycle-получателя
после сохранённой проверки перехода forced-active → standby. Production stream не менялся.
- ASAN/UBSan/LSan10/10 PASS,detect_leaks=1:db_sync,integrity,schema,merkle_protocol,merkle_sync,
member_adapter,chat_feedback,chat_poll,chat_poll_network,dm_invite. Core/libuasync/Opus инструментированы
согласованно в /tmp/utun-member-purge-sanitizers. Нативные/ASAN-объекты не смешивались.
Отдельной Makefile-цели test_member_sync нет: исключена ошибочная попытка implicit build;
соответствующие проверки входят в member_adapter. Новая sanitizer-сборка/запуск10 целей exit0.
- Временная цель через tests/Makefile: /tmp/history-standby-check.c/.mk, макрос UTUN_HAVE_STANDBY,
настоящий history-код/SQLite/uasync и управляемый standby callback. Inline OOM даёт один retry без
рекурсии, wake освобождает ожидание, allocations восстановлены;exit0. Это не запуск Android-приложения.
Документы db_sync.h/db_sync_arch.md и merkle_sync.h/merkle_sync_doc.md приведены к реализации.
Ограничения:индекс согласован при записи через модель; произвольная внешняя порча SQLite
выявляется при соответствующем чтении/перепроверке, непрерывный scrub всех байтов не заявляется.
Тесты на несколько тысяч записей, отдельные изменения строго между CHECK/DONE и платформенные
GUI/Android/Windows/FreeBSD прогоны в этом этапе не выполнялись. Общий аудит чата частичный.
Логи: /tmp/history-merkle-asan-results.log, /tmp/history-merkle-asan-test*.log,
/tmp/history-merkle-final-test_db_sync_integrity.log, /tmp/history-merkle-final-test_merkle_protocol.log,
/tmp/history-merkle-dm-timeout.log, /tmp/history-standby-check.log.
Окончательный общий прогон /tmp/history-merkle-verified-check.log пока выполняется.
+3 -2
View File
@@ -209,10 +209,11 @@ int main(void) {
uint32_t records_a = 0, records_b = 0;
uint64_t t_insert = get_time_tb();
int large = getenv("UTUN_TEST_HISTORY_LARGE") != NULL;
for (int b = 0; b < TOTAL_BATCHES && test_phase == 0; b++) {
int n = rand() % (MAX_PER_BATCH + 1);
int n = large ? 250 : rand() % (MAX_PER_BATCH + 1);
if (n == 0) continue;
int side = rand() & 1;
int side = large ? b & 1 : rand() & 1;
struct DB_SYNC_INSTANCE* si = side ? si_b : si_a;
uint64_t node = side ? NODE_ID_B : NODE_ID_A;
+16 -1
View File
@@ -285,6 +285,21 @@ static void test_send_rejection(void) {
t_destroy(&f);
puts("PASS: transport rejection retains ownership; packet is freed exactly once");
}
static void test_checkpoint_revision(void) {
for (int changed = 0; changed < 2; changed++) {
struct fixture f; t_init(&f);
struct ms_session* s = t_session(&f); s->state = MS_SERVE; s->waiting = 0;
uint8_t root[32] = {0};
t_receive(&f, MS_CHECK, 7, 0, root, sizeof(root));
assert(s->tx && s->tx->dgram[9] == MS_DONE && !f.callbacks);
if (changed) merkle_sync_changed(&f.inst, MT_DATA_MEMBERS, "1001");
uasync_poll(f.inst.ua, 0);
if (changed) assert(!f.callbacks && s->requests && s->revision != s->verified_revision);
else assert(f.callbacks == 1 && f.result == MT_OK && !s->requests);
t_destroy(&f);
}
puts("PASS: change between CHECK and DONE handoff retains the request for a new checkpoint");
}
static void t_cancel_done(uint64_t peer, const char* ns, int result, void* arg) {
struct fixture* f = arg;
@@ -478,7 +493,7 @@ int main(void) {
size_t baseline = u_get_allocated_count();
test_malformed_and_old_round(); test_rejected_data(); test_sibling_walk(); test_cancel_waiters();
test_page_size_and_cursor(); test_readonly_tree(); test_backpressure_and_disconnect(); test_peer_restart();
test_send_rejection();
test_send_rejection(); test_checkpoint_revision();
test_error_handoff(); test_blocked_error_timeout(); test_pull_lifecycle();
test_dataset_isolation(); test_full_cursor(); test_permission_rounds(); test_model_policy(); test_reattach_tokens(); test_handshake_failures();
assert(u_get_allocated_count() == baseline);