Перейти к содержанию

Bitcoin: замена и конфликт платёжной транзакции

В предыдущих главах выход мог менять глубину подтверждения. Теперь рассмотрим более сложное изменение: неподтверждённая транзакция исчезла, вместо неё появилась другая, либо кошелёк обнаружил конфликт. Если сложить оба состояния как независимые оплаты, сайт выдаст доступ на сумму, которой в текущей истории нет.

Разберём собственную модель для счёта курса 17 с ценой 30 000 сатоши. Обозначения A и B являются условными транзакциями, а не настоящими txid. Мы не выполняем замену комиссий, не подписываем транзакции и не запускаем mempool; изучаем обработку сведений источника.

Что меняется при замене

Транзакция A направляла на адрес счёта 30 000 сатоши и ещё не вошла в блок. Позднее источник увидел B, расходующую конкурирующие входы. Новый перевод может сохранять получателя и сумму, а может изменить их. Нельзя считать, что слово «замена» само гарантирует прежнюю оплату продавца.

Идентификатор выхода B отличается от A, потому что изменился txid. Поэтому журнал хранит отдельные outpoint, но текущая проекция обязана учитывать конфликт. Она не складывает все когда-либо встреченные выходы без статуса. Именно здесь история наблюдений и текущая сумма расходятся особенно заметно.

Для библиотеки важен собственный входящий выход. Комиссионная политика отправителя не становится правилом назначения заказа. Если B отправляет библиотеке только 29 000, денежная позиция неполная, даже если отправитель считает новую транзакцию «тем же платежом».

Какие сведения доступны кошельку

В gettransaction Core 31.0 есть сведения о подтверждениях и конфликтах. Некоторые поля замены относятся именно к категории отправления. Поэтому библиотечный кошелёк-получатель не должен обещать наличие всех атрибутов, которые видел бы кошелёк читателя.

Отрицательное число подтверждений сообщает конфликт с подтверждённой историей. Ноль сам по себе не доказывает наличие транзакции в mempool. Отсутствие пригодного ответа тоже не означает окончательный отказ: нужно различать предел наблюдения, конфликт и временную недоступность источника.

Нормализатор сохраняет доступные связи и контекст цепочки. Если точная связь A с B пока неизвестна, он не выдумывает её по похожей сумме. Можно направить случай на сверку, сохранив оба наблюдения и состояние неопределённости.

Строим текущую проекцию

Добавим состояния, достаточные для собственного учёта. Они не являются прямым копированием всех внутренних статусов Core; адаптер документирует, какие свидетельства источника приводят к каждой категории.

Состояние выхода Участие в допустимой сумме
active_chain По глубине выбранной политики
pending_observed Пока не включается в onchain-v1
conflicted Исключается
orphaned Исключается до нового подтверждения
observation_unknown Новое решение приостанавливается

Такой словарь не утверждает финальность. Один выход может снова оказаться подтверждённым после изменения истории. Новый переход записывается в журнал и обновляет проекцию с причиной. Не удаляем старую строку только потому, что текущее состояние неудобно объяснять.

Если источник гарантированно сообщил подтверждённый конфликт A, его сумма исключается. B проходит обычное назначение по адресу и глубине. Даже подтверждённая B не получает доступ автоматически без остальных условий счёта и срока.

Почему одной суммы недостаточно

Условная A содержит выход 30 000, B тоже содержит 30 000. Простой запрос по всей таблице истории получил бы 60 000. Корректная текущая проекция должна отражать, какая ветка расходования входов действительна сейчас, и учитывать только подходящий выход.

Если в независимой транзакции C читатель действительно доплатил ещё 5000, это другой случай. Конфликт A и B не исключает C только из-за одинакового пользователя. Нельзя объединять все транзакции счёта в одну «семью» без конкретного основания источника.

Здесь важно сохранить причинность. Два платежа могут иметь одинаковую цену, но быть независимыми. Две транзакции могут иметь разные цены, но расходовать один и тот же вход. Назначение по адресу решает вопрос счёта, а сведения конфликта решают вопрос актуального расходования.

Обрабатываем новую информацию последовательно

Рабочий цикл наблюдателя получает изменения, нормализует их и сохраняет локальной транзакцией. Внутри неё обновляются конфликтные связи, текущие выходы и контрольная точка. Затем обработчик пересчитывает денежную позицию затронутых счетов.

A pending → source reports confirmed conflict
A projection → conflicted
B output → assigned and observed
invoice eligible sum → recomputed from current outputs
decision → new qualified/review/waiting transition

Схема не означает, что источник всегда присылает все сведения одним атомарным ответом. При неполном контексте наблюдатель приостанавливает новое решение и перечитывает нужные данные. Мы не заполняем пробелы удобными положительными предположениями.

Если прежнее решение уже разрешило доступ, изменение проекции не стирает этот факт. Срабатывает процедура пересмотра согласно версии политики, которую подробнее обсудим вместе с реорганизацией. История выдачи и история цепочки остаются разными журналами.

Меняем условие: B платит другому адресу

Пусть заменяющая транзакция отправляет средства не библиотеке. A больше не отвечает текущей подтверждённой истории, а подходящего выхода B у нашего счёта нет. Нельзя показывать «Оплачено» только потому, что читатель передал новый txid или кошелёк назвал действие ускорением.

Интерфейс сообщает, что ранее обнаруженное поступление больше не подтверждает условия заказа, с временем и причиной наблюдения. Не обвиняет пользователя в намерении: источник показывает техническое состояние, а мотив отправителя нам неизвестен. Для обращения в поддержку сохраняется последовательность событий.

Если B снова платит прежнему адресу, но сумма меньше, применяется обычная частичная оплата. Комиссия новой транзакции не добавляется к полученному выходу. Условия цены остаются теми, что были сохранены при создании счёта.

Разбор результата

Замена и конфликт теперь не приводят к двойному учёту. Мы сохраняем разные outpoint, учитываем состояние источника и пересчитываем допустимую сумму по актуальной проекции. Поля отправителя не обещаются получателю, а неизвестная связь не создаётся догадкой по сумме.

В следующем уроке рассмотрим изменение целой ветки блоков и восстановление от общего предка. Полный маршрут находится в оглавлении курса.

Оглавление серии