ビットコインインフラとしてのelectrumx(server)

# はじめに

この記事は暗号通貨アドベントカレンダーの一日目の記事です

## ビットコインのインフラを使うための課題

ビットコインはJSON-RPCにてmicroserviceのようにAPIサーバーとして外部連携できる機能がある
その機能は大きく分けて以下の５つ存在する

* ウォレット
* マイニング
* P2P
* データベース（ブロック情報、トランザクション情報）
* 統計データ

microserviceとして使うときにウォレット機能で管理していないアドレスからの入金を扱うことを簡単にはできない
それはアドレスから入金トランザクションを取得するためのインデックス情報をウォレット機能でのみ保有しているため、他から利用できないため、そのようなことが起きる
そもそもビットコインにおいてウォレット管理外のトランザクションを扱うにはtxindex=1を指定して起動しないとならないためオプション扱いとなっている。

ブロックエクスプローラーがアドレスからトランザクション一覧を検索できるのはそういったデータベースを持っているからである
ブロックが配信されたタイミングやトランザクションが配信されたタイミングで独自のデータベースにインデックスを構築する

これを行うプロダクトがOSSでいくつか存在する

* https://github.com/bitpay/bitcore-node
* https://github.com/kyuupichan/electrumx
* https://github.com/CounterpartyXCP/indexd-server

これらのプロダクトはそれぞれモバイルやデスクトップのウォレットを開発していてそのインフラとして開発されている

今回はelectrumxに焦点を当てて解説する

## electrum と electrumx

electrum と electrumxはクライアント・サーバーモデルである
以下のような構成になる

```
構成図

bitcoind ---- electrumx(server) ---- electrum(client)
           |                     |
          http-jsonrpc           |
                                Stratum(tcp-jsonrpc)
```

electrum(client)はウォレット機能で秘密鍵とアドレスを管理している
秘密鍵からアドレスを生成し、そのアドレスを用いてelectrumxサーバーに問い合わせを行うことで残高を取得できる
残高を取得するにあたってアドレス情報や該当のトランザクションを問い合わせるのでプライバシーに関しては弱い
SSLでサーバーに接続することも出来るのでその場合はサーバー管理者には所有するアドレスが伝わるがそれが問題であるならば
OSSであるので自分でサーバーを構築することで回避ができる

# electrumxの機能

bitcoindから定期的にブロックを取得しBlockProcessorと呼ばれる処理でトランザクションを解析し検索に必要なインデックスを作ることを定期的に行いJSONRPC経由でデータを取得できるデータベースとして振る舞う

## データベースの内約

* アドレスに紐づくUTXO
* アドレスに紐づくトランザクションヒストリ
* ブロックのメタデータ（ハッシュ、ヘッダ、トランザクション数）

## インデックス化に用いられているデータベース

* leveldb
* rocksdb(facebookがleveldbをカスタム化したもの)

ヒストリーデータなどの時系列データを扱っているので時系列DBを採用している
leveldbかrocksdbを利用者が選択できる

## reorg対策

* 分岐が発生したときにはreorgによるロールバックが発生する
* ロールバック可能な仕組みのためスナップショット+差分形式で保存される
* 65535個（ブロック数）までしか差分がとれないので定期的にバッチで差分を集計しスナップショットを作成する必要がある
* ビットコインの場合は10分に一回のブロックスピードなので一年と少しはバッチを実行しなくても良いがブロックスピードの速いオルトコインの場合は一ヶ月ほどで65535まで到達する場合もある

## オルトコイン対応

* ビットコインクローンであれば扱うことができる
* オルトコインごとに個別にブロック処理を行えるようになっていて他のコインと同じであれば（例えばsegwitなど）それを指定することでそのまま対応できる
* 対応していない場合は必要な箇所を実装することで対応も可能

# electrumxのapi

stratumと呼ばれるプロトコルを採用している
tcp/sslの生ソケットにそのままJSON-RPCを実装した簡単なテキストプロトコルで実装されている
各言語用に実装されているのでそれを使うのが簡単である

https://github.com/d4l3k/go-electrum
https://github.com/you21979/node-electrum-client
https://github.com/Padrio/php-electrum-api
https://github.com/lclc/perl-stratum

ここでは代表的なAPIを紹介する

## server.version

electrumxのサーバーのバージョンを確認する

```
{ "id": 0, "method": "server.version", "params": [ "2.7.11", "1.0" ] }
```

```
{ "id": 0, "result": "ElectrumX 1.2" }
```

## blockchain.numblocks.subscribe

現在のブロックの高さを取得する

```
{ "id": 5, "method":
"blockchain.numblocks.subscribe", "params": [] }
```

```
{ "id": 5, "result": 316024 }
```

## blockchain.address.listunspent

アドレスに存在するUTXO一覧を取得できる
このAPIはブロックに含まれるUTXOのみを返すのでunconfirmトランザクションを取得する場合はblockchain.address.get_mempoolを使う（ただしUTXOとは限らない）
txid、vout、satoshiが含まれるのでそのままトランザクションのインプットとして使えます。

```
{ "id": 1, "method": "blockchain.address.listunspent", "params":
 ["1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"] }
```

```
{"id": 1, "result": [
 {"tx_hash":"561534ec392fa8eebf5779b233232f7f7df5fd5179c3c640d84378ee6274686b","tx_pos": 0, "value": 24990000, "height": 340242},
 {"tx_hash":"620238ab90af02713f3aef314f68c1d695bbc2e9652b38c31c025d58ec3ba968","tx_pos": 1, "value": 19890000, "height": 340242}
]}
```

## blockchain.address.get_mempool

アドレスに存在するmempool上のトランザクションを取得できる
このトランザクション一覧は未使用とは限らないのでUTXOと同様の扱いにすることはできない

```
{ "id": 1, "method": "blockchain.address.get_mempool", "params":
 ["1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"] }
```

```
{"id": 1, "result": [
 { tx_hash: '3a71e68dae55cbf2b579f12370cf25e1c600022a751532a77fb61ffe11b893d8',
    height: -1,
    fee: 35149
 },
 { tx_hash: 'f16e3e37b89a1ea88dba2b21fedf5c764be86dd1de73e1c850a4e61d78dc33e1',
    height: -1,
    fee: 54019
 }
]}
```

## blockchain.address.get_balance

アドレスの残高を取得できる

```
{ "id": 1, "method":"blockchain.address.get_balance", "params":["1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"] }
```

```
{"id": 1, "result": {"confirmed": 533506535, "unconfirmed": 27060000}}
```

## blockchain.transaction.get

トランザクションを取得できる
hex値なのでトランザクションパーサーでデシリアライズする必要がある
mempoolから消えたり、reorgされて消えたトランザクションはエラーが返る

```
{ "id": 17, "method":"blockchain.transaction.get", "params": [
"0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
] }
```

```
{ "id": 17, "result":"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"}
```

## blockchain.transaction.broadcast

トランザクションを伝搬できる

```
{ "id": 1, "method":
"blockchain.transaction.broadcast", "params":
"0100000002f327e86da3e66bd20e1129b1fb36d07056f0b9a117199e759396526b8f3a20780000000000fffffffff0ede03d75050f20801d50358829ae02c058e8677d2cc74df51f738285013c260000000000ffffffff02f028d6dc010000001976a914ffb035781c3c69e076d48b60c3d38592e7ce06a788ac00ca9a3b000000001976a914fa5139067622fd7e1e722a05c17c2bb7d5fd6df088ac00000000" }
```

```
{"id": 1, "result": "561534ec392fa8eebf5779b233232f7f7df5fd5179c3c640d84378ee6274686b"}
```

## blockchain.estimatefee

１００0バイトあたり手数料を取得する
ビットコイン以外の手数料がダイナミックに変化しないコインはほとんど-1を返す

```
{ "id": 17, "method": "blockchain.estimatefee", "params": [ 6 ] }
```

```
{ "id": 17, "result": 0.00026809 }
```

## blockchain.address.get_history

入出金履歴を取得する
このAPIは重いので履歴が多すぎるアドレスはノードによってはエラーになる場合がある
electrumxの設定でコントロール可能

```
{"id": 1, "method": "blockchain.address.get_history", "params": ["1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"] }
```

```
{"id": 1, "result": [{"tx_hash": "ac9cd2f02ac3423b022e86708b66aa456a7c863b9730f7ce5bc24066031fdced", "height": 340235}, {"tx_hash": "c4a86b1324f0a1217c80829e9209900bc1862beb23e618f1be4404145baa5ef3", "height": 340237}]}
{"jsonrpc": "2.0", "id": 1, "result": [{"tx_hash": "16c2976eccd2b6fc937d24a3a9f3477b88a18b2c0cdbe58c40ee774b5291a0fe", "height": 0, "fee": 225}]}
```

# electrumxで簡単に出来ないこと

* 支払い済みのトランザクションの行方（TXID）を追うのが難しい（支払ったかどうかはlistunspentから消えていればわかる）
* unconfirmトランザクションの扱いに工夫が必要

# 総括

ウォレットを実装するには十分なAPIがあるがブロックエクスプローラを実装するにはスペックが少し足りていないのでcoindに接続する必要がある

オルトコイン全般のコールドウォレットの入金確認や、ホットウォレットの実装に使うには十分なスペックはあると思う