Document SQLite snapshots, projections and membership contracts
This commit is contained in:
1 parent
19c3cc0434
commit
42a58881ca
2 files changed
+167
-46
No files matched your search
@@ -1,38 +1,124 @@
|
||||
# topo_node_sqlite — SQLite-персистентность топологии
|
||||
# topo_node_sqlite — хранение топологии в SQLite
|
||||
|
||||
## 1. Назначение
|
||||
Обеспечивает сохранение и восстановление топологии P2P-сети (ноды, адреса, каналы, участники каналов) в SQLite-базе. База используется как основной источник данных для chatgui и восстановления состояния между запусками utun.
|
||||
## Назначение и владение
|
||||
|
||||
В отличие от LMDB-слоя (`topo_node_lmdb`), который хранит данные в бинарном формате BGP-сообщений, SQLite-слой работает с нормализованными таблицами и используется для клиентских целей — чтение данных chatgui, получение списка участников канала, проверка online-статуса.
|
||||
Модуль работает с общей SQLite ядра: подписанные снимки узлов, производные адреса/состояния,
|
||||
описания каналов и строки участников. Базу открывает и закрывает владелец ядра; все операции
|
||||
этого модуля выполняются в его uasync-потоке. Отдельного LMDB-хранилища топологии здесь нет.
|
||||
|
||||
## 2. Как пользоваться
|
||||
1. Получить готовый `sqlite3* db` (открывается извне, обычно в `chat_sync.c`)
|
||||
2. Вызвать `topo_node_sqlite_init(db)` для создания таблиц
|
||||
3. Сохранять/обновлять ноды через `topo_node_sqlite_node_put()`
|
||||
4. Сохранять каналы через `topo_node_sqlite_channel_put()` (автоматически создаёт таблицу `peers_<channel_id>`)
|
||||
5. Добавлять участников в канал через `topo_node_sqlite_member_put()`
|
||||
6. Читать участников из `peers_<channel_id>`; постраничная сетевая сериализация принадлежит `member_sync`.
|
||||
Проверку подписи выполняют только topo_node_sqlite_snapshot_put/load.
|
||||
Канальные метаданные, ключи участников и их версии проверяют вызывающие chat_sync/member_sync.
|
||||
SQL-функции записи блоков сами не проверяют подписи и не отбрасывают старые версии.
|
||||
|
||||
**Нюансы:**
|
||||
- Имена таблиц `peers_<channel_id>` санитизируются: все символы кроме `[a-zA-Z0-9_]` заменяются на `_`
|
||||
- Сетевой формат участников описан в `src/chat/member_sync_doc.md`; SQLite-слой не дублирует сериализатор.
|
||||
- Ключи в BLOB-полях всегда 32 байта (x25519/ed25519), подписи — 64 байта
|
||||
- Функции `online` и `get_ed25519_pubkey` — простые SELECT-запросы без JOIN
|
||||
Возвращённый TOPO_NODE не зарегистрирован и имеет нулевой refcount.
|
||||
Передайте владение через topo_node_registry_store либо вызовите topo_node_destroy(groups, node).
|
||||
Массивы идентификаторов и topo_connect_peer освобождаются через u_free.
|
||||
db, groups и входные строки/объекты остаются у вызывающего.
|
||||
|
||||
## 3. API
|
||||
## Таблицы и версии
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `topo_node_sqlite_init(db)` | Создаёт таблицы `nodes`, `node_addresses`, `channels` (если не существуют) |
|
||||
| `topo_node_sqlite_node_put(db, nq)` | Сохраняет ноду и её адреса (IPv4/IPv6) в транзакции; старые адреса удаляются |
|
||||
| `topo_node_sqlite_channel_put(...)` | Сохраняет канал в `channels` + создаёт таблицу `peers_<ch_id>` |
|
||||
| `topo_node_sqlite_member_put(...)` | Добавляет/обновляет участника в `peers_<ch_id>` (INSERT OR REPLACE) |
|
||||
| `topo_node_sqlite_member_get_join(db, ch_id, node_id, ...)` | Читает join_sig и join_ts участника канала |
|
||||
| `topo_node_sqlite_member_del(db, ch_id, node_id)` | Удаляет участника из `peers_<ch_id>` |
|
||||
| `topo_node_sqlite_node_update_verified(...)` | Обновляет ключи и имя ноды по условию `update_ts < новое_значение` (защита от stale-данных) |
|
||||
| `topo_node_sqlite_channel_get(...)` | Читает метаданные канала (имя, владелец, ключи, подпись) |
|
||||
| `topo_node_sqlite_node_set_online(db, id, online)` | Устанавливает флаг `online` для ноды |
|
||||
| `topo_node_sqlite_node_get_online(db, id)` | Возвращает 1 если нода online |
|
||||
| `topo_node_sqlite_get_ed25519_pubkey(db, id, out)` | Извлекает ed25519-публичный ключ ноды (32 байта) |
|
||||
| Таблица | Назначение |
|
||||
| --- | --- |
|
||||
| node_snapshots | node_id, максимальный timestamp, полный подписанный NODEINFO в BLOB record |
|
||||
| nodes | Проекция имени и ключей, online/last_seen, update_ts, created_at; не замена подписанному снимку |
|
||||
| node_addresses | IPv4/IPv6 endpoints, protocol, socket_id, address_kind, NAT-профиль, классификация, RTT и REALITY options |
|
||||
| channels | Имя, владелец, публичные/необязательные приватные ключи, подпись канала и локальные поля |
|
||||
| peers_<id> | Блок участника, блок владельца и локальные поля конкретного канала |
|
||||
|
||||
**Структуры:** внешние — `sqlite3*`, `struct TOPO_GROUP_NODE`, `struct TOPO_NODE`; собственных структур нет.
|
||||
topo_node_sqlite_init создаёт общие таблицы/индексы, обрабатывает прежние форматы проекции адресов
|
||||
и восстанавливает deleted из adm_tags. peers_<id> создаётся при channel_put;
|
||||
member_local_init идемпотентно добавляет radio_muted.
|
||||
|
||||
В снимке timestamp — Unix microseconds и версия всей подписанной записи.
|
||||
snapshot_put проверяет подпись и заменяет BLOB только при большем timestamp.
|
||||
Старый или равный timestamp даёт успех без замены. При смене формата BLOB может инвалидироваться
|
||||
без сброса сохранённого timestamp, чтобы новая локальная публикация оставалась новее.
|
||||
|
||||
node_update_verified записывает уже проверенные ключи и непустое имя независимо от update_ts.
|
||||
В nodes.update_ts сохраняется максимум прежнего и входного значения; это не фильтр замены ключей.
|
||||
created_at сохраняется, если уже не NULL. Новая строка получает online=1,
|
||||
обновление существующей строки online не меняет.
|
||||
|
||||
## Снимок и bootstrap
|
||||
|
||||
topo_node_sqlite_node_load сначала загружает и проверяет снимок. Валидный снимок может содержать
|
||||
пустой список адресов: это полноценное опубликованное состояние, а не повод вернуть старые адреса.
|
||||
|
||||
Если пригодного снимка нет, функция собирает bootstrap из nodes и node_addresses.
|
||||
Для этого требуется хотя бы один адрес. Ключи/имя копируются из проекции, timestamp остаётся 0,
|
||||
профили сокетов сбрасываются в UNKNOWN; сохранённая SQL-классификация не считается подписанным доказательством.
|
||||
IPv4/IPv6 сохраняются отдельно, REALITY восстанавливается из options, порт 0 сохраняется как известный TCP IP.
|
||||
|
||||
Пулы берутся из groups; groups->instance->topo_groups должен указывать на тот же контейнер.
|
||||
При ошибке SQL или памяти частичные списки, имя, REALITY и statement освобождаются; возвращается NULL.
|
||||
|
||||
## Адреса, классификация и RTT
|
||||
|
||||
addrs_put заменяет все адреса узла одним savepoint: DELETE, затем INSERT IPv4/IPv6.
|
||||
Пустой список очищает проекцию. При отказе замена откатывается.
|
||||
Профиль mapping/inbound/source берётся из SOCKMETA, REALITY — из записи соответствующего socket_id.
|
||||
Функция не проверяет подпись входного узла. Замена адресов не сохраняет прежний RTT.
|
||||
|
||||
address_kind — INTERFACE/NAT/REAL, отдельно от вычисляемого addr_type.
|
||||
Классификация учитывает IP, protocol, конфигурационный тип сокета и профиль доступности.
|
||||
Адрес с port=0 хранится, но не является готовым endpoint для подключения.
|
||||
|
||||
nodeinfo_updated под savepoint обновляет node_type, storage, deleted во всех peers_*:
|
||||
|
||||
| node_type | Основание |
|
||||
| --- | --- |
|
||||
| 0 | Нет адресов или классификация неизвестна |
|
||||
| 1 | NETIF, DIRECT или NAT_STRICT |
|
||||
| 2 | NAT_EIM, NAT, CONTACT или EIM_UNVERIFIED |
|
||||
| 4 | adm_tags.supernode=yes |
|
||||
|
||||
При выборе адреса DIRECT имеет приоритет, иначе берётся минимальный addr_type.
|
||||
storage=yes применяется только при DIRECT; deleted выводится из adm_tags.deleted=yes.
|
||||
При отказе одной канальной записи весь пересчёт узла откатывается, функция возвращает -1.
|
||||
|
||||
update_rtt обновляет node_addresses.rtt и peers_*.node_RTT в единицах 0.1 ms.
|
||||
Обновление этих таблиц неатомарно; ошибки логируются. Online и connected меняются отдельными функциями.
|
||||
|
||||
## Каналы и участники
|
||||
|
||||
channel_put требует публичные ключи по 32 байта и подпись 64 байта.
|
||||
При конфликте channel_id публичные ключи должны совпасть: меняются имя/владелец/подпись,
|
||||
переданные privkey заменяют прежние, NULL сохраняет их. Криптографическую проверку делает вызывающий.
|
||||
|
||||
Суффикс таблиц строится из первых 63 символов channel_id: вне [A-Za-z0-9_] заменяются на '_'.
|
||||
Штатные channel_id — десятичные uint64. Строка channels.channel_id хранит исходное значение.
|
||||
|
||||
member_placeholder_put вставляет source=1 с нулевой join_sig; существующую строку не меняет.
|
||||
member_block_put заменяет ключи/join/update/userinfo/дерево приглашений и устанавливает source=0,
|
||||
сохраняя owner-блок и локальные поля. NULL join_sig записывается как 64 нулевых байта.
|
||||
member_owner_put меняет adm_tags/подпись/storage/deleted без вставки отсутствующей строки.
|
||||
Placeholder не участвует в Merkle-хеше; сетевой формат и проверка версий принадлежат member_sync.
|
||||
|
||||
member_exists, member_in_channel, get_member_channels учитывают любое наличие строки,
|
||||
в том числе source=1 и deleted=1. Это не проверка полной подписанной записи участника.
|
||||
member_get_join требует ровно 64 байта подписи; channel_get проверяет размер запрошенных ключей/подписи.
|
||||
|
||||
member_del физически удаляет строку без tombstone и пересчёта Merkle.
|
||||
channel_delete удаляет peers/msg/feedback, канал с приватными ключами и хеши datasets его namespace
|
||||
(если таблица Merkle существует). Все действия, включая DROP TABLE, защищены savepoint:
|
||||
ошибка возвращает -1 и откатывает удаление, сохраняя внешнюю транзакцию.
|
||||
|
||||
## Выбор пиров
|
||||
|
||||
Все выборки возвращают принадлежащий вызывающему массив. Пустой результат — NULL/0.
|
||||
Ошибка чтения/памяти — -1 без публикации частичного массива.
|
||||
|
||||
| API | Выборка |
|
||||
| --- | --- |
|
||||
| get_connect_peers | Все неудалённые участники канала, включая self; connected, затем известный RTT/RTT/node_id |
|
||||
| get_connected_peers | connected=1, deleted=0 в указанном канале |
|
||||
| get_supernode_peers | node_type=4, deleted=0 в указанном канале |
|
||||
| get_public_peers | Глобальные адреса addr_type 1/2 с записью nodes, кроме self и удалённых в указанном канале |
|
||||
| get_local_peers | Аналогично public, но addr_type 0/3 |
|
||||
|
||||
Public/local не требуют членства в указанном канале и не фильтруют port=0.
|
||||
Для get_connect_peers роль supernode берётся из adm_tags, client_type — из сохранённого
|
||||
подписанного snapshot; отсутствие снимка даёт UINT8_MAX. Адреса выборку не ограничивают.
|
||||
Сам запрос не повторяет криптографическую проверку BLOB; подпись проверяется при сохранении снимка.
|
||||
|
||||
Полный список функций, размеры буферов и возвраты — в topo_node_sqlite.h.
|
||||
Reference in new issue
Block a user