Как объясняют Андреас М. Антонопулос, Олаолува Осунтокун и Рене Пикхардт в книге Matering the Lightning Network (2021):
Lightning Network (LN) — это одноранговая сеть второго уровня, которая позволяет нам осуществлять [практически мгновенные] платежи Биткойна «вне цепи».
Если у вас есть узел LN, то вы можете отправлять, получать и направлять платежи через сеть. Если вы отправляете платеж на узел, с которым у вас нет канала, то для маршрутизации платежа будет найден путь. При попытке совершить платеж пробуются несколько путей, пока один из них не станет успешным или все не потерпят неудачу. Все попытки оплаты сопровождаются данными о различных маршрутах, по которым платеж пытался добраться до места назначения.
Возможность анализировать эти данные может привести к ценным сведениям о том, какие каналы/узлы работают хорошо и надежны, с кем можно создать канал, какие направления проявляют большую активность, влияние выбранного лимита комиссии при попытке платежа и т.д.
Работая вместе с LN Capital в рамках стажировки Summer of Bitcoin 2022 под руководством Хенрика Скогстрема, мы смогли создать ценные визуализации, представляющие попытки платежей в LN. Все данные, использованные для этой задачи, являются реальными.
Визуализация платежей с помощью networkx
Неориентированный граф
Одним из инструментов, который часто используется для анализа и визуализации графов с помощью python, является networkx
. Networkx предоставляет встроенные алгоритмы для установки положения узлов при отображении сети. Давайте рассмотрим две попытки оплаты, нарисованные с помощью networkx
, и пройдемся по элементам обоих графиков.
Код для отображения следующих рисунков выглядит следующим образом:
plt.figure(figsize=(10,10))
# G is our graph. It already has the edges and nodes attributes.
widths = np.array([w for *_, w in G.edges.data('weight')])
colors = np.array(['tab:red' if w >=1 else 'black' for *_, w in G.edges.data('fail_weight') ])
pos = nx.spring_layout(G, seed=7) # positions for all nodes - seed for reproducibi
# nodes
nx.draw_networkx_nodes(G, pos, node_size=600, alpha=0.7)
# edges
nx.draw_networkx_edges(G, pos, width=(widths+0.1)*10, edge_color=colors)
# labels
nx.draw_networkx_labels(G, pos, font_size=10, font_family="sans-serif")
ax = plt.gca()
ax.margins(0.08)
plt.axis("off")
plt.tight_layout()
Исходный узел скрыт, а конечный узел — WalletOfSatoshi.com.
![]() |
![]() |
Некоторые вещи вы можете заметить с первого взгляда:
- Края, далее мы будем называть их каналами, окрашены по-разному: они могут быть черными, зелеными или красными.
- Толщина каналов различна.
- Положение узлов на обоих графиках не имеет ничего общего друг с другом.
- В целом, оба графика выглядят беспорядочно, и трудно интерпретировать то, что они якобы показывают.
Цвет каналов не случаен. Если платеж не прошел в данном хопе, канал окрашивается в красный цвет. Если платеж прошел успешно, то успешный маршрут окрашивается в зеленый цвет. В противном случае канал должен быть черным.
Ширина канала также представляет данные из платежа. Чем больше канал используется в попытке платежа, тем толще будет этот канал.
Мы хотим добиться следующего: объединить различные попытки оплаты, агрегируя их данные, и визуально видеть эту информацию без труда. До сих пор, если мы адаптируем код, используемый для отображения вышеуказанных цифр, поиск информации становится невозможным.
Это ни о чем нам не говорит. Существует множество улучшений, чтобы сделать визуализацию читабельной и ценной, начиная с положения узлов.
Направленный граф
Давайте улучшим визуальное представление платежей:
- Добавим узел источника (наш узел).
- Раскрасим узлы источника и назначения в другой цвет, чем остальные узлы.
- Разместим узлы лучше, чтобы улучшить визуализацию маршрутов.
Что касается последнего пункта, давайте сравним две структуры графа, когда узлы хорошо расположены.
![]() |
![]() |
На рисунке 4 показан неориентированный граф. Я видел, что для других попыток оплаты некоторые ребра пересекаются, что затрудняет прослеживание маршрутов в этих платежах. Поэтому для визуализации лучше подходит направленный граф с изогнутыми ребрами.
Улучшенная визуализация:
Теперь добавим характеристики каналов, показанные на рисунках 1 и 2, плюс эти:
- Если канал красный, то есть платеж по нему не прошел, а на пути к пункту назначения еще есть каналы, то эти каналы должны быть окрашены в синий цвет. Это подчеркивает, по каким каналам не была предпринята попытка маршрутизации платежа.
- Поместите среднюю плату за канал под каждым каналом. Показ этой информации может привести к полезным выводам относительно поведения каналов.
- Установить размер узла на основе количества раз, когда узел был в маршруте.
Это выглядит гораздо лучше. Теперь вы можете четко видеть, кто отправил платеж, кто был получателем, какие маршруты были опробованы, где в этих маршрутах платеж не прошел, какие каналы не были опробованы из-за предыдущей неудачи, какие узлы и каналы приняли наибольшее участие в этой попытке платежа, а также размер комиссии в мсатах, которую собирался взимать каждый канал, если бы платеж прошел успешно.
Код для отображения рисунков 6 и 7 следующий:
def show_graph_of_routes(g, node_positions, nodes_colors, widths=1,
edges_colors='black', nodes_size=700, draw_fee_labels=False,
fee_labels=None):
plt.figure(figsize=(12, 9))
# drawing nodes
nx.draw_networkx_nodes(g, pos=node_positions, node_size=nodes_size,
node_color=nodes_colors, alpha=0.5,
)
# drawing edges
nx.draw_networkx_edges(
g, pos=node_positions,
connectionstyle="arc3,rad=-0.1", # curve the edges
width=widths,
edge_color=edges_colors,
)
# labels
nx.draw_networkx_labels(g, pos=node_positions, labels=nodes_labels,
font_size=5, font_family="sans-serif",
font_color='purple', verticalalignment='top')
if draw_fee_labels:
nx.draw_networkx_edge_labels(g, pos=node_positions, edge_labels=fee_labels,
font_size=4, label_pos=0.35)
_ = plt.title('Graph Representation of the routes attempted trying to pay an invoice', size=15)
Интерактивная визуализация с помощью Altair
У нас уже есть хороший способ отображения одной попытки оплаты с помощью networkx
. Что если бы мы могли визуализировать больше попыток оплаты в агрегированном виде без потери читабельности? Более того, что если бы мы могли решить, какие платежи, каналы или узлы отфильтровать на основе их свойств? Чтобы сделать это, нам нужно создать интерактивную визуализацию.
Altair — это инструмент визуализации данных для создания интерактивных диаграмм с помощью Python. Преимуществом перед другими библиотеками визуализации является то, что вы можете создавать визуализации с минимальным количеством кода. Вся сложная работа выполняется перед очисткой данных и преобразованием их в формат, который altair
может понять (делается с помощью pandas
).
Некоторые основные идеи для создания интерактивной визуализации попыток оплаты:
- Меню для выбора платежей для визуализации.
- Увеличение и уменьшение масштаба.
- Панорамирование.
- Перетащите указатель на узел, чтобы увидеть псевдоним узла, количество раз, когда он был частью маршрута, и количество неудач.
- Перетащите указатель на канал, чтобы увидеть среднюю плату канала, количество раз, когда он был частью маршрута, количество отказов и количество раз, когда он был частью успешного маршрута.
- Фильтрация путем перетаскивания:
- Кисть для выбора каналов на основе их платежной активности.
- Кисть для выбора узлов на основе их платежной активности.
Вот как выглядит визуализация:
Характеристики интерактивной визуализации
Масштабирование и панорамирование
Информация, отображаемая при наведении курсора на узел или канал
Фильтр путем перетаскивания
Вы можете взаимодействовать с двумя графиками в верхней части, удерживая и перетаскивая курсор, чтобы отфильтровать каналы или узлы на основе того, сколько раз они были частью маршрута.
Выбор нескольких платежей
Диаграмма слева предназначена для выбора платежа/ов, которые вы хотите визуализировать. При выборе нескольких платежей информация о каналах и узлах обновляется, основываясь на агрегированных данных.
Есть и другие идеи, которые мы хотим изучить, чтобы искать вещи визуально. Преобразование данных в визуальные представления — всегда лучший способ их понять.
Любые идеи приветствуются 🙂