Bitcoinのreorgについて知っておくべきこと

こんにちは。
泡アハです。

暗号資産ベンチャー企業に転職して、早8ヶ月が経過しました。
BitcoinやEthereumなど暗号資産については、日々勉強の毎日ですが、
先日Bitcoinのreorg(リオルグ)について調べる機会があったので、
本記事にまとめていきたいと思います。

## reorgとは？
端的に言ってしまうと
**同時にブロックをマイニングした場合に、取り込まれなかったブロックが破棄されること**
です。

ブロックチェーンは、暗号資産の取引履歴を管理するための分散型データベースです。
新しいトランザクション（取引）が発生するたびに、これらのトランザクションはブロックと呼ばれるまとまりにまとめられ、順番にチェーン状につながっていきます。

しかし、時折、複数のマイナー（採掘者）が同時にブロックを生成し、競い合うことがあります。
この競争の結果、一部のブロックは採用されず、別のブロックが採用されることがあります。
これがreorgの発生です。

Bitcoinでは、最も長いチェーンを採用するというルールがあります。
そのため、同時にブロックが生成された場合は、先に新しいブロックがつながり、最も長いチェーンと判断されたブロックのみがブロックチェーンネットワークに取り込まれ、ブロックがつながらなかった方は破棄されます。

## reorgされたブロックに取り込まれたトランザクションはどうなる？
ここで一つの疑問が発生します。

それは、
**reorgされたブロックに取り込まれたトランザクション(以降、tx)はどうなるのか？**
ということです。

結論をいってしまうと以下のようになります。
```
パターン1
競合したブロックのどちらにもtxが含まれる場合
→mempoolに戻らず、採用されたブロックに含まれ、チェーンに取り込まれる

パターン2
reorgされたブロックのみにtxが含まれる場合
→mempoolに戻る
```

言葉でいってしまえば、簡単ですが、
それぞれ簡単なデモを作成して挙動を確認してみましょう。

## デモ

手順は以下のとおりです。
```
1. nodeAでtxを生成し、ブロードキャストし、mempoolにおく
2. nodeA, nodeBのノード接続を切断する
3. nodeAでtxを生成し、mempoolにおく
4. nodeA, nodeBでそれぞれマイニングする
5. nodeA, nodeBをノード接続する
6. nodeBで1ブロックマイニングする
→手順1で生成したtxは、パターン1にあたり、mempoolにはもどらないはず
　手順3で生成したtxは、パターン2にあたり、mempoolにもどるはず
```

それでは、一つずつやってみましょう。
### 手順1. nodeAでtxを生成し、ブロードキャストし、mempoolにおく
txの生成は、sendtoaddressを使うことで行えます。
nodeA, Bそれぞれでgetrawmempoolを実行してみるとそれぞれsendtoaddressしたときのtxidが表示されているので、
nodeA、Bのmempool内に手順1のtxが存在することがわかります。
このtxは、後にreorgするブロックにも含まれていますが、正式なブロックにも取り込まれるため、mempoolには戻らないはず。
```
root@41bfba2b394e:/# bitcoin-cli sendtoaddress "bcrt1qvgr22qmhet8k2ea0y9xzjqvm5mhwxncchpyuuh" 1
808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf
# nodeAで実行
root@41bfba2b394e:/# bitcoin-cli getrawmempool
[
  "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf"
]
# nodeBで実行
root@f3084f108fa6:/# bitcoin-cli getrawmempool
[
  "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf"
]
```

### 手順2. nodeA, nodeBのノード接続を切断する
ノードの切断は、
addnode "hostname" "remove"した後、
disconnecenode "hostname"
で切断することができます。
この環境は、docker-composeで作っているので、service名をhostnameに記載することで切断できます。
```
nodeAで実行
root@41bfba2b394e:/# bitcoin-cli addnode "harry_node" "remove"
root@41bfba2b394e:/# bitcoin-cli disconnectnode "harry_node"
root@41bfba2b394e:/# bitcoin-cli getpeerinfo
[
]

nodeBで実行
root@f3084f108fa6:/# bitcoin-cli getpeerinfo
[
]
```

### 手順3. nodeAでtxを生成し、mempoolにおく
ここは、手順1とほぼ同じです。
ここでのポイントは、ここで生成したtxはmempoolに戻るはずということです。
戻るはずのtxidは、**64a2afd9b1391~**ですね。
```
nodeAで実行
root@41bfba2b394e:/# bitcoin-cli sendtoaddress "bcrt1qvgr22qmhet8k2ea0y9xzjqvm5mhwxncchpyuuh" 1
64a2afd9b1391365e2acfbb5072ea6fe95eef0b430ac193b3737c04466474f39
root@41bfba2b394e:/# bitcoin-cli getrawmempool
[
  "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf",
  "64a2afd9b1391365e2acfbb5072ea6fe95eef0b430ac193b3737c04466474f39"
]

nodeBで実行
root@f3084f108fa6:/# bitcoin-cli getrawmempool
[
  "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf"
]
```

### 手順4. nodeA, nodeBでそれぞれマイニングする
ここでは、ブロックを生成したあと、念の為、getblockにどのtxが含まれているかを確認しています。
また、それぞれでgetblockchaininfoを実行して、ブロック高が同じであることを確認しています。
```
nodeAで実行
root@41bfba2b394e:/# bitcoin-cli generatetoaddress 1 "bcrt1qntytnst44dg6uqtr8jg62rx0apdpj4enj6uh5n"
[
  "694d4d4105bfc632c24058a27e41885f922154a3adb02a0ac8f8701fafb3483f"
]
root@41bfba2b394e:/# bitcoin-cli getrawmempool
[
]
root@41bfba2b394e:/# bitcoin-cli getblock "694d4d4105bfc632c24058a27e41885f922154a3adb02a0ac8f8701fafb3483f"
{
  "hash": "694d4d4105bfc632c24058a27e41885f922154a3adb02a0ac8f8701fafb3483f",
  "confirmations": 1,
  "height": 103,
  "version": 536870912,
  "versionHex": "20000000",
  "merkleroot": "cf854df166c760e4a19ba96220190365515c618244633328a47203651b14b608",
  "time": 1690025966,
  "mediantime": 1690024761,
  "nonce": 2,
  "bits": "207fffff",
  "difficulty": 4.656542373906925e-10,
  "chainwork": "00000000000000000000000000000000000000000000000000000000000000d0",
  "nTx": 3,
  "previousblockhash": "52c90a2055b51ed117e7cd45adf0c86fb20b4f0084fe66b60d87b2b0dad0c32b",
  "strippedsize": 439,
  "size": 693,
  "weight": 2010,
  "tx": [
    "15cfb8267348cb5cc3093fb11d4000f0f39abc065923c34085f55efb255f3dd7",
    "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf",
    "64a2afd9b1391365e2acfbb5072ea6fe95eef0b430ac193b3737c04466474f39"
  ]
}
root@41bfba2b394e:/# bitcoin-cli getblockchaininfo                                                       
{
  "chain": "regtest",
  "blocks": 103,
  "headers": 103,
  "bestblockhash": "694d4d4105bfc632c24058a27e41885f922154a3adb02a0ac8f8701fafb3483f",
  "difficulty": 4.656542373906925e-10,
  "time": 1690025966,
  "mediantime": 1690024761,
  "verificationprogress": 1,
  "initialblockdownload": false,
  "chainwork": "00000000000000000000000000000000000000000000000000000000000000d0",
  "size_on_disk": 31474,
  "pruned": false,
  "warnings": ""
}


nodeBで実行
root@f3084f108fa6:/# bitcoin-cli generatetoaddress 1 bcrt1qvgr22qmhet8k2ea0y9xzjqvm5mhwxncchpyuuh
[
  "044c757146fab23ae4dfbb05e1249916b7ba22667759ddba3b8f1e5871c985ba"
]
root@f3084f108fa6:/# bitcoin-cli getblock "044c757146fab23ae4dfbb05e1249916b7ba22667759ddba3b8f1e5871c985ba"
{
  "hash": "044c757146fab23ae4dfbb05e1249916b7ba22667759ddba3b8f1e5871c985ba",
  "confirmations": 1,
  "height": 103,
  "version": 536870912,
  "versionHex": "20000000",
  "merkleroot": "8fc42439f032127bf4623dcc5bd17747bbff37cc65d7d3cc535fda7713701bf4",
  "time": 1690026323,
  "mediantime": 1690024761,
  "nonce": 0,
  "bits": "207fffff",
  "difficulty": 4.656542373906925e-10,
  "chainwork": "00000000000000000000000000000000000000000000000000000000000000d0",
  "nTx": 2,
  "previousblockhash": "52c90a2055b51ed117e7cd45adf0c86fb20b4f0084fe66b60d87b2b0dad0c32b",
  "strippedsize": 326,
  "size": 471,
  "weight": 1449,
  "tx": [
    "e9fc3ce9cf1a9a6a3e14ccc4bd0ea952aed3ed336fb4ecbb743a42be88e000c3",
    "808b50c587b7ad4f69bda8c866d6035ccdebb2b970016f3e93939ff25fad4ecf"
  ]
}
root@f3084f108fa6:/# bitcoin-cli getrawmempool
[
]
root@f3084f108fa6:/# bitcoin-cli getblockchaininfo
{
  "chain": "regtest",
  "blocks": 103,
  "headers": 103,
  "bestblockhash": "044c757146fab23ae4dfbb05e1249916b7ba22667759ddba3b8f1e5871c985ba",
  "difficulty": 4.656542373906925e-10,
  "time": 1690026323,
  "mediantime": 1690024761,
  "verificationprogress": 1,
  "initialblockdownload": false,
  "chainwork": "00000000000000000000000000000000000000000000000000000000000000d0",
  "size_on_disk": 31220,
  "pruned": false,
  "warnings": ""
}

```

### 手順5. nodeA, nodeBをノード接続する
接続は、addnode "hostname" "add"するだけでできます。
確認のため、nodeA、nodeBともにgetpeerinfoして、接続できているか確認しています。
```
nodeAで実行
root@41bfba2b394e:/# bitcoin-cli addnode "harry_node" "add"
root@41bfba2b394e:/# bitcoin-cli getpeerinfo      
[
  {
    "id": 2,
    "addr": "harry_node",
    "addrbind": "172.22.0.2:44312",
    "network": "not_publicly_routable",
    "services": "0000000000000409",
    "servicesnames": [
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

nodeBで実行
root@f3084f108fa6:/# bitcoin-cli getpeerinfo
[
  {
    "id": 2,
    "addr": "172.22.0.2:44312",
    "addrbind": "172.22.0.3:18444",
    "network": "not_publicly_routable",
    "services": "0000000000000409",
    "servicesnames": [
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```

### 手順6. nodeBで、1ブロックマイニングする
nodeBで1ブロックマイニングします。
こうすることでnodeBで生成したブロックのほうが長くなるため、nodeAで生成したブロックはreorgします。
そして、reorgしたブロックに含まれるtxであってもすでにブロックに取り込まれるかどうかで挙動が変わります。
手順3で確認した、txidは、**64a2afd9b1391~**でした。
そして、nodeAのmempoolには、**64a2afd9b1391~**が含まれています。
この結果より、パターン1、パターン2の挙動となることが証明されました。
```
nodeBで実行
root@f3084f108fa6:/# bitcoin-cli generatetoaddress 1 bcrt1qvgr22qmhet8k2ea0y9xzjqvm5mhwxncchpyuuh
[
  "1e9ce4a86c4899c34bf41b77f6770aec6cf5a5b47f690790c0c8fbf86f07817d"
]
root@f3084f108fa6:/# bitcoin-cli getrawmempool
[
]

nodeAで実行
root@41bfba2b394e:/# bitcoin-cli getrawmempool
[
  "64a2afd9b1391365e2acfbb5072ea6fe95eef0b430ac193b3737c04466474f39"
]

```

## まとめ
いかがでしたでしょうか？

わからないことがあれば、時には今回のデモのように自分の手元で丁寧に確認することも重要ですね。