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

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

Алгоритм синхронизации:

  1. INIT_SYNC: A → B: my_count_a; B вычисляет tp = min(count_a, count_b) - 1.
  2. INIT_RESP: B → A: tp, chain_hash8_b[tp], + sparse-хеши на позициях tp-1, tp-2, tp-4, tp-8, ..., tp-32768.
  3. Если hash8_b[tp] == hash8_a[tp] — цепочки совпадают до tp. Более длинная сторона шлёт «хвост» (записи после tp).
  4. Если sparse-хеши показывают расхождение: A определяет диапазон [ds, de] где хеши не совпадают. Если de-ds ≤ 1 — пустой REFINE (запрос данных). Иначе — REFINE с до 16 хешами, равномерно распределёнными в диапазоне.
  5. REFINE: B ищет первую позицию, где хеши разошлись, шлёт SEND_DATA начиная с этой позиции.
  6. SEND_DATA: A вставляет полученные записи (с верификацией Ed25519 подписи), пересчитывает каскадный chain_hash, шлёт SYNC_DONE.
  7. 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 service 0x20, вешает коллбэки на существующие и новые соединения, стартует 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-очистка неотправленных собственных записей