You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
19 KiB
19 KiB
db_sync — Distributed content-addressed table with SQLite + P2P sync
1. Назначение
Децентрализованная реплицируемая таблица JSON-записей, синхронизируемая между всеми узлами сети через ETCP. Каждый узел хранит полную копию данных каждого инстанса. Модуль поддерживает несколько независимых инстансов (таблиц), каждый идентифицируется хешем SHA256(name || id_be)[0:8].
Ключевые свойства:
- Content-addressed: целостность цепочки гарантируется
chain_hash— каскадным SHA256, гдеchain_hash[n] = SHA256(chain_hash[n-1] || id || timestamp || author || author_signature). - Ed25519-подписи: каждая запись обязательно подписана автором (
Ed25519(timestamp || json_data)). Записи без подписи или с неверной подписью отвергаются. - Multi-instance: несколько независимых таблиц внутри одного процесса (напр.
("chats", 1)и("chats", 2)). - Ordered: записи упорядочены по
(timestamp, author_signature). Первичный ключ — та же пара, определяющая уникальность (дубликаты по тому же автору в ту же миллисекунду невозможны). - Append-only: записи не редактируются и не удаляются явно, только TTL-очистка собственных неотправленных записей.
- Sync protocol: 8 типов сообщений (INIT_SYNC → INIT_RESP → REFINE → SEND_DATA → SYNC_DONE, плюс PUSH/ACK_PUSH/ERROR). Используется бинарный поиск расхождений по chain_hash.
- Push: новые записи немедленно рассылаются (PUSH) всем синхронизированным пирам.
2. Как пользоваться
Типовой сценарий
1. В конфиге: db_sync_enabled = 1, db_sync_ttl = 86400
2. db_sync_init(inst) — вызывается автоматически при старте utun_instance
3. struct DB_SYNC_INSTANCE* si = db_sync_instance_add(inst, "chats", 1);
→ Создаёт таблицу SQLite, верифицирует цепочку, запускает TTL-таймер.
→ Если есть активные ETCP-соединения — автоматически запускает синхронизацию.
4. Вставка записи:
uint64_t ts = db_sync_next_timestamp(si); // монотонно возрастающий timestamp (ms)
uint8_t sig[64];
Ed25519_sign(my_privkey, ts || my_node_id || json, sig);
db_sync_insert_signed(si, json_str, json_len, sig, 64, ts);
→ Подпись ОБЯЗАТЕЛЬНА (sig=NULL → ошибка). Запись автоматически push-ится всем synced пирам.
5. Чтение:
db_sync_select(si, offset, limit, my_callback, ctx);
6. db_sync_instance_remove(si) — деактивировать инстанс (таблица БД не удаляется).
7. db_sync_destroy(inst) — вызывается автоматически при завершении.
Ключевые концепции
Multi-instance
Каждый экземпляр DB_SYNC_INSTANCE идентифицируется 64-битным хешем hash = SHA256(name || htobe64(id))[0:8]. Этот хеш используется как routing key в синхронизационных сообщениях. На приёмной стороне по хешу находится нужный инстанс.
Chain hash
Каскадный хеш цепочки записей:
chain_hash[0] = SHA256(0x00...00 || id_0 || ts_0 || author_0 || sig_0)
chain_hash[n] = SHA256(chain_hash[n-1] || id_n || ts_n || author_n || sig_n)
Для протокола синхронизации используются первые 8 байт (chain_hash8). Сравнивая эти хеши на разных позициях, узлы находят точку расхождения.
Sync protocol (8 message types)
| Message | Direction | Описание |
|---|---|---|
DB_MSG_INIT_SYNC (0x01) |
A→B | Инициатор шлёт своё количество записей (my_count) |
DB_MSG_INIT_RESP (0x02) |
B→A | Truncation point tp = min(counts)-1, chain_hash8[tp], + до 16 sparse-хешей на позициях tp-2^k |
DB_MSG_REFINE (0x03) |
A→B, B→A | Бинарный поиск: до 16 равномерно распределённых хешей в диапазоне расхождения |
DB_MSG_SEND_DATA (0x04) |
A→B, B→A | Пакетная передача записей (до 32 за раз). Wire: [id:8][ts:8][author:8][dlen:4][data][sig_len:1=64][sig:64] |
DB_MSG_PUSH (0x05) |
A→B | Рассылка одной новой записи всем synced-пирам |
DB_MSG_ACK_PUSH (0x06) |
B→A | Подтверждение получения PUSH; обновляет delivery_chain и флаг WAS_SENT |
DB_MSG_SYNC_DONE (0x07) |
B→A | Финальное подтверждение: peer_count + last_chain_hash8 |
DB_MSG_ERROR (0x08) |
B→A | Ошибка. Коды: 0x01 — instance not found, 0x02 — instance disabled |
Алгоритм синхронизации:
INIT_SYNC: A → B:my_count_a; B вычисляетtp = min(count_a, count_b) - 1.INIT_RESP: B → A:tp,chain_hash8_b[tp], + sparse-хеши на позицияхtp-1, tp-2, tp-4, tp-8, ..., tp-32768.- Если
hash8_b[tp] == hash8_a[tp]— цепочки совпадают доtp. Более длинная сторона шлёт «хвост» (записи послеtp). - Если sparse-хеши показывают расхождение: A определяет диапазон
[ds, de]где хеши не совпадают. Еслиde-ds ≤ 1— пустойREFINE(запрос данных). Иначе —REFINEс до 16 хешами, равномерно распределёнными в диапазоне. REFINE: B ищет первую позицию, где хеши разошлись, шлётSEND_DATAначиная с этой позиции.SEND_DATA: A вставляет полученные записи (с верификацией Ed25519 подписи), пересчитывает каскадный chain_hash, шлётSYNC_DONE.SYNC_DONE: если counts/hashes не совпали — реинициируется sync.
Защита от подделок
- Ed25519-подпись автора проверяется для КАЖДОЙ вставляемой записи (и локальной, и от пиров).
- Публичный ключ автора ищется: (1) в своих ключах (если self), (2) в
topo_node_sqlite, (3) вpeer_ed25519_pubkeyактивного ETCP-соединения. - Если подпись невалидна — запись отвергается с логом "discarding as forgery".
Peer management
- При поднятии ETCP-соединения для каждого инстанса добавляется
SI_PEERи запускается синхронизация. - При разрыве соединения
sync_stateпира сбрасывается в 0. peer_checkтаймер (каждые 5с) перебираетtopo_group->senders_listи запускает синхронизацию для пиров сsync_state == 0.PUSHрассылается только пирам в состоянииsync_state >= 1.
TTL cleanup
- Периодический таймер (каждые 3600с).
- Удаляет записи authored by self, где НЕ установлен флаг
DB_REC_FLAG_WAS_SENT(0x01) иtimestamp < now - db_sync_ttl. - Чужие записи и свои отправленные не удаляются.
3. API
Глобальный жизненный цикл
int db_sync_init(struct UTUN_INSTANCE* inst);
void db_sync_destroy(struct UTUN_INSTANCE* inst);
db_sync_init— инициализирует DB_SYNC, открывает SQLite по пути<db_path>/sync, биндит ETCP service0x20, вешает коллбэки на существующие и новые соединения, стартуетpeer_checkтаймер. Еслиdb_sync_enabled = 0— создаёт структуру в disabled-режиме.db_sync_destroy— отменяет все таймеры, анбиндит ETCP service, снимает коллбэки со всех соединений, закрывает SQLite, освобождает память.
Управление инстансами
struct DB_SYNC_INSTANCE* db_sync_instance_add(struct UTUN_INSTANCE* inst, const char* name, uint64_t id);
void db_sync_instance_remove(struct DB_SYNC_INSTANCE* si);
db_sync_instance_add— создаёт/регистрирует инстанс. Проверяет валидность имени ([a-zA-Z0-9_], макс 48 символов). Вычисляет hash, создаёт SQLite-таблицуdb_sync_<name>_<id>, верифицирует цепочку хешей, запускает TTL-таймер. Если уже есть активные соединения — автоматически инициирует sync.db_sync_instance_remove— деактивирует инстанс:enabled=0, отменяет TTL-таймер, освобождает peers, удаляет из массива. Таблица БД не удаляется.
Операции с данными
int db_sync_insert_signed(struct DB_SYNC_INSTANCE* si, const char* json_data, size_t len,
const uint8_t* sig, size_t sig_len, uint64_t ts);
uint32_t db_sync_count(struct DB_SYNC_INSTANCE* si);
uint64_t db_sync_get_last_timestamp(struct DB_SYNC_INSTANCE* si);
uint64_t db_sync_next_timestamp(struct DB_SYNC_INSTANCE* si);
db_sync_insert_signed— вставляет подписанную запись.sigОБЯЗАТЕЛЬНО 64 байта (Ed25519). Проверяет подпись, проверяет дубликат по(timestamp, author_signature), вычисляет chain_hash, вставляет, каскадно пересчитывает хеши последующих записей, рассылает PUSH всем synced-пирам. Возвращает 0 (успех), 1 (уже существует), -1 (ошибка), -2 (неверная подпись).db_sync_count— количество записей в локальной БД.db_sync_get_last_timestamp— последний выданныйdb_sync_next_timestamp.db_sync_next_timestamp— возвращает монотонно возрастающий timestamp в миллисекундах. Гарантируетlast_timestamp_ms < returned.
Чтение (select)
typedef void (*db_sync_select_cb)(void* arg, uint64_t id, uint64_t timestamp,
const char* data, size_t data_len, uint64_t author,
const uint8_t* author_sig, size_t sig_len,
int delivered_peers, const char* delivery_chain);
int db_sync_select(struct DB_SYNC_INSTANCE* si, uint32_t offset, uint32_t limit,
db_sync_select_cb cb, void* arg);
db_sync_select— итератор по записям, упорядоченнымORDER BY timestamp, author_signature.limit=0— без ограничения. Для каждой записи вызываетcbс полями: id, timestamp, data (JSON), author (node_id), author_sig (64 байта), delivered_peers (счётчик доставок), delivery_chain (hex-идентификаторы пиров через запятую). Возвращает количество переданных в callback записей.
Callback на вставку
typedef void (*db_sync_insert_cb)(struct DB_SYNC_INSTANCE* si,
uint64_t record_timestamp,
const char* json_data, size_t len,
uint64_t author_node_id, void* arg);
void db_sync_set_insert_cb(struct DB_SYNC_INSTANCE* si, db_sync_insert_cb cb, void* arg);
db_sync_set_insert_cb— устанавливает callback, вызываемый после успешной вставки записи (локальной или от пира).author_node_id = selfдля локальных вставок,= peer_node_idдля записей от пиров.
Внутренние структуры
struct DB_SYNC {
struct UTUN_INSTANCE* inst; // обратная ссылка на инстанс
sqlite3* db; // SQLite handle
uint64_t last_connected_tb; // время последнего подключения (timebase)
struct DB_SYNC_INSTANCE* instances; // динамический массив инстансов
int instance_count, instance_capacity;
void* peer_check_timer; // handle uasync-таймера
uint8_t enabled; // глобальный флаг из конфига
};
struct DB_SYNC_INSTANCE {
struct DB_SYNC* db_sync; // обратная ссылка
uint64_t hash; // routing key = SHA256(name||id_be)[0:8]
char table_name[64]; // "db_sync_<name>_<id>"
uint64_t next_id; // монотонно возрастающий id записей
uint64_t last_timestamp_ms; // последний выданный timestamp (ms)
uint8_t enabled; // per-instance enable/disable
void* ttl_timer; // handle TTL-таймера
struct SI_PEER* peers; // динамический массив пиров
int peer_count, peer_capacity;
db_sync_insert_cb on_insert; // callback на вставку
void* on_insert_arg;
};
struct SI_PEER {
uint64_t node_id; // идентификатор пира
uint32_t synced_pos; // позиция, до которой синхронизированы
uint8_t sync_state; // 0=idle, 1=syncing, 2=synced
};
Конфигурация
| Параметр | Файл конфига | По умолчанию | Описание |
|---|---|---|---|
db_sync_enabled |
global |
0 |
Включить модуль синхронизации |
db_sync_ttl |
global |
86400 |
TTL неотправленных собственных записей (секунды) |
db_path |
global |
— | Путь к БД; файл создаётся как <db_path>/sync |
Константы синхронизации
| Константа | Значение | Описание |
|---|---|---|
ETCP_RT_ID_DB_SYNC |
0x20 |
ETCP service ID |
DB_REFINE_HASHES |
16 |
Макс. количество хешей в REFINE |
DB_SEND_DATA_MAX |
32 |
Макс. записей в одном SEND_DATA |
DB_SIG_SIZE |
64 |
Размер Ed25519 подписи |
DB_SYNC_PEER_CHECK_INTERVAL |
5 |
Интервал проверки пиров (секунды) |
DB_SYNC_TTL_INTERVAL |
3600 |
Интервал TTL-очистки (секунды) |
Зависимости
- etcp_api.h / etcp.h — P2P-обмен сообщениями, коллбэки соединений
- topo_group.h / topo_node_sqlite.h — топология, discovery пиров, Ed25519 pubkeys
- secure_channel.h — SHA256 (
sc_sha256_*), Ed25519 verify (sc_ed25519_verify) - lib/u_async.h — асинхронный event loop, таймеры (
uasync_set_timeout,uasync_cancel_timeout) - lib/sqlite3.h — SQLite3 (WAL, synchronous=NORMAL, auto-checkpoint 10000)
- lib/mem.h —
u_malloc,u_calloc,u_realloc,u_free - lib/debug_config.h —
DEBUG_INFO,DEBUG_WARN,DEBUG_ERROR(категорияDEBUG_CATEGORY_DEBUG) - lib/sha256.h —
SC_SHA256_CTX(если не USE_OPENSSL) - lib/platform_compat.h —
htobe64,get_time_tb,utun_gettimeofday - openssl/evp.h — низкоуровневый Ed25519 в
sc_ed25519_verify
Внутренние функции
| Функция | Назначение |
|---|---|
db_sqlite_open/db_sqlite_close |
Открытие/закрытие SQLite с WAL-режимом |
db_sha256/db_hash64/db_chain_hash_compute |
SHA256 и вычисление chain_hash |
db_instance_find/db_instance_alloc |
Поиск/выделение инстанса по hash |
si_peer_find/si_peer_add |
Поиск/добавление пира в инстансе |
db_count |
SELECT COUNT(*) из таблицы инстанса |
db_chain_hash_at/db_chain_hash8_at |
Чтение chain_hash/chain_hash8 на позиции |
db_prev_chain_hash |
Поиск chain_hash записи, предшествующей данной |
db_cascade_from |
Пересчёт chain_hash начиная с позиции from_pos |
db_get_ed25519_pubkey |
Получение Ed25519 pubkey пира |
db_verify_author_sig |
Проверка Ed25519 подписи автора записи |
db_record_insert |
Полный цикл вставки: проверка подписи, проверка дубликата, вставка, каскад chain_hash |
db_sync_send/db_sync_send_hash |
Отправка сообщения пиру через ETCP |
si_delivery_update |
Обновление delivery_chain при доставке |
si_parse_record |
Разбор одной записи из wire-формата |
si_send_data_batch |
Формирование и отправка SEND_DATA пакета |
db_sync_recv_cb |
Центральный callback приёма ETCP-сообщений, маршрутизация по instance_hash |
db_handle_init_sync/_resp/_refine/_send_data/_push/_ack_push/_sync_done/_error |
Обработчики каждого типа сообщения |
db_sync_initiate_sync |
Отправка INIT_SYNC пиру |
db_verify_chain |
Полная верификация цепочки chain_hash (при старте инстанса) |
db_sync_peer_check_cb |
Таймер: периодический поиск активных пиров и запуск sync |
db_sync_instance_ttl_cb |
Таймер: TTL-очистка неотправленных собственных записей |