Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

他サービスと連携させると、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経由でアクセスする

marutake
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした