MonapartyのAPIを拡張する
今回は実際にcounterblock
のカスタムモジュールを作成してみます。
xchain.ioのAPIの中からBurnsを題材にし、それに対応したJSON RPC APIを実装します。
APIの定義確認
Endpoint
xchain.ioのAPIではアドレスとブロックナンバーで検索出来ますが、今回はアドレスでの検索に絞ります。
Method | Endpoint | Returns |
---|---|---|
GET | /api/burns/{address} | Returns list of 'Burn' transactions |
GET | /api/burns/{block} | Returns list of 'Burn' transactions |
Paging
ページングにも対応していて、触ってみたところ最大値は500のようです。
Method | Endpoint |
---|---|
GET | endpoint/{page}/{limit} |
Return Values
burned
とearned
がString
になっていますが、これは恐らく内部的にsatoshi
単位で持っているものをdivisible
に応じて変換して返すためだと思われます。fee
などもすべて文字列でした。
Value | Type | Description | |
---|---|---|---|
data | Array | Broadcasts data | |
block_index | Integer | Block number containing the transaction | |
burned | String | The amount of Bitcoin (BTC) burned | |
earned | String | The amount of Counterparty (XCP) earned | |
source | String | Source address where broadcast originated | |
status | String | Status of the transaction | |
timestamp | Integer | A UNIX timestamp of when the transaction was processed by the network | |
tx_hash | String | Transaction Hash | |
tx_index | Integer | Transaction Index | |
total | Integer | Total number of burns |
Example Response
{ "data": [{ "block_index": 283809, "burned": "1.00000000", "earned": "1000.09090909", "source": "1EU6VM7zkA9qDw8ReFKHRpSSHJvbuXYNhq", "status": "valid", "timestamp": 1492254524, "tx_hash": "ad6609edbdb3b951627302f65df06636f2535680d69d2ee98f59af05cedf0d94", "tx_index": 3069 } ], "total": 7 }
データベースの確認
テーブル
sqlite3
をインストールして、counterparty
のデータベースに対して.table
コマンドでテーブル一覧を確認してみます。
sudo sqlite3 /var/lib/docker/volumes/federatednode_counterparty-data/_data/monaparty.db
sqlite> .table
addresses contracts orders
assets credits postqueue
balances debits rps
bet_expirations destructions rps_expirations
bet_match_expirations dividends rps_match_expirations
bet_match_resolutions executions rps_matches
bet_matches issuances rpsresolves
bets mempool sends
blocks messages storage
broadcasts nonces suicides
btcpays order_expirations transactions
burns order_match_expirations undolog
cancels order_matches undolog_block
今回ターゲットになるテーブルはburns
になるかと思われます。
テーブルの構造
次にburns
テーブルに対して.schema
コマンドで構造を確認します。(一部省略)
sqlite> .schema burns CREATE TABLE burns( tx_index INTEGER PRIMARY KEY, tx_hash TEXT UNIQUE, block_index INTEGER, source TEXT, burned INTEGER, earned INTEGER, status TEXT);
これだけではtimestamp
が足りないので、block_index
からtimestamp
を取ってこれそうなblocks
テーブルについても構造を確認します。
sqlite> .schema blocks CREATE TABLE blocks( block_index INTEGER UNIQUE, block_hash TEXT UNIQUE, block_time INTEGER, previous_block_hash TEXT UNIQUE, difficulty INTEGER, ledger_hash TEXT, txlist_hash TEXT, messages_hash TEXT);
実装
とりあえず最終的なjsonをそのまま返すAPIを作成します。
実際にはcounterblock
は直接公開せずにNginx
からNode.js
あたりに流してそこから呼ぶような感じになると思いますので、もう少し汎用的なAPIにして呼び出し元で成形するほうが良いのかもしれません。このあたりはThe手探りです。
@API.add_method
関数に@API.add_method
デコレータを付けることで、JSON RPC APIで呼べるようになります。
util.call_jsonrpc_api
モジュール内からCounterparty API
を呼ぶにはutil.call_jsonrpc_api
を使います。
メソッドはsql
を指定し、query
に生のSQLを入れたオブジェクトを渡すと、counterparty
のデータベースに対してSQLを直接実行出来ます。これは、Counterblock API
のproxy_to_counterpartyd
からでは呼べないAPIです。
blockchain.normalize_quantity
burned
とearned
はノーマライズした上で文字列で返したいので、blockchain.normalize_quantity
でノーマライズしてから小数点以下8桁付きの文字列に変換します。
コード
my_api.py
from counterblock.lib import util ,blockchain from counterblock.lib.processor import API @API.add_method def get_burns_from_address(address, offset=0, limit=500): if limit > 500: limit = 500 elif limit < 0: limit = 0 data_sql = "select burns.*, blocks.block_time as timestamp" data_sql += " from burns" data_sql += " inner join blocks" data_sql += " on burns.block_index = blocks.block_index" data_sql += " and burns.source = '" + address + "'" data_sql += " order by block_index DESC" data_sql += " limit " + str(limit) + " offset " + str(offset) data_body = util.call_jsonrpc_api("sql", {"query": data_sql}, abort_on_error=True)["result"] for x in data_body: x["burned"] = "{:.8f}".format(blockchain.normalize_quantity(x["burned"], True)) x["earned"] = "{:.8f}".format(blockchain.normalize_quantity(x["earned"], True)) total_sql = "select count(tx_index) as total" total_sql += " from burns" total_sql += " where source = '" + address + "'" total_count = util.call_jsonrpc_api("sql", {"query": total_sql}, abort_on_error=True)["result"][0]["total"] return {"data": data_body, "total": total_count}
modules.confの設定
作成したmy_api.py
をcounterblock
コンテナ内にコピーします。
一旦母艦から~/hostdir
に放り込んだものを、counterblock/lib
配下に作成したcustom_modules
ディレクトリにコピーしました。
この場合のcustom_modules
ディレクトリの名称、位置や、モジュールのファイル名は、別の場所でも別の名前でも問題ありません。
sudo docker cp hostdir/my_api.py federatednode_counterblock_1:/counterblock/counterblock/lib/custom_modules/my_api.py
modules.conf
に先ほどコピーしたファイルの位置を追記します。
nano federatednode/config/counterblock/modules.conf [LoadModule] lib/modules/assets = True lib/modules/counterwallet = True lib/modules/dex = True lib/modules/transaction_stats = True lib/modules/betting = True lib/custom_modules/my_api = True
動作確認
counterblock
を再起動して作成したモジュールを有効にします。
これはmodules.conf
を更新した場合だけではなく、後からmy_api.py
を更新した場合にも再読み込みが必要です。
再起動が終わったらAPIにアクセスしてみます。
fednode restart counterblock curl -s -X POST --data '{"jsonrpc":"2.0","id":1,"method":"get_burns_from_address","params":{"address":"MCwt89zvuPHaCvHLmY1fvgfoQKot1BApd5","offset":0,"limit":100}}' http://localhost:4100 {"id": 1, "jsonrpc": "2.0", "result": {"total": 1, "data": [{"timestamp": 1511081192, "source": "MCwt89zvuPHaCvHLmY1fvgfoQKot1BApd5", "tx_index": 195, "block_index": 1166003, "status": "valid", "tx_hash": "9f6fd3b04e0f2a54b99d4227aaac660c8dc291df66b74274e87153bfb4394a72", "earned": "1499.88840000", "burned": "1.00000000"}]}}
それっぽいレスポンスが返ってきました。