BigQueryのブロックチェーン一般公開データセットを使う

# はじめに

https://cloud.google.com/bigquery/public-data?hl=ja

BigQuery（以下、BQ）には誰もが参照できる一般公開データセットがあります。
その中にはブロックチェーンに関するデータセットも含まれます。
これらのデータセットについて調査したことやTipsをまとめました。

## ブロックチェーンの一般公開データセットについて

### どのように利用を開始するのか？

下記の手順を踏むだけでデータセットを利用開始できます。

- [マーケットプレイス](https://console.cloud.google.com/marketplace/details/ethereum/crypto-ethereum-blockchain)にアクセス
- `データセットを表示`を押下
- BQコンソールに`bigquery-public-data/crypto_*`が表示されるように

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/3839b4c4-39bb-c959-9d4b-28af9aa040d7.png">

### どのブロックチェーンが一般公開データセット化されている？

[コチラのリポジトリ](https://github.com/blockchain-etl/public-datasets)にリストが記載されており、BitcoinやPolygon等に対応していることが分かります。

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/78af3280-efbb-3a28-8606-0927c20faac7.png">

※上記画像は2023年4月26日現在

### 参考：一般公開データセットを構築する基盤

この一般公開データセットはどのように作られているのでしょうか？

まず前提として[bitcoin-etl](https://github.com/blockchain-etl/bitcoin-etl)や[ethereum-etl](https://github.com/blockchain-etl/ethereum-etl)という[Blockchain ETL](https://github.com/blockchain-etl)が開発するOSSがあります。
（ブロックチェーンからデータを取ってきてパースしCSVやJSON形式で取得できるOSS）

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/049c6196-45ce-7a9e-8b28-94263d585c27.png">

これらを利用し、一般公開データセットを構築している旨がGoogle Cloud公式ブログに記載されています。
ethereum-etlやAirflowを組み合わせてETLを行っているようです。

https://cloud.google.com/blog/products/data-analytics/ethereum-bigquery-how-we-built-dataset?hl=en

Blockchain ETL以下には[ethereum-etl-airflow](https://github.com/blockchain-etl/ethereum-etl-airflow)があり、こちらはポチポチするだけでethereum-etlとAirflowを組み合わせたETL基盤をデプロイできるものです。公式の基盤もこれに近しい形で構築されているものと思われます。

### 参考：EVMやBitcoin系以外のデータセットはないのか

他にも、Blockchain ETLにインスピレーションを受けPolkadot系のブロックチェーンをETL可能にした[substrate-etl](https://github.com/colorfulnotion/substrate-etl)が存在します。
READMEに記載の手順でPolkadot系データセットも利用できます。

## 実際に分析してみる

以降ではEthereumをターゲットに話を進めていきます。
Ethereumの一般公開データセットである`bigquery-public-data/crypto_ethereum`以下には：

- balances
- blocks
- contracts
- logs
- sessions
- token_transfers
- tokens
- traces
- transactions

といったテーブルがあり、それぞれの意味についてはDescriptionを見るのが手っ取り早いかと思います。
例えばtransactionsテーブルをBQコンソールで開くと下記の記載があります。

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/147f1e4d-f281-4a49-a98c-432a73e755c3.png">

>Each block in the blockchain is composed of zero or more transactions. Each transaction has a source address, a target address, an amount of Ether transferred, and an array of input bytes.
This table contains a set of all transactions from all blocks, and contains a block identifier to get associated block-specific information associated with each transaction.
Data is exported using https://github.com/medvedev1088/ethereum-etl

### まずはクエリを発行してみる

https://github.com/blockchain-etl/awesome-bigquery-views

上記リポジトリに`bigquery-public-data/crypto_ethereum`へのサンプルクエリが載っているので試しに実行してみます。

```top_ethereum_balances.sql
-- 残高上位1000件のアドレスとその残高を取得するためのクエリ

WITH double_entry_book AS (
    -- debits
    SELECT to_address AS address, value AS value
    FROM `bigquery-public-data.crypto_ethereum.traces`
    WHERE to_address IS NOT NULL
    AND status = 1
    AND (call_type NOT IN ('delegatecall', 'callcode', 'staticcall') OR call_type IS NULL)
    UNION ALL
    -- credits
    SELECT from_address AS address, -value AS value
    FROM `bigquery-public-data.crypto_ethereum.traces`
    WHERE from_address IS NOT NULL
    AND status = 1
    AND (call_type NOT IN ('delegatecall', 'callcode', 'staticcall') OR call_type IS NULL)
    UNION ALL
    -- transaction fees debits
    SELECT 
        miner AS address, 
        SUM(CAST(receipt_gas_used AS numeric) * CAST((receipt_effective_gas_price - COALESCE(base_fee_per_gas, 0)) as numeric)) AS value
    FROM `bigquery-public-data.crypto_ethereum.transactions` AS transactions
    join `bigquery-public-data.crypto_ethereum.blocks` AS blocks ON blocks.number = transactions.block_number
    GROUP BY blocks.number, blocks.miner
    UNION ALL
    -- transaction fees credits
    SELECT 
        from_address AS address, 
        -(CAST(receipt_gas_used AS numeric) * CAST(receipt_effective_gas_price AS numeric)) AS value
    FROM `bigquery-public-data.crypto_ethereum.transactions`
)
SELECT address, SUM(value) AS balance
FROM double_entry_book
GROUP BY address
ORDER BY balance DESC
LIMIT 1000
```

コンソール上で実行すると次のように結果が出てきます。データを整備する必要がないので楽ですね！

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/abd89361-e3fe-3885-9965-3ae7f475fe87.png">

### もっと詳細に分析したい！

どのような分析ができるのかは、Kaggleにて`crypto_ethereum`をキーワードに検索すると先人のクエリを見ることができるので、参考になるかと思います。

https://www.kaggle.com/search?q=crypto_ethereum

## 分析する上で生じた問題点

### Etherscan上のアドレスでクエリしても結果が出ない

Etherscan上のトランザクションがcrypto_ethereum内に存在するか確認してみます。
CryptoPunksに関するトランザクションをEtherscanにて調べ、最新トランザクションのfromアドレスをコピーしてクエリしてみました。

```sql
-- アドレスの一部を`.`でマスクしています

SELECT
  *
FROM
  `bigquery-public-data.crypto_ethereum.transactions`
WHERE
  from_address = "0x...B0F...A7FBBDf.............Bf3844976"
AND
    DATE(block_timestamp) = "2023-04-25"
```

結果は下記の通り「表示するデータはありません」。

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/c96c424f-3b17-af2f-4e68-f149dccb5e68.png">

なぜこのような結果になったかと言えば、Etherscanサイトに記載のトランザクションのアドレスは**チェックサム・アドレス**形式になっています。
つまり、一部に大文字が混入しているのです。

>暗号通貨アドレスには、チェックサム2と呼ばれているエラーチェック機能が組み込まれており、これによりアドレスのタイプミス（間違えて一文字抜かしてしまうなど）を防ぐことができます。イーサリアム・アドレスには、チェックサムバージョン、および非チェックサムバージョンの両方があります。チェックサム・バージョンのイーサリアム・アドレスには、大文字が含まれていますが、非チェックサム・バージョンには小文字しか含まれていません。
チェックサム・アドレス：0x4a44A0XXXXXXXXXX290217C51Df0c6158a59CAD2
非チェックサム・アドレス：0x4a44a0xxxxxxxxxx290212721df0c6158a59cad2
>
>[暗号通貨およびDeFiユーザーがよくやる9個の失敗](https://blog.makerdao.com/ja/%E6%9A%97%E5%8F%B7%E9%80%9A%E8%B2%A8%E3%81%8A%E3%82%88%E[…]1%8F%E3%82%84%E3%82%8B9%E5%80%8B%E3%81%AE%E5%A4%B1%E6%95%97/)

`LOWER()`等で全て小文字に直すことでうまくいきます。

```sql
SELECT
  *
FROM
  `bigquery-public-data.crypto_ethereum.transactions`
WHERE
  from_address = LOWER("0x...B0F...A7FBBDf.............Bf3844976")
AND
    DATE(block_timestamp) = "2023-04-25"
```

### 最新のデータがヒットしない

基本的にcrypto_ethereum等は日次で更新されていますが、一部データセットは最終更新が過去の場合があります。
テーブルの詳細から最終更新がいつなのかは確認するのが良いです。
下記のスクショは`bigquery-public-data/crypto_polygon`のトランザクションテーブルです。

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/fe9dd58f-408c-ca52-72c5-4f9e40ce1e13.png">

### クエリのコストを下げたい

ブロックチェーンデータは大きいので、素朴にクエリ発行すると数TBの処理になり課金が走ってしまいます（BQは1TB/月以上有料）。
基本的にクエリを行う前に右上に表示されるバイト数を確認する基本動作が重要です。

<img width="400" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/586d7ac7-770e-6949-2150-3b0d2cf1ce0f.png">

その他コストを削減するためのポイントは3点です。
- ブロックチェーン公開データセットのテーブルは日付でパーティションされています。そのためWHERE句に`DATE(block_timestamp) > "2023-04-01"`といったような制限を加えることでコスト削減に繋がります
    - https://cloud.google.com/bigquery/docs/partitioned-tables?hl=ja
- BQはカラムナーDBなのでLIMITを実施してもコスト削減にはなりません。代わりに`SELECT *`ではなく本当に必要なカラムだけをSELECTすればコストは下がります
- どうしても期間を限定せず、アドホックにデータ分析を行いたい場合はBQのセッション機能を使うのがオススメです
    - https://cloud.google.com/bigquery/docs/sessions-create?hl=ja

### 参考：トラブルシューティング先

どうしても分からんことが生まれたら、Blockchain ETLには公式Telegramがあるので質問してみると良いかもです。

https://t.me/BlockchainETL