はじめに
Gethの新しいバージョン1.9が2019年7月10日にリリースされました。様々な機能追加、修正、パフォーマンス向上などが行われていますが、その中でも新機能としてリリースされたGraphQL機能について、検証してみました。
GraphQLとは、Facebookが開発したSQLのようなクエリ言語で、条件を与えるとJSONのレスポンスデータとして返されます。GethにおけるGraphQLは、Gethがもつトランザクション情報やブロック情報などを、指定した条件に絞り込んだり、必要な項目情報のみを取得したりできる機能が提供されています。
Gethの起動オプション
Geth1.9では、コマンドオプションにGraphQLのためのオプションが追加されました。
- –graphql
- GraphQLサーバを起動するためのオプション
- GraphQLサーバを起動するためのオプション
- –graphql.addr <アドレス>
- GraphQLサーバがアクセスを受け付けるネットワークインターフェイス。
- このオプションを指定しない場合はローカルホストからのみ接続できる。
- 外部のサーバから受け付ける場合は「0.0.0.0」を指定する。
- –graphql.port <ポート番号>
- GraphQLサーバが起動するポート番号
- デフォルトは8547
- –graphql.corsdomain <ドメイン>
- クロスオリジンリクエストを受け付けるドメインを指定する(複数の場合はカンマ区切りで指定)
- クロスオリジンリクエストを受け付けるドメインを指定する(複数の場合はカンマ区切りで指定)
- –graphql.vhosts <ホスト名>
- リクエスト元の仮想ホスト名
- デフォルトは”localhost”。ワイルドカードは”*”。
GraphQLの画面構成
GraphQLオプションを使ってGethを起動し、ブラウザから8547(デフォルト)ポートに対してアクセスすると、以下のようなGraphQLの画面が表示されます。
上記の画面は横に4分割になっています。
- 左から1番目は、実行したクエリの履歴が表示される領域です。過去に実行したクエリをすぐに実行することができます。
- 左から2番目は、GraphQLのクエリを記述する領域です。初期表示時にはクエリの書き方やサンプルが記述されています。
- 左から3番目は、クエリの実行結果を表示する領域です。
- 左から4番目は、クエリに関するドキュメントが記載されている領域です。クエリを書く際に、このドキュメントを見ながらどのように記述するのか確認することができます。
クエリの記載方法
GraphQLのクエリは全体を { } で括り、その中に結果表示したい項目(Fields)を記載します。結果がオブジェクトである場合には、さらに { } の中に表示したい項目を記載します。
シンプルな例
以下は、gasPriceを表示するクエリです。結果がBigIntで返ってくるため、項目名を記載するだけで実行することができます。
{
gasPrice
}
このクエリを実行すると、以下のようなJSON形式のデータが表示されます。
{
"data": {
"gasPrice": "0x3b9aca00"
}
}
オブジェクトの結果が返される場合の例
以下のクエリは、ハッシュ値を指定して1つのトランザクションの結果を表示するクエリです。戻り値がTransactionオブジェクトとなるので、Transactionが持つ項目名(hash, nonce, inputData)を指定しています。
{ transaction(hash:"0x858ea1a7647a64df0861f7e61cf856172adc787b23d828dbab5008903e283014")
{
hash,
nonce,
inputData
}
}
このクエリを実行すると、以下が表示されます。
{
"data": {
"transaction": {
"hash": "0x858ea1a7647a64df0861f7e61cf856172adc787b23d828dbab5008903e283014",
"nonce": "0x5",
"inputData": "0x484c00af000000000000000000000000000000000000000000000000000000000000162e"
}
}
}
どのようなクエリが書けるのかということや、どのような項目を出力できるのかについては、GraphQLの画面右端のドキュメントを参照してもらえると良いと思います。
独自のコントラクトを実行して確かめてみる
sendTransactionによる送金のトランザクションでもGraphQLを使って結果を表示することはできますが、より実践的でよく使われそうな場面としては、ログのフィルタリングではないかと思います。
そこで、独自のコントラクトが出力する、TransactionReceiptのlogsをGraphQLを使って表示・フィルタリングできるか検証してみます。
検証のコントラクトコードは以下を使います。
contract Locker {
bytes32 passHash;
event openLog(address _sender, string _logText);
event errLog(address _sender, string _logText);
constructor(uint _pass) public {
passHash = keccak256(abi.encode(bytes32(_pass)));
}
function openLocker(uint _num) public {
if(passHash != keccak256(abi.encode(bytes32(_num)))) {
emit errLog(msg.sender, "[ERR]LOCKER OPEN FAILED.");
return;
}
emit openLog(msg.sender, "[INFO]LOCKER OPEN SUCCESS.");
}
}
ロッカーの施錠を管理するコントラクトで、デプロイ時に解錠するためのパスワードを指定します。openLocker関数が呼ばれると、指定されたパスワードがロッカーのパスワードと一致するか確認し、
- OKの場合は、「[INFO]LOCKER OPEN SUCCESS.」
- NGの場合は、「[ERR]LOCKER OPEN FAILED.」
というイベントログを出力します。
実行するアドレスやOKとNGを変化させながら、以下の結果となるように実行しました。
No | 実行ユーザ(アドレス) | OK/NG |
1 | 0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9 | OK |
2 | 0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9 | NG |
3 | 0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9 | NG |
4 | 0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9 | OK |
5 | 0xce2e4ccafeacae712db09435bbeff5567df32b67 | NG |
6 | 0xce2e4ccafeacae712db09435bbeff5567df32b67 | OK |
上記の結果をすべて取得するクエリとして、以下を作成しました。
{
logs(filter: {fromBlock: 0}) {
data
topics
account {
address
}
}
}
表示するデータとして、data, topics, address を指定しています。
実行結果は以下の通り6件になりました。
{
"data": {
"logs": [
{
"data": "0x00000000000000000000000042676035ce252b3556827f54c32ab03bd2c071760000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x00000000000000000000000042676035ce252b3556827f54c32ab03bd2c07176000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000185b4552525d4c4f434b4552204f50454e204641494c45442e0000000000000000",
"topics": [
"0xeb8310da52cb60437f19d78102e8b4468b889171946f10c00d3ab705b03944f2"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc43000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000185b4552525d4c4f434b4552204f50454e204641494c45442e0000000000000000",
"topics": [
"0xeb8310da52cb60437f19d78102e8b4468b889171946f10c00d3ab705b03944f2"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc430000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc43000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000185b4552525d4c4f434b4552204f50454e204641494c45442e0000000000000000",
"topics": [
"0xeb8310da52cb60437f19d78102e8b4468b889171946f10c00d3ab705b03944f2"
],
"account": {
"address": "0xce2e4ccafeacae712db09435bbeff5567df32b67"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc430000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0xce2e4ccafeacae712db09435bbeff5567df32b67"
}
}
]
}
}
16進数表示のためわかりにくいですが、dataの後半部分にある、
「5b494e464f5d4c4f434b4552204f50454e20535543434553532e」をASCII変換すると
「[INFO]LOCKER OPEN SUCCESS.」となり、
「5b4552525d4c4f434b4552204f50454e204641494c45442e」をASCII変換すると
「[ERR]LOCKER OPEN FAILED.」となります。
また、OKとなる場合のtopicsは「0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1」、
NGとなる場合のtopicsは
「0xeb8310da52cb60437f19d78102e8b4468b889171946f10c00d3ab705b03944f2」
となりました。
この結果に対して、OKの場合のみ表示対象となるようにフィルタリングしてみます。クエリのフィルタにtopicsを追加し、OKとなる値を指定します
{
logs(filter: {fromBlock: 0,
topics:[["0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"]]
})
{
data
topics
account {
address
}
}
}
結果は以下のように3件に絞り込まれました。
{
"data": {
"logs": [
{
"data": "0x00000000000000000000000042676035ce252b3556827f54c32ab03bd2c071760000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc430000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0x53e65a57c88086b8cf4e13c4086c0d92ca1908f9"
}
},
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc430000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0xce2e4ccafeacae712db09435bbeff5567df32b67"
}
}
]
}
}
さらに、アカウント「0xce2e4ccafeacae712db09435bbeff5567df32b67」でも絞り込んでみます。
{
logs(filter: {fromBlock: 0,
topics:[["0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"]],
addresses:["0xce2e4ccafeacae712db09435bbeff5567df32b67"]
})
{
data
topics
account {
address
}
}
}
結果は1件となりました。
{
"data": {
"logs": [
{
"data": "0x000000000000000000000000e5708328111aa61edb27e116026c237e0232dc430000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001a5b494e464f5d4c4f434b4552204f50454e20535543434553532e000000000000",
"topics": [
"0xbe04ac4b001aacb9b5103c731f8ccaa8d53e7d2db724485f5bd08074f1d120a1"
],
"account": {
"address": "0xce2e4ccafeacae712db09435bbeff5567df32b67"
}
}
]
}
}
このようにフィルタリングすることで、誰が解錠したのかということがすぐに確認することができました。
まとめ
GraphQLを使うことで、欲しい項目に絞り込んだり、条件を指定してフィルタリングすることが簡単にできるようになります。 従来は、JavaScript等のロジックで絞り込みを行っていた手間と比較すると、大幅に削減されるのではないかと思います。