Document editable history protocol and update media insert callback
This commit is contained in:
1 parent
aefef51c58
commit
e229811e20
2 files changed
+35
-18
No files matched your search
+33
-16
@@ -8,18 +8,32 @@
|
||||
|
||||
## Идентичность и корень
|
||||
|
||||
Подпись Ed25519 покрывает `timestamp[8 в порядке хоста] || точные байты data`.
|
||||
Тело имеет длину 1–4000 байт, подпись — 64 байта. Формат подписанного тела сохранён.
|
||||
Подпись Ed25519 покрывает `"utun-chat-record-v2" | group_id:8BE | message_id:32 |
|
||||
timestamp:8BE | author:8BE | nonce:16 | ver:8BE | edited_at:8BE | data_len:4BE | data`.
|
||||
Тело имеет длину 1–4000 байт, подпись — 64 байта. Числа конверта кодируются канонически.
|
||||
|
||||
`message_id = SHA256("utun-chat-message-v1" || BE64(group_id) || BE64(timestamp) || signature)`.
|
||||
`message_id = SHA256("utun-chat-message-v2" || BE64(group_id) || BE64(timestamp) || BE64(author) || nonce[16])`.
|
||||
Случайный nonce задаётся при создании. ID и timestamp создания не меняются при редактировании.
|
||||
Порядок набора — лексикографический по полному message_id[32].
|
||||
Лист хеширует канонические записи в этом порядке. В запись входят message_id,
|
||||
timestamp, author node_id, длина и точные байты тела, подпись. Дерево и границы
|
||||
timestamp, author node_id, nonce, ver, edited_at, длина и точные байты тела, подпись. Дерево и границы
|
||||
листьев описаны в [merkle_sync_doc.md](merkle_sync_doc.md).
|
||||
|
||||
Локальные id, flags, local_attrs, delivered_peers и delivery_chain в корень не входят.
|
||||
Поэтому одинаковая подписанная история даёт одинаковый корень при разных локальных id.
|
||||
Два сообщения одного автора в одной миллисекунде различаются подписью и message_id.
|
||||
Два сообщения одного автора в одной миллисекунде различаются nonce и message_id.
|
||||
|
||||
Новая запись имеет ver=1, edited_at=0. Правка получает ver+1 и Unix ms последнего
|
||||
редактирования (строго позже создания и предыдущей локальной правки). Победителя
|
||||
определяет ver, а не время. Большая версия заменяет содержимое и подпись атомарно,
|
||||
сохраняя локальный id, порядок, local_attrs и ссылки ответов/реакций. Можно впервые
|
||||
получить сразу ver=5. Меньшая версия игнорируется; точный повтор идемпотентен.
|
||||
Разные подписанные байты одного ID/ver — конфликт: ошибка без ACK или успешного DONE.
|
||||
Одновременные писатели одной идентичности не объединяют конфликтующие версии автоматически.
|
||||
|
||||
chat_core_edit_message разрешает правку собственного text/text/plain через expected_ver;
|
||||
устаревший редактор получает отказ. Меняется только d, reply_to и тип сохраняются.
|
||||
Тот же текст не создаёт версию. CHAT_EVT_MSG_UPDATED обновляет строку и цитаты без unread/звука.
|
||||
|
||||
Допуск пересылающего пира проверяется отдельно: требуется READY группы и членство,
|
||||
привязанное к X25519-ключу соединения. Статус deleted исходного автора не фильтрует
|
||||
@@ -88,7 +102,7 @@ SQLite допускает одного writer: другие записи жду
|
||||
Вставка перед уже пройденным курсором может потребовать следующего раунда.
|
||||
Изменения дерева отмечаются после COMMIT и автоматически запускают повтор.
|
||||
При прекращении изменений, доступных ключах и исправном транспорте обмен объединяет
|
||||
неизменяемые допустимые записи. Постоянный конфликт/ошибка возвращается как ошибка,
|
||||
допустимые записи, выбирая наибольшую версию каждого ID. Постоянный конфликт/ошибка возвращается как ошибка,
|
||||
без ложного done. TTL не создаёт распределённого tombstone: другая копия может вернуть
|
||||
ранее удалённую локально запись.
|
||||
|
||||
@@ -97,12 +111,13 @@ SQLite допускает одного writer: другие записи жду
|
||||
Заголовок: `service:1 | group_id:8BE | type:1`.
|
||||
PUSH (0x05) содержит одну каноническую запись:
|
||||
|
||||
`message_id:32 | timestamp:8BE | author:8BE | data_len:4BE | data | signature:64`.
|
||||
`message_id:32 | timestamp:8BE | author:8BE | nonce:16 | ver:8BE | edited_at:8BE | data_len:4BE | data | signature:64`.
|
||||
|
||||
ACK_PUSH (0x06) содержит ровно message_id[32]. После успешной вставки либо допустимого
|
||||
ACK_PUSH (0x06) содержит `message_id:32 | ver:8BE`. После успешной вставки/правки либо допустимого
|
||||
повтора получатель отправляет ACK. Постановка PUSH в очередь не считается доставкой.
|
||||
Один идемпотентный UPDATE учитывает уникальный peer_id в delivery_chain и увеличивает
|
||||
delivered_peers только один раз. WAS_SENT выставляется для локально созданной записи.
|
||||
ACK прежней версии не подтверждает новую. При правке доставка сбрасывается.
|
||||
Bulk-сходимость не подменяет адресное подтверждение конкретного PUSH.
|
||||
|
||||
PUSH ускоряет распространение; при отказе очереди committed изменение остаётся dirty
|
||||
@@ -133,14 +148,15 @@ destroy освобождает модуль, но не общую SQLite. Уни
|
||||
## Схема и TTL
|
||||
|
||||
Таблица содержит timestamp, node_id, id, flags, data, author_signature, message_id,
|
||||
bucket, local_attrs, delivered_peers, delivery_chain.
|
||||
PRIMARY KEY — (timestamp, author_signature), message_id UNIQUE, bucket — старшие 25 бит ключа.
|
||||
chain_hash удалён. При перестройке схемы подписанные и локальные поля сохраняются;
|
||||
производные поля вычисляются заново. DDL/copy/rename/COMMIT атомарны.
|
||||
Записи с доказанно неверной длиной подписи удаляются с диагностикой; подписи корректной
|
||||
длины проверяет последующий preflight.
|
||||
bucket, local_attrs, delivered_peers, delivery_chain, nonce, ver, edited_at.
|
||||
message_id NOT NULL UNIQUE — постоянный ключ; bucket — старшие 25 бит ключа.
|
||||
Новый dev-формат не переносит старую историю: атомарно пересоздаётся только таблица
|
||||
группы и очищаются её feedback, производный индекс, ссылки/ключи старых вложений и
|
||||
позиции чтения. PM, идентичность, участники и ключи группы сохраняются.
|
||||
Повторный запуск с новым форматом сохраняет историю и версии.
|
||||
|
||||
TTL сравнивает миллисекунды Unix и удаляет только старые собственные записи без WAS_SENT.
|
||||
TTL сравнивает миллисекунды Unix и удаляет только старые собственные записи без WAS_SENT;
|
||||
для правки возраст считается от edited_at, для оригинала — от timestamp.
|
||||
За транзакцию удаляется максимум 16 строк. В том же commit очищается производный
|
||||
MESSAGES-индекс канала; до завершения повторной проверки корень остаётся pending.
|
||||
Полная пачка продолжается через 1 tick, следующая плановая проверка — через час.
|
||||
@@ -157,7 +173,8 @@ test_db_sync проверяет реальный транспорт 2/3 узло
|
||||
числом строк, отличием в начале и разными локальными id.
|
||||
test_db_sync_integrity проверяет реальные подписи, SQL/crypto/NOMEM/COMMIT/timer faults,
|
||||
атомарность страниц, курсоры, поздние ключи, исключённого автора, TTL и callbacks.
|
||||
test_db_sync_schema проверяет сохранность полей и rollback миграции.
|
||||
test_db_sync_schema проверяет правки после restart, сохранность ответов/реакций и атомарный сброс старого формата.
|
||||
test_db_sync/test_db_sync_integrity проверяют скачки версий, конфликты, старые ACK и сходимость offline/live-правок.
|
||||
test_dm_invite проверяет настоящий JOIN и восстановление истории после таймаута.
|
||||
|
||||
Индекс предполагает запись содержимого через модель. Произвольное внешнее изменение
|
||||
|
||||
@@ -276,8 +276,8 @@ static int parse_media_body(const char* body, struct media_index_result* out) {
|
||||
/* ── insert-callback: storage-узлы запускают auto-download ── */
|
||||
|
||||
static void storage_on_insert(struct DB_SYNC_INSTANCE* si, const uint8_t message_id[32], uint64_t ts,
|
||||
const char* data, size_t len, uint64_t author, int initial_sync, void* arg) {
|
||||
(void)si; (void)message_id; (void)ts; (void)initial_sync;
|
||||
const char* data, size_t len, uint64_t author, int initial_sync, int updated, void* arg) {
|
||||
(void)si; (void)message_id; (void)ts; (void)initial_sync; (void)updated;
|
||||
int idx = (int)(intptr_t)arg;
|
||||
struct UTUN_INSTANCE* inst = g_inst[idx];
|
||||
if (author == inst->node_id) return;
|
||||
|
||||
Reference in new issue
Block a user