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: include internal transactions as activities #911

Merged
merged 4 commits into from
Sep 26, 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
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4412,6 +4412,8 @@ Each activity contains 3 values:

For transaction events the activity type will be `<TxType>Event`, and the payload will contain a single transaction object as displayed in the `/v2/txs` endpoint.

Transaction events can also be `IntContractCallEvents` which represent transactions that happen internally during a contract call.

```
$ curl https://mainnet.aeternity.io/mdw/v2/accounts/ak_2nVdbZTBVTbAet8j2hQhLmfNm1N2WKoAGyL7abTAHF1wGMPjzx/activities
{
Expand Down Expand Up @@ -4471,6 +4473,41 @@ $ curl https://mainnet.aeternity.io/mdw/v2/accounts/ak_2nVdbZTBVTbAet8j2hQhLmfNm
},
"type": "SpendTxEvent",
"height": 502033
},
{
"height": 659373,
"payload": {
"block_hash": "mh_MXVb7wmE1tqeA2xSPhTTksLy7DE5PvR8nsu5haC2fTGpgxxhR",
"call_tx_hash": "th_Ugtejdn7SkJHXkC3VSdCm2SnXGgPxHgUphBneMqgR3gniZzDN",
"call_txi": 34224137,
"contract_id": "ct_2AfnEfCSZCTEkxL5Yoi4Yfq6fF7YapHRaFKDJK3THMXMBspp5z",
"contract_tx_hash": "th_6memqAr5S3UQp1pc4FWXT8xUotfdrdUFgBd8VPmjM2ZRuojTF",
"contract_txi": 8392766,
"function": "Oracle.query",
Copy link
Collaborator

Choose a reason for hiding this comment

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

each IntContractCallEvent has the function attribute, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct

"height": 659373,
"internal_tx": {
"fee": 0,
"nonce": 0,
"oracle_id": "ok_AFbLSrppnBFgbKPo4GykK5XEwdq15oXgosgcdL7ud6Vo2YPsH",
"query": "YWtfcjNxRWNzWWd5Z2JjYVoxZlhQRFB0YThnZU5FUkV0OHZZaVVKNWtxQnNRNDhXVmp4NztodHRwczovL20ud2VpYm8uY24vNzc1NDY0Njg4Ny80ODE2MjEwNDI2ODYxMjg5",
"query_fee": 20000000000000,
"query_id": "oq_pcJy4ufijeP56LwCaJ47GcRNvJvW5nEUedR4BNeMzSobXXqMx",
"query_ttl": {
"type": "delta",
"value": 20
},
"response_ttl": {
"type": "delta",
"value": 20
},
"sender_id": "ak_7wqP18AHzyoqymwGaqQp8G2UpzBCggYiq7CZdJiB71VUsLpR4",
"type": "OracleQueryTx",
"version": 1
},
"local_idx": 3,
"micro_index": 0
},
"type": "IntContractCallEvent"
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe use full name "Internal.."

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Copy link
Collaborator

Choose a reason for hiding this comment

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

doesn't seem to be adjusted in docs

}
],
"next": "/v2/accounts/ak_2nVdbZTBVTbAet8j2hQhLmfNm1N2WKoAGyL7abTAHF1wGMPjzx/activities?cursor=84328-2002003-0",
Expand Down
173 changes: 140 additions & 33 deletions lib/ae_mdw/activities.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ defmodule AeMdw.Activities do
"""
alias AeMdw.Blocks
alias AeMdw.Collection
alias AeMdw.Db.Format
alias AeMdw.Db.Model
alias AeMdw.Db.Origin
alias AeMdw.Db.State
alias AeMdw.Db.Util, as: DbUtil
alias AeMdw.Error
alias AeMdw.Error.Input, as: ErrInput
alias AeMdw.Fields
alias AeMdw.Node
alias AeMdw.Txs
alias AeMdw.Util
alias AeMdw.Validate

require Model
Expand All @@ -24,9 +28,15 @@ defmodule AeMdw.Activities do
@typep cursor() :: binary() | nil
@typep txi() :: Txs.txi()
@typep activity_key() :: {Blocks.height(), txi(), non_neg_integer()}
@typep activity_value() :: {:field, Node.tx_type(), non_neg_integer() | nil}
@typep activity_value() ::
{:field, Node.tx_type(), non_neg_integer() | nil}
| :int_contract_call
@typep activity_pair() :: {activity_key(), activity_value()}

@max_pos 4
@min_int Util.min_int()
@max_int Util.max_int()

@doc """
Activities related to an account are those that affect the account in any way.

Expand Down Expand Up @@ -63,10 +73,58 @@ defmodule AeMdw.Activities do
{:ok, cursor} <- deserialize_cursor(cursor) do
{prev_cursor, activities_locators_data, next_cursor} =
fn direction ->
gens_stream = build_gens_stream(state, direction, account_pk, range, cursor)
txs_stream = build_txs_stream(state, direction, account_pk, range, cursor)

Collection.merge([txs_stream, gens_stream], direction)
{txi_scope, gen_scope} =
case range do
{:gen, first_gen..last_gen} ->
{
{first_gen, last_gen},
{DbUtil.first_gen_to_txi(state, first_gen, direction),
DbUtil.last_gen_to_txi(state, last_gen, direction)}
}

nil ->
{nil, nil}
end

{txi_cursor, local_idx_cursor} =
case cursor do
{_height, txi, local_idx} -> {txi, local_idx}
nil -> {nil, nil}
end

gens_stream = build_gens_stream(state, direction, account_pk, gen_scope, cursor)
txs_stream = build_txs_stream(state, direction, account_pk, txi_scope, txi_cursor)

int_contract_calls_stream =
build_int_contract_calls_stream(state, direction, account_pk, txi_scope, cursor)

ext_contract_calls_stream =
build_ext_contract_calls_stream(state, direction, account_pk, txi_scope, cursor)

stream =
[
txs_stream,
gens_stream,
ext_contract_calls_stream,
int_contract_calls_stream
]
|> Collection.merge(direction)
|> Stream.dedup_by(&elem(&1, 0))
jyeshe marked this conversation as resolved.
Show resolved Hide resolved

if local_idx_cursor do
Stream.drop_while(stream, fn
{{_height, ^txi_cursor, local_idx}, _data} when direction == :forward ->
local_idx < local_idx_cursor

{{_height, ^txi_cursor, local_idx}, _data} when direction == :backward ->
local_idx > local_idx_cursor

_activity_pair ->
false
end)
else
stream
end
end
|> Collection.paginate(pagination)

Expand All @@ -75,47 +133,88 @@ defmodule AeMdw.Activities do
end
end

defp build_gens_stream(_state, _direction, _account_pk, _range, _cursor) do
defp build_gens_stream(_state, _direction, _account_pk, _gen_scope, _cursor) do
[]
end

defp build_txs_stream(state, direction, account_pk, range, cursor) do
{txi_cursor, local_idx_cursor} =
case cursor do
{_height, txi, local_idx} -> {txi, local_idx}
nil -> {nil, nil}
end
defp build_ext_contract_calls_stream(state, direction, account_pk, txi_scope, cursor) do
0..@max_pos
|> Enum.map(fn pos ->
key_boundary =
case txi_scope do
{first_txi, last_txi} ->
{{account_pk, pos, first_txi, @min_int}, {account_pk, pos, last_txi, @max_int}}

stream =
state
|> Fields.account_fields_stream(account_pk, direction, range, txi_cursor)
|> Stream.transform({-1, -1, -1}, fn
{txi, tx_type, tx_field_pos}, {txi, height, local_idx} ->
{[{{height, txi, local_idx + 1}, {:field, tx_type, tx_field_pos}}],
{txi, height, local_idx + 1}}
nil ->
{{account_pk, pos, @min_int, nil}, {account_pk, pos, @max_int, nil}}
end

{txi, tx_type, tx_field_pos}, _acc ->
Model.tx(block_index: {height, _mbi}) = State.fetch!(state, Model.Tx, txi)
cursor =
case cursor do
{_height, txi_cursor, local_idx_cursor} ->
{account_pk, pos, txi_cursor, local_idx_cursor}

{[{{height, txi, 0}, {:field, tx_type, tx_field_pos}}], {txi, height, 0}}
nil ->
nil
end

state
|> Collection.stream(Model.IdIntContractCall, direction, key_boundary, cursor)
|> Stream.map(fn {^account_pk, ^pos, txi, local_idx} ->
Model.tx(block_index: {height, _mbi}) = State.fetch!(state, Model.Tx, txi)

{{height, txi, local_idx}, :int_contract_call}
end)
end)
|> Collection.merge(direction)
end

if local_idx_cursor do
Stream.drop_while(stream, fn
{{_height, ^txi_cursor, local_idx}, _data} when direction == :forward ->
local_idx < local_idx_cursor
defp build_int_contract_calls_stream(state, direction, account_pk, txi_scope, cursor) do
case Origin.tx_index(state, {:contract, account_pk}) do
{:ok, create_txi} ->
key_boundary =
case txi_scope do
{first_txi, last_txi} ->
{{create_txi, first_txi, @min_int}, {create_txi, last_txi, @max_int}}

nil ->
{{create_txi, @min_int, @min_int}, {create_txi, @max_int, @max_int}}
end

cursor =
case cursor do
{_height, txi_cursor, local_idx_cursor} -> {create_txi, txi_cursor, local_idx_cursor}
nil -> nil
end

state
|> Collection.stream(Model.GrpIntContractCall, direction, key_boundary, cursor)
|> Stream.map(fn {^create_txi, txi, local_idx} ->
Model.tx(block_index: {height, _mbi}) = State.fetch!(state, Model.Tx, txi)

{{_height, ^txi_cursor, local_idx}, _data} when direction == :backward ->
local_idx > local_idx_cursor
{{height, txi, local_idx}, :int_contract_call}
end)

_activity_pair ->
false
end)
else
stream
:not_found ->
[]
end
end

defp build_txs_stream(state, direction, account_pk, range, txi_cursor) do
state
|> Fields.account_fields_stream(account_pk, direction, range, txi_cursor)
|> Stream.transform({-1, -1, -1}, fn
{txi, tx_type, tx_field_pos}, {txi, height, local_idx} ->
{[{{height, txi, local_idx + 1}, {:field, tx_type, tx_field_pos}}],
{txi, height, local_idx + 1}}

{txi, tx_type, tx_field_pos}, _acc ->
Model.tx(block_index: {height, _mbi}) = State.fetch!(state, Model.Tx, txi)

{[{{height, txi, 0}, {:field, tx_type, tx_field_pos}}], {txi, height, 0}}
end)
end

@spec render(state(), activity_pair()) :: map()
defp render(state, {{height, txi, _local_idx}, {:field, tx_type, _tx_pos}}) do
tx = state |> Txs.fetch!(txi) |> Map.delete("tx_index")
Expand All @@ -127,6 +226,14 @@ defmodule AeMdw.Activities do
}
end

defp render(state, {{height, call_txi, local_idx}, :int_contract_call}) do
%{
height: height,
type: "InternalContractCallEvent",
payload: Format.to_map(state, {call_txi, local_idx}, Model.IntContractCall)
}
end

defp serialize_cursor(nil), do: nil

defp serialize_cursor({{{height, txi, local_idx}, _data}, is_reversed?}),
Expand Down
24 changes: 7 additions & 17 deletions lib/ae_mdw/fields.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule AeMdw.Fields do
alias AeMdw.Collection
alias AeMdw.Db.Model
alias AeMdw.Db.State
alias AeMdw.Db.Util, as: DbUtil
alias AeMdw.Node
alias AeMdw.Node.Db
alias AeMdw.Txs
Expand All @@ -15,23 +14,14 @@ defmodule AeMdw.Fields do
@typep state() :: State.t()
@typep pubkey() :: Db.pubkey()
@typep direction() :: Collection.direction()
@typep range() :: {:gen, Range.t()} | nil
@typep cursor() :: Txs.tx() | nil
@typep txi_scope() :: {Txs.txi(), Txs.txi()} | nil
@typep cursor() :: Txs.txi() | nil

@create_tx_types ~w(contract_create_tx channel_create_tx oracle_register_tx ga_attach_tx)a

@spec account_fields_stream(state(), pubkey(), direction(), range(), cursor()) :: Enumerable.t()
def account_fields_stream(state, account_pk, direction, range, cursor) do
scope =
case range do
{:gen, %Range{first: first_gen, last: last_gen}} ->
{DbUtil.first_gen_to_txi(state, first_gen, direction),
DbUtil.last_gen_to_txi(state, last_gen, direction)}

nil ->
nil
end

@spec account_fields_stream(state(), pubkey(), direction(), txi_scope(), cursor()) ::
Enumerable.t()
def account_fields_stream(state, account_pk, direction, txi_scope, cursor) do
Node.tx_types()
|> Enum.flat_map(fn tx_type ->
types_pos =
Expand All @@ -47,14 +37,14 @@ defmodule AeMdw.Fields do
end)
|> Enum.map(fn {tx_type, tx_field_pos} ->
scope =
case scope do
case txi_scope do
{first_txi, last_txi} ->
{{tx_type, tx_field_pos, account_pk, first_txi},
{tx_type, tx_field_pos, account_pk, last_txi}}

nil ->
{{tx_type, tx_field_pos, account_pk, Util.min_int()},
{tx_type, tx_field_pos, account_pk, Util.max_256bit_int()}}
{tx_type, tx_field_pos, account_pk, Util.max_int()}}
end

cursor = if cursor, do: {tx_type, tx_field_pos, account_pk, cursor}
Expand Down
24 changes: 9 additions & 15 deletions lib/ae_mdw/txs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -295,21 +295,15 @@ defmodule AeMdw.Txs do
initial_tx_types =
initial_fields |> Enum.map(fn {tx_type, _pos} -> tx_type end) |> MapSet.new()

intersection_of_tx_types =
Enum.reduce(ids_fields_rest, initial_tx_types, fn {_id, fields}, acc ->
fields
|> Enum.map(fn {tx_type, _pos} -> tx_type end)
|> MapSet.new()
|> MapSet.intersection(acc)
end)

intersection_of_tx_types =
case types do
[] -> intersection_of_tx_types
_types -> MapSet.intersection(intersection_of_tx_types, MapSet.new(types))
end

Enum.flat_map(intersection_of_tx_types, fn tx_type ->
ids_fields_rest
|> Enum.map(fn {_id, fields} ->
fields
|> Enum.map(fn {tx_type, _pos} -> tx_type end)
|> MapSet.new()
end)
|> Enum.reduce(initial_tx_types, &MapSet.intersection/2)
|> MapSet.intersection(MapSet.new(types))
|> Enum.flat_map(fn tx_type ->
{min_account_id, min_fields} =
Enum.min_by(ids_fields, fn {id, fields} ->
count_txs_for_account(state, id, fields, tx_type)
Expand Down
Loading