feat: List of internal transactions API v2 endpoint (#10994)
* feat: List of internal transactions API v2 endpoint * Process Fedor review * Fix format * Process review commentpull/11008/head
parent
fc0c5b5315
commit
aa3defae18
@ -0,0 +1,58 @@ |
||||
defmodule BlockScoutWeb.API.V2.InternalTransactionController do |
||||
use BlockScoutWeb, :controller |
||||
alias Explorer.Chain.InternalTransaction |
||||
alias Explorer.{Helper, PagingOptions} |
||||
|
||||
import BlockScoutWeb.Chain, |
||||
only: [ |
||||
split_list_by_page: 1, |
||||
paging_options: 1, |
||||
next_page_params: 3 |
||||
] |
||||
|
||||
import BlockScoutWeb.PagingHelper, |
||||
only: [ |
||||
delete_parameters_from_next_page_params: 1 |
||||
] |
||||
|
||||
import Explorer.PagingOptions, only: [default_paging_options: 0] |
||||
|
||||
action_fallback(BlockScoutWeb.API.V2.FallbackController) |
||||
|
||||
@api_true [api?: true] |
||||
|
||||
@doc """ |
||||
Function to handle GET requests to `/api/v2/internal-transactions` endpoint. |
||||
""" |
||||
@spec internal_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() |
||||
def internal_transactions(conn, params) do |
||||
paging_options = paging_options(params) |
||||
|
||||
options = |
||||
paging_options |
||||
|> Keyword.update(:paging_options, default_paging_options(), fn %PagingOptions{ |
||||
page_size: page_size |
||||
} = paging_options -> |
||||
maybe_parsed_limit = Helper.parse_integer(params["limit"]) |
||||
%PagingOptions{paging_options | page_size: min(page_size, maybe_parsed_limit && abs(maybe_parsed_limit))} |
||||
end) |
||||
|> Keyword.merge(@api_true) |
||||
|
||||
result = |
||||
options |
||||
|> InternalTransaction.fetch() |
||||
|> split_list_by_page() |
||||
|
||||
{internal_transactions, next_page} = result |
||||
|
||||
next_page_params = |
||||
next_page |> next_page_params(internal_transactions, delete_parameters_from_next_page_params(params)) |
||||
|
||||
conn |
||||
|> put_status(200) |
||||
|> render(:internal_transactions, %{ |
||||
internal_transactions: internal_transactions, |
||||
next_page_params: next_page_params |
||||
}) |
||||
end |
||||
end |
@ -0,0 +1,60 @@ |
||||
defmodule BlockScoutWeb.API.V2.InternalTransactionView do |
||||
use BlockScoutWeb, :view |
||||
|
||||
alias BlockScoutWeb.API.V2.Helper |
||||
alias Explorer.Chain.{Block, InternalTransaction} |
||||
|
||||
def render("internal_transaction.json", %{internal_transaction: nil}) do |
||||
nil |
||||
end |
||||
|
||||
def render("internal_transaction.json", %{ |
||||
internal_transaction: internal_transaction, |
||||
block: block |
||||
}) do |
||||
prepare_internal_transaction(internal_transaction, block) |
||||
end |
||||
|
||||
def render("internal_transactions.json", %{ |
||||
internal_transactions: internal_transactions, |
||||
next_page_params: next_page_params |
||||
}) do |
||||
%{ |
||||
"items" => Enum.map(internal_transactions, &prepare_internal_transaction(&1, &1.block)), |
||||
"next_page_params" => next_page_params |
||||
} |
||||
end |
||||
|
||||
@doc """ |
||||
Prepares internal transaction object to be returned in the API v2 endpoints. |
||||
""" |
||||
@spec prepare_internal_transaction(InternalTransaction.t(), Block.t() | nil) :: map() |
||||
def prepare_internal_transaction(internal_transaction, block \\ nil) do |
||||
%{ |
||||
"error" => internal_transaction.error, |
||||
"success" => is_nil(internal_transaction.error), |
||||
"type" => internal_transaction.call_type || internal_transaction.type, |
||||
"transaction_hash" => internal_transaction.transaction_hash, |
||||
"transaction_index" => internal_transaction.transaction_index, |
||||
"from" => |
||||
Helper.address_with_info(nil, internal_transaction.from_address, internal_transaction.from_address_hash, false), |
||||
"to" => |
||||
Helper.address_with_info(nil, internal_transaction.to_address, internal_transaction.to_address_hash, false), |
||||
"created_contract" => |
||||
Helper.address_with_info( |
||||
nil, |
||||
internal_transaction.created_contract_address, |
||||
internal_transaction.created_contract_address_hash, |
||||
false |
||||
), |
||||
"value" => internal_transaction.value, |
||||
# todo: keep next line for compatibility with frontend and remove when new frontend is bound to `block_number` property |
||||
"block" => internal_transaction.block_number, |
||||
"block_number" => internal_transaction.block_number, |
||||
"timestamp" => (block && block.timestamp) || internal_transaction.block.timestamp, |
||||
"index" => internal_transaction.index, |
||||
"gas_limit" => internal_transaction.gas, |
||||
"block_index" => internal_transaction.block_index |
||||
} |
||||
end |
||||
end |
@ -0,0 +1,93 @@ |
||||
defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do |
||||
use BlockScoutWeb.ConnCase |
||||
|
||||
alias Explorer.Chain.{Address, InternalTransaction} |
||||
|
||||
describe "/internal-transactions" do |
||||
test "empty list", %{conn: conn} do |
||||
request = get(conn, "/api/v2/internal-transactions") |
||||
|
||||
assert response = json_response(request, 200) |
||||
assert response["items"] == [] |
||||
assert response["next_page_params"] == nil |
||||
end |
||||
|
||||
test "non empty list", %{conn: conn} do |
||||
tx = |
||||
:transaction |
||||
|> insert() |
||||
|> with_block() |
||||
|
||||
insert(:internal_transaction, |
||||
transaction: tx, |
||||
block_hash: tx.block_hash, |
||||
index: 0, |
||||
block_index: 0 |
||||
) |
||||
|
||||
request = get(conn, "/api/v2/internal-transactions") |
||||
|
||||
assert response = json_response(request, 200) |
||||
assert Enum.count(response["items"]) == 1 |
||||
assert response["next_page_params"] == nil |
||||
end |
||||
|
||||
test "internal transactions with next_page_params", %{conn: conn} do |
||||
transaction = insert(:transaction) |> with_block() |
||||
|
||||
internal_transaction = |
||||
insert(:internal_transaction, |
||||
transaction: transaction, |
||||
transaction_index: 0, |
||||
block_number: transaction.block_number, |
||||
block_hash: transaction.block_hash, |
||||
index: 0, |
||||
block_index: 0 |
||||
) |
||||
|
||||
transaction_2 = insert(:transaction) |> with_block() |
||||
|
||||
internal_transactions = |
||||
for i <- 0..49 do |
||||
insert(:internal_transaction, |
||||
transaction: transaction_2, |
||||
transaction_index: 0, |
||||
block_number: transaction_2.block_number, |
||||
block_hash: transaction_2.block_hash, |
||||
index: i, |
||||
block_index: i |
||||
) |
||||
end |
||||
|
||||
internal_transactions = [internal_transaction | internal_transactions] |
||||
|
||||
request = get(conn, "/api/v2/internal-transactions") |
||||
assert response = json_response(request, 200) |
||||
|
||||
request_2nd_page = get(conn, "/api/v2/internal-transactions", response["next_page_params"]) |
||||
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||
|
||||
check_paginated_response(response, response_2nd_page, internal_transactions) |
||||
end |
||||
end |
||||
|
||||
defp compare_item(%InternalTransaction{} = internal_transaction, json) do |
||||
assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] |
||||
assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] |
||||
assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] |
||||
assert internal_transaction.block_number == json["block_number"] |
||||
assert internal_transaction.block_index == json["block_index"] |
||||
end |
||||
|
||||
defp check_paginated_response(first_page_resp, second_page_resp, internal_transactions) do |
||||
assert Enum.count(first_page_resp["items"]) == 50 |
||||
assert first_page_resp["next_page_params"] != nil |
||||
compare_item(Enum.at(internal_transactions, 50), Enum.at(first_page_resp["items"], 0)) |
||||
|
||||
compare_item(Enum.at(internal_transactions, 1), Enum.at(first_page_resp["items"], 49)) |
||||
|
||||
assert Enum.count(second_page_resp["items"]) == 1 |
||||
assert second_page_resp["next_page_params"] == nil |
||||
compare_item(Enum.at(internal_transactions, 0), Enum.at(second_page_resp["items"], 0)) |
||||
end |
||||
end |
Loading…
Reference in new issue