Document Merkle v3 and signed feedback state contracts
This commit is contained in:
1 parent
6437bf780b
commit
04ff801531
2 files changed
+38
-10
No files matched your search
@@ -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 хранит подписанные байты независимо от наличия исходного сообщения;
|
||||
семантику выбранных пунктов и эмодзи проверяет вызывающий модуль до учёта результата.
|
||||
Reference in new issue
Block a user