Document verified history deletion and message-specific delivery
This commit is contained in:
1 parent
aa97e053cd
commit
143788cdcd
2 files changed
+108
-25
No files matched your search
@@ -1,7 +1,9 @@
|
||||
member_sync / merkle_sync — аудит контрактов, 26.10.2026
|
||||
|
||||
Текущий статус: продолжение аудита db_sync остановлено на подтверждённом удалении
|
||||
корректной истории при ошибке проверки и испорченном входящем дубликате (раздел10).
|
||||
Текущий статус: дефекты удаления корректной истории и доставки 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.
|
||||
Дефекты очистки MEMBERS и запуска feedback исправлены и проверены.
|
||||
Исключение скрывает результаты, сохраняя историю; отложенные ключи и их межканальное
|
||||
разрешение поддерживаются. Архитектурные пробелы раздела8 устранены реализацией раздела9.
|
||||
@@ -550,3 +552,75 @@ BAD_FORMAT, DEFERRED, ERROR), сохранение корректной суще
|
||||
Предупреждение и уточнение контракта добавлены в db_sync.h/arch.md, задачи — в doc/tasks.md.
|
||||
Новые полный check/sanitizers/GUI/Android/Windows/FreeBSD не запускались; STUN пропущен.
|
||||
Результаты прошлых исправлений member/feedback сохраняются и не объявляются покрытием новых случаев.
|
||||
|
||||
11. Исправление db_sync после rebase — 26.10.2026
|
||||
|
||||
По запросу пользователя выполнен fetch/rebase на origin/master=03b05094.
|
||||
Три локальных коммита аудита перенесены без конфликтов; range-diff:3/3 идентичны.
|
||||
Резервная ветка backup/db-sync-pre-rebase-61f5ab4f сохраняет исходный HEAD.
|
||||
Прежний запрет запуска STUN отменён новым запросом пользователя проверить его после rebase.
|
||||
|
||||
Реализация:
|
||||
- Проверка конкретных байтов возвращает VALID, BAD_SIGNATURE, BAD_FORMAT, DEFERRED или ERROR.
|
||||
Только SC_ERR_AUTH_FAILED завершённой операции означает BAD_SIGNATURE; CRYPTO и SQL lookup error — ERROR.
|
||||
Неизвестный ключ — DEFERRED. SQLITE_NOMEM при чтении BLOB не превращается в BAD_FORMAT.
|
||||
Формат здесь означает непустое тело до4000 байт и подпись ровно64 байта, не проверку JSON-схемы приложения.
|
||||
- Перед удалением дубликата внутри BEGIN IMMEDIATE независимо проверяется сохранённая строка.
|
||||
Корректная строка переживает неверную входящую копию. Доказанная сохранённая порча удаляется;
|
||||
при корректном входе вместо неё атомарно сохраняется исправная копия.
|
||||
Любой отказ чтения, DELETE, INSERT или COMMIT приводит к ROLLBACK без продвижения next_id/позиций.
|
||||
- Выдача пачки проверяет длины и подпись самой сохранённой строки. SELECT завершается до DELETE по rowid.
|
||||
Одна доказанно битая строка удаляется в собственной транзакции, затем пачка старого раунда не отправляется.
|
||||
После COMMIT запускается пересчёт с0 и сбрасываются позиции/состояния пиров; другой канал не затрагивается.
|
||||
- Recalc имеет отдельный pending, не зависящий от наличия таймера. Батч50 атомарен;
|
||||
только успешный COMMIT продвигает позицию. Отказ SQL/хеша/таймера сохраняет работу.
|
||||
Flush возвращает ошибку; обработчики не публикуют заведомо неготовые хеши как успешный результат.
|
||||
Битая длина сохранённой подписи, обнаруженная recalc, удаляется с проверкой SQL и COMMIT.
|
||||
- Парсер не передаёт короткую подпись дальше к чтению64 байт. Ошибка вставки/проверки/формата
|
||||
прерывает SEND_DATA без подтверждения позиций и SYNC_DONE; успешно вставленная часть остаётся для повтора.
|
||||
Старый peer-empty цикл теперь продвигается на фактически отправленное число записей при ограничении буфера.
|
||||
- ACK_PUSH изменён с(timestamp,author) на message_id[32], общий размер33 с типом; совместимость не поддерживается.
|
||||
SQL больше не использует отсутствующую author. Один UPDATE обновляет только нужную запись,
|
||||
повтор ACK от того же пира не увеличивает счётчик; WAS_SENT выставляется только для локального автора.
|
||||
Постановка PUSH в очередь доставкой не считается. Корректный дубликат PUSH тоже получает ACK.
|
||||
Подготовка/bind/step/finalize SQL и неизвестный message_id диагностируются.
|
||||
|
||||
Проверки и доказательства:
|
||||
- Прежний /tmp/db-sync-audit-verify-failure.c теперь exit0, сохранение2/2 PASS;
|
||||
положительные контроли удаления неверной подписи и тела4001 PASS, другой канал и allocations сохранены.
|
||||
Лог /tmp/db-sync-preservation-fixed.log. Исторический EXPLAIN старого неверного SQL
|
||||
в этом стенде ожидаемо остаётся SQLITE_ERROR; новую доставку проверяют штатные тесты ниже.
|
||||
- Новый tests/test_db_sync_integrity.c использует собственную SQLite, настоящие Ed25519 подписи,
|
||||
uasync и точечные --wrap отказы.10 введённых отказов: VerifyInit входа/сохранённой копии/выдачи,
|
||||
BLOB NOMEM во вставке/выдаче/recalc, COMMIT удаления/recalc/вставки, создание recalc-таймера.
|
||||
Дополнительно реальные SQL-триггеры запрещают DELETE, обновление хеша и ACK; проверены откат и повтор.
|
||||
Неизвестный ключ и SQL lookup error сохраняют историю. Проверены две таблицы, reset позиций,
|
||||
замена порчи корректной копией, длины подписи0/1/63/65/255, тело4001 и55 записей (несколько батчей).
|
||||
ACK для двух подписей одного автора при одном timestamp обновляет только конкретную запись и идемпотентен.
|
||||
После teardown tracked allocations восстановлены. Лог /tmp/db-sync-integrity-final.log,exit0.
|
||||
- tests/test_db_sync.c дополнен двумя сообщениями одного автора с одинаковым timestamp:
|
||||
до poll доставки нет, затем реальные PUSH/ACK подтверждают оба сообщения независимо.
|
||||
Все прежние сценарии2/3 узлов, merge, mid/tail PUSH, reply и randomized reconnect также PASS.
|
||||
Лог /tmp/db-sync-network-final.log; схема — /tmp/db-sync-schema-final.log, оба exit0.
|
||||
- Полный ./check.sh в отдельном root network/mount namespace:203/203 PASS,0 failed/0 skipped;
|
||||
proxy9/9,burst1/1 (8388608 байт),load8/8 (134217728 байт),exit0,195с.
|
||||
Лог /tmp/db-sync-full-final-check.log. Финальная целевая сборка без предупреждений.
|
||||
- STUN: отдельный запуск и общий набор PASS. Прежнее падение assert(bind) после rebase не воспроизведено;
|
||||
причина прежнего отказа и первый падающий коммит не установлены, исправление STUN не заявляется.
|
||||
В общем STUN-логе есть штатно пропущенный подслучай bind-to-device (нет разрешения);
|
||||
основные RFC5780/fallback/cache/lifecycle/isolation проверки выполнены.
|
||||
Логи /tmp/stun-after-rebase.log и tests/logs/test_stun_nat.log.
|
||||
- Отдельная копия HEAD9131789c: ASAN+UBSan и detect_leaks=1,halt_on_error=1;
|
||||
core/libuasync/Opus согласованно инструментированы.8/8 PASS: db_sync_integrity,db_sync,
|
||||
db_sync_schema,member_adapter,chat_feedback,chat_poll,chat_poll_network,merkle_protocol.
|
||||
/tmp/db-sync-asan-results.log и /tmp/db-sync-asan-test_*.log; признаков sanitizer errors нет.
|
||||
|
||||
Границы:
|
||||
Полный аудит db_sync не закрыт. В существующем db_handle_sync_done ветка одинакового count
|
||||
с разными chain_hash всё ещё явно принимает состояние как converged; это отдельный дефект
|
||||
финального критерия, не проверявшийся новым runtime-стендом. Wire раундов не имеет generation ID;
|
||||
поведение поздних пакетов прежнего раунда требует отдельного анализа. Также остаются TTL
|
||||
(единицы времени/пересчёт после удаления) и жизненный цикл callback-списка.
|
||||
Эти места не включены в доказанное покрытие текущего исправления. GUI/Android/Windows/FreeBSD не запускались.
|
||||
Следующий этап аудита требует отдельного исправления финального критерия; по AGENTS.md
|
||||
дальнейшее расширение текущей реализации остановлено на этом замечании.
|
||||
+32
-23
@@ -1,10 +1,9 @@
|
||||
# db_sync — децентрализованная реплицируемая таблица с цепным хешированием
|
||||
|
||||
Аудит 26.10.2026 частичный. Подтверждён дефект: отказ инициализации OpenSSL при
|
||||
повторной вставке удаляет корректную историю. Испорченный входящий дубликат также
|
||||
может удалить корректную сохранённую копию. Runtime-код не исправлялся;
|
||||
продолжение остановлено по AGENTS.md. Условия удаления и воспроизведение —
|
||||
[отчёт, раздел10](../../doc/member_merkle_audit_findings.txt).
|
||||
Аудит 26.10.2026 частичный. Исправлены удаление корректной истории при отказе
|
||||
проверки/неверном входящем повторе и SQL доставки. ACK_PUSH теперь содержит
|
||||
message_id; старый формат не поддерживается. Условия удаления, проверки и
|
||||
оставшиеся границы аудита — [отчёт, разделы10–11](../../doc/member_merkle_audit_findings.txt).
|
||||
|
||||
## 1. Архитектура
|
||||
|
||||
@@ -47,7 +46,7 @@
|
||||
- `next_id` — автоинкрементный ID записей
|
||||
- `last_timestamp_ms` — последний использованный timestamp (монотонный)
|
||||
- `peers[]` — динамический массив SI_PEER (состояние синхронизации с каждым пиром)
|
||||
- `recalc` — отложенный пересчёт цепных хешей (асинхронно, батчами по 50)
|
||||
- `recalc` — позиция, pending и таймер пересчёта; ошибка сохраняет pending, батчи по50
|
||||
- `ttl_timer` — периодическая TTL-очистка (каждый час)
|
||||
- `on_insert` / `on_insert_arg` — коллбэк при вставке записи
|
||||
|
||||
@@ -73,6 +72,7 @@ CREATE TABLE "msg_GENERAL" (
|
||||
flags INTEGER DEFAULT 0, -- DB_REC_FLAG_WAS_SENT (0x01)
|
||||
data BLOB, -- JSON-данные записи
|
||||
author_signature BLOB NOT NULL, -- Ed25519(ts || data), 64 байта
|
||||
message_id BLOB NOT NULL UNIQUE CHECK(length(message_id)=32),
|
||||
local_attrs TEXT DEFAULT '', -- локальные атрибуты (не реплицируются)
|
||||
delivered_peers INTEGER DEFAULT 0, -- счётчик пиров, принявших PUSH
|
||||
delivery_chain TEXT DEFAULT '', -- цепочка peer_id hex через запятую
|
||||
@@ -95,7 +95,7 @@ chain_hash[0] = SHA256(0x00..00 [32] || id_0 || ts_0 || author_0 || sig_0)
|
||||
|----------|----------|
|
||||
| **Цепной хеш** | Каждая запись ссылается на хеш предыдущей — защита от вставки/удаления в середине цепочки |
|
||||
| **Ed25519-подписи** | Каждая запись подписана автором. Проверяется при вставке (и локальной, и от пира) |
|
||||
| **Async recalc** | Пересчёт `chain_hash` батчами по 50 записей (через `uasync_set_timeout`) — не блокирует event loop |
|
||||
| **Async recalc** | Батчи по50 с атомарным продвижением после COMMIT; перед обменом flush завершается синхронно, ошибка прерывает раунд |
|
||||
| **Master/Slave** | Узел с бОльшим `node_id` — мастер (шлёт `INIT_SYNC`). Узел с меньшим — слейв (шлёт `REQUEST_SYNC`) |
|
||||
| **Периодическая проверка** | Каждые 5 секунд — поиск пиров с sync_state=0 и запуск синхронизации, детекция таймаутов (15с) |
|
||||
| **TTL-очистка** | Каждый час — удаление неподтверждённых (`flags&1==0`) записей локального автора старше `db_sync_ttl` |
|
||||
@@ -371,20 +371,22 @@ tp=10 → entry[0]: pos=9 (tp - 1)
|
||||
|
||||
### 3.7 DB_MSG_ACK_PUSH (0x06)
|
||||
|
||||
**Отправитель:** получатель PUSH → автору
|
||||
**Отправитель:** получатель PUSH → переславшему пиру
|
||||
**Назначение:** подтверждение получения PUSH-записи
|
||||
|
||||
```
|
||||
┌──────┬──────────┬──────────────┐
|
||||
│ 0x06 │ ts (8) │ author (8) │
|
||||
└──────┴──────────┴──────────────┘
|
||||
total: 17 bytes
|
||||
┌──────┬──────────────────┐
|
||||
│ 0x06 │ message_id (32) │
|
||||
└──────┴──────────────────┘
|
||||
total: 33 bytes
|
||||
```
|
||||
|
||||
- `ts` — timestamp записи (uint64, host byte order)
|
||||
- `author` — node_id автора (uint64, host byte order)
|
||||
- `message_id` — SHA256 идентичности записи (group_id, timestamp, подпись; см. chat_feedback.h).
|
||||
|
||||
При получении ACK_PUSH автор помечает запись флагом `DB_REC_FLAG_WAS_SENT` (0x01) и обновляет `delivery_chain`/`delivered_peers`.
|
||||
Получатель ACK одним UPDATE находит ровно эту запись, добавляет peer_id в delivery_chain
|
||||
и увеличивает delivered_peers только при первом ACK от этого пира. Для локального автора
|
||||
также устанавливается DB_REC_FLAG_WAS_SENT. Постановка PUSH в очередь доставкой не считается.
|
||||
ACK отправляется и при корректном дубликате; записи с одинаковыми автором/временем независимы.
|
||||
|
||||
### 3.8 DB_MSG_SYNC_DONE (0x07)
|
||||
|
||||
@@ -488,7 +490,8 @@ tp=10 → entry[0]: pos=9 (tp - 1)
|
||||
1. `db_sync_flush_recalc()` — синхронно завершает все отложенные пересчёты
|
||||
2. Парсит `from`, `count`, `vp`, `want_from`
|
||||
3. Для каждой записи (макс. 32): `si_parse_record()` → `db_record_insert()`
|
||||
- Проверка Ed25519-подписи (если неверна — удаление записи)
|
||||
- Проверка длины тела и подписи до чтения64 байт; проверка подписи конкретных входящих байтов
|
||||
- Если строка уже есть, независимо проверяются сохранённые байты: только доказанная порча допускает DELETE
|
||||
- Проверка дубликата по `(timestamp, author_signature)`
|
||||
- Вычисление chain_hash
|
||||
- Вставка в SQLite, запуск async recalc
|
||||
@@ -497,6 +500,11 @@ tp=10 → entry[0]: pos=9 (tp - 1)
|
||||
5. Если `want_from != DB_WANT_FROM_NONE`: отправляет встречный SEND_DATA
|
||||
6. Если `want_from == DB_WANT_FROM_NONE` и `sync_state == 1`: отправляет `SYNC_DONE`
|
||||
|
||||
Ошибка формата, подписи, ключа, SQL или пересчёта прерывает раунд без подтверждения его позиций.
|
||||
Уже успешно вставленные записи остаются в БД; повторный раунд принимает их как дубликаты.
|
||||
При выдаче пачки сохранённая порча удаляется после завершения SELECT в своей транзакции;
|
||||
пачка старого раунда не отправляется. После COMMIT сбрасываются позиции всех пиров и пересчёт начинается с0.
|
||||
|
||||
### 4.5 Обработка SYNC_DONE
|
||||
|
||||
1. `db_sync_flush_recalc()`
|
||||
@@ -511,14 +519,13 @@ tp=10 → entry[0]: pos=9 (tp - 1)
|
||||
1. При локальной вставке (`db_sync_insert_signed`):
|
||||
- Формирует PUSH-сообщение
|
||||
- Рассылает всем пирам с `sync_state >= 1`
|
||||
- Обновляет `delivery_chain` для каждого пира
|
||||
2. При получении PUSH:
|
||||
- Вставляет запись через `db_record_insert()`
|
||||
- Шлёт `ACK_PUSH` обратно автору
|
||||
- После успешной вставки/корректного повтора шлёт `ACK_PUSH(message_id)` переславшему пиру
|
||||
- Если запись вставлена в середину (не в конец) — сбрасывает `synced_pos` всех пиров до позиции вставки-1
|
||||
3. При получении ACK_PUSH:
|
||||
- Помечает запись флагом `WAS_SENT`
|
||||
- Обновляет `delivery_chain`
|
||||
- Для локального автора помечает запись флагом `WAS_SENT`
|
||||
- Идемпотентно обновляет `delivery_chain`/`delivered_peers` по message_id
|
||||
|
||||
---
|
||||
|
||||
@@ -528,14 +535,16 @@ tp=10 → entry[0]: pos=9 (tp - 1)
|
||||
Требуемый контракт удаления: завершённая проверка установила неверность подписи самих сохранённых байтов
|
||||
либо доказан повреждённый формат этой записи. Отсутствие ключа, отказ OpenSSL/памяти/SQL не доказывают порчу.
|
||||
Неверная входящая копия не доказывает неверность корректной строки с тем же ключом.
|
||||
Текущий db_verify_author_sig объединяет эти причины, а db_record_insert и si_send_data_batch используют отказ для DELETE;
|
||||
это подтверждённый неисправленный дефект, а не реализованный требуемый контракт.
|
||||
db_verify_author_sig различает VALID/BAD_SIGNATURE/BAD_FORMAT/DEFERRED/ERROR.
|
||||
Операционные ошибки, включая SQLITE_NOMEM при чтении BLOB, сохраняют строки.
|
||||
DELETE и замена повреждённой строки атомарны; сброс позиций и планирование цепочки выполняются после COMMIT.
|
||||
2. **Цепной хеш:** SHA256-цепочка защищает от вставки/удаления записей в середине истории. При проверке (`db_sync_chain_verify`) или при обнаружении расхождения во время отправки — запускается пересчёт с позиции ошибки.
|
||||
3. **Публичные ключи:** Ed25519 публичный ключ пира получается из трёх источников (по приоритету):
|
||||
- Локальный `inst->my_ed25519_pubkey` (для своих записей)
|
||||
- `topo_node_sqlite_get_ed25519_pubkey()` (из БД узлов)
|
||||
- `conn->peer_ed25519_pubkey` (из ETCP handshake)
|
||||
4. **Отсутствие удаления/редактирования:** записи не редактируются и не удаляются явно — только TTL-очистка неподтверждённых.
|
||||
4. **Удаление:** кроме TTL неподтверждённых, удаляются доказанно повреждённые сохранённые строки.
|
||||
Полный аудит TTL, финального сравнения хешей и callbacks ещё не завершён.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user