【LN】LNDのHTLCを解析するツールを作ってみました
はじめに
「生存者バイアス」という言葉を聞いたことがありますか?
上図の赤い点は戦地から帰還した爆撃機の損傷個所を表していますが、犠牲を最小限にするためにあなたならどの部分を補強しますか…というのがよくある例です。赤い点の部分が一番攻撃されているんだからそこを補強しようと考えてしまいがちですが、この図は「帰還した爆撃機」すなわち「生存者」のデータをもとに作られていることを考慮する必要があります。つまり、赤い点がない部分が生存可否にクリティカルに影響する部分であり、真に補強すべき箇所というわけです。
先日、小川さんが公開して下さった記事はまさにこれと同じで、成功したルーティングだけを見ていては真のルーティング需要を見誤るのではないか、という指摘でした(まだ読んでいない方は是非読んでみて下さい)。
これを読んで自分のノードでも調べてみたいと思ったのは私だけではないでしょう。ところがこの記事で使用しているstream-lnd-htlcsというツールは、その名の通りLNDのHTLCをリアルタイムにJSON形式のファイルとして出力するシンプルなもので、どのようなトランザクションがあったか調べるには出力されたファイルを更に解析する必要がありました。
というわけで面白そうだけど大変そうだなぁと見送っていたんですが、ここのところ弊ノードにチャンネルを開いて下さるノードが増えており、チャンネルのキャパシティバランスがイン側に偏り始めました。
チャンネルのキャパシティバランスがイン側に偏った状態とは、ノードが持つ全てのアウトバウンドキャパシティを全チャンネルで均等に割ったとしても、50:50のバランスされた状態にできないことを意味します。すなわち、リバランスする対象を取捨選択する必要が出てくるのです。
どのようなチャンネルにアウトバウンドキャパシティ割いてリバランスをすべきか…その判断基準となるのはまさに前述の「真の需要」と言えるでしょう。
そんなわけで前置きが長くなりましたが、stream-lnd-htlcsが出力したJSONファイルを解析するツール「analyze_lnd_htlc」を作ってみました。githubで公開しているので、どなたでもダウンロードして使用することが可能です。
使い方
stream-lnd-htlcsの実行
まずはstream-lnd-htlcsを動かして、JSONファイルを作成しないことには始まりません。このツールは実行中に生じたHTLCをファイルに書き出す仕様のため、調査したいノード上でバックグラウンド実行して放置しておくと良いです。私はSSHでノードにアクセスし、こんな感じで実行しています。
$nohup python3 stream-lnd-htlcs.py > /dev/null 2>&1 &
こうすることでSSHを切断してもノード上で実行され続け、同じディレクトリ内に「htlc-stream.json」というファイルが生成されます。ただし、このままだと際限なくファイルが肥大化するため、適当にログローテーション設定をする必要があります。ログローテーション設定の詳細は割愛しますが、私は「/etc/logrotate.d」に「htlc-stream」というファイルを作り、下記のような感じでログローテーション設定しています(毎週1回ローテーションし、4世代まで保存する)。
/home/umbrel/dev/stream-lnd-htlcs/htlc-stream.json {
ifempty
dateformat .%Y%m%d
missingok
weekly
rotate 4
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript
}
analyze_lnd_htlcの実行
出来上がった「htlc-stream.json」のパスをパラメータとして与えて実行することで、データを解析します。
$python3 analyze_lnd_htlc.py -i ../stream-lnd-htlcs/htlc-stream.json
READMEにも書きましたが、「-s」「-e」パラメータを使えば解析したい日時(UTC時間であることに注意)を限定することも可能です。
2021年10月17日現在、スクリプトを実行すると3つの解析結果が出力されます。
1. FORWARD EVENT SUMMARY (OUTGOING PEER)
ルーティング状況をoutgoing peer、つまりトランザクションが出ていくチャンネルに着目してまとめた結果です。トランザクションがアウト側に流れるチャンネルの需要を大雑把に調べることができます。
チャンネルのイベント単位でその回数とルーティング量、得られた(もしくは得られるはずだった)ルーティング手数料の合計、最小、最大、平均、標準偏差が表示されます。
イベントは大きく分けて、「link_fail_event(自ノードが原因でルーティングに失敗した)」と「foward_event(自ノードはルーティングに成功した)」に分かれます。さらに後者は「forward_fail_event(自ノードよりも後続のノードが原因でルーティングに失敗した)」と「settle_event(ルーティングに成功した)」に分類されます。
Ride The LightningやThunderHubではこれらのイベントのうち「settle_event」しか検知できません。そのうえで上に貼った実行例を見るとだいぶ違って見えるのではないでしょうか。
例えばCoinGate。このノードは極端に偏ったイン側の流れがあるノードですが、逆に言えばアウトバウンドキャパシティは常に余り気味で、リバランスで他のチャンネルにキャパシティを移すのも一苦労です。この経験からはCoinGateにアウト側に流れる需要は一切無いように感じられますが、実際には「forward_fail_event」が大量に発生しています。つまり、需要自体はあるもののCoinGateより先のチャンネルに何らかの問題があり(キャパシティの極端な偏りなど)トランザクションが通れない状況なのです。
2. FORWARD EVENT SUMMARY (INCOMING PEER)
今度は逆に、ルーティング状況をincoming peer、つまりトランザクションが入ってくるチャンネルに着目してまとめた結果です。トランザクションがイン側に流れ込みやすいチャンネルを大雑把に調べることができます。
ただしこのイベントが発生するのはイン側に十分なキャパシティが存在し、自ノードまでトランザクションが到達できたチャンネルだけである点は注意が必要です(=生存者バイアスがかかっている)。それでも、意図した通りにイン側からトランザクションが流れているのか(あるいは流れようとしているのか)を確認することができます。
3. LINK FAIL EVENT DETAIL
「link_fail_event」の明細を時系列で表示します。「forward_fail_event」は自ノードより後続のチャンネルに原因があるため、自ノードでは何ら対策が取れません。自ノードに原因がある「link_fail_event」、とりわけチャンネルのアウトバウンドキャパシティ不足が原因でルーティングに失敗したことを示す「INSUFFICIENT_BALANCE」が発生した明細を確認することで、どのチャンネルをリバランスすべきか検討できます。
トランザクションが入ってきたチャンネルと出ていこうとしたチャンネル、そのチャンネルのキャパシティとイベント発生時のアウトバウンドキャパシティ、ルーティング量、得られるはずだったルーティング手数料が表示されます。
終わりに
取り急ぎPython初心者かつGitHub初心者が勢いで作って公開したスクリプトなので改善すべき点が多々あるかと思いますが、そこはやんわりとissuesで指摘いただけると助かります(もちろんpull requestも受け付けております)。こんな解析できたら面白そうだ、というアイデアも是非お寄せください。
また、この手のツールを自作しつつ引き続きノード運用を続けておりますので、チャンネル開設ノードをお探しの方はどうぞお気軽に。