|
|
|
|
@ -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 хранит подписанные байты независимо от наличия исходного сообщения; |
|
|
|
|
семантику выбранных пунктов и эмодзи проверяет вызывающий модуль до учёта результата. |
|
|
|
|
|