Merge pull request #1184 from poanetwork/wsa-save-emission-reward-data-from-blockchain-call
Save reward data from blockchain callsa-turn-off-invalidconsensus-checker
commit
48fe3a450c
@ -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 |
@ -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 |
@ -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 |
@ -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 |
Loading…
Reference in new issue