[#6] Adding Ethereum Latest Block Collector

This commit is contained in:
Felipe Ripoll 2018-05-10 16:10:43 -06:00
parent 0f20bc6165
commit ad678893da
8 changed files with 212 additions and 12 deletions

View File

@ -9,6 +9,15 @@ defmodule POAAgent.Format do
## A literal hex string like "0x0123456789abcdef"
@type t :: String.t
@spec decimalize(t) :: String.t | :format_error
def decimalize("0x" <> trimmed_hex) do
{integer, _} = Integer.parse(trimmed_hex, 16)
Integer.to_string(integer)
end
def decimalize(_) do
:format_error
end
end
defmodule TrimmedHex do

View File

@ -67,10 +67,10 @@ defmodule POAAgent.Plugins.Collector do
def collect(:no_state) do
IO.puts "I am collecting data!"
{:ok, "data retrieved", :no_state}
{:transfer, "data retrieved", :no_state}
end
def terminate(_reason, _state) do
def terminate(_state) do
:ok
end
@ -89,9 +89,12 @@ defmodule POAAgent.Plugins.Collector do
@doc """
In this callback is where the metrics collection logic must be placed.
It must return `{:ok, data, state}`. `data` is the retrieved metrics.
It must return `{:transfer, data, state}` where `data` is the retrieved metrics or
`{:notransfer, state} when for some reason we don't want to send data to the transfer int
that moment
"""
@callback collect(state :: any()) :: {:ok, data :: any(), state :: any()}
@callback collect(state :: any()) :: {:transfer, data :: any(), state :: any()}
| {:notransfer, state :: any()}
@doc """
This callback is called just before the Process goes down. This is a good place for closing connections.
@ -122,8 +125,11 @@ defmodule POAAgent.Plugins.Collector do
@doc false
def handle_info(:collect, state) do
{:ok, data, internal_state} = collect(state.internal_state)
transfer(data, state.label, state.transfers)
internal_state =
state.internal_state
|> collect()
|> transfer(state.label, state.transfers)
set_collector_timer(state.frequency)
{:noreply, %{state | internal_state: internal_state}}
end
@ -147,9 +153,12 @@ defmodule POAAgent.Plugins.Collector do
end
@doc false
defp transfer(data, label, transfers) do
defp transfer({:transfer, data, internal_state}, label, transfers) do
Enum.each(transfers, &GenServer.cast(&1, %{label: label, data: data}))
:ok
internal_state
end
defp transfer({:notransfer, internal_state}, _label, _transfers) do
internal_state
end
@doc false

View File

@ -0,0 +1,98 @@
defmodule POAAgent.Plugins.Collectors.Eth.LatestBlock do
use POAAgent.Plugins.Collector
@moduledoc """
This is a Collector's Plugin which makes requests to a Ethereum node in order to know if
a new block has been added.
This Collector needs the url of the node to iteract. That url must be placed in the args field
in the config file. For example:
{:eth_latest_block, POAAgent.Plugins.Collectors.Eth.LatestBlock, 500, :latest_block, [url: "http://localhost:8545"]}
In this example, the Collector will check with the Ethereum node every 500 miliseconds if a new block
has been added to the blockchain. If that is the case it will retrieve it and send it to the Transfers
encapsulated in a `POAAgent.Entity.Ethereum.Block` struct
"""
@typep internal_state :: %{last_block: String.t}
@doc false
@spec init_collector(term()) :: {:ok, internal_state()}
def init_collector(args) do
:ok = config(args)
{:ok, %{last_block: get_latest_block()}}
end
@doc false
@spec collect(internal_state()) :: term()
def collect(%{last_block: latest_block} = state) do
case get_latest_block() do
nil ->
{:notransfer, state}
^latest_block ->
{:notransfer, state}
latest_block ->
{:ok, block} = Ethereumex.HttpClient.eth_get_block_by_number(latest_block, :false)
{:transfer, format_block(block), %{state | last_block: latest_block}}
end
end
@doc false
@spec terminate(internal_state()) :: :ok
def terminate(_state) do
:ok
end
@doc false
defp config([url: url]) do
Application.put_env(:ethereumex, :url, url)
:ok
end
@doc false
defp get_latest_block() do
case Ethereumex.HttpClient.eth_block_number do
{:ok, latest_block} -> latest_block
_ -> nil
end
end
@doc false
defp format_block(block) do
difficulty = POAAgent.Format.Literal.Hex.decimalize(block["difficulty"])
gas_limit = String.to_integer(POAAgent.Format.Literal.Hex.decimalize(block["gasLimit"]))
gas_used = String.to_integer(POAAgent.Format.Literal.Hex.decimalize(block["gasUsed"]))
number = String.to_integer(POAAgent.Format.Literal.Hex.decimalize(block["number"]))
size = String.to_integer(POAAgent.Format.Literal.Hex.decimalize(block["size"]))
timestamp = String.to_integer(POAAgent.Format.Literal.Hex.decimalize(block["timestamp"]))
total_difficulty = POAAgent.Format.Literal.Hex.decimalize(block["totalDifficulty"])
%POAAgent.Entity.Ethereum.Block{
author: block["author"],
difficulty: difficulty,
extra_data: block["extraData"],
gas_limit: gas_limit,
gas_used: gas_used,
hash: block["hash"],
miner: block["miner"],
number: number,
parent_hash: block["parentHash"],
receipts_root: block["receiptsRoot"],
seal_fields: block["sealFields"],
sha3_uncles: block["sha3Uncles"],
signature: block["signature"],
size: size,
state_root: block["stateRoot"],
step: block["step"],
timestamp: timestamp,
total_difficulty: total_difficulty,
transactions: block["transactions"],
transactions_root: block["transactionsRoot"],
uncles: block["uncles"]
}
end
end

View File

@ -49,7 +49,7 @@ defmodule POAAgent.Plugins.Transfer do
{:ok, :no_state}
end
def terminate(_reason, _state) do
def terminate(_state) do
:ok
end

View File

@ -24,8 +24,12 @@ defmodule POAAgent.MixProject do
defp deps do
[
{:ethereumex, "~> 0.3"},
# Tests
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
{:mock, "~> 0.3", only: [:test], runtime: false},
# Docs
{:ex_doc, "~> 0.18", only: :dev, runtime: false}
@ -41,6 +45,9 @@ defmodule POAAgent.MixProject do
"Plugins": [
POAAgent.Plugins.Collector,
POAAgent.Plugins.Transfer,
],
"Ethereum Plugins": [
POAAgent.Plugins.Collectors.Eth.LatestBlock
]
]
]

View File

@ -1,8 +1,20 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "hexpm"},
"ethereumex": {:hex, :ethereumex, "0.3.2", "ee01a49c781c9751317b2918f799e84185203c81c8314ebebb1f1e22b754db3e", [], [{:httpoison, "~> 1.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.12.1", "8bf2d0e11e722e533903fe126e14d6e7e94d9b7983ced595b75f532e04b7fdc7", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
}

View File

@ -10,7 +10,7 @@ defmodule POAAgent.PluginsTest do
end
def collect(:no_state) do
{:ok, "data retrieved", :no_state}
{:transfer, "data retrieved", :no_state}
end
def terminate(_state) do
@ -64,7 +64,7 @@ defmodule POAAgent.PluginsTest do
def collect(test_pid) do
data = "data retrieved"
send test_pid, {:sent, self(), data}
{:ok, data, test_pid}
{:transfer, data, test_pid}
end
def terminate(_state) do

View File

@ -0,0 +1,65 @@
defmodule POAAgent.Plugins.Collectors.Eth.LatestBlockTest do
use ExUnit.Case
import Mock
test "latest block sent to the transfer" do
with_mock Ethereumex.HttpClient, [
eth_get_block_by_number: fn(_, _) -> {:ok, ethereumex_block()} end,
eth_block_number: fn() -> {:ok, 0} end
] do
{:transfer, block, _} = POAAgent.Plugins.Collectors.Eth.LatestBlock.collect(%{last_block: 1})
assert block == expected_block()
end
end
defp ethereumex_block() do
%{"author" => "0xdf9c9701e434c5c9f755ef8af18d6a4336550206",
"difficulty" => "0xfffffffffffffffffffffffffffffffd",
"extraData" => "0xd583010a008650617269747986312e32342e31826c69",
"gasLimit" => "0x7a1200",
"gasUsed" => "0x0",
"hash" =>
"0xf974c07ac165f8490ef225d47f24b81161e2f2bd8ffd5b926a1a37bb22a02462",
"miner" => "0xdf9c9701e434c5c9f755ef8af18d6a4336550206",
"number" => "0x3b7d9",
"parentHash" => "0xaf6f3c960045aea9edda21e119984028211f4eb3233700c18efca4f8e4c0c2fc",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"sealFields" => ["0x841230dcf5", "0xb841b5ebbb6d5ff185598a86d5e96d9a238ed020b239e94eb1219a6ce1e425c7f23b768ce411474e935c2e3ee61f812ded702e1b7ca2d1c41ab4053f4420440b651901"],
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"signature" => "b5ebbb6d5ff185598a86d5e96d9a238ed020b239e94eb1219a6ce1e425c7f23b768ce411474e935c2e3ee61f812ded702e1b7ca2d1c41ab4053f4420440b651901",
"size" => "0x243",
"stateRoot" => "0x590452386894d2ff6ec146a23f61fd0f459259bf0e20c59504c7f2fa3e4feeb1",
"step" => "305192181",
"timestamp" => "0x5af450c9",
"totalDifficulty" => "0x3b7d8ffffffffffffffffffffffffedcd6b32", "transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []}
end
defp expected_block() do
%POAAgent.Entity.Ethereum.Block{
author: "0xdf9c9701e434c5c9f755ef8af18d6a4336550206",
difficulty: "340282366920938463463374607431768211453",
extra_data: "0xd583010a008650617269747986312e32342e31826c69",
gas_limit: 8_000_000,
gas_used: 0,
hash: "0xf974c07ac165f8490ef225d47f24b81161e2f2bd8ffd5b926a1a37bb22a02462",
miner: "0xdf9c9701e434c5c9f755ef8af18d6a4336550206",
number: 243_673,
parent_hash: "0xaf6f3c960045aea9edda21e119984028211f4eb3233700c18efca4f8e4c0c2fc",
receipts_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
seal_fields: ["0x841230dcf5", "0xb841b5ebbb6d5ff185598a86d5e96d9a238ed020b239e94eb1219a6ce1e425c7f23b768ce411474e935c2e3ee61f812ded702e1b7ca2d1c41ab4053f4420440b651901"],
sha3_uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
signature: "b5ebbb6d5ff185598a86d5e96d9a238ed020b239e94eb1219a6ce1e425c7f23b768ce411474e935c2e3ee61f812ded702e1b7ca2d1c41ab4053f4420440b651901",
size: 579,
state_root: "0x590452386894d2ff6ec146a23f61fd0f459259bf0e20c59504c7f2fa3e4feeb1",
step: "305192181",
timestamp: 1_525_960_905,
total_difficulty: "82917625194725838207510880716721255084813106",
transactions: [],
transactions_root: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}
end
end