Blockscout core API v2 (#6429)
* Add tests; Fix /transactions bug * Add /internal-transactions and /logs tests * Checksum all address hashes; Done transaction_controller_test.exs * Add block_controller_test.exs * Done block_controller_test.exs * Add /counters, change /token-transfers for /addresses; Add timestamp to token transfer body * Drop params from next_page_params; Add address_controller_test.exs * Fix pending txs pagination; Add tests for address controller * Tests for address in progress * Add coin balances test for address * Done address_controller_test.exs * Done tests for API v2; Fix pagination for search; Add cache for transactions for API v2 * Add ERC filtering for transactions/0x../token-transfers; Add network utilization to /stats * Return nil for nil address_hash instead of struct * Fix token transfers * Remove decoded field from revert_reason bodypull/6478/head
parent
84f2b3ebf8
commit
0e0931e130
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,329 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.BlockControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
alias Explorer.Chain.{Address, Block, Transaction} |
||||||
|
|
||||||
|
setup do |
||||||
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) |
||||||
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) |
||||||
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id()) |
||||||
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id()) |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
describe "/blocks" do |
||||||
|
test "empty lists", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/blocks") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "uncle"}) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "reorg"}) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "block"}) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "get block", %{conn: conn} do |
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
compare_item(block, Enum.at(response["items"], 0)) |
||||||
|
end |
||||||
|
|
||||||
|
test "type=block returns only consensus blocks", %{conn: conn} do |
||||||
|
blocks = |
||||||
|
4 |
||||||
|
|> insert_list(:block) |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
for index <- 0..3 do |
||||||
|
uncle = insert(:block, consensus: false) |
||||||
|
insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) |
||||||
|
end |
||||||
|
|
||||||
|
2 |
||||||
|
|> insert_list(:block, consensus: false) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "block"}) |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 4 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
for index <- 0..3 do |
||||||
|
compare_item(Enum.at(blocks, index), Enum.at(response["items"], index)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
test "type=block can paginate", %{conn: conn} do |
||||||
|
blocks = |
||||||
|
51 |
||||||
|
|> insert_list(:block) |
||||||
|
|
||||||
|
filter = %{"type" => "block"} |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, blocks) |
||||||
|
end |
||||||
|
|
||||||
|
test "type=reorg returns only non consensus blocks", %{conn: conn} do |
||||||
|
blocks = |
||||||
|
5 |
||||||
|
|> insert_list(:block) |
||||||
|
|
||||||
|
for index <- 0..3 do |
||||||
|
uncle = insert(:block, consensus: false) |
||||||
|
insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) |
||||||
|
end |
||||||
|
|
||||||
|
reorgs = |
||||||
|
4 |
||||||
|
|> insert_list(:block, consensus: false) |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "reorg"}) |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 4 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
for index <- 0..3 do |
||||||
|
compare_item(Enum.at(reorgs, index), Enum.at(response["items"], index)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
test "type=reorg can paginate", %{conn: conn} do |
||||||
|
reorgs = |
||||||
|
51 |
||||||
|
|> insert_list(:block, consensus: false) |
||||||
|
|
||||||
|
filter = %{"type" => "reorg"} |
||||||
|
request = get(conn, "/api/v2/blocks", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, reorgs) |
||||||
|
end |
||||||
|
|
||||||
|
test "type=uncle returns only uncle blocks", %{conn: conn} do |
||||||
|
blocks = |
||||||
|
4 |
||||||
|
|> insert_list(:block) |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
uncles = |
||||||
|
for index <- 0..3 do |
||||||
|
uncle = insert(:block, consensus: false) |
||||||
|
insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) |
||||||
|
uncle |
||||||
|
end |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
4 |
||||||
|
|> insert_list(:block, consensus: false) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks", %{"type" => "uncle"}) |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 4 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
for index <- 0..3 do |
||||||
|
compare_item(Enum.at(uncles, index), Enum.at(response["items"], index)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
test "type=uncle can paginate", %{conn: conn} do |
||||||
|
blocks = |
||||||
|
51 |
||||||
|
|> insert_list(:block) |
||||||
|
|
||||||
|
uncles = |
||||||
|
for index <- 0..50 do |
||||||
|
uncle = insert(:block, consensus: false) |
||||||
|
insert(:block_second_degree_relation, uncle_hash: uncle.hash, nephew: Enum.at(blocks, index)) |
||||||
|
uncle |
||||||
|
end |
||||||
|
|
||||||
|
filter = %{"type" => "uncle"} |
||||||
|
request = get(conn, "/api/v2/blocks", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/blocks", Map.merge(response["next_page_params"], filter)) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, uncles) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/blocks/{block_hash_or_number}" do |
||||||
|
test "return 422 on invalid parameter", %{conn: conn} do |
||||||
|
request_1 = get(conn, "/api/v2/blocks/0x123123") |
||||||
|
assert %{"message" => "Invalid hash"} = json_response(request_1, 422) |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/123qwe") |
||||||
|
assert %{"message" => "Invalid number"} = json_response(request_2, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return 404 on non existing block", %{conn: conn} do |
||||||
|
block = build(:block) |
||||||
|
|
||||||
|
request_1 = get(conn, "/api/v2/blocks/#{block.number}") |
||||||
|
assert %{"message" => "Not found"} = json_response(request_1, 404) |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/#{block.hash}") |
||||||
|
assert %{"message" => "Not found"} = json_response(request_2, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "get the same blocks by hash and number", %{conn: conn} do |
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
request_1 = get(conn, "/api/v2/blocks/#{block.number}") |
||||||
|
assert response_1 = json_response(request_1, 200) |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/#{block.hash}") |
||||||
|
assert response_2 = json_response(request_2, 200) |
||||||
|
|
||||||
|
assert response_2 == response_1 |
||||||
|
compare_item(block, response_2) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/blocks/{block_hash_or_number}/transactions" do |
||||||
|
test "return 422 on invalid parameter", %{conn: conn} do |
||||||
|
request_1 = get(conn, "/api/v2/blocks/0x123123/transactions") |
||||||
|
assert %{"message" => "Invalid hash"} = json_response(request_1, 422) |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/123qwe/transactions") |
||||||
|
assert %{"message" => "Invalid number"} = json_response(request_2, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return 404 on non existing block", %{conn: conn} do |
||||||
|
block = build(:block) |
||||||
|
|
||||||
|
request_1 = get(conn, "/api/v2/blocks/#{block.number}/transactions") |
||||||
|
assert %{"message" => "Not found"} = json_response(request_1, 404) |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/#{block.hash}/transactions") |
||||||
|
assert %{"message" => "Not found"} = json_response(request_2, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "get empty list", %{conn: conn} do |
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks/#{block.number}/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks/#{block.hash}/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "get relevant tx", %{conn: conn} do |
||||||
|
10 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block(block) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks/#{block.number}/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
compare_item(tx, Enum.at(response["items"], 0)) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks/#{block.hash}/transactions") |
||||||
|
assert response_1 = json_response(request, 200) |
||||||
|
assert response_1 == response |
||||||
|
end |
||||||
|
|
||||||
|
test "get txs with working next_page_params", %{conn: conn} do |
||||||
|
2 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block(block) |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/blocks/#{block.number}/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/blocks/#{block.number}/transactions", response["next_page_params"]) |
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, txs) |
||||||
|
|
||||||
|
request_1 = get(conn, "/api/v2/blocks/#{block.hash}/transactions") |
||||||
|
assert response_1 = json_response(request_1, 200) |
||||||
|
|
||||||
|
assert response_1 == response |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/blocks/#{block.hash}/transactions", response_1["next_page_params"]) |
||||||
|
assert response_2 = json_response(request_2, 200) |
||||||
|
assert response_2 == response_2nd_page |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Block{} = block, json) do |
||||||
|
assert to_string(block.hash) == json["hash"] |
||||||
|
assert block.number == json["height"] |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Transaction{} = transaction, json) do |
||||||
|
assert to_string(transaction.hash) == json["hash"] |
||||||
|
assert transaction.block_number == json["block"] |
||||||
|
assert to_string(transaction.value.value) == json["value"] |
||||||
|
assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] |
||||||
|
assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] |
||||||
|
end |
||||||
|
|
||||||
|
defp check_paginated_response(first_page_resp, second_page_resp, list) do |
||||||
|
assert Enum.count(first_page_resp["items"]) == 50 |
||||||
|
assert first_page_resp["next_page_params"] != nil |
||||||
|
compare_item(Enum.at(list, 50), Enum.at(first_page_resp["items"], 0)) |
||||||
|
compare_item(Enum.at(list, 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(list, 0), Enum.at(second_page_resp["items"], 0)) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,22 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.ConfigControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
describe "/config/json-rpc-url" do |
||||||
|
test "get json rps url if set", %{conn: conn} do |
||||||
|
url = "http://rps.url:1234/v1" |
||||||
|
Application.put_env(:block_scout_web, :json_rpc, url) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/config/json-rpc-url") |
||||||
|
|
||||||
|
assert %{"json_rpc_url" => ^url} = json_response(request, 200) |
||||||
|
end |
||||||
|
|
||||||
|
test "get empty json rps url if not set", %{conn: conn} do |
||||||
|
Application.put_env(:block_scout_web, :json_rpc, nil) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/config/json-rpc-url") |
||||||
|
|
||||||
|
assert %{"json_rpc_url" => nil} = json_response(request, 200) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,65 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.MainPageControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
alias Explorer.Chain.{Address, Block, Transaction} |
||||||
|
|
||||||
|
setup do |
||||||
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) |
||||||
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id()) |
||||||
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) |
||||||
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
describe "/main-page/blocks" do |
||||||
|
test "get empty list when no blocks", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/main-page/blocks") |
||||||
|
assert [] = json_response(request, 200) |
||||||
|
end |
||||||
|
|
||||||
|
test "get last 4 blocks", %{conn: conn} do |
||||||
|
blocks = insert_list(10, :block) |> Enum.take(-4) |> Enum.reverse() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/main-page/blocks") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response) == 4 |
||||||
|
|
||||||
|
for i <- 0..3 do |
||||||
|
compare_item(Enum.at(blocks, i), Enum.at(response, i)) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/main-page/transactions" do |
||||||
|
test "get empty list when no txs", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/main-page/transactions") |
||||||
|
assert [] = json_response(request, 200) |
||||||
|
end |
||||||
|
|
||||||
|
test "get last 5 txs", %{conn: conn} do |
||||||
|
txs = insert_list(10, :transaction) |> with_block() |> Enum.take(-6) |> Enum.reverse() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/main-page/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response) == 6 |
||||||
|
|
||||||
|
for i <- 0..5 do |
||||||
|
compare_item(Enum.at(txs, i), Enum.at(response, i)) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Block{} = block, json) do |
||||||
|
assert to_string(block.hash) == json["hash"] |
||||||
|
assert block.number == json["height"] |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Transaction{} = transaction, json) do |
||||||
|
assert to_string(transaction.hash) == json["hash"] |
||||||
|
assert transaction.block_number == json["block"] |
||||||
|
assert to_string(transaction.value.value) == json["value"] |
||||||
|
assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] |
||||||
|
assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,147 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.SearchControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
alias Explorer.Chain.Address |
||||||
|
|
||||||
|
setup do |
||||||
|
insert(:block) |
||||||
|
insert(:unique_smart_contract) |
||||||
|
insert(:unique_token) |
||||||
|
insert(:transaction) |
||||||
|
address = insert(:address) |
||||||
|
insert(:unique_address_name, address: address) |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
describe "/search" do |
||||||
|
test "search block", %{conn: conn} do |
||||||
|
block = insert(:block) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{block.hash}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "block" |
||||||
|
assert item["block_number"] == block.number |
||||||
|
assert item["block_hash"] == to_string(block.hash) |
||||||
|
assert item["url"] =~ to_string(block.hash) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{block.number}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "block" |
||||||
|
assert item["block_number"] == block.number |
||||||
|
assert item["block_hash"] == to_string(block.hash) |
||||||
|
assert item["url"] =~ to_string(block.hash) |
||||||
|
end |
||||||
|
|
||||||
|
test "search address", %{conn: conn} do |
||||||
|
address = insert(:address) |
||||||
|
name = insert(:unique_address_name, address: address) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{address.hash}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "address" |
||||||
|
assert item["name"] == name.name |
||||||
|
assert item["address"] == Address.checksum(address.hash) |
||||||
|
assert item["url"] =~ Address.checksum(address.hash) |
||||||
|
end |
||||||
|
|
||||||
|
test "search contract", %{conn: conn} do |
||||||
|
contract = insert(:unique_smart_contract) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{contract.name}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "contract" |
||||||
|
assert item["name"] == contract.name |
||||||
|
assert item["address"] == Address.checksum(contract.address_hash) |
||||||
|
assert item["url"] =~ Address.checksum(contract.address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
test "check pagination", %{conn: conn} do |
||||||
|
name = "contract" |
||||||
|
contracts = insert_list(51, :smart_contract, name: name) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{name}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 50 |
||||||
|
assert response["next_page_params"] != nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "contract" |
||||||
|
assert item["name"] == name |
||||||
|
|
||||||
|
request_2 = get(conn, "/api/v2/search", response["next_page_params"]) |
||||||
|
assert response_2 = json_response(request_2, 200) |
||||||
|
|
||||||
|
assert Enum.count(response_2["items"]) == 1 |
||||||
|
assert response_2["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response_2["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "contract" |
||||||
|
assert item["name"] == name |
||||||
|
|
||||||
|
assert item not in response["items"] |
||||||
|
end |
||||||
|
|
||||||
|
test "search token", %{conn: conn} do |
||||||
|
token = insert(:unique_token) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{token.name}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "token" |
||||||
|
assert item["name"] == token.name |
||||||
|
assert item["symbol"] == token.symbol |
||||||
|
assert item["address"] == Address.checksum(token.contract_address_hash) |
||||||
|
assert item["token_url"] =~ Address.checksum(token.contract_address_hash) |
||||||
|
assert item["address_url"] =~ Address.checksum(token.contract_address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
test "search transaction", %{conn: conn} do |
||||||
|
tx = insert(:transaction) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/search?q=#{tx.hash}") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
|
||||||
|
item = Enum.at(response["items"], 0) |
||||||
|
|
||||||
|
assert item["type"] == "transaction" |
||||||
|
assert item["tx_hash"] == to_string(tx.hash) |
||||||
|
assert item["url"] =~ to_string(tx.hash) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,57 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.StatsControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
alias Explorer.Counters.{AddressesCounter, AverageBlockTime} |
||||||
|
|
||||||
|
describe "/stats" do |
||||||
|
setup do |
||||||
|
start_supervised!(AddressesCounter) |
||||||
|
start_supervised!(AverageBlockTime) |
||||||
|
|
||||||
|
Application.put_env(:explorer, AverageBlockTime, enabled: true) |
||||||
|
|
||||||
|
on_exit(fn -> |
||||||
|
Application.put_env(:explorer, AverageBlockTime, enabled: false) |
||||||
|
end) |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
test "get all fields", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/stats") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert Map.has_key?(response, "total_blocks") |
||||||
|
assert Map.has_key?(response, "total_addresses") |
||||||
|
assert Map.has_key?(response, "total_transactions") |
||||||
|
assert Map.has_key?(response, "average_block_time") |
||||||
|
assert Map.has_key?(response, "coin_price") |
||||||
|
assert Map.has_key?(response, "total_gas_used") |
||||||
|
assert Map.has_key?(response, "transactions_today") |
||||||
|
assert Map.has_key?(response, "gas_used_today") |
||||||
|
assert Map.has_key?(response, "gas_prices") |
||||||
|
assert Map.has_key?(response, "static_gas_price") |
||||||
|
assert Map.has_key?(response, "market_cap") |
||||||
|
assert Map.has_key?(response, "network_utilization_percentage") |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/stats/charts/market" do |
||||||
|
test "get empty data", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/stats/charts/market") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert response["chart_data"] == [] |
||||||
|
assert response["available_supply"] == 0 |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/stats/charts/transactions" do |
||||||
|
test "get empty data", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/stats/charts/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
assert response["chart_data"] == [] |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,582 @@ |
|||||||
|
defmodule BlockScoutWeb.API.V2.TransactionControllerTest do |
||||||
|
use BlockScoutWeb.ConnCase |
||||||
|
|
||||||
|
alias Explorer.Chain.{Address, InternalTransaction, Log, TokenTransfer, Transaction} |
||||||
|
|
||||||
|
setup do |
||||||
|
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) |
||||||
|
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) |
||||||
|
|
||||||
|
:ok |
||||||
|
end |
||||||
|
|
||||||
|
describe "/transactions" do |
||||||
|
test "empty list", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/transactions") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "non empty list", %{conn: conn} do |
||||||
|
1 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "txs with next_page_params", %{conn: conn} do |
||||||
|
txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/transactions", response["next_page_params"]) |
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, txs) |
||||||
|
end |
||||||
|
|
||||||
|
test "filter=pending", %{conn: conn} do |
||||||
|
pending_txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|
||||||
|
_mined_txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
filter = %{"filter" => "pending"} |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) |
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, pending_txs) |
||||||
|
end |
||||||
|
|
||||||
|
test "filter=validated", %{conn: conn} do |
||||||
|
_pending_txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|
||||||
|
mined_txs = |
||||||
|
51 |
||||||
|
|> insert_list(:transaction) |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
filter = %{"filter" => "validated"} |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/transactions", Map.merge(response["next_page_params"], filter)) |
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, mined_txs) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/transactions/{tx_hash}" do |
||||||
|
test "return 404 on non existing tx", %{conn: conn} do |
||||||
|
tx = build(:transaction) |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}") |
||||||
|
|
||||||
|
assert %{"message" => "Not found"} = json_response(request, 404) |
||||||
|
end |
||||||
|
|
||||||
|
test "return 422 on invalid tx hash", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/transactions/0x") |
||||||
|
|
||||||
|
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return existing tx", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/" <> to_string(tx.hash)) |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
compare_item(tx, response) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/transactions/{tx_hash}/internal-transactions" do |
||||||
|
test "return empty list on non existing tx", %{conn: conn} do |
||||||
|
tx = build(:transaction) |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return 422 on invalid tx hash", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/transactions/0x/internal-transactions") |
||||||
|
|
||||||
|
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return empty list", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return relevant internal transaction", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
transaction: tx, |
||||||
|
index: 0, |
||||||
|
block_number: tx.block_number, |
||||||
|
transaction_index: tx.index, |
||||||
|
block_hash: tx.block_hash, |
||||||
|
block_index: 0 |
||||||
|
) |
||||||
|
|
||||||
|
internal_tx = |
||||||
|
insert(:internal_transaction, |
||||||
|
transaction: tx, |
||||||
|
index: 1, |
||||||
|
block_number: tx.block_number, |
||||||
|
transaction_index: tx.index, |
||||||
|
block_hash: tx.block_hash, |
||||||
|
block_index: 1 |
||||||
|
) |
||||||
|
|
||||||
|
tx_1 = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
0..5 |
||||||
|
|> Enum.map(fn index -> |
||||||
|
insert(:internal_transaction, |
||||||
|
transaction: tx_1, |
||||||
|
index: index, |
||||||
|
block_number: tx_1.block_number, |
||||||
|
transaction_index: tx_1.index, |
||||||
|
block_hash: tx_1.block_hash, |
||||||
|
block_index: index |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
compare_item(internal_tx, Enum.at(response["items"], 0)) |
||||||
|
end |
||||||
|
|
||||||
|
test "return list with next_page_params", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
insert(:internal_transaction, |
||||||
|
transaction: tx, |
||||||
|
index: 0, |
||||||
|
block_number: tx.block_number, |
||||||
|
transaction_index: tx.index, |
||||||
|
block_hash: tx.block_hash, |
||||||
|
block_index: 0 |
||||||
|
) |
||||||
|
|
||||||
|
internal_txs = |
||||||
|
51..1 |
||||||
|
|> Enum.map(fn index -> |
||||||
|
insert(:internal_transaction, |
||||||
|
transaction: tx, |
||||||
|
index: index, |
||||||
|
block_number: tx.block_number, |
||||||
|
transaction_index: tx.index, |
||||||
|
block_hash: tx.block_hash, |
||||||
|
block_index: index |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/internal-transactions", response["next_page_params"]) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, internal_txs) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/transactions/{tx_hash}/logs" do |
||||||
|
test "return empty list on non existing tx", %{conn: conn} do |
||||||
|
tx = build(:transaction) |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return 422 on invalid tx hash", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/transactions/0x/logs") |
||||||
|
|
||||||
|
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return empty list", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return relevant log", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
log = |
||||||
|
insert(:log, |
||||||
|
transaction: tx, |
||||||
|
index: 1, |
||||||
|
block: tx.block, |
||||||
|
block_number: tx.block_number |
||||||
|
) |
||||||
|
|
||||||
|
tx_1 = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
0..5 |
||||||
|
|> Enum.map(fn index -> |
||||||
|
insert(:log, |
||||||
|
transaction: tx_1, |
||||||
|
index: index, |
||||||
|
block: tx_1.block, |
||||||
|
block_number: tx_1.block_number |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
compare_item(log, Enum.at(response["items"], 0)) |
||||||
|
end |
||||||
|
|
||||||
|
test "return list with next_page_params", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
logs = |
||||||
|
50..0 |
||||||
|
|> Enum.map(fn index -> |
||||||
|
insert(:log, |
||||||
|
transaction: tx, |
||||||
|
index: index, |
||||||
|
block: tx.block, |
||||||
|
block_number: tx.block_number |
||||||
|
) |
||||||
|
end) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/logs", response["next_page_params"]) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, logs) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "/transactions/{tx_hash}/token-transfers" do |
||||||
|
test "return empty list on non existing tx", %{conn: conn} do |
||||||
|
tx = build(:transaction) |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return 422 on invalid tx hash", %{conn: conn} do |
||||||
|
request = get(conn, "/api/v2/transactions/0x/token-transfers") |
||||||
|
|
||||||
|
assert %{"message" => "Invalid parameter(s)"} = json_response(request, 422) |
||||||
|
end |
||||||
|
|
||||||
|
test "return empty list", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert response["items"] == [] |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
end |
||||||
|
|
||||||
|
test "return relevant token transfer", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
token_transfer = insert(:token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) |
||||||
|
|
||||||
|
tx_1 = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
insert_list(6, :token_transfer, transaction: tx_1, block: tx_1.block, block_number: tx_1.block_number) |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") |
||||||
|
|
||||||
|
assert response = json_response(request, 200) |
||||||
|
assert Enum.count(response["items"]) == 1 |
||||||
|
assert response["next_page_params"] == nil |
||||||
|
compare_item(token_transfer, Enum.at(response["items"], 0)) |
||||||
|
end |
||||||
|
|
||||||
|
test "return list with next_page_params", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
token_transfers = |
||||||
|
insert_list(51, :token_transfer, transaction: tx, block: tx.block, block_number: tx.block_number) |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers") |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", response["next_page_params"]) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, token_transfers) |
||||||
|
end |
||||||
|
|
||||||
|
test "check filters", %{conn: conn} do |
||||||
|
tx = |
||||||
|
:transaction |
||||||
|
|> insert() |
||||||
|
|> with_block() |
||||||
|
|
||||||
|
erc_1155_token = insert(:token, type: "ERC-1155") |
||||||
|
|
||||||
|
erc_1155_tt = |
||||||
|
for x <- 0..50 do |
||||||
|
insert(:token_transfer, |
||||||
|
transaction: tx, |
||||||
|
block: tx.block, |
||||||
|
block_number: tx.block_number, |
||||||
|
token_contract_address: erc_1155_token.contract_address, |
||||||
|
token_ids: [x] |
||||||
|
) |
||||||
|
end |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
erc_721_token = insert(:token, type: "ERC-721") |
||||||
|
|
||||||
|
erc_721_tt = |
||||||
|
for x <- 0..50 do |
||||||
|
insert(:token_transfer, |
||||||
|
transaction: tx, |
||||||
|
block: tx.block, |
||||||
|
block_number: tx.block_number, |
||||||
|
token_contract_address: erc_721_token.contract_address, |
||||||
|
token_ids: [x] |
||||||
|
) |
||||||
|
end |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
erc_20_token = insert(:token, type: "ERC-20") |
||||||
|
|
||||||
|
erc_20_tt = |
||||||
|
for _ <- 0..50 do |
||||||
|
insert(:token_transfer, |
||||||
|
transaction: tx, |
||||||
|
block: tx.block, |
||||||
|
block_number: tx.block_number, |
||||||
|
token_contract_address: erc_20_token.contract_address |
||||||
|
) |
||||||
|
end |
||||||
|
|> Enum.reverse() |
||||||
|
|
||||||
|
# -- ERC-20 -- |
||||||
|
filter = %{"type" => "ERC-20"} |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get( |
||||||
|
conn, |
||||||
|
"/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", |
||||||
|
Map.merge(response["next_page_params"], filter) |
||||||
|
) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, erc_20_tt) |
||||||
|
# -- ------ -- |
||||||
|
|
||||||
|
# -- ERC-721 -- |
||||||
|
filter = %{"type" => "ERC-721"} |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get( |
||||||
|
conn, |
||||||
|
"/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", |
||||||
|
Map.merge(response["next_page_params"], filter) |
||||||
|
) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, erc_721_tt) |
||||||
|
# -- ------ -- |
||||||
|
|
||||||
|
# -- ERC-1155 -- |
||||||
|
filter = %{"type" => "ERC-1155"} |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get( |
||||||
|
conn, |
||||||
|
"/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", |
||||||
|
Map.merge(response["next_page_params"], filter) |
||||||
|
) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
check_paginated_response(response, response_2nd_page, erc_1155_tt) |
||||||
|
# -- ------ -- |
||||||
|
|
||||||
|
# two filters simultaneously |
||||||
|
filter = %{"type" => "ERC-1155,ERC-20"} |
||||||
|
request = get(conn, "/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", filter) |
||||||
|
assert response = json_response(request, 200) |
||||||
|
|
||||||
|
request_2nd_page = |
||||||
|
get( |
||||||
|
conn, |
||||||
|
"/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", |
||||||
|
Map.merge(response["next_page_params"], filter) |
||||||
|
) |
||||||
|
|
||||||
|
assert response_2nd_page = json_response(request_2nd_page, 200) |
||||||
|
|
||||||
|
assert Enum.count(response["items"]) == 50 |
||||||
|
assert response["next_page_params"] != nil |
||||||
|
compare_item(Enum.at(erc_1155_tt, 50), Enum.at(response["items"], 0)) |
||||||
|
compare_item(Enum.at(erc_1155_tt, 1), Enum.at(response["items"], 49)) |
||||||
|
|
||||||
|
assert Enum.count(response_2nd_page["items"]) == 50 |
||||||
|
assert response_2nd_page["next_page_params"] != nil |
||||||
|
compare_item(Enum.at(erc_1155_tt, 0), Enum.at(response_2nd_page["items"], 0)) |
||||||
|
compare_item(Enum.at(erc_20_tt, 50), Enum.at(response_2nd_page["items"], 1)) |
||||||
|
compare_item(Enum.at(erc_20_tt, 2), Enum.at(response_2nd_page["items"], 49)) |
||||||
|
|
||||||
|
request_3rd_page = |
||||||
|
get( |
||||||
|
conn, |
||||||
|
"/api/v2/transactions/#{to_string(tx.hash)}/token-transfers", |
||||||
|
Map.merge(response_2nd_page["next_page_params"], filter) |
||||||
|
) |
||||||
|
|
||||||
|
assert response_3rd_page = json_response(request_3rd_page, 200) |
||||||
|
assert Enum.count(response_3rd_page["items"]) == 2 |
||||||
|
assert response_3rd_page["next_page_params"] == nil |
||||||
|
compare_item(Enum.at(erc_20_tt, 1), Enum.at(response_3rd_page["items"], 0)) |
||||||
|
compare_item(Enum.at(erc_20_tt, 0), Enum.at(response_3rd_page["items"], 1)) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Transaction{} = transaction, json) do |
||||||
|
assert to_string(transaction.hash) == json["hash"] |
||||||
|
assert transaction.block_number == json["block"] |
||||||
|
assert to_string(transaction.value.value) == json["value"] |
||||||
|
assert Address.checksum(transaction.from_address_hash) == json["from"]["hash"] |
||||||
|
assert Address.checksum(transaction.to_address_hash) == json["to"]["hash"] |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%InternalTransaction{} = internal_tx, json) do |
||||||
|
assert internal_tx.block_number == json["block"] |
||||||
|
assert to_string(internal_tx.gas) == json["gas_limit"] |
||||||
|
assert internal_tx.index == json["index"] |
||||||
|
assert to_string(internal_tx.transaction_hash) == json["transaction_hash"] |
||||||
|
assert Address.checksum(internal_tx.from_address_hash) == json["from"]["hash"] |
||||||
|
assert Address.checksum(internal_tx.to_address_hash) == json["to"]["hash"] |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%Log{} = log, json) do |
||||||
|
assert to_string(log.data) == json["data"] |
||||||
|
assert log.index == json["index"] |
||||||
|
assert Address.checksum(log.address_hash) == json["address"]["hash"] |
||||||
|
end |
||||||
|
|
||||||
|
defp compare_item(%TokenTransfer{} = token_transfer, json) do |
||||||
|
assert Address.checksum(token_transfer.from_address_hash) == json["from"]["hash"] |
||||||
|
assert Address.checksum(token_transfer.to_address_hash) == json["to"]["hash"] |
||||||
|
assert to_string(token_transfer.transaction_hash) == json["tx_hash"] |
||||||
|
end |
||||||
|
|
||||||
|
defp check_paginated_response(first_page_resp, second_page_resp, txs) do |
||||||
|
assert Enum.count(first_page_resp["items"]) == 50 |
||||||
|
assert first_page_resp["next_page_params"] != nil |
||||||
|
compare_item(Enum.at(txs, 50), Enum.at(first_page_resp["items"], 0)) |
||||||
|
compare_item(Enum.at(txs, 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(txs, 0), Enum.at(second_page_resp["items"], 0)) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,27 @@ |
|||||||
|
defmodule Explorer.Chain.Cache.TransactionsApiV2 do |
||||||
|
@moduledoc """ |
||||||
|
Caches the latest imported transactions |
||||||
|
""" |
||||||
|
|
||||||
|
alias Explorer.Chain.Transaction |
||||||
|
|
||||||
|
use Explorer.Chain.OrderedCache, |
||||||
|
name: :transactions_api_v2, |
||||||
|
max_size: 51, |
||||||
|
preloads: [ |
||||||
|
:block, |
||||||
|
created_contract_address: :names, |
||||||
|
from_address: :names, |
||||||
|
to_address: :names |
||||||
|
], |
||||||
|
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval], |
||||||
|
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl] |
||||||
|
|
||||||
|
@type element :: Transaction.t() |
||||||
|
|
||||||
|
@type id :: {non_neg_integer(), non_neg_integer()} |
||||||
|
|
||||||
|
def element_to_id(%Transaction{block_number: block_number, index: index}) do |
||||||
|
{block_number, index} |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue