こんばんは。ライトニングは少額送金を安い手数料で行えるため、従来より気軽にビットコインを使った送金ができる点で画期的です。しかし、送金時に「オンチェーンなら○サトシかかるよ!」とか「累計で○サトシ節約したよ!」と表示されるとテンションが上がるのではないかと思いつきました。(ライトニングノードを運用するユーザーが集まるDiamond Handsコミュニティの誰かの発言に着想を得ました。) そこで、累計で節約した手数料を計算する方法を考えてみます。

もちろん、いくつか大事かつ非現実的な前提があります。「次のブロックに取り込まれる可能性が高い中で安めの手数料設定で計算する」「最安のトランザクション形式を使ったと仮定する」「オンチェーン送金が不可能・非現実的な送金額でも送金したことにする」という条件で、どのようにしたら求められるか考えてみます。

必要なデータ

まず、計算に必要なデータを考えてみましょう。

実際に支払ったLNの手数料は過去の送金データを取得することで簡単に求められます。例えばLNDならLightning.ListPaymentsというAPIやlncli listpaymentsというコマンドから取得することができます。それぞれの送金について、UNIX時間で支払った日時と、送金にかかった手数料をsatsあるいはmsatsで入手できます。(正確さにこだわるならmsats/1000で参照するのが良いでしょう。)

オンチェーンで支払っていた場合にかかっていた手数料の計算はもう少し難しいです。

まず、最安のトランザクション形式ですが、2022年現在は1 input 2 output (宛先+お釣り)ですべてのアドレスがP2WSH (native segwit)という仮定だと、1送金あたり141 vbyteと求められます。これに手数料率sats/vbyteをかけて小数点未満を切り上げたものが、オンチェーントランザクションの手数料です。

次に手数料率ですが、特定の時間についてこれを取得するのがこのプロジェクトの一番厄介な部分なので、次の部分で説明します。

これが揃えば、すべての送金について「当時次のブロックに安めで入れた場合のトランザクション手数料」と「実際に払ったLN送金手数料」を比較することができます。

ビットコインのタイムスタンプ

過去の特定の時間のトランザクションについて、かかっただろう手数料を推定することの難しさは、ビットコインにおけるブロックのタイムスタンプの不正確性に起因します。

実はビットコインのブロックに記録されるタイムスタンプ(採掘日時)はあまり正確ではなく、実際の採掘日時と数十分程度のズレが許されています。ノードが検証時に確かめる2つのルールは「過去11ブロックの中央値より時間が進んでいること(=6ブロック巻き戻されても時計が進んでいること)」と、「ピア間で共有される現在時刻の中央値より2時間以上未来ではないこと」です。

このようなルールが存在する背景には難易度調整が絡んでいます。マイニングの難易度調整は2週間ごとに生成されたブロック数によって行われますが、参照される時間はブロックのタイムスタンプであるため、時間が巻き戻されると難易度調整がずっと来なかったり、逆に早く経過しすぎると大幅な難易度低下を招きます。しかし、どこかの権威的な時刻サーバーなどに頼るのは単一障害点になるため、各ピアのローカル時計と過去のタイムスタンプとの比較による緩めのルールを使用することになっています。

したがって、場合によっては新しいブロックのほうがタイムスタンプが遅くなることもあります!当時からずっと稼働しているノードで該当するログを残していない限り、実際にブロックが伝達されてきた時間はわからないので、仕方なくタイムスタンプを正確なものとみなして利用するしかなさそうです。

過去の「タラレバ手数料」を計算する

したがって、LN支払いのタイムスタンプによって次のブロックとその手数料水準を参照するための表を作る必要があります。まず、過去のブロックについてタイムスタンプと手数料水準を取得します。

bitcoin-cli getblockstats NというコマンドでN番目のブロックについてfeerate_percentiles[]という手数料率の分布が取得できます。5つ中2つめの要素が「安い方から25%の手数料率」なので、手数料にケチなLNユーザーはこれを採用しましょう。手数料を払いすぎるウォレットもシミュレーションしたいなら「上から10%の手数料率」を示す5番目の要素や最高手数料率を示すmaxfeerateも使えます。

とりあえずtime順にブロック高を記録した表をCSVファイルやDBに格納しましょう。そうすれば、上記のコマンドで任意の手数料率を後で取得できます。ライトニングがmainnetローンチしたのは2018年の初めだったので、一般ユーザーはおよそ500,000ブロック目、あるいは自分が使い始めた時点からのデータだけにするのがパフォーマンスの面、ストレージの面からは好ましいでしょう。(このデータセットを他の目的に使いたい場合はジェネシスブロックからが良いかもしれません。)

豆知識ですが、sqlite3コマンドがCSVファイルにも使えるのを最近初めて知りました。CSVファイルにSQLが使えます!

もちろん必要なブロックについてのみ、時間からブロックを検索できるブロックチェーンエクスプローラーなどで取得するお手軽な実装方法も考えられます。ただ、送金数が多いとクローリングと見なされてアクセスを制限されたり、LN支払いを行った時間についてのプライバシー流出につながるため、個人的には推奨できません。せっかく手元にブロックチェーンがあるので、それを使ってみましょう。

最後に、先程取得方法を説明したLNの過去の送金すべてについて、CSVやDBから「その時点から次のブロック」をtimeの数値を使って検索し、LNのfee_msat/1000とオンチェーンのfeerate_percentiles[1]*141などを比較して、その差額を合計しましょう。これで累計で「節約した」手数料が求められるはずです。

まとめ

今回作り方を考えてみたスクリプトは、もう少しブラッシュアップして公開するつもりです。面白い割に意外とシンプルなプロジェクトだと思うので、ビットコインやLNを用いたアプリケーション開発に興味がある方のプログラミングの練習にもってこいかもしれません。