[#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"
|
||||
@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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
end
|
||||
|
||||
def terminate(_reason, _state) do
|
||||
def terminate(_state) do
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
9
mix.exs
9
mix.exs
|
@ -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,8 +45,11 @@ defmodule POAAgent.MixProject do
|
|||
"Plugins": [
|
||||
POAAgent.Plugins.Collector,
|
||||
POAAgent.Plugins.Transfer,
|
||||
],
|
||||
"Ethereum Plugins": [
|
||||
POAAgent.Plugins.Collectors.Eth.LatestBlock
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
|
|
12
mix.lock
12
mix.lock
|
@ -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"},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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