Merge pull request #1034 from RobertoSchneiders/gettxinfo

Add gettxinfo API endpoint
pull/1050/head
Andrew Cravenho 6 years ago committed by GitHub
commit 8ef6d77388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  2. 113
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  3. 34
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  4. 110
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs

@ -3,6 +3,26 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
alias Explorer.Chain 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 def gettxreceiptstatus(conn, params) do
with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params), with {:txhash_param, {:ok, txhash_param}} <- fetch_txhash(params),
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param) do {: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")} {:txhash_param, Map.fetch(params, "txhash")}
end 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 defp to_transaction_hash(transaction_hash_string) do
{:format, Chain.string_to_transaction_hash(transaction_hash_string)} {:format, Chain.string_to_transaction_hash(transaction_hash_string)}
end end
@ -54,4 +81,11 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
_ -> "" _ -> ""
end end
end end
defp max_block_number do
case Chain.max_block_number() do
{:ok, number} -> number
{:error, :not_found} -> 0
end
end
end end

@ -321,6 +321,30 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "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 %{ @transaction_gettxreceiptstatus_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -428,6 +452,28 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("18") 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 %{ @address_balance %{
name: "AddressBalance", name: "AddressBalance",
fields: %{ 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 %{ @transaction_status_model %{
name: "TransactionStatus", name: "TransactionStatus",
fields: %{ 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 %{ @transaction_gettxreceiptstatus_action %{
name: "gettxreceiptstatus", name: "gettxreceiptstatus",
description: "Get transaction receipt status.", description: "Get transaction receipt status.",
@ -1692,6 +1804,7 @@ defmodule BlockScoutWeb.Etherscan do
@transaction_module %{ @transaction_module %{
name: "transaction", name: "transaction",
actions: [ actions: [
@transaction_gettxinfo_action,
@transaction_gettxreceiptstatus_action, @transaction_gettxreceiptstatus_action,
@transaction_getstatus_action @transaction_getstatus_action
] ]

@ -3,6 +3,11 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.RPCView
def render("gettxinfo.json", %{transaction: transaction, max_block_number: max_block_number, logs: logs}) do
data = prepare_transaction(transaction, max_block_number, logs)
RPCView.render("show.json", data: data)
end
def render("gettxreceiptstatus.json", %{status: status}) do def render("gettxreceiptstatus.json", %{status: status}) do
prepared_status = prepare_tx_receipt_status(status) prepared_status = prepare_tx_receipt_status(status)
RPCView.render("show.json", data: %{"status" => prepared_status}) RPCView.render("show.json", data: %{"status" => prepared_status})
@ -44,4 +49,33 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
"errDescription" => error |> Atom.to_string() |> String.replace("_", " ") "errDescription" => error |> Atom.to_string() |> String.replace("_", " ")
} }
end 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.first_topic, log.second_topic, log.third_topic, log.fourth_topic]
end
end end

@ -310,4 +310,114 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["message"] == "OK" assert response["message"] == "OK"
end end
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)
log =
insert(:log,
address: address,
transaction: transaction,
first_topic: "first topic",
second_topic: "second topic"
)
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" => "#{log.data}",
"topics" => ["first topic", "second topic", 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 end

Loading…
Cancel
Save