Перейти к содержимому

Ticket to Online

Интернет-маркетинг

Как базы данных питают биткоин-кошельки: LevelDB, RocksDB и кастомная индексация

Почти каждый современный биткоин-кошелёк обещает простой интерфейс: баланс в сатоши, история операций, мгновенные платежи и даже отображение стоимости в фиате — от долларов до локальных валют (многим важно видеть и курс биткоина к рублю). Но за этим «прозрачным стеклом» работает сложная машина: парсинг блоков и мемпула, хранение UTXO, индексы по адресам/скриптам, кэширование, консистентные снапшоты. Сердце этой машины — базы данных, чаще всего семейства LSM-tree (LevelDB, RocksDB) плюс слой кастомной индексации. Разберём, как всё устроено изнутри, чтобы лучше понимать возможности, ограничения и точки оптимизации.

1) Зачем кошелькам базы данных и какие требования они закрывают

Кошелёк — это не просто «ключи». Это локальный реестр, который должен быстро отвечать на вопросы: какие UTXO принадлежат пользователю, какие транзакции подтверждены/в мемпуле, каков доступный баланс по конкретным аккаунтам, какие метаданные (метки, заметки, invoice-id) связаны с выходами. Требования к хранилищу вытекают из этих задач:

  • Быстрый ключевой доступ к сотням тысяч небольших объектов (UTXO, транзакционные записи, фильтры).
  • Высокая скорость записи мелких обновлений (подписки на скрипты/адреса, отметки подтверждений, состояние сканирования).
  • Надёжность и консистентность при крэшах и реорганизациях (reorg) блокчейна.
  • Компактность на диске и предсказуемая эксплуатация на мобильных устройствах.

LSM-tree‑движки (LevelDB, RocksDB) как раз и оптимизированы для интенсивной записи и последующего чтения с итерацией по префиксам, что идеально совпадает с кошелёчными шаблонами данных.

2) LevelDB: лёгкая key-value «рабочая лошадка»

LevelDBLevelDB — компактный и проверенный временем движок от Google, лежащий в основе цепочных структур Bitcoin Core (например, block index и chainstate). Он использует LSM‑дерево: записи сначала попадают в журнал (WAL) и мемтаблицу, затем вычисляются SSTable‑файлы, которые в фоне «уплотняются» (compaction) по уровням. Ключевые для кошелька свойства:

  • Префиксы и итераторы. Удобно хранить разные сущности в одном пространстве ключей: например, префикс U/ для UTXO, T/ для транзакций, A/ для адресных подписок. Итераторы проходят только нужный диапазон.
  • Bloom‑фильтры. Позволяют ускорить отрицательные запросы (например, «нет такого адреса/скрипта»), уменьшая случайные чтения.
  • Снапшоты и батчи. Атомарные обновления сложных состояний: отметили подтверждение, обновили UTXO‑сет, записали позицию сканера — всё в одном батче.

Слабые места: ограниченные настройки по сравнению с RocksDB, меньше инструментов для онлайн‑бэкапов и тонкой настройки compaction. Но для настольных/мобильных кошельков и light‑индексов его возможностей обычно достаточно.

3) Модель данных кошелька: UTXO, адреса, дескрипторы, метаданные

Чтобы кошелёк мгновенно показывал баланс и знал, «что наше», он строит собственную модель поверх сырых блоков.

UTXO и транзакции. Базовая единица — «непотраченный выход» (txid:vout). Запись содержит сумму в сатоши, scriptPubKey, высоту блока/статус, флажки (заморожен/забронирован под платёж), ссылку на исходную транзакцию. В образовательных целях полезно напоминать, что 1 биткоин равен 100 000 000 сатоши: кошельку выгодно хранить суммы целыми числами (int64), избегая ошибок округления.

Адресная/скриптовая подписка. Современные кошельки всё чаще используют дескрипторы (miniscript/Output Descriptors) вместо «голых адресов»: в БД хранится структура вывода (script template) и «мастер‑деривация» (xpub + путь). Это позволяет эффективно сканировать блоки: из дескриптора получают набор релевантных скриптов и быстро проверяют попадание.

Метаданные. Чеки, ярлыки, назначение, связи с инвойсами/заказами. Они лежат рядом с транзакциями/UTXO под отдельным префиксом, чтобы не мешать «горячим» путям.

Индексы по префиксам. Пример ключей:
U/<txid:vout> → UTXO‑запись
T/<txid> → сериализованная транзакция + статусы
S/<script-hash> → список касаний (txid/высота)
M/<txid> → метаданные

Такой дизайн даёт быстрый ответ «что касается этого скрипта/адреса» и «какие UTXO доступны сейчас», а также гибко прикручивает дополнительные функции (заметки, категории, пометки для отчётов).

4) RocksDB: когда нужны масштаб и тонкая настройка

RocksDB — «старший брат» LevelDB от Meta (Facebook), ориентированный на продакшн‑нагрузки с интенсивной записью и большими объёмами. Его выбирают биржи, платёжные провайдеры, enterprise‑кошельки, а иногда и тяжёлые десктоп‑клиенты.

Что даёт RocksDB:

  • Column Families. Логическое разделение таблиц внутри одной БД: можно хранить UTXO, индексы по скриптам и метаданные раздельно с разными настройками compaction/кэшей.
  • Расширенные compaction‑алгоритмы. Leveled/Universal, настроенные размеры уровней, rate‑limiter для IO — меньше «ступенек» в латентности.
  • Префиксные итераторы и блок‑кэши. Позволяют эффективнее вычитывать диапазоны по префиксам и держать «горячие» ключи в памяти.
  • Онлайн‑бэкапы, TTL‑таблицы, различные фильтры. Удобно для оперативной эксплуатации и сервиса, работающего 24/7.

Минус — сложность настройки и больший «операционный след». Если кошельку нужна максимально предсказуемая производительность при больших базах и плотной конкуренции операций (сканирование, мемпул‑апдейты, пользовательские действия), RocksDB часто выигрывает.

5) Кастомная индексация: адресные индексы, фильтры и приватность

Помимо хранения UTXO и транзакций, кошелькам нужно быстро отвечать на запросы «покажи все операции по этому скрипту/адресу» и «соответствует ли этот блок моим подпискам». Подходы:

Адресный/скриптовый индекс. Хэшируем scriptPubKey (или используем конструкцию из дескриптора) и строим обратный указатель на списки касаний. Важно помнить о приватности: индекс «адрес → txid» в самокастодиальном кошельке — внутреннее дело; в серверном приложении такой индекс — потенциальный риск deanonymization, нужна сегрегация по пользователям и шифрование.

BIP158 (Golomb‑Rice фильтры) и нейтрино‑клиенты. Лёгкие кошельки получают компактные фильтры на блок и проверяют локально «есть ли совпадение моих скриптов с этим блоком». Фильтры — тоже данные: их можно хранить в LevelDB/RocksDB с ключом по высоте блока для быстрых проверок/инвалидаций при reorg.

Индекс мемпула. Чтобы показывать входящие «в ожидании», кошельки держат в БД (или в памяти с периодическим дампом) актуальные незакреплённые транзакции по подпискам, со временем жизни и политикой отбрасывания.

6) Синхронизация и восстановление: правильный конвейер

Сканировать историю «с нуля» — дорогая операция. Хорошая практика — вести прогресс‑маркер (лучший известный блок, контрольная точка, номер партии деривации) и хранить состояние поиска адресов (gap‑limit, до какой ветки HD‑деривации дошли). Конвейер обычно таков:

  1. Получить снапшот лучшей цепи (высоту/хэш) и список новых блоков.
  2. По каждому блоку: извлечь релевантные выходы/входы → сформировать батчи обновлений БД → применить атомарно.
  3. На reorg: откатить записи до точки ветвления (по высоте), переиндексировать новую ветку.

Идемпотентность критична: если приложение упало на середине блока, повтор должен быть безопасным. Снапшоты/батчи LevelDB/RocksDB и «журналы прогресса» (checkpoints) обеспечивают эту гарантию.

7) Консистентность и безопасность: WAL, fsync, шифрование

Чтобы не потерять состояние при внезапном отключении питания, включают журналирование (WAL), а для критичных обновлений — принудительный fsync. Нужно понимать компромисс: слишком частые fsync ухудшают производительность, слишком редкие — повышают риск потери последних записей.

Ключевой материал (seed/приватные ключи) часто хранят отдельно от «операционной» БД: в защищённом хранилище ОС, в отдельном зашифрованном файле/модуле (HSM), либо в самих БД, но под сильным шифрованием с PBKDF/Argon2 и солью. Метаданные тоже выдают поведенческие детали, поэтому их разумно шифровать/обфусцировать в мультипользовательских сервисах.

8) Производительность: кэширование и настройки LSM

Для «живого» кошелька важны низкая латентность UI и быстрое применение апдейтов. Приёмы:

  • Префиксная схема ключей и компактные значения (protobuf/CBOR) для экономии IO.
  • Горячий кэш UTXO и последних N блоков, LRU‑кэш списков касаний по популярным скриптам.
  • Batch‑запись подтверждений/маркеров вместо «по одному ключу».
  • Настройка размера мемтаблиц/кэшей и политики compaction под профиль (мобильный/десктоп/сервер).

Периодический «вакуум» (compactRange) полезен после больших миграций/переиндексации: он уменьшает фрагментацию и ускоряет чтение.

9) Мобильные кошельки: LevelDB/RocksDB, SQLite или гибрид?

На iOS/Android часто выбирают SQLite за его зрелость и встроенность, особенно если много «реляционных» запросов (сложные фильтры истории, объединения таблиц метаданных). Однако для ключевых путей UTXO/скриптовых индексов KV‑хранилища остаются быстрее и проще: многие приложения используют гибрид — LevelDB/RocksDB для «ядра кошелька» и SQLite/CoreData/Room для UI‑метаданных и кэшированных представлений.

Важно помнить об ограничениях фоновых задач, размерах БД, лимитах на внутреннюю память: регулярная архивация старых записей и lazy‑загрузка истории улучшают UX и экономят батарею.

10) Бэкапы и миграции: что критично, а что — восстановимо

Главный бэкап — seed (и дополнительные секреты, если это мультиподпись/MPC). По нему можно восстановить средства на новом устройстве, даже потеряв БД. Но БД хранит полезные метаданные: метки, категория расходов, состояние сканирования, быстрые индексы; их тоже стоит резервировать (шифруя).

При миграции версий не ломайте формат ключей без мигратора. Хорошая схема — versioned‑ключи (например, префикс V1_/V2_) и «ленивая» миграция при первом доступе, чтобы не блокировать UI долгим апгрейдом.

11) Лучшие практики проектирования кошелёчной БД

  • Делите ключевые пространства по префиксам/column families: UTXO, TX, индексы, метаданные, прогресс‑маркеры.
  • Фиксируйте прогресс синхронизации атомарно вместе с итогами обработки блока.
  • Учтите reorg: храните «карты» блок → изменения, чтобы быстро откатиться.
  • Шифруйте всё, что может раскрывать поведение пользователя (даже метки и поисковые индексы) в мультиаккаунтных сервисах.
  • Стройте негативные пути быстрыми (bloom/prefix фильтры), позитивные — предсказуемыми (кэш+префиксные итераторы).
  • Проверяйте целостность БД на старте и держите plan B (reindex) при подозрении на повреждение SSTable.

12) Выводы

Кошелёк кажется простым приложением — баланс, «Отправить», «Получить». На деле это система с плотной связкой сетевого стека и хранилища: LevelDB/RocksDB обеспечивают быстрые записи и предсказуемые чтения, а кастомные индексы дают мгновенные ответы по адресам/скриптам и мемпулу. Правильная модель данных (UTXO + дескрипторы + метаданные), дисциплина синхронизации (батчи, снапшоты, reorg‑safe конвейер), продуманная конфигурация LSM‑движка и забота о безопасности (шифрование, fsync‑политики, раздельное хранение ключей) превращают набор файлов на диске в «сердце» кошелька.

Если вы проектируете новый продукт, начните с прототипа на LevelDB и чёткой схемы ключей; при росте нагрузки мигрируйте на RocksDB с column families. И всегда отделяйте «деньги» (сид/ключи) от «режиссуры» (индексы и история) — так вы сохраните средства пользователя при любом сбое и обеспечите тот самый опыт, когда кнопка «Отправить» действительно работает мгновенно и надёжно.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *