Browse Source

Document Merkle v3 and signed feedback state contracts

master
evgeny 1 week ago
parent
commit
04ff801531
  1. 46
      src/chat/merkle_sync_doc.md
  2. 2
      src/routing_layer/topo_node_sqlite.h

46
src/chat/merkle_sync_doc.md

@ -1,17 +1,23 @@
# merkle_sync — протокол 2
# merkle_sync — протокол 3
## Разделение ответственности
- merkle_tree: производный индекс SQLite, хеши листьев и родителей.
- merkle_sync: обмен, backpressure и жизненный цикл сессий.
- member_sync: формат записей, подписи, версии, применение страниц.
- member_sync: формат участников, подписи, версии, применение страниц.
- chat_feedback: подписанные состояния реакций/ответов, ver, отмены и применение страниц.
Движок не знает CHAT-групп. Namespace — каноническая десятичная строка uint64_t.
Все операции выполняются в одном потоке uasync экземпляра.
Сессия определяется тройкой (peer, dataset, namespace). MEMBERS=0, FEEDBACK=1.
В индексе SQLite namespace хранится как `dataset:channel_id`; корни наборов независимы.
## Дерево и транзакции
Корень имеет уровень 0. Уровни 1–5 разбивают старшие 25 бит ключа на группы по 5 бит.
Полный ключ курсора — 32 байта с лексикографическим порядком; ветвь задаётся первыми 8 байтами.
Для участника ключ — BE64(node_id) и 24 нулевых байта. Для feedback — SHA256 идентичности записи.
Совпадение первых 64 бит не объединяет записи и не пропускает их при пагинации.
SHA256 листа: `0x4d || level:1 || item_hashes`, записи упорядочены по unsigned key.
SHA256 родителя: `0x4d || level:1 || child_hash[0] || ... || child_hash[31]`.
Хеш пустого поддерева — 32 нулевых байта; строка индекса для него отсутствует.
@ -24,19 +30,22 @@ SHA256 родителя: `0x4d || level:1 || child_hash[0] || ... || child_hash[
## API
init принимает service и data_ops: update_bucket_hash, get_page, apply_items,
init принимает service и data_ops начальной модели MEMBERS. register добавляет другие наборы.
Каждая модель задаёт update_bucket_hash, get_page, apply_items,
необязательные validate_peer и finish.
update_bucket_hash добавляет хеши одного листа в переданный SHA256-контекст.
get_page получает ёмкость буфера и курсор; возвращает размер, последний ключ и флаг продолжения.
apply_items проверяет весь формат и применяет страницу вместе с индексом атомарно.
finish проверяет отложенные зависимости модели перед подтверждением корня.
start подключает namespace к текущему соединению и запрашивает контрольную точку.
start подключает (dataset, namespace) к текущему соединению и запрашивает контрольную точку.
Повторные start объединяются, обход не сбрасывается, каждый принятый callback вызывается один раз.
После подключения изменения синхронизируются автоматически. Broadcast/send-back не требуются.
MT_OK означает совпадение корней конкретного раунда, а не отсутствие будущих изменений.
cancel, cancel_ns, cancel_peer и destroy освобождают запросы без callback.
cancel отменяет только заданный dataset/peer/namespace.
cancel_ns отменяет все datasets канала, cancel_peer — все datasets пира.
Все cancel и destroy освобождают запросы без callback.
DOWN/DELETE/REINIT завершают запросы ошибкой и освобождают сессию.
После восстановления соединения интеграционный слой повторяет start.
Callback может отменить сессию; уничтожение всего экземпляра следует отложить через call_soon.
@ -66,16 +75,16 @@ WAKE также перезапускает незавершённый раунд
## Wire
Заголовок: `service:1 | namespace:8 | type:1 | round:8 | request:4` (22 байта).
Заголовок: `service:1 | namespace:8 | type:1 | round:8 | request:4 | dataset:1` (23 байта).
Все целые big-endian. Максимальный пакет вместе с заголовком — 16384 байта.
Типы 0x10–0x19: WAKE, BEGIN, READY, QUERY, HASHES, PAGE, TURN, CHECK, DONE, ERROR.
| Тип | Payload |
|---|---|
| WAKE, BEGIN, READY, TURN | пустой; request=0; у WAKE также round=0 |
| QUERY | level:1, prefix:8, after_valid:1, after:8 |
| QUERY | level:1, prefix:8, after_valid:1, after:32 |
| HASHES | 32 хеша детей по 32 байта в порядке индексов |
| PAGE | more:1, next:8, model_data |
| PAGE | more:1, next:32, model_data |
| CHECK, DONE | корень 32 байта; request=0 |
| ERROR | положительный код ошибки 4 байта; request=0 |
@ -97,4 +106,23 @@ DEBUG — TX/RX, номера запросов, размеры, состояни
test_merkle_sync проверяет индекс, односторонний start, двустороннее слияние, несколько узлов,
параллельные изменения, большой лист с пагинацией и автоматическое обновление.
test_merkle_protocol проверяет ошибочные пакеты/курсоры, сохранение соседних ветвей,
отмену waiter, ошибки apply/SQLite, backpressure, disconnect и повторное подключение.
отмену waiter, ошибки apply/SQLite, backpressure, disconnect и повторное подключение,
изоляцию datasets и пагинацию разных ключей с одинаковым 64-битным префиксом.
test_chat_feedback проверяет подписи, монотонный ver, отмены, старые записи,
конфликты одного ver, атомарность страниц, восстановление дерева и удаление канала.
## Подписанные состояния feedback
Таблица feedback_<channel> хранит message_id, node_id, kind, ver, data и signature.
Идентичность записи — (message_id, node_id, kind); kind=1 — reaction, kind=2 — answer.
ver положителен, максимум INT64_MAX. Меньшая версия игнорируется.
При одинаковом ver выбирается лексикографически большее data; конфликт логируется.
Пустое data — сохраняемая отмена, её нельзя удалять TTL-очисткой.
Подпись покрывает domain `utun-chat-feedback-v1`, BE64(channel_id) и wire-тело без подписи.
Wire-запись: message_id:32, node_id:8, kind:1, ver:8, data_len:2, data, signature:64.
Страница: count:2 и count записей; все числа big-endian, data максимум 2048 байт.
Хеш листа включает канонические тела без signature: разные корректные подписи одного тела
не создают расхождения корней. Входящие и сохранённые подписи всё равно проверяются.
Членство проверяется через подписи дерева приглашений и join, привязывающий Ed25519 к узлу.
Слой feedback хранит подписанные байты независимо от наличия исходного сообщения;
семантику выбранных пунктов и эмодзи проверяет вызывающий модуль до учёта результата.

2
src/routing_layer/topo_node_sqlite.h

@ -42,7 +42,7 @@ int topo_node_sqlite_channel_put(sqlite3* db, const char* channel_id,
const uint8_t* signature);
/* Полное локальное удаление канала: DROP peers_<id>/msg_<id>, DELETE channels,
* DELETE merkle_tree_hash WHERE namespace=<channel_id>. 0=ок. */
* Удаляются feedback_<channel_id> и merkle_tree_hash всех datasets этого канала. 0=ок. */
int topo_node_sqlite_channel_delete(sqlite3* db, const char* channel_id);
/* Упрощённый плейсхолдер мембера от topo_group (source=1, не участвует в merkle-хеше). */

Loading…
Cancel
Save