From de97c6b8f97fe10095ceb2ba9e8162d9d4989250 Mon Sep 17 00:00:00 2001 From: robertoschneiders Date: Wed, 31 Oct 2018 18:41:23 -0300 Subject: [PATCH 1/4] Add gettxinfo API endpoint --- .../api/rpc/transaction_controller.ex | 34 ++++++ .../lib/block_scout_web/etherscan.ex | 113 ++++++++++++++++++ .../views/api/rpc/transaction_view.ex | 43 +++++++ .../api/rpc/transaction_controller_test.exs | 101 ++++++++++++++++ 4 files changed, 291 insertions(+) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex index a167d92638..7b5104a458 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -3,6 +3,26 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do alias Explorer.Chain + def gettxinfo(conn, params) do + with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), + {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param), + {:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash) do + max_block_number = max_block_number() + + logs = Chain.transaction_to_logs(transaction) + render(conn, :gettxinfo, %{transaction: transaction, max_block_number: max_block_number, logs: logs}) + else + {:transaction, :error} -> + render(conn, :error, error: "Transaction not found") + + {:txhash_param, :error} -> + render(conn, :error, error: "Query parameter txhash is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid txhash format") + end + end + def gettxreceiptstatus(conn, params) do with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), {:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do @@ -35,6 +55,13 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do {:txhash_param, Map.fetch(params, "txhash")} end + defp transaction_from_hash(transaction_hash) do + case Chain.hash_to_transaction(transaction_hash, necessity_by_association: %{block: :required}) do + {:error, :not_found} -> {:transaction, :error} + {:ok, transaction} -> {:transaction, {:ok, transaction}} + end + end + defp to_transaction_hash(transaction_hash_string) do {:format, Chain.string_to_transaction_hash(transaction_hash_string)} end @@ -54,4 +81,11 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do _ -> "" end end + + defp max_block_number do + case Chain.max_block_number() do + {:ok, number} -> number + {:error, :not_found} -> 0 + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 033be85e52..4bb6a624d2 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -321,6 +321,30 @@ defmodule BlockScoutWeb.Etherscan do "result" => nil } + @transaction_gettxinfo_example_value %{ + "status" => "1", + "result" => %{ + "blockNumber" => "3", + "confirmations" => "0", + "from" => "0x000000000000000000000000000000000000000c", + "gasLimit" => "91966", + "gasUsed" => "95123", + "hash" => "0x0000000000000000000000000000000000000000000000000000000000000004", + "input" => "0x04", + "logs" => [ + %{ + "address" => "0x000000000000000000000000000000000000000e", + "data" => "0x00", + "topics" => ["First Topic", "Second Topic", "Third Topic", "Fourth Topic"] + } + ], + "success" => true, + "timeStamp" => "1541018182", + "to" => "0x000000000000000000000000000000000000000d", + "value" => "67612" + } + } + @transaction_gettxreceiptstatus_example_value %{ "status" => "1", "message" => "OK", @@ -428,6 +452,28 @@ defmodule BlockScoutWeb.Etherscan do example: ~s("18") } + @logs_details %{ + name: "Log Detail", + fields: %{ + address: @address_hash_type, + topics: %{ + type: "topics", + definition: "An array including the topics for the log.", + example: ~s(["0xf63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a8545"]) + }, + data: %{ + type: "data", + definition: "Non-indexed log parameters.", + example: ~s("0x") + }, + blockNumber: %{ + type: "block number", + definition: "A nonnegative number used to identify blocks.", + example: ~s("0x5c958") + } + } + } + @address_balance %{ name: "AddressBalance", fields: %{ @@ -735,6 +781,35 @@ defmodule BlockScoutWeb.Etherscan do } } + @transaction_info_model %{ + name: "TransactionInfo", + fields: %{ + hash: @transaction_hash_type, + timeStamp: %{ + type: "timestamp", + definition: "The transaction's block-timestamp.", + example: ~s("1439232889") + }, + blockNumber: @block_number_type, + confirmations: @confirmation_type, + success: %{ + type: "boolean", + definition: "Flag for success during tx execution", + example: ~s(true) + }, + from: @address_hash_type, + to: @address_hash_type, + value: @wei_type, + input: @input_type, + gasLimit: @wei_type, + gasUsed: @gas_type, + logs: %{ + type: "array", + array_type: @logs_details + } + } + } + @transaction_status_model %{ name: "TransactionStatus", fields: %{ @@ -1569,6 +1644,43 @@ defmodule BlockScoutWeb.Etherscan do ] } + @transaction_gettxinfo_action %{ + name: "gettxinfo", + description: "Get transaction info.", + required_params: [ + %{ + key: "txhash", + placeholder: "transactionHash", + type: "string", + description: "Transaction hash. Hash of contents of the transaction." + } + ], + optional_params: [], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@transaction_gettxinfo_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "model", + model: @transaction_info_model + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@transaction_gettxreceiptstatus_example_value_error) + } + ] + } + @transaction_gettxreceiptstatus_action %{ name: "gettxreceiptstatus", description: "Get transaction receipt status.", @@ -1692,6 +1804,7 @@ defmodule BlockScoutWeb.Etherscan do @transaction_module %{ name: "transaction", actions: [ + @transaction_gettxinfo_action, @transaction_gettxreceiptstatus_action, @transaction_getstatus_action ] diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 6c19ac24c0..7dd88efe1b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -3,6 +3,18 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do alias BlockScoutWeb.API.RPC.RPCView + def render("gettxinfo.json", %{transaction: transaction, max_block_number: max_block_number, logs: logs}) do + try do + data = prepare_transaction(transaction, max_block_number, logs) + IO.puts "after prepare" + IO.inspect data + RPCView.render("show.json", data: data) + catch + x -> "Got #{x}" + :exit, _ -> "not really" + end + end + def render("gettxreceiptstatus.json", %{status: status}) do prepared_status = prepare_tx_receipt_status(status) RPCView.render("show.json", data: %{"status" => prepared_status}) @@ -44,4 +56,35 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do "errDescription" => error |> Atom.to_string() |> String.replace("_", " ") } end + + defp prepare_transaction(transaction, max_block_number, logs) do + %{ + "hash" => "#{transaction.hash}", + "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", + "blockNumber" => "#{transaction.block_number}", + "confirmations" => "#{(max_block_number - transaction.block_number)}", + "success" => if(transaction.status == :ok, do: true, else: false), + "from" => "#{transaction.from_address_hash}", + "to" => "#{transaction.to_address_hash}", + "value" => "#{transaction.value.value}", + "input" => "#{transaction.input}", + "gasLimit" => "#{transaction.gas}", + "gasUsed" => "#{transaction.gas_used}", + "logs" => Enum.map(logs, &prepare_log/1) + } + end + + defp prepare_log(log) do + %{ + "address" => "#{log.address_hash}", + "topics" => get_topics(log), + "data" => "#{log.data}" + } + end + + defp get_topics(log) do + log + |> Map.take([:first_topic, :second_topic, :third_topic, :fourth_topic]) + |> Map.values() + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 008bb64300..026eb43890 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -310,4 +310,105 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do assert response["message"] == "OK" end end + + describe "gettxinfo" do + test "with missing txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "txhash is required" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with an invalid txhash", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "badhash" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Invalid txhash format" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a txhash that doesn't exist", %{conn: conn} do + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170" + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["message"] =~ "Transaction not found" + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "with a txhash with ok status", %{conn: conn} do + block = insert(:block) + + transaction = + :transaction + |> insert() + |> with_block(block, status: :ok) + + address = insert(:address) + insert(:log, address: address, transaction: transaction) + + params = %{ + "module" => "transaction", + "action" => "gettxinfo", + "txhash" => "#{transaction.hash}" + } + + expected_result = %{ + "hash" => "#{transaction.hash}", + "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", + "blockNumber" => "#{transaction.block_number}", + "confirmations" => "0", + "success" => true, + "from" => "#{transaction.from_address_hash}", + "to" => "#{transaction.to_address_hash}", + "value" => "#{transaction.value.value}", + "input" => "#{transaction.input}", + "gasLimit" => "#{transaction.gas}", + "gasUsed" => "#{transaction.gas_used}", + "logs" => [%{ + "address" => "#{address}", + "data" => "0x00", + "topics" => [nil, nil, nil, nil] + }] + } + + assert response = + conn + |> get("/api", params) + |> json_response(200) + + assert response["result"] == expected_result + assert response["status"] == "1" + assert response["message"] == "OK" + end + end end From 6a3b3b3c430f1e54dad9b0c6b3d8dd6910864655 Mon Sep 17 00:00:00 2001 From: robertoschneiders Date: Wed, 31 Oct 2018 18:45:32 -0300 Subject: [PATCH 2/4] Removes debug code --- .../block_scout_web/views/api/rpc/transaction_view.ex | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 7dd88efe1b..b12081ec86 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -4,15 +4,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do alias BlockScoutWeb.API.RPC.RPCView def render("gettxinfo.json", %{transaction: transaction, max_block_number: max_block_number, logs: logs}) do - try do - data = prepare_transaction(transaction, max_block_number, logs) - IO.puts "after prepare" - IO.inspect data - RPCView.render("show.json", data: data) - catch - x -> "Got #{x}" - :exit, _ -> "not really" - end + data = prepare_transaction(transaction, max_block_number, logs) + RPCView.render("show.json", data: data) end def render("gettxreceiptstatus.json", %{status: status}) do From b50b80dd1e2853ea4949a096948d32a8c2390689 Mon Sep 17 00:00:00 2001 From: robertoschneiders Date: Wed, 31 Oct 2018 19:39:38 -0300 Subject: [PATCH 3/4] Fixes broken test --- .../views/api/rpc/transaction_view.ex | 2 +- .../api/rpc/transaction_controller_test.exs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index b12081ec86..2820163235 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -55,7 +55,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do "hash" => "#{transaction.hash}", "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", "blockNumber" => "#{transaction.block_number}", - "confirmations" => "#{(max_block_number - transaction.block_number)}", + "confirmations" => "#{max_block_number - transaction.block_number}", "success" => if(transaction.status == :ok, do: true, else: false), "from" => "#{transaction.from_address_hash}", "to" => "#{transaction.to_address_hash}", diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 026eb43890..7d28864e5f 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -374,7 +374,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do |> with_block(block, status: :ok) address = insert(:address) - insert(:log, address: address, transaction: transaction) + log = insert(:log, address: address, transaction: transaction) params = %{ "module" => "transaction", @@ -382,7 +382,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do "txhash" => "#{transaction.hash}" } - expected_result = %{ + expected_result = %{ "hash" => "#{transaction.hash}", "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", "blockNumber" => "#{transaction.block_number}", @@ -394,11 +394,13 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do "input" => "#{transaction.input}", "gasLimit" => "#{transaction.gas}", "gasUsed" => "#{transaction.gas_used}", - "logs" => [%{ - "address" => "#{address}", - "data" => "0x00", - "topics" => [nil, nil, nil, nil] - }] + "logs" => [ + %{ + "address" => "#{address}", + "data" => "#{log.data}", + "topics" => [nil, nil, nil, nil] + } + ] } assert response = From 505568657889b24a5b89d2e9aaabe8c243eee885 Mon Sep 17 00:00:00 2001 From: robertoschneiders Date: Thu, 1 Nov 2018 09:55:13 -0300 Subject: [PATCH 4/4] Fixes the topics order in the gettxinfo endpoint --- .../block_scout_web/views/api/rpc/transaction_view.ex | 4 +--- .../api/rpc/transaction_controller_test.exs | 11 +++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 2820163235..73dae614e6 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -76,8 +76,6 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do end defp get_topics(log) do - log - |> Map.take([:first_topic, :second_topic, :third_topic, :fourth_topic]) - |> Map.values() + [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic] end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 7d28864e5f..bc0cfcca96 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -374,7 +374,14 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do |> with_block(block, status: :ok) address = insert(:address) - log = insert(:log, address: address, transaction: transaction) + + log = + insert(:log, + address: address, + transaction: transaction, + first_topic: "first topic", + second_topic: "second topic" + ) params = %{ "module" => "transaction", @@ -398,7 +405,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do %{ "address" => "#{address}", "data" => "#{log.data}", - "topics" => [nil, nil, nil, nil] + "topics" => ["first topic", "second topic", nil, nil] } ] }