diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index bc5892a152..4fc27546ee 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -2053,7 +2053,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do end test "returns all the required fields", %{conn: conn} do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) @@ -2062,7 +2062,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do |> with_block(block, gas_used: 1) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(1)) |> Wei.from(:wei) @@ -2092,7 +2092,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do end test "with a block with one transaction", %{conn: conn} do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) @@ -2101,7 +2101,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do |> with_block(block, gas_used: 1) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(1)) |> Wei.from(:wei) @@ -2131,7 +2131,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do end test "with pagination options", %{conn: conn} do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block_numbers = Range.new(range.from, range.to) @@ -2147,7 +2147,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do |> with_block(block2, gas_used: 2) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(4)) |> Wei.from(:wei) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs index cf4027f54d..bda89fe49a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs @@ -41,7 +41,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do end test "with a valid block", %{conn: conn} do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) :transaction @@ -49,7 +49,7 @@ defmodule BlockScoutWeb.API.RPC.BlockControllerTest do |> with_block(block, gas_used: 1) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(1)) |> Decimal.to_string(:normal) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiary.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiary.ex index 3abe09a58f..daf41053cd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiary.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiary.ex @@ -3,6 +3,15 @@ defmodule EthereumJSONRPC.FetchedBeneficiary do A single balance request params for the beneficiary of a block. """ - @type params :: %{address_hash: EthereumJSONRPC.hash(), block_number: non_neg_integer()} + alias Explorer.Chain.Block.Reward.AddressType + alias Explorer.Chain.Hash + + @type params :: %{ + address_hash: EthereumJSONRPC.hash(), + address_type: AddressType.t(), + block_hash: Hash.Full.t(), + block_number: non_neg_integer(), + reward: String.t() + } @type error :: %{code: integer(), message: String.t(), data: %{block_number: non_neg_integer()}} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex index e207d29fd4..6ee59474c8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex @@ -6,7 +6,64 @@ defmodule EthereumJSONRPC.Parity.FetchedBeneficiaries do import EthereumJSONRPC, only: [quantity_to_integer: 1] @doc """ - Converts `responses` to `t/0`. + Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`. + + responses - List with trace_block responses + id_to_params - Maps request id to query params + + ## Examples + iex> EthereumJSONRPC.Parity.FetchedBeneficiaries.from_responses( + ...> [ + ...> %{ + ...> id: 0, + ...> result: [ + ...> %{ + ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"}, + ...> "blockHash" => "0xFFF", + ...> "blockNumber" => 12, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => nil, + ...> "transactionPosition" => nil, + ...> "type" => "reward" + ...> }, + ...> %{ + ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"}, + ...> "blockHash" => "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca", + ...> "blockHash" => "0xFFF", + ...> "blockNumber" => 12, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => nil, + ...> "transactionPosition" => nil, + ...> "type" => "reward" + ...> } + ...> ] + ...> } + ...> ], + ...> %{0 => %{block_quantity: "0xC"}} + ...> ) + %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [], + params_set: #MapSet<[ + %{ + address_hash: "0x1", + address_type: :validator, + block_hash: "0xFFF", + block_number: 12, + reward: "0x0" + }, + %{ + address_hash: "0x2", + address_type: :emission_funds, + block_hash: "0xFFF", + block_number: 12, + reward: "0x0" + } + ]> + } """ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do responses @@ -69,26 +126,52 @@ defmodule EthereumJSONRPC.Parity.FetchedBeneficiaries do end defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do - Enum.reduce(traces, MapSet.new(), fn trace, acc -> - MapSet.union(acc, trace_to_params_set(trace, block_number)) + traces + |> Stream.filter(&(&1["type"] == "reward")) + |> Stream.with_index() + |> Enum.reduce(MapSet.new(), fn {trace, index}, acc -> + MapSet.union(acc, trace_to_params_set(trace, block_number, index)) end) end - defp trace_to_params_set(%{"action" => %{"callType" => _}, "blockNumber" => block_number}, block_number), - do: MapSet.new() - - defp trace_to_params_set(%{"type" => type, "blockNumber" => block_number}, block_number) - when type in ~w(create suicide), - do: MapSet.new() - defp trace_to_params_set( %{ - "action" => %{"rewardType" => reward_type, "author" => address_hash_data}, + "action" => %{ + "rewardType" => reward_type, + "author" => address_hash_data, + "value" => reward_value + }, + "blockHash" => block_hash, "blockNumber" => block_number }, - block_number + block_number, + index ) when is_integer(block_number) and reward_type in ~w(block external uncle) do - MapSet.new([%{address_hash: address_hash_data, block_number: block_number}]) + MapSet.new([ + %{ + address_hash: address_hash_data, + block_hash: block_hash, + block_number: block_number, + reward: reward_value, + address_type: get_address_type(reward_type, index) + } + ]) end + + # Beneficiary's address type will depend on the responses' action.rewardType, + # which will vary depending on which network is being indexed + # + # On POA networks, rewardType will always be external and the type of the address being + # rewarded will depend on its position. + # First address will always be the validator's while the second will be the EmissionsFunds address + # + # On PoW networks, like Ethereum, the reward type will already specify the type for the + # address being rewarded + # The rewardType "block" will show the reward for the consensus block validator + # The rewardType "uncle" will show reward for validating an uncle block + defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator + defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds + defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator + defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index d91458c4ab..16b92a121f 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -87,7 +87,9 @@ defmodule EthereumJsonrpc.MixProject do # `:verify_fun` for `Socket.Web.connect` {:ssl_verify_fun, "~> 1.1"}, # `EthereumJSONRPC.WebSocket` - {:websocket_client, "~> 1.3"} + {:websocket_client, "~> 1.3"}, + {:decimal, "~> 1.0"}, + {:decorator, "~> 1.2"} ] end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs new file mode 100644 index 0000000000..769432b7f4 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs @@ -0,0 +1,247 @@ +defmodule EthereumJSONRPC.Parity.FetchedBeneficiariesTest do + use ExUnit.Case, async: true + + alias EthereumJSONRPC + alias EthereumJSONRPC.Parity.FetchedBeneficiaries + + describe "from_responses/2" do + test "when block is not found" do + block_quantity = EthereumJSONRPC.integer_to_quantity(1_000) + responses = [%{id: 0, result: nil}] + id_to_params = %{0 => %{block_quantity: block_quantity}} + + expected_output = %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [%{code: 404, data: %{block_quantity: block_quantity}, message: "Not Found"}], + params_set: MapSet.new([]) + } + + assert FetchedBeneficiaries.from_responses(responses, id_to_params) == expected_output + end + + test "with an error result" do + block_number = 1_000 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + error_code = -32603 + error_message = "Internal error occurred: {}, this should not be the case with eth_call, most likely a bug." + + responses = [%{id: 0, error: %{code: error_code, message: error_message}}] + + id_to_params = %{0 => %{block_quantity: block_quantity}} + + expected_output = %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [%{code: error_code, data: %{block_quantity: block_quantity}, message: error_message}], + params_set: MapSet.new() + } + + assert FetchedBeneficiaries.from_responses(responses, id_to_params) == expected_output + end + + test "when reward type is external" do + block_hash = "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca" + block_number = 1_000 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + hash1 = "0xef481b4e2c3ed62265617f2e9dfcdf3cf3efc11a" + hash2 = "0x523b6539ff08d72a6c8bb598af95bf50c1ea839c" + reward = "0xde0b6b3a7640000" + + responses = [ + %{ + id: 0, + result: [ + %{ + "action" => %{"author" => hash1, "rewardType" => "external", "value" => reward}, + "blockHash" => block_hash, + "blockNumber" => block_number, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => nil, + "transactionPosition" => nil, + "type" => "reward" + }, + %{ + "action" => %{"author" => hash2, "rewardType" => "external", "value" => reward}, + "blockHash" => "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca", + "blockNumber" => block_number, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => nil, + "transactionPosition" => nil, + "type" => "reward" + } + ] + } + ] + + id_to_params = %{0 => %{block_quantity: block_quantity}} + + expected_output = %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [], + params_set: + MapSet.new([ + %{ + address_hash: hash1, + address_type: :validator, + block_hash: block_hash, + block_number: block_number, + reward: reward + }, + %{ + address_hash: hash2, + address_type: :emission_funds, + block_hash: block_hash, + block_number: block_number, + reward: reward + } + ]) + } + + assert FetchedBeneficiaries.from_responses(responses, id_to_params) == expected_output + end + + test "when reward type is block with uncles" do + block_hash = "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca" + block_number = 1_000 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + hash1 = "0xef481b4e2c3ed62265617f2e9dfcdf3cf3efc11a" + hash2 = "0x523b6539ff08d72a6c8bb598af95bf50c1ea839c" + reward = "0xde0b6b3a7640000" + + responses = [ + %{ + id: 0, + result: [ + %{ + "action" => %{"author" => hash1, "rewardType" => "block", "value" => reward}, + "blockHash" => block_hash, + "blockNumber" => block_number, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => nil, + "transactionPosition" => nil, + "type" => "reward" + }, + %{ + "action" => %{"author" => hash2, "rewardType" => "uncle", "value" => reward}, + "blockHash" => block_hash, + "blockNumber" => block_number, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => nil, + "transactionPosition" => nil, + "type" => "reward" + } + ] + } + ] + + id_to_params = %{0 => %{block_quantity: block_quantity}} + + expected_output = %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [], + params_set: + MapSet.new([ + %{ + address_hash: hash1, + address_type: :validator, + block_hash: block_hash, + block_number: block_number, + reward: reward + }, + %{ + address_hash: hash2, + address_type: :uncle, + block_hash: block_hash, + block_number: block_number, + reward: reward + } + ]) + } + + assert FetchedBeneficiaries.from_responses(responses, id_to_params) == expected_output + end + + test "ignores non-reward responses" do + block_hash = "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca" + block_number = 1_000 + block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) + hash1 = "0xef481b4e2c3ed62265617f2e9dfcdf3cf3efc11a" + hash2 = "0x523b6539ff08d72a6c8bb598af95bf50c1ea839c" + reward = "0xde0b6b3a7640000" + + responses = [ + %{ + id: 0, + result: [ + %{ + "action" => %{ + "callType" => "call", + "from" => hash1, + "gas" => "0x0", + "input" => "0x", + "to" => hash2, + "value" => "0x4a817c800" + }, + "blockHash" => block_hash, + "blockNumber" => block_number, + "result" => %{"gasUsed" => "0x0", "output" => "0x"}, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => "0x5acf90f846b8216bdbc309cf4eb24adc69d730bf29304dc0e740cf6df850666e", + "transactionPosition" => 0, + "type" => "call" + }, + %{ + "action" => %{"author" => hash1, "rewardType" => "block", "value" => reward}, + "blockHash" => block_hash, + "blockNumber" => block_number, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => nil, + "transactionPosition" => nil, + "type" => "reward" + } + ] + } + ] + + id_to_params = %{0 => %{block_quantity: block_quantity}} + + expected_output = %EthereumJSONRPC.FetchedBeneficiaries{ + errors: [], + params_set: + MapSet.new([ + %{ + address_hash: hash1, + address_type: :validator, + block_hash: block_hash, + block_number: block_number, + reward: reward + } + ]) + } + + assert FetchedBeneficiaries.from_responses(responses, id_to_params) == expected_output + end + end + + describe "requests/1" do + test "maps multiple ids to request params map" do + input = %{ + 0 => %{block_quantity: EthereumJSONRPC.integer_to_quantity(0)}, + 1 => %{block_quantity: EthereumJSONRPC.integer_to_quantity(1)} + } + + expected_output = [ + %{id: 0, jsonrpc: "2.0", method: "trace_block", params: [EthereumJSONRPC.integer_to_quantity(0)]}, + %{id: 1, jsonrpc: "2.0", method: "trace_block", params: [EthereumJSONRPC.integer_to_quantity(1)]} + ] + + assert FetchedBeneficiaries.requests(input) == expected_output + end + end +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs index 756d8e406e..fe52efa6a6 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs @@ -241,6 +241,7 @@ defmodule EthereumJSONRPC.ParityTest do test "with valid block range, returns {:ok, addresses}", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + block_hash = "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca" block_number = 5_080_887 block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) hash1 = "0xef481b4e2c3ed62265617f2e9dfcdf3cf3efc11a" @@ -259,7 +260,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, - "blockHash" => "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca", + "blockHash" => block_hash, "blockNumber" => block_number, "result" => nil, "subtraces" => 0, @@ -293,13 +294,28 @@ defmodule EthereumJSONRPC.ParityTest do EthereumJSONRPC.Parity.fetch_beneficiaries(5_080_887..5_080_887, json_rpc_named_arguments) assert Enum.count(params_set) == 2 - assert %{block_number: block_number, address_hash: hash2} in params_set - assert %{block_number: block_number, address_hash: hash1} in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash2, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash1, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set end test "with 'external' 'rewardType'", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + block_hash = "0xf19a4ea2bb4f2d8839f4c3ec11e0e86c29d57799d7073713958fe1990e197cf5" block_number = 5_609_295 block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) hash1 = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" @@ -318,7 +334,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "external", "value" => "0xde0b6b3a7640000" }, - "blockHash" => "0xf19a4ea2bb4f2d8839f4c3ec11e0e86c29d57799d7073713958fe1990e197cf5", + "blockHash" => block_hash, "blockNumber" => 5_609_295, "result" => nil, "subtraces" => 0, @@ -352,8 +368,22 @@ defmodule EthereumJSONRPC.ParityTest do EthereumJSONRPC.Parity.fetch_beneficiaries(5_609_295..5_609_295, json_rpc_named_arguments) assert Enum.count(params_set) == 2 - assert %{block_number: block_number, address_hash: hash1} in params_set - assert %{block_number: block_number, address_hash: hash2} in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash1, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash2, + address_type: :emission_funds, + reward: "0xde0b6b3a7640000" + } in params_set end test "with no rewards, returns {:ok, []}", %{ @@ -375,6 +405,7 @@ defmodule EthereumJSONRPC.ParityTest do test "ignores non-reward traces", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + block_hash = "0x6659a4926d833a7eab74379fa647ec74c9f5e65f8029552a35264126560f300a" block_number = 5_077_429 block_quantity = EthereumJSONRPC.integer_to_quantity(block_number) hash1 = "0xcfa53498686e00d3b4b41f3bea61604038eebb58" @@ -396,7 +427,7 @@ defmodule EthereumJSONRPC.ParityTest do "to" => "0xe797a1da01eb0f951e0e400f9343de9d17a06bac", "value" => "0x4a817c800" }, - "blockHash" => "0x6659a4926d833a7eab74379fa647ec74c9f5e65f8029552a35264126560f300a", + "blockHash" => block_hash, "blockNumber" => block_number, "result" => %{"gasUsed" => "0x0", "output" => "0x"}, "subtraces" => 0, @@ -411,7 +442,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, - "blockHash" => "0x6659a4926d833a7eab74379fa647ec74c9f5e65f8029552a35264126560f300a", + "blockHash" => block_hash, "blockNumber" => block_number, "result" => nil, "subtraces" => 0, @@ -426,7 +457,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, - "blockHash" => "0x6659a4926d833a7eab74379fa647ec74c9f5e65f8029552a35264126560f300a", + "blockHash" => block_hash, "blockNumber" => block_number, "result" => nil, "subtraces" => 0, @@ -445,17 +476,35 @@ defmodule EthereumJSONRPC.ParityTest do EthereumJSONRPC.Parity.fetch_beneficiaries(5_077_429..5_077_429, json_rpc_named_arguments) assert Enum.count(params_set) == 2 - assert %{block_number: block_number, address_hash: hash2} in params_set - assert %{block_number: block_number, address_hash: hash1} in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash2, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number, + block_hash: block_hash, + address_hash: hash1, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set end test "with multiple blocks with repeat beneficiaries", %{ json_rpc_named_arguments: json_rpc_named_arguments } do + block_hash1 = "0xd2170e27857452d130128ac94c5258828a22cc69b07ab6e7fc12f7dd9938ff1c" block_number1 = 5_080_886 block_quantity1 = EthereumJSONRPC.integer_to_quantity(block_number1) + + block_hash2 = "0x52a8d2185282506ce681364d2aa0c085ba45fdeb5d6c0ddec1131617a71ee2ca" block_number2 = 5_080_887 block_quantity2 = EthereumJSONRPC.integer_to_quantity(block_number2) + hash1 = "0xadc702c4bb09fbc502dd951856b9c7a1528a88de" hash2 = "0xef481b4e2c3ed62265617f2e9dfcdf3cf3efc11a" hash3 = "0x523b6539ff08d72a6c8bb598af95bf50c1ea839c" @@ -474,6 +523,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, + "blockHash" => block_hash1, "blockNumber" => block_number1, "result" => nil, "subtraces" => 0, @@ -488,6 +538,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, + "blockHash" => block_hash1, "blockNumber" => block_number1, "result" => nil, "subtraces" => 0, @@ -509,6 +560,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, + "blockHash" => block_hash2, "blockNumber" => block_number2, "result" => nil, "subtraces" => 0, @@ -523,6 +575,7 @@ defmodule EthereumJSONRPC.ParityTest do "rewardType" => "block", "value" => "0xde0b6b3a7640000" }, + "blockHash" => block_hash2, "blockNumber" => block_number2, "result" => nil, "subtraces" => 0, @@ -540,13 +593,44 @@ defmodule EthereumJSONRPC.ParityTest do end assert {:ok, %FetchedBeneficiaries{params_set: params_set}} = - EthereumJSONRPC.Parity.fetch_beneficiaries(5_080_886..5_080_887, json_rpc_named_arguments) + EthereumJSONRPC.Parity.fetch_beneficiaries( + block_number1..block_number2, + json_rpc_named_arguments + ) assert Enum.count(params_set) == 4 - assert %{block_number: block_number1, address_hash: hash3} in params_set - assert %{block_number: block_number2, address_hash: hash3} in params_set - assert %{block_number: block_number2, address_hash: hash2} in params_set - assert %{block_number: block_number1, address_hash: hash1} in params_set + + assert %{ + block_number: block_number1, + block_hash: block_hash1, + address_hash: hash1, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number1, + block_hash: block_hash1, + address_hash: hash3, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number2, + block_hash: block_hash2, + address_hash: hash2, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set + + assert %{ + block_number: block_number2, + block_hash: block_hash2, + address_hash: hash3, + address_type: :validator, + reward: "0xde0b6b3a7640000" + } in params_set end test "with error, returns {:error, reason}", %{ diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 19374d9f5d..5b5aab9867 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -38,7 +38,7 @@ defmodule Explorer.Chain do Wei } - alias Explorer.Chain.Block.Reward + alias Explorer.Chain.Block.EmissionReward alias Explorer.{PagingOptions, Repo} alias Explorer.Counters.{ @@ -366,15 +366,15 @@ defmodule Explorer.Chain do from( block in Block, left_join: transaction in assoc(block, :transactions), - inner_join: block_reward in Reward, - on: fragment("? <@ ?", block.number, block_reward.block_range), + inner_join: emission_reward in EmissionReward, + on: fragment("? <@ ?", block.number, emission_reward.block_range), where: block.number == ^block_number, - group_by: block_reward.reward, + group_by: emission_reward.reward, select: %{ transaction_reward: %Wei{ value: default_if_empty(sum_of_products(transaction.gas_used, transaction.gas_price), 0) }, - static_reward: block_reward.reward + static_reward: emission_reward.reward } ) diff --git a/apps/explorer/lib/explorer/chain/block/emission_reward.ex b/apps/explorer/lib/explorer/chain/block/emission_reward.ex new file mode 100644 index 0000000000..82ab015ac9 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/block/emission_reward.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Chain.Block.EmissionReward do + @moduledoc """ + Represents the static reward given to the miner of a block in a range of block numbers. + """ + + use Ecto.Schema + + alias Explorer.Chain.Block.{EmissionReward, Range} + alias Explorer.Chain.Wei + + @typedoc """ + The static reward given to the miner of a block. + + * `:block_range` - Range of block numbers + * `:reward` - Reward given in Wei + """ + @type t :: %EmissionReward{ + block_range: Range.t(), + reward: Wei.t() + } + + @primary_key false + schema "emission_rewards" do + field(:block_range, Range) + field(:reward, Wei) + end +end diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index 0ec65dba80..06aa0d2b53 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -1,27 +1,43 @@ defmodule Explorer.Chain.Block.Reward do @moduledoc """ - Represents the static reward given to the miner of a block in a range of block numbers. + Represents the total reward given to an address in a block. """ - use Ecto.Schema + use Explorer.Schema - alias Explorer.Chain.Block.{Range, Reward} - alias Explorer.Chain.Wei + alias Explorer.Chain.Block.Reward.AddressType + alias Explorer.Chain.{Hash, Wei} + + @required_attrs ~w(address_hash address_type block_hash reward)a @typedoc """ - The static reward given to the miner of a block. + The validation reward given related to a block. - * `:block_range` - Range of block numbers - * `:reward` - Reward given in Wei + * `:address_hash` - Hash of address who received the reward + * `:address_type` - Type of the address_hash, either emission_funds, uncle or validator + * `:block_hash` - Hash of the validated block + * `:reward` - Total block reward """ - @type t :: %Reward{ - block_range: Range.t(), + @type t :: %__MODULE__{ + address_hash: Hash.Address.t(), + address_type: AddressType.t(), + block_hash: Hash.Full.t(), reward: Wei.t() } @primary_key false schema "block_rewards" do - field(:block_range, Range) + field(:address_hash, Hash.Address) + field(:address_type, AddressType) + field(:block_hash, Hash.Full) field(:reward, Wei) + + timestamps() + end + + def changeset(%__MODULE__{} = reward, attrs) do + reward + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) end end diff --git a/apps/explorer/lib/explorer/chain/block/reward/address_type.ex b/apps/explorer/lib/explorer/chain/block/reward/address_type.ex new file mode 100644 index 0000000000..aac552a16e --- /dev/null +++ b/apps/explorer/lib/explorer/chain/block/reward/address_type.ex @@ -0,0 +1,103 @@ +defmodule Explorer.Chain.Block.Reward.AddressType do + @moduledoc """ + Block reward address types + """ + + @behaviour Ecto.Type + + @typedoc """ + * `:emission_funds` + * `:uncle` + * `:validator` + """ + @type t :: :emission_funds | :uncle | :validator + + @doc """ + Casts `term` to `t:t/0` + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.Block.Reward.AddressType.cast(:emission_funds) + {:ok, :emission_funds} + iex> Explorer.Chain.Block.Reward.AddressType.cast(:uncle) + {:ok, :uncle} + iex> Explorer.Chain.Block.Reward.AddressType.cast(:validator) + {:ok, :validator} + + If `term` is a `String.t`, then it is converted to the corresponding `t:t/0`. + + iex> Explorer.Chain.Block.Reward.AddressType.cast("emission_funds") + {:ok, :emission_funds} + iex> Explorer.Chain.Block.Reward.AddressType.cast("uncle") + {:ok, :uncle} + iex> Explorer.Chain.Block.Reward.AddressType.cast("validator") + {:ok, :validator} + + Unsupported `String.t` return an `:error`. + + iex> Explorer.Chain.Block.Reward.AddressType.cast("hard-fork") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(t) when t in ~w(emission_funds uncle validator)a, do: {:ok, t} + def cast("emission_funds"), do: {:ok, :emission_funds} + def cast("uncle"), do: {:ok, :uncle} + def cast("validator"), do: {:ok, :validator} + def cast(_), do: :error + + @doc """ + Dumps the `atom` format to `String.t` format used in the database. + + iex> Explorer.Chain.Block.Reward.AddressType.dump(:emission_funds) + {:ok, "emission_funds"} + iex> Explorer.Chain.Block.Reward.AddressType.dump(:uncle) + {:ok, "uncle"} + iex> Explorer.Chain.Block.Reward.AddressType.dump(:validator) + {:ok, "validator"} + + + Other atoms return an error + + iex> Explorer.Chain.Block.Reward.AddressType.dump(:other) + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, String.t()} | :error + def dump(:emission_funds), do: {:ok, "emission_funds"} + def dump(:uncle), do: {:ok, "uncle"} + def dump(:validator), do: {:ok, "validator"} + def dump(_), do: :error + + @doc """ + Loads the `t:String.t/0` from the database. + + iex> Explorer.Chain.Block.Reward.AddressType.load("emission_funds") + {:ok, :emission_funds} + iex> Explorer.Chain.Block.Reward.AddressType.load("uncle") + {:ok, :uncle} + iex> Explorer.Chain.Block.Reward.AddressType.load("validator") + {:ok, :validator} + + Other `t:String.t/0` return `:error` + + iex> Explorer.Chain.Block.Reward.AddressType.load("other") + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load("emission_funds"), do: {:ok, :emission_funds} + def load("uncle"), do: {:ok, :uncle} + def load("validator"), do: {:ok, :validator} + def load(_), do: :error + + @doc """ + The underlying database type: `:string` + """ + @impl Ecto.Type + @spec type() :: :string + def type, do: :string +end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 28d62ee454..308b3f3797 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -12,6 +12,7 @@ defmodule Explorer.Chain.Import do Import.Addresses, Import.Address.CoinBalances, Import.Blocks, + Import.Block.Rewards, Import.Block.SecondDegreeRelations, Import.Transactions, Import.Transaction.Forks, diff --git a/apps/explorer/lib/explorer/chain/import/block_rewards.ex b/apps/explorer/lib/explorer/chain/import/block_rewards.ex new file mode 100644 index 0000000000..7eb4c2ddca --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/block_rewards.ex @@ -0,0 +1,78 @@ +defmodule Explorer.Chain.Import.Block.Rewards do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Block.Reward.t/0`. + """ + + import Ecto.Query, only: [from: 2] + + alias Ecto.{Changeset, Multi} + alias Explorer.Chain.Block.Reward + alias Explorer.Chain.Import + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [Reward.t()] + + @impl Import.Runner + def ecto_schema_module, do: Reward + + @impl Import.Runner + def option_key, do: :block_rewards + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, option_key(), fn _ -> insert(changes_list, insert_options) end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert([map()], %{ + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: {:ok, [Reward.t()]} | {:error, [Changeset.t()]} + defp insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + when is_list(changes_list) do + Import.insert_changes_list( + changes_list, + conflict_target: [:address_hash, :address_type, :block_hash], + on_conflict: on_conflict(), + for: ecto_schema_module(), + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end + + defp on_conflict do + from( + block_reward in Reward, + update: [ + set: [ + address_hash: block_reward.address_hash, + address_type: block_reward.address_type, + block_hash: block_reward.block_hash, + reward: block_reward.reward, + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", block_reward.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", block_reward.updated_at) + ] + ] + ) + end +end diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index f89dc6cad7..a4d4649d97 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -9,7 +9,7 @@ defmodule Explorer.Etherscan do alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.{Block, Hash, InternalTransaction, Transaction, Wei} - alias Explorer.Chain.Block.Reward + alias Explorer.Chain.Block.EmissionReward @default_options %{ order_by_direction: :asc, @@ -186,7 +186,7 @@ defmodule Explorer.Etherscan do from( b in Block, left_join: t in assoc(b, :transactions), - inner_join: r in Reward, + inner_join: r in EmissionReward, on: fragment("? <@ ?", b.number, r.block_range), where: b.miner_hash == ^address_hash, order_by: [desc: b.number], diff --git a/apps/explorer/priv/repo/migrations/20181206200140_rename_block_rewards_to_emission_rewards.exs b/apps/explorer/priv/repo/migrations/20181206200140_rename_block_rewards_to_emission_rewards.exs new file mode 100644 index 0000000000..6ec5a8c466 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181206200140_rename_block_rewards_to_emission_rewards.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.RenameBlockRewardsToEmissionRewards do + use Ecto.Migration + + def change do + rename(table(:block_rewards), to: table(:emission_rewards)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20181206200312_create_new_block_rewards.exs b/apps/explorer/priv/repo/migrations/20181206200312_create_new_block_rewards.exs new file mode 100644 index 0000000000..253b75c528 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181206200312_create_new_block_rewards.exs @@ -0,0 +1,16 @@ +defmodule Explorer.Repo.Migrations.CreateNewBlockRewards do + use Ecto.Migration + + def change do + create table(:block_rewards, primary_key: false) do + add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false) + add(:address_type, :string, null: false) + add(:block_hash, references(:blocks, column: :hash, on_delete: :delete_all, type: :bytea), null: false) + add(:reward, :numeric, precision: 100, null: true) + + timestamps(null: false, type: :utc_datetime) + end + + create(unique_index(:block_rewards, [:address_hash, :address_type, :block_hash])) + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 62c4052aec..c3cfcb2e42 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2159,15 +2159,15 @@ defmodule Explorer.ChainTest do describe "block_reward/1" do setup do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) insert(:transaction) - {:ok, block: block, block_reward: block_reward} + {:ok, block: block, emission_reward: emission_reward} end - test "with block containing transactions", %{block: block, block_reward: block_reward} do + test "with block containing transactions", %{block: block, emission_reward: emission_reward} do :transaction |> insert(gas_price: 1) |> with_block(block, gas_used: 1) @@ -2177,7 +2177,7 @@ defmodule Explorer.ChainTest do |> with_block(block, gas_used: 2) expected = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(3)) |> Wei.from(:wei) @@ -2185,8 +2185,8 @@ defmodule Explorer.ChainTest do assert expected == Chain.block_reward(block) end - test "with block without transactions", %{block: block, block_reward: block_reward} do - assert block_reward.reward == Chain.block_reward(block) + test "with block without transactions", %{block: block, emission_reward: emission_reward} do + assert emission_reward.reward == Chain.block_reward(block) end end diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index c100edabe3..cb6ff93005 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -1168,7 +1168,7 @@ defmodule Explorer.EtherscanTest do describe "list_blocks/1" do test "it returns all required fields" do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) @@ -1180,7 +1180,7 @@ defmodule Explorer.EtherscanTest do |> with_block(block, gas_used: 1) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(1)) |> Wei.from(:wei) @@ -1197,7 +1197,7 @@ defmodule Explorer.EtherscanTest do end test "with block containing multiple transactions" do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) @@ -1213,7 +1213,7 @@ defmodule Explorer.EtherscanTest do |> with_block(block, gas_used: 2) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(3)) |> Wei.from(:wei) @@ -1230,7 +1230,7 @@ defmodule Explorer.EtherscanTest do end test "with block without transactions" do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) @@ -1241,7 +1241,7 @@ defmodule Explorer.EtherscanTest do %{ number: block.number, timestamp: block.timestamp, - reward: block_reward.reward + reward: emission_reward.reward } ] @@ -1249,7 +1249,7 @@ defmodule Explorer.EtherscanTest do end test "with multiple blocks" do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block_numbers = Range.new(range.from, range.to) @@ -1280,13 +1280,13 @@ defmodule Explorer.EtherscanTest do |> with_block(block2, gas_used: 3) expected_reward_block1 = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(8)) |> Wei.from(:wei) expected_reward_block2 = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(18)) |> Wei.from(:wei) @@ -1308,7 +1308,7 @@ defmodule Explorer.EtherscanTest do end test "with pagination options" do - %{block_range: range} = block_reward = insert(:block_reward) + %{block_range: range} = emission_reward = insert(:emission_reward) block_numbers = Range.new(range.from, range.to) @@ -1324,7 +1324,7 @@ defmodule Explorer.EtherscanTest do |> with_block(block1, gas_used: 2) expected_reward = - block_reward.reward + emission_reward.reward |> Wei.to(:wei) |> Decimal.add(Decimal.new(4)) |> Wei.from(:wei) @@ -1333,7 +1333,7 @@ defmodule Explorer.EtherscanTest do %{ number: block2.number, timestamp: block2.timestamp, - reward: block_reward.reward + reward: emission_reward.reward } ] diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index e1aac30fd8..5781ca16c5 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -9,7 +9,7 @@ defmodule Explorer.Factory do alias Comeonin.Bcrypt alias Explorer.Accounts.{User, UserContact} alias Explorer.Admin.Administrator - alias Explorer.Chain.Block.{Range, Reward} + alias Explorer.Chain.Block.{EmissionReward, Range} alias Explorer.Chain.{ Address, @@ -404,7 +404,7 @@ defmodule Explorer.Factory do } end - def block_reward_factory do + def emission_reward_factory do # Generate ranges like 1 - 10,000; 10,001 - 20,000, 20,001 - 30,000; etc x = sequence("block_range", & &1) lower = x * Kernel.+(10_000, 1) @@ -419,7 +419,7 @@ defmodule Explorer.Factory do reward = Decimal.mult(reward_multiplier, wei_per_ether) - %Reward{ + %EmissionReward{ block_range: %Range{from: lower, to: upper}, reward: reward } diff --git a/apps/indexer/lib/indexer/address/coin_balances.ex b/apps/indexer/lib/indexer/address/coin_balances.ex index 129110fab3..03f0c4f847 100644 --- a/apps/indexer/lib/indexer/address/coin_balances.ex +++ b/apps/indexer/lib/indexer/address/coin_balances.ex @@ -7,6 +7,13 @@ defmodule Indexer.Address.CoinBalances do Enum.reduce(import_options, MapSet.new(), &reducer/2) end + defp reducer({:beneficiary_params, beneficiary_params}, acc) when is_list(beneficiary_params) do + Enum.into(beneficiary_params, acc, fn %{address_hash: address_hash, block_number: block_number} + when is_binary(address_hash) and is_integer(block_number) -> + %{address_hash: address_hash, block_number: block_number} + end) + end + defp reducer({:blocks_params, blocks_params}, acc) when is_list(blocks_params) do # a block MUST have a miner_hash and number Enum.into(blocks_params, acc, fn %{miner_hash: address_hash, number: block_number} diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 1d48135f6e..76acbbae52 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -32,6 +32,7 @@ defmodule Indexer.Block.Fetcher do address_token_balances: Import.Runner.options(), blocks: Import.Runner.options(), block_second_degree_relations: Import.Runner.options(), + block_rewards: Import.Runner.options(), broadcast: term(), logs: Import.Runner.options(), token_transfers: Import.Runner.options(), @@ -122,12 +123,13 @@ defmodule Indexer.Block.Fetcher do }), coin_balances_params_set = %{ + beneficiary_params: MapSet.to_list(beneficiary_params_set), blocks_params: blocks, logs_params: logs, transactions_params: transactions_with_receipts } - |> CoinBalances.params_set() - |> MapSet.union(beneficiary_params_set), + |> CoinBalances.params_set(), + block_rewards <- fetch_block_rewards(beneficiary_params_set, transactions_with_receipts), address_token_balances = TokenBalances.params_set(%{token_transfers_params: token_transfers}), {:ok, inserted} <- __MODULE__.import( @@ -138,6 +140,7 @@ defmodule Indexer.Block.Fetcher do address_token_balances: %{params: address_token_balances}, blocks: %{params: blocks}, block_second_degree_relations: %{params: block_second_degree_relations_params}, + block_rewards: %{params: block_rewards}, logs: %{params: logs}, token_transfers: %{params: token_transfers}, tokens: %{on_conflict: :nothing, params: tokens}, @@ -214,6 +217,29 @@ defmodule Indexer.Block.Fetcher do {:beneficiaries, result} end + defp fetch_block_rewards(beneficiaries, transactions) do + Enum.map(beneficiaries, fn beneficiary -> + case beneficiary.address_type do + :validator -> + validation_reward = fetch_validation_reward(beneficiary, transactions) + + "0x" <> reward_hex = beneficiary.reward + {reward, _} = Integer.parse(reward_hex, 16) + + %{beneficiary | reward: reward + validation_reward} + + _ -> + beneficiary + end + end) + end + + defp fetch_validation_reward(beneficiary, transactions) do + transactions + |> Stream.filter(fn t -> t.block_number == beneficiary.block_number end) + |> Enum.reduce(0, fn t, acc -> acc + t.gas_used * t.gas_price end) + end + # `fetched_balance_block_number` is needed for the `CoinBalanceFetcher`, but should not be used for `import` because the # balance is not known yet. defp pop_address_hash_to_fetched_balance_block_number(options) do @@ -221,6 +247,7 @@ defmodule Indexer.Block.Fetcher do get_and_update_in(options, [:addresses, :params, Access.all()], &pop_hash_fetched_balance_block_number/1) address_hash_to_fetched_balance_block_number = Map.new(address_hash_fetched_balance_block_number_pairs) + {address_hash_to_fetched_balance_block_number, import_options} end