[#6] Adding Ethereum Latest Block Collector
This commit is contained in:
parent
0f20bc6165
commit
ad678893da
|
@ -9,6 +9,15 @@ defmodule POAAgent.Format do
|
||||||
|
|
||||||
## A literal hex string like "0x0123456789abcdef"
|
## A literal hex string like "0x0123456789abcdef"
|
||||||
@type t :: String.t
|
@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
|
end
|
||||||
|
|
||||||
defmodule TrimmedHex do
|
defmodule TrimmedHex do
|
||||||
|
|
|
@ -67,10 +67,10 @@ defmodule POAAgent.Plugins.Collector do
|
||||||
|
|
||||||
def collect(:no_state) do
|
def collect(:no_state) do
|
||||||
IO.puts "I am collecting data!"
|
IO.puts "I am collecting data!"
|
||||||
{:ok, "data retrieved", :no_state}
|
{:transfer, "data retrieved", :no_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate(_reason, _state) do
|
def terminate(_state) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -89,9 +89,12 @@ defmodule POAAgent.Plugins.Collector do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
In this callback is where the metrics collection logic must be placed.
|
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 """
|
@doc """
|
||||||
This callback is called just before the Process goes down. This is a good place for closing connections.
|
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
|
@doc false
|
||||||
def handle_info(:collect, state) do
|
def handle_info(:collect, state) do
|
||||||
{:ok, data, internal_state} = collect(state.internal_state)
|
internal_state =
|
||||||
transfer(data, state.label, state.transfers)
|
state.internal_state
|
||||||
|
|> collect()
|
||||||
|
|> transfer(state.label, state.transfers)
|
||||||
|
|
||||||
set_collector_timer(state.frequency)
|
set_collector_timer(state.frequency)
|
||||||
{:noreply, %{state | internal_state: internal_state}}
|
{:noreply, %{state | internal_state: internal_state}}
|
||||||
end
|
end
|
||||||
|
@ -147,9 +153,12 @@ defmodule POAAgent.Plugins.Collector do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@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}))
|
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
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
|
|
@ -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
|
|
@ -49,7 +49,7 @@ defmodule POAAgent.Plugins.Transfer do
|
||||||
{:ok, :no_state}
|
{:ok, :no_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate(_reason, _state) do
|
def terminate(_state) do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
9
mix.exs
9
mix.exs
|
@ -24,8 +24,12 @@ defmodule POAAgent.MixProject do
|
||||||
|
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
|
{:ethereumex, "~> 0.3"},
|
||||||
|
|
||||||
|
# Tests
|
||||||
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
|
{:credo, "~> 0.9", only: [:dev, :test], runtime: false},
|
||||||
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
|
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
|
||||||
|
{:mock, "~> 0.3", only: [:test], runtime: false},
|
||||||
|
|
||||||
# Docs
|
# Docs
|
||||||
{:ex_doc, "~> 0.18", only: :dev, runtime: false}
|
{:ex_doc, "~> 0.18", only: :dev, runtime: false}
|
||||||
|
@ -41,8 +45,11 @@ defmodule POAAgent.MixProject do
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
POAAgent.Plugins.Collector,
|
POAAgent.Plugins.Collector,
|
||||||
POAAgent.Plugins.Transfer,
|
POAAgent.Plugins.Transfer,
|
||||||
|
],
|
||||||
|
"Ethereum Plugins": [
|
||||||
|
POAAgent.Plugins.Collectors.Eth.LatestBlock
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
12
mix.lock
12
mix.lock
|
@ -1,8 +1,20 @@
|
||||||
%{
|
%{
|
||||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"},
|
"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"},
|
"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"},
|
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"},
|
||||||
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [], [], "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"},
|
"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"},
|
"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"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule POAAgent.PluginsTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
def collect(:no_state) do
|
def collect(:no_state) do
|
||||||
{:ok, "data retrieved", :no_state}
|
{:transfer, "data retrieved", :no_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate(_state) do
|
def terminate(_state) do
|
||||||
|
@ -64,7 +64,7 @@ defmodule POAAgent.PluginsTest do
|
||||||
def collect(test_pid) do
|
def collect(test_pid) do
|
||||||
data = "data retrieved"
|
data = "data retrieved"
|
||||||
send test_pid, {:sent, self(), data}
|
send test_pid, {:sent, self(), data}
|
||||||
{:ok, data, test_pid}
|
{:transfer, data, test_pid}
|
||||||
end
|
end
|
||||||
|
|
||||||
def terminate(_state) do
|
def terminate(_state) do
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue