Bitcoin-платежи: политика подтверждений заказа
У приложения уже есть журнал выходов и их текущая проекция. Но видимый платёж ещё не обязательно разрешает доступ к курсу. Нужно определить политику подтверждений: какие сведения считаются достаточными, как учитывается давность источника и что делать, если история цепочки меняется после принятого решения.
Для учебной библиотеки выберем условное правило onchain-v1: полная сумма счёта и не менее двух подтверждений у учитываемых выходов. Число два служит параметром лаборатории, а не универсальной рекомендацией для настоящих платежей. Код и переходы сейчас разбираются без запуска узла или проверок.
Отличаем наблюдение от решения
Наблюдение отвечает на вопрос о конкретном выходе и состоянии источника. Решение отвечает на вопрос о конкретном счёте по фиксированной версии правил. В журнале эти объекты должны быть связаны, но не смешаны в одно поле paid.
Например, выход 30 000 сатоши уже присутствует в mempool. Наблюдатель сохранил его, поэтому интерфейс может показать «Перевод обнаружен». Политика ещё не разрешила доступ, потому что глубина ноль. Это честный промежуточный результат, а не ошибка кошелька и не повод просить повторный перевод.
Если на следующий день правила продавца изменились, старое решение не переписывается задним числом. Счёт содержит policy_version, а запись принятия решения сохраняет использованные наблюдения. Так можно объяснить, почему два счёта с разными условиями прошли разные маршруты.
Вычисляем достаточную сумму
Для лабораторной функции используем уже нормализованные проекции. Не передаём сюда сырые details кошелька. У каждого выхода известны его принадлежность счёту, сумма, состояние и глубина на актуальной цепочке.
from dataclasses import dataclass
@dataclass(frozen=True)
class ConfirmPolicy:
version: str
min_confirmations: int
ONCHAIN_V1 = ConfirmPolicy("onchain-v1", 2)
def eligible_sats(outputs, policy):
return sum(
item.amount_sats for item in outputs
if item.state == "active_chain"
and item.confirmations >= policy.min_confirmations
)
На входе функция предполагает непротиворечивую свежую проекцию одного источника. Она не разрешает доступ сама: вызывающий обработчик дополнительно сравнивает сумму со счётом, срок и состояние заказа. Если источник устарел, достаточное число в старой проекции не превращается автоматически в новое решение.
Не вычисляем подтверждения простым увеличением счётчика раз в десять минут. Их происхождение связано с текущей основной цепочкой. Условная формула высоты применима только после установления, что блок транзакции действительно находится на этой ветке. Равенство высоты без сравнения хеша не решает вопрос реорганизации.
Фиксируем паспорт решения
Когда условий достаточно, создаём запись принятия решения. Она содержит id счёта, версию политики, время, сумму допустимых выходов и контрольную точку источника. Не записываем только флаг, потому что он не объясняет, какие данные были использованы.
invoice: inv_lab_course17
policy: onchain-v1
required: 30000 sats
eligible: 30000 sats
source checkpoint: saved regtest tip
decision: payment_qualified
Этот паспорт является условным разбором, а не данными действительного узла. В будущей реализации checkpoint содержит реальный хеш, а источники суммы — сохранённые outpoint. Данные решения остаются историей даже после изменения цепочки; текущий статус может получить новый переход с причиной.
Название payment_qualified намеренно не равно access_granted. Выдача доступа происходит другим компонентом и может временно задержаться. Позже transactional outbox свяжет эти этапы так, чтобы ошибка доставки не порождала новый платёж или двойную выдачу.
Разбираем три состояния
Для одного выхода 30 000 сатоши возможны три последовательных состояния. При нуле подтверждений он обнаружен, при одном ожидает глубину, при двух отвечает выбранному правилу. Страница показывает смысл перехода словами, а не заставляет читателя угадывать по числу в кошельке.
| Сведения проекции | Решение лаборатории |
|---|---|
| Выход виден, глубина 0 | Перевод обнаружен, доступа ещё нет |
| Выход в основной цепочке, глубина 1 | Ожидание подтверждений |
| Выход в основной цепочке, глубина 2 | Условия платежа выполнены |
Глубина не гарантирует неизменность навсегда. Таблица описывает текущую политику и данные, а не математическую финальность Bitcoin. При реорганизации появится новая информация, которую нельзя игнорировать только потому, что интерфейс раньше показал зелёную отметку.
Меняем условие: оплата состоит из двух частей
Пусть читатель отправил 12 000 и 18 000 сатоши. Первый выход уже имеет три подтверждения, второй пока одно. Обнаруженная сумма равна цене, но допустимая по onchain-v1 сумма составляет только 12 000. Доступ ещё ожидает выбранную глубину второго выхода.
После его второго подтверждения допустимая сумма достигает 30 000. Это решение не требует, чтобы обе части находились в одной транзакции или одном блоке. Важны назначение счёту и состояние каждой учитываемой проекции. Подробную политику частичной оплаты разберём в следующей главе.
Если один выход конфликтный, отрицательная глубина не складывается с положительной как обычная арифметика. Выход исключается из допустимой суммы по состоянию. Оставшиеся 12 000 становятся частичной оплатой, а не «средней глубиной» двух транзакций.
Учитываем срок и давность
Истечение срока заказа относится к политике продавца, а глубина — к наблюдению цепочки. Поздний перевод может достичь нужного числа подтверждений, но получить отдельное решение по сроку. Поэтому функция суммирования не должна самостоятельно продлевать заказ.
Давность источника тоже требует отдельного поля. Если последняя успешная синхронизация была давно, не выдаём новое решение на основе неизвестной актуальности. Для уже выданного доступа действует заранее выбранная процедура инцидента; нельзя молча стирать прежнее решение при каждом временном timeout.
Отделяем параметр политики от настройки страницы
Порог подтверждений нельзя брать из query браузера или переключателя интерфейса. Читатель может выбрать способ отображения статуса, но не вправе понизить условия продавца. Даже служебное изменение порога оформляется новой версией правил, потому что оно влияет на решение о доступе.
При будущем запуске полезно сохранить пример, где одна и та же свежая проекция обрабатывается двумя версиями: учебной с порогом два и другой с порогом три. Различие ожидаемого решения объясняется только правилом, а не иной суммой или другой транзакцией. Это позволит отделить смену коммерческой политики от ошибки источника.
Разбор результата
Платёжный маршрут теперь имеет определённое правило и объяснимый паспорт принятия решения. Наблюдение может существовать раньше разрешения, а достаточная глубина не подменяет выдачу доступа. Это позволит поддерживать интерфейс и разбирать спорные случаи без выдуманных гарантий.
В следующем уроке добавим частичную и избыточную оплату. Оглавление курса показывает дальнейший переход к конфликтам, восстановлению и Lightning.