DEX への送信は混雑時に詰まる── 手数料の上がり方・置き換え・再編成を前提にした再送設計
ブロックチェーン

DEX への送信は混雑時に詰まる── 手数料の上がり方・置き換え・再編成を前提にした再送設計

FTL編集部

分散型取引所(DEX)へのトランザクションの送信は、平時には次のブロックか数ブロックのうちに取り込まれ、問題なく動いているように見えます。崩れるのは相場が急に動いた日です。手数料が足りずに送信が滞留し、後続の送信もまとめて止まり、相場の購読が切れて状態が古いまま判断を続けます。どれも障害ではなく、ブロックチェーンの仕様どおりの動作です。

本稿では、Ethereum とその互換チェーンを前提に、手数料の上がり方、滞留した送信の置き換え、nonce の抜けによる詰まり、購読が切れたときの戻し方、再編成と確定の扱いを、仕様と代表的なノード実装(geth)の既定値をもとに整理します。統合方法の選び方と送信前に決めることは前稿で扱っています。

手数料の基準額はブロックごとに最大 12.5% 上がる

EIP-1559 以降、手数料はブロックごとに決まる基準額(base fee)と、送信者が上乗せする優先手数料の 2 段構えです。基準額は、直前のブロックが目標の使用量(ブロック上限の半分)を超えると上がり、下回ると下がります。上がり幅は超過分に比例し、ブロックが上限まで埋まったときに最大で 1/8、つまり 12.5% 上がります。

送信者は、トランザクションに 1 ガスあたりの支払い上限(max_fee_per_gas)と優先手数料の上限(max_priority_fee_per_gas)を書きます。そのブロックの基準額が支払い上限を超えていると、そのブロックには取り込まれません。実際に払うのは基準額と、上限の範囲内の優先手数料の合計で、上限との差額は払いません。基準額の分は誰にも支払われず焼却されます。

設計で効くのは、混雑が続くと基準額が複利で上がる点です。満杯のブロックが 6 回続けば、基準額は 1.125 の 6 乗で約 2 倍になります。直近の基準額ぎりぎりに支払い上限を置くと、混雑の始まりで取り込まれなくなります。

  • 支払い上限は、待てるブロック数ぶんの上昇を見込んで置く。上限を高く置いても、払うのは実際の基準額と優先手数料までである
  • 残高は「ガスの上限 × 支払い上限」を賄える必要がある。上限を高く置くほど、送信に必要な残高も増える
  • 支払い上限そのものの上限を設定値として持つ。相場の急変時に無制限に上げる作りにしない

滞留した送信は同じ nonce で置き換える

手数料が足りずに滞留した送信は、同じ送信元から同じ nonce で、手数料を上げたトランザクションを送ると置き換えられます。どちらか一方しか取り込まれないので、二重に約定する余地がないのが置き換えの利点です。新しい nonce で送り直すのは別の操作で、元の送信が後から取り込まれれば両方が成立します。

置き換えを受け付ける条件は、ノードの取引プールの設定で決まります。geth の既定値では、既存のトランザクションを置き換えるには価格を 10% 以上上げる必要があります(--txpool.pricebump)。少しずつ上げて何度も送る実装は、この条件に届かずに拒否されます。

取り消したい場合も同じ仕組みを使います。同じ nonce で、価値を動かさないトランザクション(自分宛ての 0 の送金など)を高い手数料で送れば、元の送信は取り込まれなくなります。ただし、元の送信が先に取り込まれれば置き換えは失敗します。置き換えは「必ず効く取消」ではないので、どちらが取り込まれたかを確定の段階で確かめます。置き換えるたびにトランザクションのハッシュは変わるので、業務側の識別子には nonce と送信元を紐づけ、同じ nonce のハッシュをすべて記録しておきます。

DEX への送信が混雑時に詰まる流れと対処を示した図。基準額はブロックごとに最大 12.5% 上がる。滞留した送信は同じ nonce で 10% 以上手数料を上げて置き換える。nonce に抜けがあると後続は実行待ちの列で止まり 3 時間で捨てられる。購読が切れたら最後に処理したブロックから取り直し、再編成で取り消されたログは逆に戻す
置き換えは同じ nonce で、取り込みは最後に処理したブロックから

nonce の抜けは後続をすべて止める

同じアカウントからの送信は、nonce の順にしか取り込まれません。途中の番号が抜けていると、それより後の送信は実行できない列に入って待ちます。geth の既定値は次のとおりです。

  • 実行できる送信の枠 — アカウントごとに最低 16 件(--txpool.accountslots)、全体で 5,120 件
  • 実行できない送信の枠 — アカウントごとに最大 64 件(--txpool.accountqueue)、全体で 1,024 件
  • 実行できない送信が列に残れる時間 — 3 時間(--txpool.lifetime)

つまり、抜けを放置すると後続は実行されないまま最長 3 時間で捨てられます。手元では送信済みのつもりでも、チェーン上には何も起きていない状態です。ノードの再起動や、別のノードへの送信で、プールの中身が失われることもあります。

対策は、nonce を自分で採番して記録し、チェーン上で確定した nonce と突き合わせることです。確定した番号と送信済みの番号の差が一定時間縮まらなければ、抜けか滞留です。最も小さい未確定の番号から置き換えで埋めます。前稿で触れたとおり、送信するアカウントを用途ごとに分けておけば、詰まりの影響はそのアカウントの中に閉じます。

購読は切れると過去分が戻らない

新しいブロックやイベントのログを WebSocket で購読(eth_subscribe)している場合、geth の説明では、購読は接続に結び付いており、接続が切れるとその上の購読はすべて消えます。通知されるのは現在のイベントだけで、過去のイベントは届きません。さらに、受け取り側の処理が遅れて未送信の通知が 1 万件に達すると、接続そのものが切られます。

したがって、再接続して購読し直すだけでは、切れていた間のイベントが抜けます。再接続の手順は次のようにします。

  • 最後に処理し終えたブロックの番号を記録しておく
  • 再接続して購読し直したら、記録した番号の次から現在までを eth_getLogs で取得して埋める
  • 購読で届いたものと取得で埋めたものを、同じ取り込み処理に通す。同じログが両方から届いても 1 度だけ反映する

購読の取り込みが遅れて接続を切られる、という経路もあるので、受け取った通知はまず列に積み、重い処理は別に回します。受け取りの処理の中で判断や送信まで行う作りは、混雑の日ほど遅れて切断を招きます。

再編成で取り消されたログは逆に戻す

ブロックは、確定する前に別のブロックに置き換わること(再編成)があります。geth の購読では、再編成が起きると新しいチェーンの先頭のブロックが通知され、古いチェーンにあったログは removed が true の状態で送り直されます。eth_getLogs の結果でも、removed は再編成で取り除かれたログを示します。

取り込み処理は、removed が立ったログを受け取ったら、そのログで反映した内容を元に戻す必要があります。追加だけを前提にした実装は、再編成のたびに残高や約定の記録がずれます。

どの時点の状態を信じるかは、JSON-RPC のブロック指定で選べます。latest は直近に提案されたブロック、safe は安全とみなされた先頭のブロック、finalized は確定したブロックです。Ethereum の proof of stake では、12 秒のスロットを 32 個まとめたエポックの区切りで、検証者の 3 分の 2 以上の投票によってチェックポイントが確定します。金額の小さい画面表示は latest、残高の照合や出金の判断は finalized というように、用途ごとに参照する時点を決めておきます。

1 エポックは 12 秒 × 32 で 6.4 分です。finalized を待ってから次に進む処理は、送信から分単位で遅れる前提で組みます。秒単位で次の判断が要る処理は、latest で進めつつ、確定の段階でずれを検知して直す、という 2 段構えにします。

まとめ

DEX への送信は、混雑した日に詰まる前提で設計します。基準額は満杯のブロックが続くと 1 ブロックで最大 12.5% ずつ上がるので、支払い上限は待てるブロック数ぶんの上昇を見込みます。滞留した送信は同じ nonce で手数料を 10% 以上上げて置き換え、新しい nonce での送り直しはしません。nonce の抜けは後続を止め、geth の既定では最長 3 時間で捨てられるので、確定した番号と突き合わせて埋めます。購読は切れると過去分が戻らないので、最後に処理したブロックから取得して埋め、再編成で removed が立ったログは逆に戻します。参照する時点は用途ごとに latest・safe・finalized から選びます。

金融テクノロジー総合研究所では、DEX を含むオンチェーン連携の設計・実装を受託しています。送信と取り込みの処理の点検や、混雑時の挙動を前提にした再送の設計からご相談いただけます。Crypto DEX API 開発の詳細もあわせてご覧ください。ご相談はお問い合わせよりご連絡ください。

参考資料

記事一覧に戻る