Ethereum
json-rpc
SmartContract
browser-solidity

[Ethereum] コントラクトをJSON-RPCから呼び出す

概要

他サービスと連携させると、web3.jsを使えないケースが往々にしてある。
json-rpcでコントラクトを呼び出せると、できることの幅が広がる。

というわけでやる。
今回は燃料不要なメソッド(view)だけ

  • contract.totalSuppry()
  • name()
  • contract.getMaru(uint256)

下準備

gethのインストール 〜 トークン作成まで

json-rpcに必要な情報を取得する

rpcから起動するにはシグネチャが必要らしい
How to call a contract method using the eth_call JSON-RPC API

めんどいのでgethからcontractObj.methodName.getData(methodParams) で取得する。

1.gethコンソールにいく

geth --networkid "19" --nodiscover --datadir "eth_private"  --rpc --rpcaddr "localhost" --rpcport "8545" --rpcapi "eth,net,web3,personal,accounts,miner,admin" --rpccorsdomain "*" --unlock "0"  console 2>> eth_private/geth_err.log

> Unlocking account 0 | Attempt 1/3
> Passphrase: 
> Welcome to the Geth JavaScript console!

#改行を削除したabiをここにベタっと貼る
abi =  [{"constant":true,"in... "event"}]
#コントラクトのアドレス
address = "0x9876..."

test=eth.contract(abi).at(address)

#2枚買っとく
test.mint({from:eth.accounts[0],value:web3.toWei(1),gas:30000000})
test.mint({from:eth.accounts[0],value:web3.toWei(1),gas:30000000})

#掘る(自動化推奨)
miner.start(1)
miner.stop()

# json-rpcでの呼び出しに必要な情報の取得
test.totalSuppry.getData()
> "0x18160ddd"
test.name.getData()
> "0x06fdde03"
test.getMaru.getData(1)
> "0x1e20aef70000000000000000000000000000000000000000000000000000000000000001"

ここでgetDataで取得した値は、後でコントラクトのメソッドを叩く時に使う。
ファンクションシグネチャとか言うらしい。
まぁ何だろう、コントラクトはブロックチェーン上にデプロイされていてbytecodeになっているので、そのメソッドをrpcで叩きたいならbytecodeでやらないといかん的なあれそれ。

多分関数のアドレスなのかな?引数がある時はお尻につく。

例:
getMaruは_tokenIndexを引数にしているので、getDataにその引数を渡してやる。
すると、得られるbytecodeの末尾に引数の1が追加されている。

json-rpcを叩いてみる

  • 前提
    • eth.accounts[0]のアドレスは"0x12345..." とする
    • コントラクトのアドレスは "0x9876..." とする

ターミナルを開く

#まず繋がってるか確認
 curl -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":1}'
> {"jsonrpc":"2.0","id":1,"result":"Geth/v1.8.2-stable/darwin-amd64/go1.10"}


## 大丈夫そうならコントラクトを叩く

#contract.totalSuppry() == 6 の時
curl -H 'Origin: localhost' -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x12345...", "to":"0x9876...", "data":"0x18160ddd"},"latest"],"id":1}'
> {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000006"}

#contract.name() == "NewMaruToken"の時
curl -H 'Origin: localhost' -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x12345...", "to":"0x9876...", "data":"0x06fdde03"},"latest"],"id":1}'
> {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c4e65774d617275546f6b656e0000000000000000000000000000000000000000"}

#contract.getMaru(1) == ["0x12345...", "foo", 3, 0, 1522406948, 1000000000000000000, 2] の時
 curl -H 'Origin: localhost' -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x12345...", "to":"0x9876...", "data":"0x1e20aef70000000000000000000000000000000000000000000000000000000000000001"},"latest"],"id":1}'
> {"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000075e2ce17d5b38bd1982f209d089d76797afc736600000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005abe16240000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000"}

戻り値がちょっとわかんない

戻り値のresultは見たまんまバイト列なのでweb3.toDecimalとかweb3.toUtf8とかかけるといいらしい

# case:1 totalSuppry() のresult ==> 6 
"0x0000000000000000000000000000000000000000000000000000000000000006"
↓
web3.toDecimal("0x0000000000000000000000000000000000000000000000000000000000000006")
> 6


# case:2 name() のresult ==> "NewMaruToken" 
"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c4e65774d617275546f6b656e0000000000000000000000000000000000000000"

web3.toUtf8("0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c4e65774d617275546f6b656e0000000000000000000000000000000000000000")
> ""
web3.toAscii("")
> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\fNewMaruToken\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

いきなり躓く。
toAsciiの奥の方に一応 "fNewMaruToken" なるものがあったが、こんなん貰ってもしようもない。

ちょっと考える

送信がbyteだから戻りもbyteだろう。
んで固定長だろう。
totalSuppryの長さで改行してみる。0xは除去

# nameのresult

0000000000000000000000000000000000000000000000000000000000000020
000000000000000000000000000000000000000000000000000000000000000c
4e65774d617275546f6b656e0000000000000000000000000000000000000000

"NewMaruToken" が12文字で16進だと"c"。
"4e65774d617275546f6b656e0000000000000000000000000000000000000000"
は分からんけど文字な気がする

web3.toUtf8("4e65774d617275546f6b656e0000000000000000000000000000000000000000")
> "NewMaruToken"

解決。020は何だろう、行番号かな?
とりあえずgetMaruも改行してみる。

# getMaru(1)
# ["0x12345...", "foo", 3, 0, 1522406948, 1000000000000000000, 2]

00000000000000000000000012345...................................
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000003
0000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000005abe1624
0000000000000000000000000000000000000000000000000de0b6b3a7640000
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000003
666f6f0000000000000000000000000000000000000000000000000000000000
↓
#行番号入れてみる
000 : 00000000000000000000000012345...................................
020 : 00000000000000000000000000000000000000000000000000000000000000e0
040 : 0000000000000000000000000000000000000000000000000000000000000003
060 : 0000000000000000000000000000000000000000000000000000000000000000
080 : 000000000000000000000000000000000000000000000000000000005abe1624
0a0 : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
0c0 : 0000000000000000000000000000000000000000000000000000000000000002
0e0 : 0000000000000000000000000000000000000000000000000000000000000003
100 : 666f6f0000000000000000000000000000000000000000000000000000000000

行番号くさい。数値の32(e0)と番地指定のe0はどう見分けるのか。
いや多分型を知ってる前提なのだろう。stringの場合は文字数が格納されてるアドレスへの参照で、文字の実体は次に存在するみたいな。

確かめる

文字列返しまくりなtestメソッドを作成する

ERC721MaruToken.sol
    function testMaru() public view returns(string,string,string,string,string,string,string){
        return ('いっち','2','three','四','5','6','7');
    }


compile

runタブ移ってcreate

gethでabiとaddress設定して、いつもの

#geth
abi=/* 今作ったやつのabi */
address=/* 今作ったやつのaddress */
test=eth.contract(abi).at(address)
test.testMaru.getdata()
> 0x54a70de6

んでcurlからの整列

# testMaru() ==> ["いっち", "2", "three", "四", "5", "6", "7"]

curl -H 'Origin: localhost' -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":"0x75e2ce17d5b38bd1982f209d089d76797afc7366", "to":"0xed2e9733bedb0a6ae5647a632626bf892549f918", "data":"0x54a70de6"},"latest"],"id":1}'
> {"jsonrpc":"2.0","id":1,"result":"0x00000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000009e38184e381a3e381a1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000574687265650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e59b9b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000135000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013700000000000000000000000000000000000000000000000000000000000000"}

↓

00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000120
0000000000000000000000000000000000000000000000000000000000000160
00000000000000000000000000000000000000000000000000000000000001a0
00000000000000000000000000000000000000000000000000000000000001e0
0000000000000000000000000000000000000000000000000000000000000220
0000000000000000000000000000000000000000000000000000000000000260
0000000000000000000000000000000000000000000000000000000000000009
e38184e381a3e381a10000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001
3200000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000005
7468726565000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000003
e59b9b0000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000001
3500000000000000000000000000000000000000000000000000000000000000

↓

000 : 00000000000000000000000000000000000000000000000000000000000000e0
020 : 0000000000000000000000000000000000000000000000000000000000000120
040 : 0000000000000000000000000000000000000000000000000000000000000160
060 : 00000000000000000000000000000000000000000000000000000000000001a0
080 : 00000000000000000000000000000000000000000000000000000000000001e0
0a0 : 0000000000000000000000000000000000000000000000000000000000000220
0c0 : 0000000000000000000000000000000000000000000000000000000000000260
0e0 : 0000000000000000000000000000000000000000000000000000000000000009
100 : e38184e381a3e381a10000000000000000000000000000000000000000000000
120 : 0000000000000000000000000000000000000000000000000000000000000001
140 : 3200000000000000000000000000000000000000000000000000000000000000
160 : 0000000000000000000000000000000000000000000000000000000000000005
180 : 7468726565000000000000000000000000000000000000000000000000000000
1a0 : 0000000000000000000000000000000000000000000000000000000000000003
1c0 : e59b9b0000000000000000000000000000000000000000000000000000000000
1e0 : 0000000000000000000000000000000000000000000000000000000000000001
200 : 3500000000000000000000000000000000000000000000000000000000000000
220 : 0000000000000000000000000000000000000000000000000000000000000001
240 : 3600000000000000000000000000000000000000000000000000000000000000
260 : 0000000000000000000000000000000000000000000000000000000000000001
280 : 3700000000000000000000000000000000000000000000000000000000000000

目が痛い。番地で間違いなさそう。

まとめ

  • getDataは便利
  • ブロックチェーン上のコントラクトがbytecodeなのでjson-rpcでやる時もbytecode
  • params、resultに型情報は入ってない(だからabiが必要なのやも)
  • 可変長のデータはstring同様、参照が入ってるはず
#curl書式
curl -H "Content-type: application/json" -H "Accept: application/json" -X POST http://localhost:8545  --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"from":sender_address, "to":contract_address, "data":function_bytecode},"latest"],"id":1}'

参考

EthereumノードにJSON-RPC API経由でアクセスする