これはElixir Advent Calendar 2017の11日目の記事です。
昨日はTobiasGSmollettさんの「Macro Tips」でした。
Dockerにmonacoindを立てて、ホストからElixirでJSON-RPC APIを叩くコードを試行錯誤しながら書いてみた、
というお話。
以下、Elixir 1.5.2で動作を確認している。
昨日はTobiasGSmollettさんの「Macro Tips」でした。
Dockerにmonacoindを立てて、ホストからElixirでJSON-RPC APIを叩くコードを試行錯誤しながら書いてみた、
というお話。
以下、Elixir 1.5.2で動作を確認している。
Dockerでmonacoindを起動
まずは、monacoindを起動するDockerfileを書く。FROM ubuntu:16.04 RUN apt update && \ apt install -y software-properties-common && \ add-apt-repository ppa:visvirial/monacoin && \ apt update && \ apt install -y monacoind RUN mkdir /root/.monacoin && \ touch /root/.monacoin/monacoin.conf && \ echo 'rpcuser=monacoinuser' >> /root/.monacoin/monacoin.conf && \ echo 'rpcpassword=pass' >> /root/.monacoin/monacoin.conf && \ echo 'rpcport=12345' >> /root/.monacoin/monacoin.conf && \ echo 'server=1' >> /root/.monacoin/monacoin.conf && \ echo 'rpcallowip=127.0.0.1' >> /root/.monacoin/monacoin.conf && \ echo 'rpcallowip=192.168.0.0/16' >> /root/.monacoin/monacoin.conf EXPOSE 12345 CMD ["/usr/bin/monacoind"]monacoin.confに追記している
rpcallowip=192.168.0.0/16
は、ホストからのJSON-RPCリクエストを許可するための設定。これがないと、403 Forbiddenが返ってきてしまう。
IPアドレスハードコードではなく、Docker コンテナ内からホストの IP アドレスを知るなどを参考にDockerfileに
ip route | awk 'NR==1 {print "rpcallowip=" $3}'
などと書けばいいかと思いきや、これで得られるIPアドレスではないようで、ruimarinho/bitcoin-coreのdocker-compose.ymlなどを参考にハードコードする事にした。
docker run -p 12345:12345 --rm -it monacoind
で起動する。ホストからAPIを叩く
一旦、curlで実行できるか試してみる。curl --data-binary '{"method": "getinfo" }' -H 'content-type: text/plain;' http://monacoinuser:pass@192.168.99.100:12345/
{"result":{"version":140200,"protocolversion":70015,"walletversion":130000,"balance":0.00000000,"blocks":621413,"timeoffset":0,"connections":5,"proxy":"","difficulty":248.9643403030258,"testnet":false,"keypoololdest":1512533231,"keypoolsize":100,"paytxfee":0.00000000,"relayfee":0.00100000,"errors":""},"error":null,"id":null}
curl --data-binary '{"method": "getaccountaddress", "params": ["zaneli"] }' -H 'content-type: text/plain;' http://monacoinuser:pass@192.168.99.100:12345/
{"result":"M8iLT3USjLwxTtk8t4aueWp9dKFmRBUMK8","error":null,"id":null}よさそう。
ElixirでAPIを叩く
上記curlと同じ事をHTTPアクセスすればいいはずだが、せっかくなのでElixirのJSON-RPCライブラリがあれば使ってみたい。jsonrpc2-elixirがあった。
- mix.exs
defmodule Monalixir.Mixfile do use Mix.Project def project do [app: :monalixir, version: "0.1.0", elixir: "~> 1.5", build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps()] end def application do [applications: [:jsonrpc2, :poison, :hackney]] end defp deps do [{:jsonrpc2, "~> 1.0"}, {:poison, "~> 3.1"}, {:hackney, "~> 1.7"}] end end
- config/config.exs
use Mix.Config config :monalixir, host: "192.168.99.100", port: "12345", username: "monacoinuser", password: "pass"
- lib/monalixir.ex
defmodule Monalixir do alias JSONRPC2.Clients.HTTP @host Application.get_env(:monalixir, :host) @port Application.get_env(:monalixir, :port) @username Application.get_env(:monalixir, :username) @password Application.get_env(:monalixir, :password) @url "http://#{@username}:#{@password}@#{@host}:#{@port}/" def get_info do HTTP.call(@url, "getinfo", []) end def get_account_address(account) do HTTP.call(@url, "getaccountaddress", [account]) end end
iex -S mix
で実行してみる。iex(1)> Monalixir.get_info {:error, {:invalid_response, %{"error" => nil, "id" => 0, "result" => %{"balance" => 0.0, "blocks" => 208, "connections" => 1, "difficulty" => 2.44140625e-4, "errors" => "", "keypoololdest" => 1512533231, "keypoolsize" => 100, "paytxfee" => 0.0, "protocolversion" => 70015, "proxy" => "", "relayfee" => 0.001, "testnet" => false, "timeoffset" => 0, "version" => 140200, "walletversion" => 130000}}}}…あれ?一見正しいレスポンスが返ってきていそうだが、
:error
になっている。調べてみたところ、jsonrpc2-elixirではJSON-RPC 2.0の仕様に従い
レスポンスの形式が
{"jsonrpc": "2.0", "id": ..., "result": ...}
でないと:invalid_response
として扱うようだ。そして、monacoindのAPIはそうではない、つまりJSON-RPC 1.0。
jsonrpc2-elixirのserializerを差し替える
通常ならJSON-RPC 1.0・2.0間の仕様の差異を鑑みて大人しく直接poisonとhackneyでアクセスするのが真っ当だとは思うので、ここから先は「できそうなのでやってみた」という蛇足になるが…。
jsonrpc2-elixirのREADMEを見たところ、JSONのシリアライズ・デシリアライズ処理を設定で差し替える事ができるようだ。
という事は、無理やりJSON-RPC 2.0の形式にする事もやろうと思えばできるのでは…?
- config/config.exs
use Mix.Config config :jsonrpc2, serializer: Monalixir.JsonRPC1 config :monalixir, host: "192.168.99.100", port: "12345", username: "monacoinuser", password: "pass"
- lib/serializer.ex
defmodule Monalixir.JsonRPC1 do def encode(value) do Poison.encode(Map.delete(value, "jsonrpc")) end def decode(iodata) do case Poison.decode(iodata) do {:ok, value} -> {:ok, Map.put(value, "jsonrpc", "2.0")} other -> other end end end
iex(1)> Monalixir.get_info {:ok, %{"balance" => 0.0, "blocks" => 646595, "connections" => 5, "difficulty" => 554.21183660971, "errors" => "", "keypoololdest" => 1512533231, "keypoolsize" => 100, "paytxfee" => 0.0, "protocolversion" => 70015, "proxy" => "", "relayfee" => 0.001, "testnet" => false, "timeoffset" => 0, "version" => 140200, "walletversion" => 130000}} iex(2)> Monalixir.get_account_address("zaneli") {:ok, "M8iLT3USjLwxTtk8t4aueWp9dKFmRBUMK8"}とりあえず、
:ok
が返ってくるのは確認できたのでよしとしよう。明日はtoku_bassさんの「EExでconfigファイルのテンプレート化」です。