Кейс · Бэкенд · Rust

ledger-core: двойная запись и хэш-цепочка журнала

Бухгалтерское ядро с двойной записью на Rust: счета, проводки, неизменяемый журнал с хэш-цепочкой, проверка целостности и оборотно-сальдовая ведомость. Деньги только целыми числами (i64, копейки), сумма проводки строго 0, арифметика checked. Каждая запись хранит prev_hash и SHA-256 от своих полей, GET /v1/verify пересчитывает цепочку и указывает подменённую запись. Идемпотентность по Idempotency-Key.

  • Rust
  • axum
  • SQLite
  • SHA-256
  • proptest
  • musl
  • distroless
  • Docker
  • cargo-audit
  • GitHub Actions

01 · Как выглядит

Скриншоты

Скриншот проекта «ledger-core: двойная запись и хэш-цепочка журнала» на экране 1280 пикселей
Десктоп, 1280 px
Скриншот проекта «ledger-core: двойная запись и хэш-цепочка журнала» на экране телефона, 390 пикселей
Телефон, 390 px

02 · О проекте

Что это и зачем

Задача: показать, как держать инварианты учёта на уровне сервиса, а не «в надежде на клиента»: деньги без float, проводка всегда в нуле, журнал нельзя тихо переписать.

Решение: Rust (axum + SQLite). Суммы это i64 в копейках, арифметика checked; каждая запись хранит prev_hash и SHA-256 от всех своих полей с префиксами длины, а GET /v1/verify пересчитывает цепочку и называет подменённую запись. Триггеры SQLite запрещают UPDATE и DELETE.

Ограничения

Цепочка ловит правку и удаление в середине, но не усечение хвоста: head_hash нужно сохранять снаружи.

Тот, у кого есть файл БД, может снять триггеры: защита это проверка цепочки.

Числа больше 2^53 теряют точность в JavaScript-клиентах; сторно отдельной сущностью нет.

Проект работает на небольшом VPS с демо-данными: без отказоустойчивости и резервных копий.

Что внутри

  • Инвариант проводки: сумма строк строго 0, от 2 до 100 строк, нулевые суммы запрещены, одна валюта; переполнение даёт 422, а не панику
  • Идемпотентность по Idempotency-Key: тот же ключ и то же тело возвращает исходную запись, другое тело даёт 409
  • Хэш-цепочка и /v1/verify: непрерывность нумерации, prev_hash и пересчёт хэша каждой записи
  • Оборотно-сальдовая ведомость; итоги дебета и кредита обязаны совпасть
  • 19 тестов; property-тесты: сумма проводки всегда 0, любая подмена суммы, комментария или счёта ловится и указывает на нужную запись
  • Результат: образ 11,4 МБ, 3,5 МиБ в покое и 4,5 МиБ после 200 запросов (100 параллельных проводок и 100 чтений), цепочка осталась целой

03 · Как запустить

Запуск одной командой

git clone <репозиторий по запросу> ledger-core
cd ledger-core
DEMO_SEED=1 cargo run --release
# http://localhost:8115

04 · Код

Репозиторий приватный

Исходники лежат в приватном репозитории. Напишите в Telegram, открою доступ.

Запросить код