2024年10月、Bitcoin Coreプロジェクトは、私が作成した、v25.0より前のBitcoin Coreバージョンにおけるinv-to-sendセットが肥大化しすぎることによるサービス拒否(Denial-of-Service)を公表しました。当時行った調査から、いくつかメモとスクリーンショットがあるので、ここに残しておきたいと思います。2023年5月初旬、私の監視インフラストラクチャは、メインネットノードに影響を与えるこのバグに気づき、問題の発生源を特定することができました。修正に取り組んだのは、Anthony Towns氏です。
観察
2023年5月2日、監視ノードの1つで、インバウンド接続数が約190
1から約2日間でわずか35に減少していることに気づきました。通常、ノードは再起動するか、ネットワーク接続を失うまで、インバウンドスロットを維持します。
![]()
私のBitcoin Coreノードのインバウンドおよびアウトバウンド接続数を監視するためのGrafanaダッシュボードのスクリーンショット。黄色のバーは、インバウンド接続数を示しています。これらはグラフの最後に減少しています。
他のコントリビューターに確認したところ、ノードのCPU使用率が100%になっていることに気づきました。これにより、ノードはピアとの通信を維持できなくなり、インバウンド接続がタイムアウトして切断されました。ノードプロセスで
perf top
を使用すると、
b-msghand
スレッドの
CTxMemPool::CompareDepthAndScore()
で多くのCPU時間が費やされていることがわかりました。次のフレイムグラフを記録しました。これは、
CompareDepthAndScore()
を呼び出す
make_heap()
が、プロセスのCPU時間の45%以上を使用していることを示しています。

Bitcoin CoreプロセスのCPU時間がどこに費やされているかを示すフレイムグラフ。新しいタブで開いて、このフレイムグラフを操作してください。
同時に、Bitcoin Coreのデバッグモードビルドで、オープンで無関係な
CPU使用率100%の問題がありました。これにより、デバッグモードビルドを実行していないにもかかわらず、ノードで高いCPU使用率に気づいた一部のコントリビューターやユーザーが混乱しました。デバッグモードの問題は一部の開発者にのみ影響を与えた可能性がありますが、もう1つの高いCPU使用率の問題はネットワーク全体に影響を与えました。これには、たとえば、
AntPoolなどのマイニングプールが含まれており、受信したブロックをタイムリーに処理できないために、マイニングオペレーションで問題が発生したと報告しています。
影響
ネットワーク全体のpingタイミングを観察すると、このDenial-of-Serviceの影響が明らかになります。Bitcoin Coreのメッセージ処理はシングルスレッドであるため、一度に1つのメッセージしか作成または処理できず、他のすべてのピアは待機する必要があります。待機時間が長くなると、pingの応答時間に影響します。
KIT DSN Bitcoin monitoringには、ICMPおよびBitcoinプロトコルのpingに関するデータがあります。これらを比較すると、ノードソフトウェアがメッセージ処理に追いつくのに問題がある時期を判断できます。データによると、ホストへのICMP pingは影響を受けませんでしたが、Bitcoinノードソフトウェアへの中央値pingは、4月末から5月初旬にかけて約25msから50ms以上にほぼ倍増しました。Bitcoinの中央値pingは5月8日に200msに急上昇しましたが、ICMP pingは影響を受けませんでした。

BitcoinプロトコルpingとICMP pingの中央値。DSN KIT(
https://www.dsn.kastel.kit.edu/bitcoin/)のデータに基づく
この影響は、
KIT DSN Bitcoin monitoringによって収集されたブロック伝播遅延データを見ることによっても確認できます。2023年5月8日頃、ブロック伝播遅延の急上昇が見られます。到達可能なノードの50%がブロックを監視ノードにアナウンスするのにかかる時間は、1秒未満から5秒以上に増加しました。同様に、90%の測定値は、約2秒から20秒以上に急上昇しました。

ブロック伝播遅延:ネットワークの50%および90%がブロックをアナウンスするまでの平均時間。DSN KIT(
https://www.dsn.kastel.kit.edu/bitcoin/)によるデータに基づく
ブロック伝播が悪いと、マイニングプールが古いブロックでより長くマイニングするため、より多くのステールブロックが発生します。まだ見ていない新しいブロックがすでにネットワークに存在する場合です。私の
stale-blocksデータセットのデータに基づくと、5月3日(ステールブロック788016から開始)から5月10日(ブロック789147で終了)までの週に、10個のステールブロックが観察されました。これは、1000ブロックあたり約8.84個のステールブロックの割合です。比較として、ブロック800000から900000(約2年間)の間には、73個のステールブロックが観察されました。これは、1000ブロックあたり0.73個のステールブロックの割合です。このステールブロック率の10倍の増加は、ブロック伝播が大幅に影響を受けたことが原因であると考えられます。
原因
なぜ関数
CTxMemPool::CompareDepthAndScore()
は、ノードの速度を低下させ、P2Pメッセージの処理に苦労するようになったのでしょうか?Bitcoin Coreでは、
b-msghand
スレッドがP2Pメッセージを処理します。たとえば、新しく受信したブロックを検証に渡したり、pingに応答したり、トランザクションを他のピアにアナウンスしたりするなど、多くの処理を行います。
関数
CTxMemPool::CompareDepthAndScore()
は、次にどのトランザクションをピアにアナウンスするかを決定するときに使用されます。Bitcoin P2Pプロトコルでは、トランザクションは
inv
(インベントリ)メッセージを介してアナウンスされます。ピアへのBitcoin Coreトランザクションアナウンスには、通常、最大35個の
wtxid
エントリが含まれています。次にどのトランザクションをピアにアナウンスするかを追跡するために、ピアごとに
m_tx_inventory_to_send
セットがあります。これには、ノードがピアがまだ見ていないと考えているトランザクションが含まれています。ピアのインベントリメッセージを作成するとき、セットはトランザクションの依存関係とフィーレートでソートされ、高いフィーレートのトランザクションを優先し、ノードがトランザクションについて学習した順序をリークしないようにします。このために、
CTxMemPool::CompareDepthAndScore()
比較関数が使用されます。
2023年5月初旬には、
BRC-20トークンに関連する大量のトランザクションがブロードキャストされました。これは、
m_tx_inventory_to_send
セットが通常よりも速く、通常よりも大きくなったことを意味します。その結果、セットのソートに時間がかかるようになりました。5月7日(UTC)の夕方、VMPX BRC-20トークンのミントが開始され、他の進行中のBRC-20トークンのミントに加えて、6時間で30万件以上のトランザクションがブロードキャストされました。これにより、5月8日に観察された中央値pingとブロック伝播時間の急上昇が発生しました。
この影響は、
inv
メッセージのみをリッスンし、トランザクションを独自にアナウンスしない、いわゆるスパイノードによって増幅されます。ピアがノードにトランザクションをアナウンスすると、ノードはピアに知られているため、
m_tx_inventory_to_send
セットから削除できます。これは、スパイノードのセットがさらに大きく、ドレインされるのが遅いため、ソートにさらに時間がかかることを意味しました。たとえば、
LinkingLionなどのスパイノードは一般的であり、ノードへの複数の接続を並行して開いていることがよくあります。時には、私のノードへの非スパイノード接続よりも、想定されるスパイノードの数が多いことがあります。
大量のトランザクションのブロードキャストと、スパイノードによる増幅、および
CTxMemPool::CompareDepthAndScore()
による大きな
m_tx_inventory_to_send
セットの最適でないソートが組み合わさって、ノードはトランザクションリレー用の新しいインベントリメッセージの作成に多くの時間を費やすようになりました。メッセージ処理はシングルスレッドであるため、他のピアとの通信が大幅に遅くなりました。これにより、ブロックがタイムリーに処理されなくなり、一部の接続がタイムアウトするようになりました。
修正
修正は2つあります。まず、すでにマイニングされているか、何らかの理由でmempoolに存在しない、アナウンスされる予定のすべてのトランザクションは、
m_tx_inventory_to_send
セットがソートされる前に削除されました。以前は、これらのトランザクションはセットがソートされた後にのみ削除されていました。これにより、アナウンスされることのないトランザクションエントリのソートに時間を費やすことを回避し、ソートされるセットのサイズを縮小します。次に、
m_tx_inventory_to_send
セットが大きい場合、セットからドレインするエントリの数は、セットサイズに基づいて動的に増加します。これは、多くのトランザクションがブロードキャストされると、ノードはセットが再び小さくなるまで、より多くのトランザクションをピアにアナウンスすることを意味します。この修正は、2023年5月末のv25.0リリースに間に合うようにバックポートされました。
考察
一部の通常のコントリビューターはこれが起こっていることを知っていましたが、この問題は公に公開されませんでした。同時に議論されていたデバッグモードでの
CPU使用率100%の問題は、通常のBitcoin Coreコントリビューターの間でも混乱を引き起こしました。当時、私はこれを静かに修正できる可能性があり、当面は多くの宣伝を必要としないと感じていました。後から考えると、この問題をより公に、より透明にすることもできたかもしれません。BRC-20のブロードキャストの数は約1週間しか続きませんでしたが(これは事前にわかっていませんでした)、ノードを再起動するとしばらくの間は役立ちました。たとえば、修正を含むバージョンにすぐにアップグレードできないマイニングプール(カスタムパッチで実行しているため)の問題を軽減するために、
スパイノードの禁止リストが作成されましたが、それが実際に使用されたかどうかはわかりません。
このイベント専用のコミュニケーションチャネルはありませんでしたが、P2Pコントリビューターとの非公開のIRCチャネルが使用され、関心のあるコントリビューターはダイレクトメッセージを介して招待またはイベントについて知らされました。私が知る限り、インシデント対応チャネルはなく、Bitcoin開発のアドホックで分散型の性質を考えると、それが役立つかどうかはわかりません。インシデント対応に責任を負うコントリビューターはいませんが、誰もが助けることができます。
個人的には、私の監視がこれに役立つことが証明されてうれしいです。当時、接続が切断された場合のアラートを設定しておらず、ダッシュボードを見て初めて気づきましたが、それがあって助かりました。問題を特定するために、いくつかのノードを自由に操作したり、たとえば
perf top
を実行したりできることが役立ちました。今後の監視には、ping時間と接続が切断された場合のアラートを含める必要があります。
接続数をデフォルトの125接続から200に増やしました。 ↩︎