Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: imports hardforks preset accounts #805

Merged
merged 4 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2913,10 +2913,12 @@ Besides specifying of scope and direction as with other streaming endpoints (via
- `reward_oracle` (reward for the operator of the oracle (on transaction basis))
- `reward_block` (reward for the miner (on block basis))
- `reward_dev` (reward for funding of the development (on block basis))
- `accounts_minerva`, `accounts_fortuna` and `accounts_lima` (added on hardforks including migrated ERC20 amounts)

It it possible to provide just a prefix of the kind in interest, e.g.: "reward" will return all rewards, "fee" will return all fees.
It it possible to provide just a prefix of the kind in interest, e.g.: "reward" will return all rewards, "fee" will return all fees,
"accounts" will return credits after hardforks.

- `account` - account which received rewards or was charged fees
- `account` - account which received rewards or that was charged fees or that received tokens after a hardfork migration.

Listing internal transfers in range

Expand Down
5 changes: 5 additions & 0 deletions lib/ae_mdw/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,11 @@ defmodule AeMdw.Application do
:ok
end

def start_phase(:hardforks_presets, _start_type, []) do
AeMdw.Db.HardforkPresets.import_account_presets()
:ok
end

def start_phase(:start_sync, _start_type, []) do
AeMdw.Db.AsyncStore.init()
AeMdw.Db.Aex9BalancesCache.init()
Expand Down
54 changes: 54 additions & 0 deletions lib/ae_mdw/db/hardfork_presets.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
defmodule AeMdw.Db.HardforkPresets do
@moduledoc """
Saves hardfork presets into Mdw database.
"""
alias AeMdw.Database
alias AeMdw.Db.IntTransfersMutation
alias AeMdw.Db.Model
alias AeMdw.Db.State

@doc """
Imports hardfork migrated accounts.
"""
@spec import_account_presets() :: :ok
def import_account_presets do
case Database.next_key(Model.KindIntTransferTx, {"accounts_lima", {-1, -1}, <<>>, -1}) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we turn this entirely into a mutation? That way we continue encapsulating Database calls through the State

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Database is used because the goal is to check if the data is already persisted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that's what it does. I was suggesting changing how it does it. It's optional anyway, feel free to merge it

Copy link
Member Author

@jyeshe jyeshe Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am talking about how not to encapsulate things to make the purpose clearer.

{:ok, {"accounts_lima", _bi, _target, _txi}} ->
:ok

_missing ->
do_import_account_presets()
end
end

defp do_import_account_presets() do
minerva_mutations = hardfork_mutations(:minerva, &:aec_fork_block_settings.minerva_accounts/0)
fortuna_mutations = hardfork_mutations(:fortuna, &:aec_fork_block_settings.fortuna_accounts/0)
lima_mutations = hardfork_mutations(:lima, &:aec_fork_block_settings.lima_accounts/0)

State.commit(State.new(), [minerva_mutations, fortuna_mutations, lima_mutations])
end

defp hardfork_mutations(hardfork, fork_settings_accounts_fn) do
transfers = hardfork_transfers("accounts_#{hardfork}", fork_settings_accounts_fn)

hardfork
|> hardfork_height()
|> IntTransfersMutation.new(transfers)
end

defp hardfork_transfers(kind, fork_settings_accounts_fn) do
fork_settings_accounts_fn.()
|> Enum.map(fn {account_pk, amount} ->
{kind, account_pk, amount}
end)
end

defp hardfork_height(hardfork) do
hf_vsn = :aec_hard_forks.protocol_vsn(hardfork)

:aec_governance.get_network_id()
|> :aec_hard_forks.protocols_from_network_id()
|> Map.get(hf_vsn)
end
end
6 changes: 3 additions & 3 deletions lib/ae_mdw/db/int_transfer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ defmodule AeMdw.Db.IntTransfer do
require Ex2ms
require Model

alias AeMdw.Db.BlockRewardsMutation
alias AeMdw.Db.IntTransfersMutation
alias AeMdw.Node.Db

# @type kind() :: "fee_lock_name" | "fee_refund_name" | "fee_spend_name" |
Expand All @@ -30,7 +30,7 @@ defmodule AeMdw.Db.IntTransfer do
@reward_dev_kind "reward_dev"

@spec block_rewards_mutation(Blocks.height(), Blocks.key_header(), Blocks.block_hash()) ::
BlockRewardsMutation.t()
IntTransfersMutation.t()
def block_rewards_mutation(height, key_header, key_hash) do
delay = :aec_governance.beneficiary_reward_delay()
dev_benefs = Enum.map(:aec_dev_reward.beneficiaries(), &elem(&1, 0))
Expand All @@ -48,7 +48,7 @@ defmodule AeMdw.Db.IntTransfer do
{kind, target_pk, amount}
end)

BlockRewardsMutation.new(height, block_rewards)
IntTransfersMutation.new(height, block_rewards)
end

@spec fee(
Expand Down
38 changes: 0 additions & 38 deletions lib/ae_mdw/db/mutations/block_rewards_mutation.ex

This file was deleted.

43 changes: 43 additions & 0 deletions lib/ae_mdw/db/mutations/int_transfers_mutation.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule AeMdw.Db.IntTransfersMutation do
@moduledoc """
Saves internal transfers rewarded or migrated into the blockchain including block and dev rewards.
"""

alias AeMdw.Blocks
alias AeMdw.Db.IntTransfer
alias AeMdw.Db.State

@derive AeMdw.Db.Mutation
defstruct [:height, :transfers]

@type transfer() :: {IntTransfer.kind(), IntTransfer.target(), IntTransfer.amount()}

@opaque t() :: %__MODULE__{
height: Blocks.height(),
transfers: [transfer()]
}

@ref_txi -1
@txi_pos -1

@spec new(Blocks.height(), [transfer()]) :: t()
def new(height, transfers) do
%__MODULE__{height: height, transfers: transfers}
end

@spec execute(t(), State.t()) :: State.t()
def execute(%__MODULE__{height: height, transfers: transfers}, state) do
gen_txi_pos = {height, @txi_pos}

Enum.reduce(transfers, state, fn {kind, target_pk, amount}, state ->
new_state = IntTransfer.write(state, gen_txi_pos, kind, target_pk, @ref_txi, amount)

if kind in ["reward_dev", "reward_block"] do
stat_kind = if kind == "reward_dev", do: :dev_reward, else: :block_reward
State.inc_stat(new_state, stat_kind, amount)
else
new_state
end
end)
end
end
4 changes: 3 additions & 1 deletion lib/ae_mdw/transfers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ defmodule AeMdw.Transfers do

@pagination_params ~w(limit cursor rev direction scope tx_hash)

@kinds ~w(fee_lock_name fee_refund_name fee_spend_name reward_block reward_dev reward_oracle)
@hardforks_accounts ~w(accounts_minerva accounts_fortuna accounts_lima)
@kinds ~w(fee_lock_name fee_refund_name fee_spend_name reward_block reward_dev reward_oracle) ++
@hardforks_accounts

@int_transfer_table Model.IntTransferTx
@target_kind_int_transfer_tx_table Model.TargetKindIntTransferTx
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ defmodule AeMdw.MixProject do
def application() do
[
mod: {AeMdw.Application, []},
start_phases: [migrate_db: [], start_sync: []],
start_phases: [migrate_db: [], hardforks_presets: [], start_sync: []],
extra_applications: [:logger, :runtime_tools]
]
end
Expand Down
32 changes: 32 additions & 0 deletions test/ae_mdw/db/hardfork_presets_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule AeMdw.Db.HardforkPresetsTest do
use ExUnit.Case, async: false

alias AeMdw.Collection
alias AeMdw.Db.HardforkPresets
alias AeMdw.Db.Model
alias AeMdw.Db.State

describe "import_account_presets" do
test "saves minerva, fortuna and lima migrated accounts" do
HardforkPresets.import_account_presets()

assert 325 ==
State.new()
|> Collection.stream(Model.KindIntTransferTx, {"accounts_minerva", nil, nil, nil})
|> Stream.take_while(&match?({"accounts_minerva", _bi, _target, _txi}, &1))
|> Enum.count()

assert 304 ==
State.new()
|> Collection.stream(Model.KindIntTransferTx, {"accounts_fortuna", nil, nil, nil})
|> Stream.take_while(&match?({"accounts_fortuna", _bi, _target, _txi}, &1))
|> Enum.count()

assert 702 ==
State.new()
|> Collection.stream(Model.KindIntTransferTx, {"accounts_lima", nil, nil, nil})
|> Stream.take_while(&match?({"accounts_lima", _bi, _target, _txi}, &1))
|> Enum.count()
end
end
end
113 changes: 113 additions & 0 deletions test/ae_mdw_web/controllers/transfer_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
defmodule AeMdwWeb.TransferControllerTest do
use AeMdwWeb.ConnCase

alias AeMdw.Db.HardforkPresets

setup_all _ do
HardforkPresets.import_account_presets()
:ok
end

describe "transfers" do
test "returns hardfork kind transfers with accounts prefix", %{
conn: conn
} do
kind_prefix = "accounts"
kinds = ~w(accounts_minerva accounts_fortuna accounts_lima)

conn = get(conn, "/v2/transfers", direction: "forward", kind: kind_prefix, limit: 100)
response = json_response(conn, 200)

assert Enum.count(response["data"]) == 100

assert Enum.all?(response["data"], fn %{"kind" => kind} ->
String.starts_with?(kind, kind_prefix) and kind in kinds
end)

conn_next = get(conn, response["next"])
response_next = json_response(conn_next, 200)

assert Enum.count(response_next["data"]) == 100

assert Enum.all?(response_next["data"], fn %{"kind" => kind} ->
String.starts_with?(kind, kind_prefix) and kind in kinds
end)
end

test "returns fortuna accounts transfers filtering by kind", %{
conn: conn
} do
kind_prefix = "accounts_fortuna"

conn = get(conn, "/v2/transfers", direction: "forward", kind: kind_prefix, limit: 100)
response = json_response(conn, 200)

assert Enum.count(response["data"]) == 100

fortuna_height =
:aec_governance.get_network_id()
|> :aec_hard_forks.protocols_from_network_id()
|> Map.get(3)

assert Enum.all?(response["data"], fn %{
"kind" => kind,
"amount" => amount,
"height" => height
} ->
kind == kind_prefix and amount > 0 and height == fortuna_height
end)

conn_next = get(conn, response["next"])
response_next = json_response(conn_next, 200)

assert Enum.count(response_next["data"]) == 100

assert Enum.all?(response_next["data"], fn %{
"account_id" => account_id,
"kind" => kind,
"amount" => amount,
"height" => height
} ->
if account_id in [
"ak_292g7TxJNGFfFXxsvFhJ5MfP6RzMrFGMJxRaY4Tx7byaBFQTg4",
"ak_2qdNcDDcELtsSPwot9ojjojuUxsucpCTcqbqZPT4nKjo1wtEEj"
] do
kind == kind_prefix and amount == 0 and height == fortuna_height
else
kind == kind_prefix and amount > 0 and height == fortuna_height
end
end)
end

test "returns a lima account transfer filtering by account", %{
conn: conn
} do
account_id = "ak_zGR3f3QQ3BDAwhuPktLanrqcb6vrbViBr8RzU5VeqGNDbEyx9"

conn = get(conn, "/v2/transfers", direction: "forward", account: account_id)
response = json_response(conn, 200)

lima_height =
:aec_governance.get_network_id()
|> :aec_hard_forks.protocols_from_network_id()
|> Map.get(4)

assert Enum.count(response["data"]) == 1

assert %{
"account_id" => ^account_id,
"kind" => "accounts_lima",
"amount" => 1_186_111_575_000_000_000_000,
"height" => ^lima_height
} = List.first(response["data"])
end

test "renders error when the range is invalid", %{conn: conn} do
range = "invalid"
error_msg = "invalid range: #{range}"
conn = get(conn, "/v2/transfers", scope: "gen:#{range}")

assert %{"error" => ^error_msg} = json_response(conn, 400)
end
end
end
Loading