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

Lightning и канал оплаты

Оглавление курса «Bitcoin и Lightning: платежи на сайте» · Урок 19 из 30.

В первой части курса мы создали модель оплаты библиотеки обычной Bitcoin-транзакцией: заказ хранит назначение, сервер наблюдает поступление и применяет собственную политику подтверждений. Теперь введём второй способ оплаты — Lightning. Для курса 17 сумма остаётся той же: 30 000 сатоши. Меняется механизм движения платежа и источник наблюдения, поэтому правила on-chain нельзя механически перенести в новый адаптер.

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

Канал как отдельное состояние

Lightning использует каналы, где участники поддерживают согласованное состояние, связанное с финансированием в Bitcoin. Для понимания этой части полезны официальные спецификации BOLT. Сайт не получает возможность переписать Bitcoin-реестр: он взаимодействует с узлом или платёжным поставщиком, а те работают по своему протоколу.

В нашей модели канал соединяет читателя и библиотеку напрямую. Это намеренное упрощение. В настоящем маршруте между ними могут существовать другие участники, комиссии, ограничения и несколько частей платежа. Прямая линия помогает сначала увидеть главное: после успешного платежа меняется распределение доступных сумм, а не общая искусственная вместимость модели.

Зададим вместимость 100 000 сатоши. Пусть у читателя 70 000, у библиотеки 30 000. Чтобы читатель оплатил ещё 30 000, достаточно его стороны модели. После расчёта значения становятся 40 000 и 60 000. Это не подсчёт фактического доступного остатка реального канала: здесь пока нет резервов, pending HTLC, ограничений и комиссий.

Модель на целых значениях

Для Lightning используем миллисатоши. Один сатоши равен тысяче миллисатоши, поэтому наш учебный платёж составляет 30 000 000 msat. В Python целые числа не теряют точность из-за дробной арифметики. Денежные поля в JSON передаём строками, чтобы клиентские представления не округляли большие значения.

from dataclasses import dataclass, replace

@dataclass(frozen=True)
class TeachingChannel:
    capacity_msat: int
    reader_msat: int
    library_msat: int

    def check(self):
        if min(self.reader_msat, self.library_msat) < 0:
            raise ValueError('Отрицательный баланс')
        if self.reader_msat + self.library_msat != self.capacity_msat:
            raise ValueError('Нарушена вместимость модели')

def settle_direct(channel, amount_msat):
    channel.check()
    if amount_msat <= 0 or amount_msat > channel.reader_msat:
        raise ValueError('Недостаточная сумма стороны читателя')
    result = replace(channel,
        reader_msat=channel.reader_msat - amount_msat,
        library_msat=channel.library_msat + amount_msat)
    result.check()
    return result

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

Предполагаемый вход содержит capacity_msat равный 100 000 000, reader_msat равный 70 000 000 и library_msat равный 30 000 000. После вызова с суммой 30 000 000 ожидаем 40 000 000 и 60 000 000. При попытке передать 80 000 000 функция должна отказать. Ни один из этих случаев здесь фактически не выполнялся.

Где находится заказ сайта

Канал не хранит таблицу заказов ProfessorWeb и не знает, кому открыть курс 17. Сервер создаёт отдельный orderId, выбирает метод оплаты и связывает его со счётом получателя. После достоверного наблюдения расчёта применяется политика доступа. Эта связь остаётся задачей нашего приложения, как и при обычной Bitcoin-оплате.

Можно иметь несколько заказов на одинаковую сумму. Поэтому недостаточно увидеть, что баланс библиотеки вырос на 30 000 сатоши, и выбрать ближайший неоплаченный заказ. Изменение общего баланса не содержит достаточного назначения. Нужен идентификатор соответствующего счёта и связь с серверным orderId, которую введём в следующих главах.

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

Публичная библиотека и платный пример

Наш платёжный пример не превращает все существующие статьи в закрытый продукт. Курс 17 служит предметом лабораторного заказа и отдельного учебного права. Публичные HTML-страницы ProfessorWeb продолжают иметь обычные URL и доступ для чтения. Добавление кабинета или оплаты не должно случайно заменить библиотеку экраном подключения кошелька.

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

Направление платежа меняет будущие возможности

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

Представим второй платёж на те же 30 000 сатоши. После него у читателя останется 10 000. Третий одинаковый платёж уже не помещается в прямую модель. Это не говорит, что библиотека потеряла полученную оплату. Это означает, что для нового поступления требуется другое распределение или другой маршрут. В следующем уроке разберём эту особенность как направление ликвидности.

Ожидаемый результат урока

Мы получили отдельный расчёт для канала и отдельное место для заказа сайта. У модели есть сохранение общей вместимости, проверка отрицательных значений и явная сумма в msat. Но у неё нет подписи, HTLC, протокольного обмена и действительного settlement. Эти ограничения названы рядом с кодом, поэтому арифметический пример нельзя случайно принять за готовый кошелёк.

При чтении будущего адаптера полезно задавать два вопроса. Какое состояние платёжной системы он наблюдает? Как он связывает это состояние с нашим заказом? Если ответ основан только на общей сумме баланса или на нажатии кнопки читателем, доказательства недостаточно. Такой договор позволит добавить Lightning, сохранив честные состояния всего платёжного маршрута.

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