diff --git a/lib/ae_mdw/channels.ex b/lib/ae_mdw/channels.ex new file mode 100644 index 000000000..9619dcd22 --- /dev/null +++ b/lib/ae_mdw/channels.ex @@ -0,0 +1,52 @@ +defmodule AeMdw.Channels do + @moduledoc """ + Main channels module. + """ + + alias AeMdw.Collection + alias AeMdw.Db.ChannelCloseMutation + alias AeMdw.Db.ChannelOpenMutation + alias AeMdw.Db.ChannelSpendMutation + alias AeMdw.Db.Model + alias AeMdw.Db.State + alias AeMdw.Node + alias AeMdw.Txs + + require Model + + @typep state() :: State.t() + @type closing_type() :: + :channel_close_solo_tx + | :channel_close_mutual_tx + | :channel_settle_tx + + @spec close_mutation(closing_type(), Node.aetx()) :: ChannelCloseMutation.t() + def close_mutation(tx_type, tx), do: ChannelCloseMutation.new(tx_type, tx) + + @spec open_mutation(Node.aetx()) :: ChannelOpenMutation.t() + def open_mutation(tx), do: ChannelOpenMutation.new(tx) + + @spec deposit_mutation(Node.aetx()) :: ChannelSpendMutation.t() + def deposit_mutation(tx), do: ChannelSpendMutation.new(:aesc_deposit_tx.amount(tx)) + + @spec withdraw_mutation(Node.aetx()) :: ChannelSpendMutation.t() + def withdraw_mutation(tx), do: ChannelSpendMutation.new(-:aesc_withdraw_tx.amount(tx)) + + @spec channels_opened_count(state(), Txs.txi(), Txs.txi()) :: non_neg_integer() + def channels_opened_count(state, from_txi, next_txi), + do: type_count(state, :channel_create_tx, from_txi, next_txi) + + @spec channels_closed_count(state(), Txs.txi(), Txs.txi()) :: non_neg_integer() + def channels_closed_count(state, from_txi, next_txi) do + type_count(state, :channel_close_solo_tx, from_txi, next_txi) + + type_count(state, :channel_close_mutual_tx, from_txi, next_txi) + + type_count(state, :channel_settle_tx, from_txi, next_txi) + end + + defp type_count(state, type, from_txi, next_txi) do + state + |> Collection.stream(Model.Type, {type, from_txi}) + |> Stream.take_while(&match?({^type, txi} when txi < next_txi, &1)) + |> Enum.count() + end +end diff --git a/lib/ae_mdw/db/model.ex b/lib/ae_mdw/db/model.ex index d752e6e80..44a0b90a4 100644 --- a/lib/ae_mdw/db/model.ex +++ b/lib/ae_mdw/db/model.ex @@ -540,7 +540,10 @@ defmodule AeMdw.Db.Model do block_reward: 0, dev_reward: 0, locked_in_auctions: 0, - burned_in_auctions: 0 + burned_in_auctions: 0, + channels_opened: 0, + channels_closed: 0, + locked_in_channels: 0 ] defrecord :delta_stat, @delta_stat_defaults @@ -557,7 +560,10 @@ defmodule AeMdw.Db.Model do block_reward: integer(), dev_reward: integer(), locked_in_auctions: integer(), - burned_in_auctions: integer() + burned_in_auctions: integer(), + channels_opened: non_neg_integer(), + channels_closed: non_neg_integer(), + locked_in_channels: integer() ) # summarized statistics @@ -574,7 +580,9 @@ defmodule AeMdw.Db.Model do inactive_oracles: 0, contracts: 0, locked_in_auctions: 0, - burned_in_auctions: 0 + burned_in_auctions: 0, + locked_in_channels: 0, + open_channels: 0 ] defrecord :total_stat, @total_stat_defaults @@ -591,7 +599,9 @@ defmodule AeMdw.Db.Model do inactive_oracles: integer(), contracts: integer(), locked_in_auctions: non_neg_integer(), - burned_in_auctions: non_neg_integer() + burned_in_auctions: non_neg_integer(), + locked_in_channels: non_neg_integer(), + open_channels: non_neg_integer() ) @stat_defaults [:index, :payload] diff --git a/lib/ae_mdw/db/mutations/channel_close_mutation.ex b/lib/ae_mdw/db/mutations/channel_close_mutation.ex new file mode 100644 index 000000000..b1b51f20f --- /dev/null +++ b/lib/ae_mdw/db/mutations/channel_close_mutation.ex @@ -0,0 +1,40 @@ +defmodule AeMdw.Db.ChannelCloseMutation do + @moduledoc """ + Increases channels_closed stat and refund locked AE. + """ + + alias AeMdw.Channels + alias AeMdw.Db.State + alias AeMdw.Node + + @derive AeMdw.Db.Mutation + defstruct [:tx_type, :tx] + + @typep tx_type() :: Channels.closing_type() + + @opaque t() :: %__MODULE__{tx_type: tx_type(), tx: Node.aetx()} + + @spec new(tx_type(), Node.aetx()) :: t() + def new(tx_type, tx), do: %__MODULE__{tx_type: tx_type, tx: tx} + + @spec execute(t(), State.t()) :: State.t() + def execute(%__MODULE__{tx_type: tx_type, tx: tx}, state) do + state + |> State.inc_stat(:channels_closed) + |> State.inc_stat(:locked_in_channels, -released_amount(tx_type, tx)) + end + + defp released_amount(:channel_close_solo_tx, _tx), do: 0 + + defp released_amount(:channel_close_mutual_tx, tx) do + :aesc_close_mutual_tx.initiator_amount_final(tx) + + :aesc_close_mutual_tx.responder_amount_final(tx) + end + + defp released_amount(:channel_settle_tx, tx) do + %{"initiator_amount_final" => initiator_amount, "responder_amount_final" => responder_amount} = + :aesc_settle_tx.for_client(tx) + + initiator_amount + responder_amount + end +end diff --git a/lib/ae_mdw/db/mutations/channel_open_mutation.ex b/lib/ae_mdw/db/mutations/channel_open_mutation.ex new file mode 100644 index 000000000..d60a84c9d --- /dev/null +++ b/lib/ae_mdw/db/mutations/channel_open_mutation.ex @@ -0,0 +1,28 @@ +defmodule AeMdw.Db.ChannelOpenMutation do + @moduledoc """ + Increases channels_opened stat. + """ + + alias AeMdw.Db.State + alias AeMdw.Node + + @derive AeMdw.Db.Mutation + defstruct [:tx] + + @opaque t() :: %__MODULE__{ + tx: Node.aetx() + } + + @spec new(Node.aetx()) :: t() + def new(tx), do: %__MODULE__{tx: tx} + + @spec execute(t(), State.t()) :: State.t() + def execute(%__MODULE__{tx: tx}, state) do + initiator_amount = :aesc_create_tx.initiator_amount(tx) + responder_amount = :aesc_create_tx.responder_amount(tx) + + state + |> State.inc_stat(:channels_opened) + |> State.inc_stat(:locked_in_channels, initiator_amount + responder_amount) + end +end diff --git a/lib/ae_mdw/db/mutations/channel_spend_mutation.ex b/lib/ae_mdw/db/mutations/channel_spend_mutation.ex new file mode 100644 index 000000000..1aae4f007 --- /dev/null +++ b/lib/ae_mdw/db/mutations/channel_spend_mutation.ex @@ -0,0 +1,20 @@ +defmodule AeMdw.Db.ChannelSpendMutation do + @moduledoc """ + Logs withdraws/deposists from channels. + """ + + alias AeMdw.Db.State + + @derive AeMdw.Db.Mutation + defstruct [:amount] + + @opaque t() :: %__MODULE__{amount: integer()} + + @spec new(integer()) :: t() + def new(amount), do: %__MODULE__{amount: amount} + + @spec execute(t(), State.t()) :: State.t() + def execute(%__MODULE__{amount: amount}, state) do + State.inc_stat(state, :locked_in_channels, amount) + end +end diff --git a/lib/ae_mdw/db/mutations/stats_mutation.ex b/lib/ae_mdw/db/mutations/stats_mutation.ex index 556ba0d7e..c8cde5f97 100644 --- a/lib/ae_mdw/db/mutations/stats_mutation.ex +++ b/lib/ae_mdw/db/mutations/stats_mutation.ex @@ -6,6 +6,7 @@ defmodule AeMdw.Db.StatsMutation do alias AeMdw.Collection alias AeMdw.Db.Model alias AeMdw.Blocks + alias AeMdw.Channels alias AeMdw.Db.IntTransfer alias AeMdw.Db.Model alias AeMdw.Db.Name @@ -13,25 +14,32 @@ defmodule AeMdw.Db.StatsMutation do alias AeMdw.Db.Origin alias AeMdw.Db.State alias AeMdw.Stats + alias AeMdw.Txs alias AeMdw.Util require Model @derive AeMdw.Db.Mutation - defstruct [:height, :key_hash, :tps, :all_cached?] + defstruct [:height, :key_hash, :from_txi, :next_txi, :tps, :all_cached?] + + @typep txi() :: Txs.txi() @type t() :: %__MODULE__{ height: Blocks.height(), key_hash: Blocks.block_hash(), + from_txi: txi(), + next_txi: txi(), tps: Stats.tps(), all_cached?: boolean() } - @spec new(Blocks.height(), Blocks.block_hash(), Stats.tps(), boolean()) :: t() - def new(height, key_hash, tps, all_cached?) do + @spec new(Blocks.height(), Blocks.block_hash(), txi(), txi(), Stats.tps(), boolean()) :: t() + def new(height, key_hash, from_txi, next_txi, tps, all_cached?) do %__MODULE__{ height: height, key_hash: key_hash, + from_txi: from_txi, + next_txi: next_txi, tps: tps, all_cached?: all_cached? } @@ -39,10 +47,17 @@ defmodule AeMdw.Db.StatsMutation do @spec execute(t(), State.t()) :: State.t() def execute( - %__MODULE__{height: height, key_hash: key_hash, tps: tps, all_cached?: all_cached?}, + %__MODULE__{ + height: height, + key_hash: key_hash, + from_txi: from_txi, + next_txi: next_txi, + tps: tps, + all_cached?: all_cached? + }, state ) do - m_delta_stat = make_delta_stat(state, height, all_cached?) + m_delta_stat = make_delta_stat(state, height, from_txi, next_txi, all_cached?) # delta/transitions are only reflected on total stats at height + 1 m_total_stat = make_total_stat(state, height + 1, m_delta_stat) @@ -65,8 +80,8 @@ defmodule AeMdw.Db.StatsMutation do # # Private functions # - @spec make_delta_stat(State.t(), Blocks.height(), boolean()) :: Model.delta_stat() - defp make_delta_stat(state, height, true = _all_cached?) do + @spec make_delta_stat(State.t(), Blocks.height(), txi(), txi(), boolean()) :: Model.delta_stat() + defp make_delta_stat(state, height, _from_txi, _next_txi, true = _all_cached?) do Model.delta_stat( index: height, auctions_started: get(state, :auctions_started, 0), @@ -79,11 +94,14 @@ defmodule AeMdw.Db.StatsMutation do block_reward: get(state, :block_reward, 0), dev_reward: get(state, :dev_reward, 0), locked_in_auctions: get(state, :locked_in_auctions, 0), - burned_in_auctions: get(state, :burned_in_auctions, 0) + burned_in_auctions: get(state, :burned_in_auctions, 0), + channels_opened: get(state, :channels_opened, 0), + channels_closed: get(state, :channels_closed, 0), + locked_in_channels: get(state, :locked_in_channels, 0) ) end - defp make_delta_stat(state, height, false = _all_cached?) do + defp make_delta_stat(state, height, from_txi, next_txi, false = _all_cached?) do Model.total_stat( active_auctions: prev_active_auctions, active_names: prev_active_names, @@ -94,6 +112,8 @@ defmodule AeMdw.Db.StatsMutation do current_active_names = State.count_keys(state, Model.ActiveName) current_active_auctions = State.count_keys(state, Model.AuctionExpiration) current_active_oracles = State.count_keys(state, Model.ActiveOracle) + channels_opened = Channels.channels_opened_count(state, from_txi, next_txi) + channels_closed = Channels.channels_closed_count(state, from_txi, next_txi) {height_revoked_names, height_expired_names} = state @@ -123,6 +143,7 @@ defmodule AeMdw.Db.StatsMutation do spent_in_auctions = height_int_amount(state, height, :spend_name) refund_in_auctions = height_int_amount(state, height, :refund_name) locked_in_auctions = spent_in_auctions - refund_in_auctions + locked_in_channels = height_int_amount(state, height, :lock_channel) Model.delta_stat( index: height, @@ -136,7 +157,10 @@ defmodule AeMdw.Db.StatsMutation do block_reward: current_block_reward, dev_reward: current_dev_reward, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + channels_opened: channels_opened, + channels_closed: channels_closed, + locked_in_channels: locked_in_channels ) end @@ -155,7 +179,10 @@ defmodule AeMdw.Db.StatsMutation do block_reward: inc_block_reward, dev_reward: inc_dev_reward, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + channels_opened: channels_opened, + channels_closed: channels_closed, + locked_in_channels: locked_in_channels ) ) do Model.total_stat( @@ -169,7 +196,9 @@ defmodule AeMdw.Db.StatsMutation do inactive_oracles: prev_inactive_oracles, contracts: prev_contracts, locked_in_auctions: prev_locked_in_auctions, - burned_in_auctions: prev_burned_in_acutions + burned_in_auctions: prev_burned_in_acutions, + open_channels: prev_open_channels, + locked_in_channels: prev_locked_in_channels ) = fetch_total_stat(state, height - 1) token_supply_delta = AeMdw.Node.token_supply_delta(height - 1) @@ -187,7 +216,9 @@ defmodule AeMdw.Db.StatsMutation do inactive_oracles: prev_inactive_oracles + oracles_expired, contracts: prev_contracts + contracts_created, locked_in_auctions: prev_locked_in_auctions + locked_in_auctions, - burned_in_auctions: prev_burned_in_acutions + burned_in_auctions + burned_in_auctions: prev_burned_in_acutions + burned_in_auctions, + open_channels: prev_open_channels + channels_opened - channels_closed, + locked_in_channels: prev_locked_in_channels + locked_in_channels ) end diff --git a/lib/ae_mdw/db/sync/block.ex b/lib/ae_mdw/db/sync/block.ex index b15b14ac7..82892fdcc 100644 --- a/lib/ae_mdw/db/sync/block.ex +++ b/lib/ae_mdw/db/sync/block.ex @@ -99,7 +99,7 @@ defmodule AeMdw.Db.Sync.Block do block_rewards_mutation, NamesExpirationMutation.new(height), OraclesExpirationMutation.new(height), - Stats.mutation(height, key_block, micro_blocks, starting_from_mb0?), + Stats.mutation(height, key_block, micro_blocks, from_txi, txi, starting_from_mb0?), next_kb_mutation ] diff --git a/lib/ae_mdw/db/sync/transaction.ex b/lib/ae_mdw/db/sync/transaction.ex index a2d2ba0e1..bb04a76c3 100644 --- a/lib/ae_mdw/db/sync/transaction.ex +++ b/lib/ae_mdw/db/sync/transaction.ex @@ -4,6 +4,7 @@ defmodule AeMdw.Db.Sync.Transaction do " alias AeMdw.Blocks + alias AeMdw.Channels alias AeMdw.Contract alias AeMdw.Db.Model alias AeMdw.Db.ContractCallMutation @@ -30,6 +31,8 @@ defmodule AeMdw.Db.Sync.Transaction do require Model + @channel_closing_types ~w(channel_close_solo_tx channel_close_mutual_tx channel_settle_tx)a + defmodule TxContext do @moduledoc """ Transaction context struct that contains necessary information to build a transaction mutation. @@ -200,13 +203,26 @@ defmodule AeMdw.Db.Sync.Transaction do type: :channel_create_tx, signed_tx: signed_tx, txi: txi, + tx: tx, tx_hash: tx_hash }) do {:ok, channel_pk} = :aesc_utils.channel_pubkey(signed_tx) - Origin.origin_mutations(:channel_create_tx, nil, channel_pk, txi, tx_hash) + [ + Channels.open_mutation(tx), + Origin.origin_mutations(:channel_create_tx, nil, channel_pk, txi, tx_hash) + ] end + defp tx_mutations(%TxContext{type: tx_type, tx: tx}) when tx_type in @channel_closing_types, + do: Channels.close_mutation(tx_type, tx) + + defp tx_mutations(%TxContext{type: :channel_deposit_tx, tx: tx}), + do: Channels.deposit_mutation(tx) + + defp tx_mutations(%TxContext{type: :channel_withdraw_tx, tx: tx}), + do: Channels.withdraw_mutation(tx) + defp tx_mutations(%TxContext{type: :ga_attach_tx, tx: tx, txi: txi, tx_hash: tx_hash}) do contract_pk = :aega_attach_tx.contract_pubkey(tx) diff --git a/lib/ae_mdw/stats.ex b/lib/ae_mdw/stats.ex index efbac7306..1d8bbac6d 100644 --- a/lib/ae_mdw/stats.ex +++ b/lib/ae_mdw/stats.ex @@ -22,6 +22,7 @@ defmodule AeMdw.Stats do @type cursor() :: binary() | nil @type tps() :: non_neg_integer() + @typep txi() :: Blocks.height() @typep height() :: Blocks.height() @typep direction() :: Database.direction() @typep limit() :: Database.limit() @@ -29,8 +30,9 @@ defmodule AeMdw.Stats do @tps_stat_key :max_tps - @spec mutation(height(), Db.key_block(), [Db.micro_block()], boolean()) :: StatsMutation.t() - def mutation(height, key_block, micro_blocks, starting_from_mb0?) do + @spec mutation(height(), Db.key_block(), [Db.micro_block()], txi(), txi(), boolean()) :: + StatsMutation.t() + def mutation(height, key_block, micro_blocks, from_txi, next_txi, starting_from_mb0?) do header = :aec_blocks.to_header(key_block) time = :aec_headers.time_in_msecs(header) {:ok, key_hash} = :aec_headers.hash_header(header) @@ -46,7 +48,7 @@ defmodule AeMdw.Stats do tps = if total_time > 0, do: round(total_txs * 100_000 / total_time) / 100, else: 0 - StatsMutation.new(height, key_hash, tps, starting_from_mb0?) + StatsMutation.new(height, key_hash, from_txi, next_txi, tps, starting_from_mb0?) end @spec max_tps_key() :: atom() @@ -192,7 +194,10 @@ defmodule AeMdw.Stats do block_reward: block_reward, dev_reward: dev_reward, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + channels_opened: channels_opened, + channels_closed: channels_closed, + locked_in_channels: locked_in_channels ) ) do %{ @@ -207,7 +212,10 @@ defmodule AeMdw.Stats do block_reward: block_reward, dev_reward: dev_reward, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + channels_opened: channels_opened, + channels_closed: channels_closed, + locked_in_channels: locked_in_channels } end @@ -224,7 +232,9 @@ defmodule AeMdw.Stats do dev_reward: dev_reward, total_supply: total_supply, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + open_channels: open_channels, + locked_in_channels: locked_in_channels ) ) do %{ @@ -239,7 +249,9 @@ defmodule AeMdw.Stats do sum_dev_reward: dev_reward, total_token_supply: total_supply, locked_in_auctions: locked_in_auctions, - burned_in_auctions: burned_in_auctions + burned_in_auctions: burned_in_auctions, + open_channels: open_channels, + locked_in_channels: locked_in_channels } end diff --git a/mix.exs b/mix.exs index dd485861e..3d011c5e0 100644 --- a/mix.exs +++ b/mix.exs @@ -66,7 +66,12 @@ defmodule AeMdw.MixProject do :aeo_response_tx, :aeo_state_tree, :aeo_utils, + :aesc_close_mutual_tx, + :aesc_create_tx, + :aesc_deposit_tx, + :aesc_settle_tx, :aesc_utils, + :aesc_withdraw_tx, :aeser_api_encoder, :aeser_contract_code, :aeser_id, diff --git a/priv/migrations/20220815120000_add_channels_stats.ex b/priv/migrations/20220815120000_add_channels_stats.ex new file mode 100644 index 000000000..b46520589 --- /dev/null +++ b/priv/migrations/20220815120000_add_channels_stats.ex @@ -0,0 +1,223 @@ +defmodule AeMdw.Migrations.AddChannelsStats do + @moduledoc """ + Add channels info to total/delta stats. + """ + + alias AeMdw.Collection + alias AeMdw.Db.Model + alias AeMdw.Db.State + alias AeMdw.Db.WriteMutation + + require Model + + @log_freq 1_000 + + @spec run(boolean()) :: {:ok, {non_neg_integer(), non_neg_integer()}} + def run(_from_start?) do + begin = DateTime.utc_now() + state = State.new() + + total_gens = + case state |> Collection.stream(Model.DeltaStat, :backward, nil, nil) |> Enum.take(1) do + [total_gens] -> total_gens + [] -> 0 + end + + {delta_opened_stats, delta_locked_stats} = + state + |> type_txs(:channel_create_tx) + |> Enum.reduce({Map.new(), Map.new()}, fn Model.tx(block_index: {height, _mbi}, id: tx_hash), + {open_acc, locked_acc} -> + tx = core_tx(tx_hash) + initiator_amount = :aesc_create_tx.initiator_amount(tx) + responder_amount = :aesc_create_tx.responder_amount(tx) + + amount = initiator_amount + responder_amount + + { + Map.update(open_acc, height, 1, &(&1 + 1)), + Map.update(locked_acc, height, amount, &(&1 + amount)) + } + end) + + delta_locked_stats = + state + |> type_txs(:channel_deposit_tx) + |> Enum.reduce(delta_locked_stats, fn Model.tx(block_index: {height, _mbi}, id: tx_hash), + locked_acc -> + tx = core_tx(tx_hash) + amount = :aesc_deposit_tx.amount(tx) + Map.update(locked_acc, height, amount, &(&1 + amount)) + end) + + delta_locked_stats = + state + |> type_txs(:channel_withdraw_tx) + |> Enum.reduce(delta_locked_stats, fn Model.tx(block_index: {height, _mbi}, id: tx_hash), + locked_acc -> + tx = core_tx(tx_hash) + amount = :aesc_withdraw_tx.amount(tx) + Map.update(locked_acc, height, -amount, &(&1 - amount)) + end) + + delta_closed_stats = + state + |> type_txs(:channel_close_solo_tx) + |> Enum.reduce(Map.new(), fn Model.tx(block_index: {height, _mbi}), closed_acc -> + Map.update(closed_acc, height, 1, &(&1 + 1)) + end) + + {delta_closed_stats, delta_locked_stats} = + state + |> type_txs(:channel_close_mutual_tx) + |> Enum.reduce({delta_closed_stats, delta_locked_stats}, fn Model.tx( + block_index: {height, _mbi}, + id: tx_hash + ), + {closed_acc, locked_acc} -> + tx = core_tx(tx_hash) + + amount = + :aesc_close_mutual_tx.initiator_amount_final(tx) + + :aesc_close_mutual_tx.responder_amount_final(tx) + + { + Map.update(closed_acc, height, 1, &(&1 + 1)), + Map.update(locked_acc, height, -amount, &(&1 - amount)) + } + end) + + {delta_closed_stats, delta_locked_stats} = + state + |> type_txs(:channel_settle_tx) + |> Enum.reduce({delta_closed_stats, delta_locked_stats}, fn Model.tx( + block_index: {height, _mbi}, + id: tx_hash + ), + {closed_acc, locked_acc} -> + tx = core_tx(tx_hash) + + %{ + "initiator_amount_final" => initiator_amount, + "responder_amount_final" => responder_amount + } = :aesc_settle_tx.for_client(tx) + + amount = initiator_amount + responder_amount + + { + Map.update(closed_acc, height, 1, &(&1 + 1)), + Map.update(locked_acc, height, -amount, &(&1 - amount)) + } + end) + + _channels_acc = + state + |> Collection.stream(Model.DeltaStat, nil) + |> Enum.reduce({0, 0}, fn height, {channels_opened, channels_locked_amount} -> + delta_opened = Map.get(delta_opened_stats, height, 0) + delta_closed = Map.get(delta_closed_stats, height, 0) + delta_locked = Map.get(delta_locked_stats, height, 0) + + new_open = channels_opened + delta_opened - delta_closed + new_locked = channels_locked_amount + delta_locked + + new_delta_stat = + state + |> State.fetch!(Model.DeltaStat, height) + |> transform_delta_stat() + |> Model.delta_stat( + channels_opened: delta_opened, + channels_closed: delta_closed, + locked_in_channels: delta_locked + ) + + new_total_stat = + state + |> State.fetch!(Model.TotalStat, height + 1) + |> transform_total_stat() + |> Model.total_stat( + open_channels: new_open, + locked_in_channels: new_locked + ) + + State.commit( + state, + [ + WriteMutation.new(Model.DeltaStat, new_delta_stat), + WriteMutation.new(Model.TotalStat, new_total_stat) + ] + ) + + if rem(height, @log_freq) == 0, do: IO.puts("Processed #{height} of #{total_gens}") + + {new_open, new_locked} + end) + + duration = DateTime.diff(DateTime.utc_now(), begin) + + {:ok, {total_gens, duration}} + end + + defp transform_delta_stat( + {:delta_stat, height, auctions_started, names_activated, names_expired, names_revoked, + oracles_registered, oracles_expired, contracts_created, block_reward, dev_reward, + locked_in_auctions, burned_in_auctions} + ) do + Model.delta_stat( + index: height, + auctions_started: auctions_started, + names_activated: names_activated, + names_expired: names_expired, + names_revoked: names_revoked, + oracles_registered: oracles_registered, + oracles_expired: oracles_expired, + contracts_created: contracts_created, + block_reward: block_reward, + dev_reward: dev_reward, + burned_in_auctions: burned_in_auctions, + locked_in_auctions: locked_in_auctions + ) + end + + defp transform_delta_stat(Model.delta_stat() = delta_stat), do: delta_stat + + defp transform_total_stat( + {:total_stat, height, block_reward, dev_reward, total_supply, active_auctions, + active_names, inactive_names, active_oracles, inactive_oracles, contracts, + locked_in_auctions, burned_in_auctions} + ) do + Model.total_stat( + index: height, + block_reward: block_reward, + dev_reward: dev_reward, + total_supply: total_supply, + active_auctions: active_auctions, + active_names: active_names, + inactive_names: inactive_names, + active_oracles: active_oracles, + inactive_oracles: inactive_oracles, + contracts: contracts, + burned_in_auctions: burned_in_auctions, + locked_in_auctions: locked_in_auctions + ) + end + + defp transform_total_stat(Model.total_stat() = total_stat), do: total_stat + + defp type_txs(state, type) do + state + |> Collection.stream(Model.Type, {type, 0}) + |> Stream.take_while(&match?({^type, _txi}, &1)) + |> Stream.map(fn {^type, txi} -> State.fetch!(state, Model.Tx, txi) end) + end + + defp core_tx(tx_hash) do + {_mod, tx} = + tx_hash + |> :aec_db.get_signed_tx() + |> :aetx_sign.tx() + |> :aetx.specialize_callback() + + tx + end +end diff --git a/test/ae_mdw/db/mutations/stats_mutation_test.exs b/test/ae_mdw/db/mutations/stats_mutation_test.exs index b2e83668a..e612a8ac7 100644 --- a/test/ae_mdw/db/mutations/stats_mutation_test.exs +++ b/test/ae_mdw/db/mutations/stats_mutation_test.exs @@ -1,9 +1,11 @@ defmodule AeMdw.Db.StatsMutationTest do use ExUnit.Case, async: false - alias AeMdw.Collection alias AeMdw.Database + alias AeMdw.Db.MemStore alias AeMdw.Db.Model + alias AeMdw.Db.NullStore + alias AeMdw.Db.Store alias AeMdw.Db.State alias AeMdw.Db.StatsMutation @@ -35,7 +37,7 @@ defmodule AeMdw.Db.StatsMutationTest do Database.dirty_write(Model.DeltaStat, prev_delta_stat) Database.dirty_write(Model.TotalStat, prev_total_stat) - mutation = StatsMutation.new(height, nil, nil, false) + mutation = StatsMutation.new(height, "", 0, 0, 0, false) State.commit(State.new(), [mutation]) @@ -91,7 +93,7 @@ defmodule AeMdw.Db.StatsMutationTest do State.new() |> State.inc_stat(:block_reward, @first_block_reward) - mutation = StatsMutation.new(height, nil, nil, true) + mutation = StatsMutation.new(height, "", 0, 0, 0, true) State.commit(state, [mutation]) @@ -156,7 +158,7 @@ defmodule AeMdw.Db.StatsMutationTest do |> State.inc_stat(:dev_reward, increased_dev_reward) |> State.inc_stat(:names_activated, 1) - mutation = StatsMutation.new(height, nil, nil, true) + mutation = StatsMutation.new(height, "", 0, 0, 0, true) State.commit(state, [mutation]) @@ -189,7 +191,7 @@ defmodule AeMdw.Db.StatsMutationTest do describe "execute/2" do test "with all_cached? = false, on 1st block reward it stores the stat using database counts" do height = 21 - mutation = StatsMutation.new(height, nil, nil, false) + mutation = StatsMutation.new(height, "", 0, 0, 0, false) expected_delta = Model.delta_stat( @@ -219,45 +221,38 @@ defmodule AeMdw.Db.StatsMutationTest do contracts: 0 ) - with_mocks [ - {Database, [], - [ - get: fn - Model.TotalStat, ^height -> {:ok, Model.total_stat(active_auctions: 1)} - Model.InactiveName, _plain_name -> {:ok, Model.name()} - Model.Stat, _key -> :not_found - end, - count: fn - Model.ActiveName -> 3 - Model.ActiveOracle -> 4 - Model.AuctionExpiration -> 5 - end, - next_key: fn _tab, _init_key -> :none end, - dirty_write: fn _txs, _record -> :ok end - ]}, - {State, [:passthrough], put: fn state, _tab, _record -> state end}, - {Collection, [], - [ - stream: fn - _state, Model.InactiveNameExpiration, _dir, _scope, _cursor -> - [{1, "a.chain"}, {2, "b.chain"}] - - _state, Model.InactiveOracleExpiration, _dir, _scope, _cursor -> - [] - end, - stream: fn - _state, Model.Origin, _start_key -> [1, 2, 3] - _state, Model.IntTransferTx, _start_key -> [] - _state, Model.KindIntTransferTx, _start_key -> [] - end - ]} - ] do - state = State.new() - assert StatsMutation.execute(mutation, state) - - assert_called(State.put(state, Model.DeltaStat, expected_delta)) - assert_called(State.put(state, Model.TotalStat, expected_total)) - end + state = + NullStore.new() + |> MemStore.new() + |> Store.put(Model.TotalStat, Model.total_stat(index: height, active_auctions: 1)) + |> Store.put(Model.InactiveName, Model.name()) + |> Store.put(Model.ActiveName, Model.name(index: "name1.chain")) + |> Store.put(Model.ActiveName, Model.name(index: "name2.chain")) + |> Store.put(Model.ActiveName, Model.name(index: "name3.chain")) + |> Store.put( + Model.InactiveNameExpiration, + Model.expiration(index: {height, "name1-inactive.chain"}) + ) + |> Store.put( + Model.InactiveNameExpiration, + Model.expiration(index: {height, "name2-inactive.chain"}) + ) + |> Store.put(Model.InactiveName, Model.name(index: "name1-inactive.chain")) + |> Store.put(Model.InactiveName, Model.name(index: "name2-inactive.chain")) + |> Store.put(Model.AuctionExpiration, Model.expiration(index: {22, "name1.chain"})) + |> Store.put(Model.AuctionExpiration, Model.expiration(index: {22, "name2.chain"})) + |> Store.put(Model.AuctionExpiration, Model.expiration(index: {22, "name3.chain"})) + |> Store.put(Model.AuctionExpiration, Model.expiration(index: {22, "name4.chain"})) + |> Store.put(Model.AuctionExpiration, Model.expiration(index: {22, "name5.chain"})) + |> Store.put(Model.ActiveOracle, Model.oracle(index: "oracle-pk1")) + |> Store.put(Model.ActiveOracle, Model.oracle(index: "oracle-pk2")) + |> Store.put(Model.ActiveOracle, Model.oracle(index: "oracle-pk3")) + |> Store.put(Model.ActiveOracle, Model.oracle(index: "oracle-pk4")) + |> State.new() + + state = StatsMutation.execute(mutation, state) + assert ^expected_delta = State.fetch!(state, Model.DeltaStat, height) + assert ^expected_total = State.fetch!(state, Model.TotalStat, height + 1) end test "with all_cached? = true, on 1st block reward, it stores stats using ets cache" do @@ -267,7 +262,7 @@ defmodule AeMdw.Db.StatsMutationTest do State.new() |> State.inc_stat(:block_reward, 5) - mutation = StatsMutation.new(height, nil, nil, true) + mutation = StatsMutation.new(height, "", 0, 0, 0, true) expected_delta = Model.delta_stat(