bitfeed/server/lib/mempool.ex

111 lines
3.1 KiB
Elixir

Application.ensure_all_started(:hackney)
defmodule BitcoinStream.Mempool do
@moduledoc """
Agent for retrieving and maintaining mempool info (primarily tx count)
"""
use Agent
@doc """
Start a new mempool tracker,
connecting to a bitcoin node at RPC `host:port` for ground truth data
"""
def start_link(opts) do
{port, opts} = Keyword.pop(opts, :port);
{host, opts} = Keyword.pop(opts, :host);
IO.puts("Starting mempool agent on #{host} port #{port}");
case Agent.start_link(fn -> %{count: 0, host: host, port: port} end, opts) do
{:ok, pid} ->
sync(pid);
{:ok, pid}
result -> result
end
end
def getHost(pid) do
Agent.get(pid, &Map.get(&1, :host))
end
def getPort(pid) do
Agent.get(pid, &Map.get(&1, :port))
end
def set(pid, n) do
Agent.update(pid, &Map.update(&1, :count, 0, fn(_) -> n end))
end
def get(pid) do
Agent.get(pid, &Map.get(&1, :count))
end
def increment(pid) do
Agent.update(pid, &Map.update(&1, :count, 0, fn(x) -> x + 1 end))
end
def decrement(pid) do
Agent.update(pid, &Map.update(&1, :count, 0, fn(x) -> x - 1 end))
end
def add(pid, n) do
Agent.update(pid, &Map.update(&1, :count, 0, fn(x) -> x + n end))
end
def subtract(pid, n) do
Agent.update(pid, &Map.update(&1, :count, 0, fn(x) -> x - n end))
end
def sync(pid) do
host = getHost(pid);
port = getPort(pid);
IO.puts("Syncing mempool with bitcoin node on #{host} port #{port}");
with { user, pw } <- rpc_creds(),
{:ok, rpc_request} <- Jason.encode(%{method: "getmempoolinfo", params: [], request_id: 0}),
{:ok, 200, _headers, body_ref} <- :hackney.request(:post, "http://#{host}:#{port}", [{"content-type", "application/json"}], rpc_request, [basic_auth: { user, pw }]),
{:ok, body} <- :hackney.body(body_ref),
{:ok, %{"result" => info}} <- Jason.decode(body),
%{"size" => pool_size} <- info do
IO.puts("Synced pool: size = #{pool_size}");
set(pid, pool_size)
else
{:error, reason} ->
IO.puts("Pool sync failed");
IO.inspect(reason)
:error
err ->
IO.puts("Pool sync failed: (unknown reason)");
IO.inspect(err);
:error
end
end
defp rpc_creds() do
cookie_path = System.get_env("BITCOIN_RPC_COOKIE");
rpc_user = System.get_env("BITCOIN_RPC_USER");
rpc_pw = System.get_env("BITCOIN_RPC_PASS");
cond do
(rpc_user != nil && rpc_pw != nil)
-> { rpc_user, rpc_pw }
(cookie_path != nil)
->
IO.puts("loading bitcoin rpc cookie at #{cookie_path}");
with {:ok, cookie} <- File.read(cookie_path),
[ user, pw ] <- String.split(cookie, ":") do
{ user, pw }
else
{:error, reason} ->
IO.puts("Failed to load bitcoin rpc cookie");
IO.inspect(reason)
:error
err ->
IO.puts("Failed to load bitcoin rpc cookie: (unknown reason)");
IO.inspect(err);
:error
end
true ->
IO.puts("Missing bitcoin rpc credentials");
:error
end
end
end