Ethereumの開発で、transactionを伴ったmessage callをサーバー側からRubyを使って行う必要が出てきて、gethのapi の仕様について少し勉強したので共有しようと思います。
利用するメソッド
Transactionを伴うmessage call(smart contractの関数呼び出し)の場合gethの
eth_sendRawTransaction というメソッドを利用します。
手順
このメソッドでは、署名付きのトランザクションデータをパラメータとして渡します。
Example
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[<signed transaction data>],"id":1}'
// Result
{
"id":1,
"jsonrpc": "2.0",
"result": "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
}
引用元
まずは署名付きのtransactionを作成するために、僕はRubyを使っていたので ruby-eth というライブラリを利用しました。
まずは自分の秘密鍵を元にKeyインスタンスを作成します。
key = Eth::Key.new priv: <Your private key>
次にTransactionインスタンスを作成しますが、この中に呼び出す関数や引数、コントラクトに送金するETHの値などの情報を入れる必要があります。
tx = Eth::Tx.new({
data: hex_data, //ここに関数名と引数の情報を入れます
gas_limit: 21_000,
gas_price: 3_141_592,
nonce: 1, // 自分のアカウントの現在のnonceの値。nonceの値は eth_getTransactionCount というメソッドで取得できます。
to: <contract_address>, // 呼び出し先のcontractのアドレス
value: 1_000_000_000_000, //送金するethの値
})
このdataの構造が少しめんどくさかったので下で改めて説明します。
次に作成したKeyインスタンスを利用してtransactionに署名をして、署名付きのトランザクションデータを作成しパラメータに入れてリクエストを送ります。
tx.sign key
// tx.sign key 後に tx.hexで署名付きのtransactionデータを取得できる。
リクエスト
tx.hexをparamsに入れてリクエストをしてあげてくださいさい。
curl <json-rpcのエンドポイント> -X POST -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":[tx.hex],"id":1}'
smart contract関数のabiについて
ETH::Tx.newで使った hex_data の作り方について説明します。
Ethereumのsmart contractの関数を呼び出す際には、関数呼び出しのインターフェースとして、関数名はkeccak256でハッシュ化し値の最初の4byteを、引数は16進数のhexstringを、32byteになるようにpaddingした値を渡してあげます。
keccak256をrubyで利用するために僕は digest-sha3-rubyというライブラリを利用しました。
Digest::SHA3.hexdigest("文字列", 256) でSmart Contractのabiに必要なhash値を生成できます。
例
function sample(string _string, uint256 _uint){ // 何かしらの処理 }
という関数を呼び出す場合
関数セレクタ
まずは呼び出す関数を決める方法を紹介します。
Digest::SHA3.hexdigest(sample(string,uint256),256)
// ef432c498906197465c1b51178a9f3bdd2c2fb281ce919552edb985d3ee0cfa3
の結果、 ef432c498906197465c1b51178a9f3bdd2c2fb281ce919552edb985d3ee0cfa3
という値の先頭の4byte、 ef432c49 を関数セレクタとして利用します。
引数の指定
次に引数を指定する方法を紹介します。
引数は関数名と異なり、uintやboolなど固定長の場合と、bytesやsringなど可変長の場合で値の私方が異なります。
固定長の引数の場合
uintやboolの場合は渡す値を左側からpaddingして32byteの長さになるようにします。
上の例の関数で12を引数の渡す場合は
0x000000000000000000000000000000000000000000000000000000000000000c
という値を渡します。
可変長の場合
可変長の場合は、引数の情報を32byteずつの3つの部分に分けて渡します。
順番に
- 引数の情報が始まる位置の情報(関数名部分をのぞいて何byte目から該当の引数の値の情報が始まるか)
- headerとして引数の値の長さをbyte数で渡します。
- 最後に実際のデータを、固定長の時とは逆に右側からpaddingした値を渡します。
以上を踏まえてsample(string _string,uint256 _uint)の関数に"unko",12という値を渡す場合を考えると、unkoは16進数のhexstring(utf8)で表すと756e6b6fになるので、
- 関数名: ef432c49
- _string:
-
0x0000000000000000000000000000000000000000000000000000000000000040
-
0x0000000000000000000000000000000000000000000000000000000000000008
-
0x756e6b6f000000000000000000000000000000000000000000000000000000000
-
- _uint: 0x000000000000000000000000000000000000000000000000000000000000000c
を
1(関数セレクタ) ⇨ 2-1(引数1) ⇨ 3-1(引数2) ⇨ 2-2(引数1のヘッダ) ⇨ 2-3(引数1の中身) という順番で繋げて渡します。
渡すデータ
0xef432c490000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000008756e6b6f00000000000000000000000000000000000000000000000000000000
つまり固定長の場合はそのまま値を16進数にして渡し、可変長の場合は引数にはデータの開始位置に関する情報を渡し、中身は後から渡す形になります。
引数として渡すデータの構造を調べていて少し時間がかかってしまったので一応メモがわりに書きました。