Lightning Network: направление и ликвидность канала
Оглавление курса «Bitcoin и Lightning: платежи на сайте» · Урок 20 из 30.
В предыдущем уроке прямой платёж уменьшил сторону читателя и увеличил сторону библиотеки. Теперь разберём, почему сумма получателя и возможность принять новый платёж являются разными величинами. Для сайта это практический вопрос: выставленный счёт может быть правильным, а расчёт не состояться из-за направления доступной ликвидности. Результатом урока станет ограниченная модель маршрута и диагностическое объяснение, которое не обещает успех по одному общему балансу.
Модель не запускалась и не запрашивает каналы сети. Она специально исключает многие параметры настоящего протокола. Числа принадлежат собственной лаборатории, поэтому их нельзя использовать как инструкцию распределить реальные средства. Мы рассматриваем техническую готовность способа оплаты, а не доходность, инвестицию или универсальное правило эксплуатации узла.
Вместимость и доступное направление
Вместимость описывает ресурс канала в целом. Для движения к библиотеке важно, сколько соответствующая сторона может передать в выбранном направлении. Получатель может иметь большой локальный баланс, но недостаточно доступного входящего направления на нужной линии. Связь между балансами и поступлениями объясняется в руководстве Lightning Labs по ликвидности.
Вернёмся к прямой модели: вместимость 100 000 сатоши, читатель 40 000, библиотека 60 000. Платёж курса 17 на 30 000 ещё помещается со стороны читателя. После него читатель имеет 10 000, библиотека 90 000. Попытка нового входящего платежа того же размера не укладывается в эту конкретную линию, хотя получатель выглядит обеспеченным по общей сумме.
В многозвенном маршруте необходимо, чтобы движение было возможно на каждом звене. Один большой канал возле получателя не исправляет недостаток на предыдущем участке. И наоборот, отказ одного маршрута не доказывает невозможность всех остальных. Поэтому наше приложение не должно выводить универсальное «недостаточно средств у покупателя», если источник сообщил лишь, что конкретная попытка не нашла подходящий путь.
Упрощённая проверка маршрута
Представим линейный маршрут reader → relay → library. Каждое звено хранит собственную верхнюю границу разрешённой передачи в указанном направлении. Комиссии в этом примере задаются готовыми целыми величинами для расчёта, а не параметрами реальной сети.
from dataclasses import dataclass
@dataclass(frozen=True)
class TeachingHop:
name: str
available_msat: int
fee_msat: int
def explain_route(hops, recipient_msat):
if recipient_msat <= 0:
raise ValueError('Нужна положительная сумма')
required = recipient_msat
rows = []
for hop in reversed(hops):
required += hop.fee_msat
rows.append({
'hop': hop.name,
'required_msat': required,
'available_msat': hop.available_msat,
'fits': required <= hop.available_msat,
})
return list(reversed(rows))
Путь считаем от получателя назад, потому что upstream-звено должно покрыть сумму следующего участка и выбранную искусственную комиссию. Настоящий поиск маршрута использует протокольные параметры и ограничения реализации; наш список нужен лишь для объяснения арифметики. Значение fits не является сетевой гарантией и не учитывает конкурирующие платежи.
Пусть библиотеке требуется 30 000 000 msat. Последнее звено имеет искусственную комиссию ноль и доступно на 35 000 000. Предыдущее добавляет 1000 msat и доступно на 30 000 500. Второе звено нашей таблицы помещает платёж, но первое требует 30 001 000 и не проходит. Разница в 500 msat способна быть важной для модели, хотя округление до тысяч сатоши скрыло бы её.
Почему снимок быстро устаревает
Между чтением доступности и попыткой оплаты могут начаться другие передачи. Поэтому состояние, которое выглядело достаточным при подготовке, не доказывает последующее выполнение. Приложение использует предварительное наблюдение для понятной диагностики, а окончательный результат получает из источника платежа.
Не делаем отдельную базу сайта «главной книгой всех каналов». Сервер не управляет состоянием всех промежуточных узлов и не знает их полных ограничений. Свой адаптер возвращает доступность услуги и конкретный результат, а не выдуманную глобальную карту гарантированного пути. Для публичного интерфейса достаточно честно сообщить, что метод временно не может завершить эту попытку.
Также общая сумма ожидающих HTLC не равна свободной ликвидности. Ресурсы могут быть заняты незавершёнными передачами, а некоторые ограничения касаются числа или минимального размера частей. В нашей модели этих полей нет. Если будущий адаптер использует их для диагностики, каждое значение получает свой источник и смысл, а не добавляется под общим названием balance.
Доступность и цена заказа
Цена курса определяется серверным заказом и не меняется от того, что маршрут затруднён. Не увеличиваем amount_msat счёта случайным приближением «на всякий случай хватит комиссий». У платежа есть сумма получателю и отдельные условия расходов отправителя. Их смешение способно привести к неверной проверке поступления и непонятному интерфейсу.
Если выбранный метод временно недоступен, можно предложить другой путь оплаты с новым явным инструментом. Старый счёт сохраняет связь с заказом и свою судьбу. Нельзя одновременно выдавать несколько способов без учёта двойного поступления: читатель может завершить оба, особенно после задержки первого. Политика переплаты и сверки относится к приложению, а не решается сменой маршрута.
Что увидит читатель
Нормальный экран показывает сумму, метод и срок счёта. При затруднении пишет «попытка не завершена; уточняем результат» либо достоверную причину отказа, которую умеет классифицировать адаптер. Он не раскрывает приватную структуру каналов библиотеки и не обвиняет покупателя в отсутствии средств без соответствующего наблюдения.
При сохранённом unknown пользователю доступна проверка исходного платежа. Новая попытка создаётся согласно правилам платёжной системы, а не как повтор GET-запроса. Для локального примера можем сравнить две готовые таблицы маршрута, но не отправляем никаких действительных платежей.
Ожидаемые случаи
Первый маршрут имеет достаточно выбранного ресурса на каждом звене: таблица показывает fits для всех строк. Это лишь предварительное соответствие модели. Второй маршрут содержит недостаточную upstream-границу и показывает конкретное звено отказа. Третий сначала подходит, но данные устаревают: окончательный адаптер вправе сообщить неуспех или неизвестный результат независимо от старой таблицы.
Полученная модель отделяет вместимость, направление, предварительную доступность и settlement. Это позволяет избежать ложного вывода «у нас много средств, значит любой входящий платёж пройдёт». В следующем уроке рассмотрим условность передачи через хеш и срок, чтобы понять, почему pending остаётся самостоятельным состоянием.
Для нашей таблицы доступность относится к одному заданному направлению, а не к сумме всех балансов узла. Сохранение этого уточнения предотвращает неверный вывод при следующем платеже.