Merge pull request #1184 from poanetwork/wsa-save-emission-reward-data-from-blockchain-call

Save reward data from blockchain call
sa-turn-off-invalidconsensus-checker
William Sanches 6 years ago committed by GitHub
commit 48fe3a450c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  2. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/block_controller_test.exs
  3. 11
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/fetched_beneficiary.ex
  4. 109
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex
  5. 4
      apps/ethereum_jsonrpc/mix.exs
  6. 247
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/fetched_beneficiaries_test.exs
  7. 116
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs
  8. 10
      apps/explorer/lib/explorer/chain.ex
  9. 27
      apps/explorer/lib/explorer/chain/block/emission_reward.ex
  10. 36
      apps/explorer/lib/explorer/chain/block/reward.ex
  11. 103
      apps/explorer/lib/explorer/chain/block/reward/address_type.ex
  12. 1
      apps/explorer/lib/explorer/chain/import.ex
  13. 78
      apps/explorer/lib/explorer/chain/import/block_rewards.ex
  14. 4
      apps/explorer/lib/explorer/etherscan.ex
  15. 7
      apps/explorer/priv/repo/migrations/20181206200140_rename_block_rewards_to_emission_rewards.exs
  16. 16
      apps/explorer/priv/repo/migrations/20181206200312_create_new_block_rewards.exs
  17. 12
      apps/explorer/test/explorer/chain_test.exs
  18. 24
      apps/explorer/test/explorer/etherscan_test.exs
  19. 6
      apps/explorer/test/support/factory.ex
  20. 7
      apps/indexer/lib/indexer/address/coin_balances.ex
  21. 31
      apps/indexer/lib/indexer/block/fetcher.ex

@ -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)

@ -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)

@ -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

@ -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

@ -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

@ -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

@ -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}", %{

@ -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
}
)

@ -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

@ -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

@ -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

@ -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,

@ -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

@ -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],

@ -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

@ -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

@ -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

@ -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
}
]

@ -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
}

@ -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}

@ -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

Loading…
Cancel
Save