Blockchain 解析用ソフトウェア: BlockSci を使ってみる

この記事は、[ブロックチェーン Advent Calendar 2017](https://qiita.com/advent-calendar/2017/blockchain) の 20 日目の記事です。

## 概要

BlockSci というのは、Blockchain 解析用のソフトウェアです。
Blockchain を Jupyter Notebook 使って解析したいな〜というときにか〜なり便利です。

## 注意

インメモリデータベースなので、メモリをかなり食います。AMI をもとに、EC2 にインスタンスをサクッと立ち上げるのが良いでしょう。
公式が公開している AMI を使うと、立ち上げて ssh トンネリングするだけで Jupyter Notebook が使える状態になります。メチャクチャ楽です。
だいたい 3.5 時間後にすべてのデータが読み込まれ、クエリの処理速度が最高速に達します。

## 導入

Readme にある最新の AMI を元に EC2 インスタンスを立ち上げます。
2017/12/19 現在では `ami-7cf38706` が最新となっています。
メモリ領域が 60GB 以上あることが期待されており, `r4.2xlarge` が推奨されています。

![](https://qiita-image-store.s3.amazonaws.com/0/29618/4badd080-fa51-ddcd-abeb-386fefdac712.png)

`r4.2xlarge` は $0.532 / hour なので, スポットインスタンス使わず一ヶ月回すと $383.04 程度かかります。

![](https://qiita-image-store.s3.amazonaws.com/0/29618/bdc56ce0-3194-35b6-860b-a9dbc2574cf5.png)

自分もこの記事のために昨日から回していて、$10 が失われました。
立ち上がると, Jupyter Notebook も起きているので,

```
ssh -i .ssh/your_private_key.pem -N -L 8888:localhost:8888 ubuntu@your_url.amazonaws.com
```

したら `localhost:8888` へ見に行くだけです。

## 利用例

### 事前準備

```python
import blocksci
import matplotlib.pyplot as plt
import matplotlib.ticker
import collections
import pandas as pd
import numpy as np
%matplotlib notebook
```

```python
# parser_data_directory should be set to the data-directory which the blocksci_parser output
chain = blocksci.Blockchain("/home/ubuntu/bitcoin/")
```

### Class: Block

ref: https://citp.github.io/BlockSci/chain/block.html

```python
# 最新の block を取得
recent_block = chain[len(chain) - 1]
# coinbase
recent_block.coinbase_tx
# hash: ハッシュ
print("hash is", recent_block.hash)
#  height: ブロック高さ
print("height is", recent_block.height)
# input_count: ブロックに含まれるトランザクションインプットの数
print("input count is", recent_block.input_count)
# input_value: ブロックに含まれるトランザクションインプットに含まれる satoshi の総数
print("input value is", recent_block.input_value)
#  inputs: ブロックに含まれるトランザクションインプットを取得
recent_block.inputs
#  net_address_type_value: Returns a set of the net change in the utxo pool after this block split up by address type
print("net_address_type_value is", recent_block.net_address_type_value)
# net_full_type_value(self: blocksci.blocksci_interface.Block)
print("net_full_type_value", recent_block.net_full_type_value)
# next_block: 次のブロック
recent_block.next_block
# nonce: nonce の数値
print("nonce is", recent_block.nonce)
# output_count: ブロックに含まれるトランザクションアウトプットの数
print("output count is", recent_block.output_count)
# output_value: ブロックに含まれるトランザクションアウトプットに含まれる satoshi の総数
print("output value is", recent_block.output_value)
# outsputs: ブロックに含まれるトランザクションアウトプットを取得
recent_block.outputs
# prev_block: 前のブロック
recent_block.prev_block
# size_bytes: ブロックサイズ
print("size bytes is", recent_block.size_bytes)
# time: タイム
print("time is", recent_block.time)
# timestamp: ブロックヘッダのタイムスタンプ
print("timestamp is", recent_block.timestamp)
# total_spent_of_ages: Returns a list of sum of all the outputs in the block that were spent within a certain of blocks, up to the max age given
print("total_spent_of_ages is", recent_block.total_spent_of_ages)
# txes: ブロックに含まれるトランザクションを取得
recent_block.txes
# version: ブロックヘッダのプロトコルバージョン
print("version is", recent_block.version)
```

    hash is 0000000000000000006271a8c189b9591ad06a369e29d24dbe25e6a1819d6cee
    height is 498919
    input count is 4913
    input value is 1290872675640
    net_address_type_value is <bound method PyCapsule.net_address_type_value of Block(numTxes=3031, height=498919, header_hash=0000000000000000006271a8c189b9591ad06a369e29d24dbe25e6a1819d6cee, version=536870912, timestamp=1513094055, bits=402698477, nonce=231235938)>
    net_full_type_value <bound method PyCapsule.net_full_type_value of Block(numTxes=3031, height=498919, header_hash=0000000000000000006271a8c189b9591ad06a369e29d24dbe25e6a1819d6cee, version=536870912, timestamp=1513094055, bits=402698477, nonce=231235938)>
    nonce is 231235938
    output count is 6720
    output value is 1292122675640
    size bytes is 1050481
    time is 2017-12-12 15:54:15
    timestamp is 1513094055
    total_spent_of_ages is <bound method PyCapsule.total_spent_of_ages of Block(numTxes=3031, height=498919, header_hash=0000000000000000006271a8c189b9591ad06a369e29d24dbe25e6a1819d6cee, version=536870912, timestamp=1513094055, bits=402698477, nonce=231235938)>
    version is 536870912


### CurrencyConverter


```python
converter = blocksci.CurrencyConverter()
converter.satoshi_to_currency(100, recent_block.time.date())
```




    0.0171781025



### サンプル

#### 手数料のヒストグラム


```python
USD_JPY_RATE = 112
fees = [converter.satoshi_to_currency(fee, recent_block.time) * USD_JPY_RATE for fee in recent_block.txes.fee]
```


```python
plt.hist(fees, bins=20, range=(0, 6000))
```




    (array([   6.,    8.,   20.,  217.,  870.,  897.,  464.,  126.,   22.,
             137.,   39.,   21.,   54.,   11.,   12.,   21.,   18.,   14.,
              10.,    4.]),
     array([    0.,   300.,   600.,   900.,  1200.,  1500.,  1800.,  2100.,
             2400.,  2700.,  3000.,  3300.,  3600.,  3900.,  4200.,  4500.,
             4800.,  5100.,  5400.,  5700.,  6000.]),
     <a list of 20 Patch objects>)

![output_9_1.png](https://qiita-image-store.s3.amazonaws.com/0/29618/3e9ac159-2778-2246-9138-eeedbf32ae5a.png)



#### トランザクションから手数料などを取り出してみる


```python
all_tx = recent_block.txes.all
sample_tx = all_tx[1]
# https://blockchain.info/ja/tx/4fe8cca3318ac086eaf4dcb3958dbd1db324b446591fdb03dbec1342e664570c
print(sample_tx.hash)
fee = sample_tx.fee
input_value = sum(sample_tx.inputs.value)
output_value = sum(sample_tx.outputs.value)
print(fee, input_value, output_value)
print(input_value - output_value == fee)
converter.satoshi_to_currency(fee, recent_block.time.date())
```

    4fe8cca3318ac086eaf4dcb3958dbd1db324b446591fdb03dbec1342e664570c
    700000 832822188 832122188
    True





    120.2467175



#### 最大の手数料を探す


```python
tx = sorted(recent_block.txes.all, key=lambda tx: - tx.fee)[0]
tx.fee
```




    10000000




```python
# それぞれのブロックの最大の手数料を持つ TX のリスト
txes = [sorted(block.txes.all, key=lambda tx: - tx.fee)[0] for block in chain]
```


```python
fees = [tx.fee for tx in txes]
df = pd.DataFrame({"Fees":fees})
ax = df.plot(legend=False)
ax.set_ylim(ymin=0)
plt.tight_layout()
```


![output_15_0.png](https://qiita-image-store.s3.amazonaws.com/0/29618/adc92112-08e8-6782-3b21-8c15d34513e8.png)




```python
# 最大の手数料を持つ TX
tx = sorted(txes, key=lambda tx: - tx.fee)[0]
```


```python
print("Maximum fee is %s JPY" % (converter.satoshi_to_currency(tx.fee, recent_block.time) * USD_JPY_RATE))
print("hash is", tx.hash)
print("time is", tx.block.time)
```

    Maximum fee is 560332195.6279321 JPY
    hash is cc455ae816e6cdafdb58d54e35d4f46d860047458eacf1c7405dc634631c570d
    time is 2016-04-26 14:15:22


[I smell a "Where did my money go?" moment : Bitcoin](https://www.reddit.com/r/Bitcoin/comments/4gjrm9/i_smell_a_where_did_my_money_go_moment/)


まあこんな感じで遊べるので, 楽しい。`$0.532/hour` 失いながら遊んでいきましょう。