Skip to content

Commit

Permalink
feat: allow filtering channels by active/inactive (#1367)
Browse files Browse the repository at this point in the history
  • Loading branch information
sborrazas committed May 31, 2023
1 parent ca2fff8 commit c303cce
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 22 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3768,6 +3768,8 @@ $ curl -s "https://mainnet.aeternity.io/mdw/v2/oracles?state=active&limit=1&expa

Returns active channels ordered by the txi of the last update.

These can also be filtered by `state=active` or `state=inactive`.

Besides the participants balances it includes some fields intrinsic to the channel such as:
- the reserve deposited in the channel for paying fees and assuring refunds;
- lock parameters, in case of individual actions; and
Expand Down
18 changes: 14 additions & 4 deletions docs/swagger_v2/channels.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ schemas:
type: integer
description: Amount owned by responder
example: 4500000000000000000
round:
round:
type: integer
description: Round after last transaction
example: 1
solo_round:
solo_round:
type: integer
description: Round of last solo transaction
example: 0
example: 0
state_hash:
type: string
description: The hash of the current channel state
Expand All @@ -69,7 +69,7 @@ schemas:
type: string
description: The amount of times the channel's been updated by any of the channel transactions
example: 2

paths:
/channels:
get:
Expand All @@ -80,6 +80,16 @@ paths:
- $ref: '#/components/parameters/LimitParam'
- $ref: '#/components/parameters/ScopeParam'
- $ref: '#/components/parameters/DirectionParam'
- name: state
in: query
description: Exclusively filter by active/inactive channels.
required: false
schema:
type: string
enum:
- active
- inactive
example: inactive
responses:
'200':
description: Returns paginated channels
Expand Down
61 changes: 47 additions & 14 deletions lib/ae_mdw/channels.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ defmodule AeMdw.Channels do
@typep type_block_hash() :: {Db.hash_type(), Db.hash()}
@typep pagination() :: Collection.direction_limit()
@typep range() :: {:gen, Range.t()} | nil
@typep query() :: map()
@typep cursor() :: binary()
@typep pagination_cursor() :: Collection.pagination_cursor()

Expand All @@ -36,6 +37,8 @@ defmodule AeMdw.Channels do
@table_inactive Model.InactiveChannel
@table_inactive_activation Model.InactiveChannelActivation

@states ~w(active inactive)

@channel_tx_mod %{
:channel_create_tx => :aesc_create_tx,
:channel_close_solo_tx => :aesc_close_solo_tx,
Expand All @@ -49,20 +52,23 @@ defmodule AeMdw.Channels do
:channel_snapshot_solo_tx => :aesc_snapshot_solo_tx
}

@spec fetch_channels(state(), pagination(), range(), cursor()) ::
{:ok, pagination_cursor(), [channel()], pagination_cursor()} | {:error, Error.t()}
def fetch_channels(state, pagination, range, cursor) do
with {:ok, cursor} <- deserialize_cursor(cursor) do
@spec fetch_channels(state(), pagination(), range(), query(), cursor()) ::
{:ok, {pagination_cursor(), [channel()], pagination_cursor()}}
| {:error, Error.t()}
def fetch_channels(state, pagination, range, query, cursor) do
with {:ok, cursor} <- deserialize_cursor(cursor),
{:ok, filters} <- convert_params(query) do
scope = deserialize_scope(range)

{prev_cursor, expiration_keys, next_cursor} =
state
|> build_streamer(scope, cursor)
filters
|> Map.new()
|> build_streamer(state, scope, cursor)
|> Collection.paginate(pagination)

channels = render_channels(state, expiration_keys)

{:ok, serialize_cursor(prev_cursor), channels, serialize_cursor(next_cursor)}
{:ok, {serialize_cursor(prev_cursor), channels, serialize_cursor(next_cursor)}}
end
end

Expand Down Expand Up @@ -131,14 +137,28 @@ defmodule AeMdw.Channels do
|> Enum.count()
end

defp build_streamer(state, scope, cursor) do
defp build_streamer(%{state: "active"}, state, scope, cursor) do
fn direction ->
[@table_active_activation, @table_inactive_activation]
|> Enum.map(fn table ->
state
|> Collection.stream(table, direction, scope, cursor)
|> Stream.map(&{&1, table})
end)
state
|> Collection.stream(@table_active_activation, direction, scope, cursor)
|> Stream.map(&{&1, @table_active_activation})
end
end

defp build_streamer(%{state: "inactive"}, state, scope, cursor) do
fn direction ->
state
|> Collection.stream(@table_inactive_activation, direction, scope, cursor)
|> Stream.map(&{&1, @table_inactive_activation})
end
end

defp build_streamer(_query, state, scope, cursor) do
fn direction ->
[
build_streamer(%{state: "active"}, state, scope, cursor).(direction),
build_streamer(%{state: "inactive"}, state, scope, cursor).(direction)
]
|> Collection.merge(direction)
end
end
Expand Down Expand Up @@ -317,4 +337,17 @@ defmodule AeMdw.Channels do
defp get_block_index(state, :micro, block_hash) do
DbUtil.micro_block_height_index(state, block_hash)
end

defp convert_params(query) do
Enum.reduce_while(query, {:ok, []}, fn param, {:ok, filters} ->
case convert_param(param) do
{:ok, filter} -> {:cont, {:ok, [filter | filters]}}
{:error, reason} -> {:halt, {:error, reason}}
end
end)
end

defp convert_param({"state", val}) when val in @states, do: {:ok, {:state, val}}

defp convert_param(other_param), do: {:error, ErrInput.Query.exception(value: other_param)}
end
8 changes: 4 additions & 4 deletions lib/ae_mdw_web/controllers/channel_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ defmodule AeMdwWeb.ChannelController do

@spec channels(Conn.t(), map()) :: Conn.t()
def channels(%Conn{assigns: assigns} = conn, _params) do
%{state: state, pagination: pagination, scope: scope, cursor: cursor} = assigns
%{state: state, pagination: pagination, scope: scope, query: query, cursor: cursor} = assigns

with {:ok, prev_cursor, channels, next_cursor} <-
Channels.fetch_channels(state, pagination, scope, cursor) do
Util.paginate(conn, prev_cursor, channels, next_cursor)
with {:ok, paginated_channels} <-
Channels.fetch_channels(state, pagination, scope, query, cursor) do
Util.paginate(conn, paginated_channels)
end
end

Expand Down
89 changes: 89 additions & 0 deletions test/ae_mdw_web/controllers/channel_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,95 @@ defmodule AeMdwWeb.ChannelControllerTest do
end
end

test "when filtering by state=inactive, it returns inactive channels only", %{
conn: conn,
store: store
} do
channel_pk1 = <<0::256>>
channel_pk2 = <<1::256>>
channel_pk3 = <<2::256>>
initiator_pk = <<3::256>>
responder_pk = <<4::256>>
tx_hash = <<5::256>>
block_hash = <<6::256>>
enc_tx_hash = encode(:tx_hash, tx_hash)
channel_id3 = encode(:channel, channel_pk3)
initiator = encode_account(initiator_pk)
responder = encode_account(responder_pk)

{:ok, state_hash} =
:aeser_api_encoder.safe_decode(
:state,
"st_Wwxms0IVM7PPCHpeOXWeeZZm8h5p/SuqZL7IHIbr3CqtlCL+"
)

encoded_state_hash = encode(:state, state_hash)

m_channel3 =
Model.channel(
index: channel_pk3,
active: 3,
initiator: initiator_pk,
responder: responder_pk,
state_hash: state_hash,
updates: [{{600_000, 1}, {3_000, -1}}]
)

store =
store
|> Store.put(Model.ActiveChannelActivation, Model.activation(index: {1, channel_pk1}))
|> Store.put(Model.ActiveChannelActivation, Model.activation(index: {2, channel_pk2}))
|> Store.put(Model.InactiveChannel, m_channel3)
|> Store.put(Model.InactiveChannelActivation, Model.activation(index: {3, channel_pk3}))

with_mocks [
{AeMdw.Db.Util, [:passthrough],
[
read_node_tx_details: fn _state, {3_000, -1} ->
{:tx, :channel_deposit_tx, tx_hash, :channel_deposit_tx, block_hash}
end
]},
{:aec_chain, [:passthrough],
get_channel_at_hash: fn pubkey, ^block_hash ->
amount = 8_000_000

{:ok,
{:channel, {:id, :channel, pubkey}, {:id, :account, initiator_pk},
{:id, :account, responder_pk}, %{initiator: [], responder: []}, amount, 3_400_000,
3_600_000, 500_000, :basic, :basic,
<<13, 54, 141, 196, 223, 107, 172, 150, 198, 45, 62, 102, 159, 21, 123, 151, 241,
235, 20, 175, 223, 198, 242, 127, 137, 194, 129, 204, 227, 139, 197, 132>>, 1, 2,
3, 500_003, 3}}
end}
] do
assert %{"data" => [channel3], "next" => nil} =
conn
|> with_store(store)
|> get("/v2/channels", state: "inactive")
|> json_response(200)

assert %{
"channel" => ^channel_id3,
"active" => false,
"amount" => 8_000_000,
"last_updated_height" => 600_000,
"last_updated_tx_hash" => ^enc_tx_hash,
"last_updated_tx_type" => "ChannelDepositTx",
"updates_count" => 1,
"responder" => ^responder,
"initiator" => ^initiator,
"channel_reserve" => 500_000,
"initiator_amount" => 3_400_000,
"responder_amount" => 3_600_000,
"round" => 1,
"solo_round" => 2,
"lock_period" => 3,
"locked_until" => 500_003,
"state_hash" => ^encoded_state_hash
} = channel3
end
end

test "when no channels, it returns empty data", %{conn: conn, store: store} do
assert %{"data" => []} =
conn |> with_store(store) |> get("/v2/channels") |> json_response(200)
Expand Down

0 comments on commit c303cce

Please sign in to comment.