diff --git a/.dialyzer-ignore b/.dialyzer-ignore
index 1535f582c8..79833b82e3 100644
--- a/.dialyzer-ignore
+++ b/.dialyzer-ignore
@@ -12,4 +12,6 @@ apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'fa
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324
-lib/block_scout_web/views/layout_view.ex:143
\ No newline at end of file
+lib/block_scout_web/views/layout_view.ex:143
+lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:21
+lib/block_scout_web/controllers/api/rpc/transaction_controller.ex:22
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f9b9b2283..a527f79bad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,20 @@
### Chore
+## 3.2.0-beta
+
+### Features
+- [#3154](https://github.com/poanetwork/blockscout/pull/3154) - Support of Hyperledger Besu client
+- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: logs decoding using implementation ABI
+- [#3153](https://github.com/poanetwork/blockscout/pull/3153) - Proxy contracts: methods decoding using implementation ABI
+- [#3149](https://github.com/poanetwork/blockscout/pull/3149) - Display and store revert reason of tx on demand at transaction details page and at gettxinfo API endpoint.
+
+### Fixes
+
+### Chore
+- [#3152](https://github.com/poanetwork/blockscout/pull/3152) - Fix contract compilation tests for old versions of compiler
+
+
## 3.1.3-beta
### Features
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 565ea4b6a5..4c7abad3fe 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
@@ -5,16 +5,30 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
alias Explorer.Chain
+ alias Explorer.Chain.Transaction
+
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),
+ {:transaction, {:ok, %Transaction{revert_reason: revert_reason, error: error} = transaction}} <-
+ transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do
logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs)
+ transaction_updated =
+ if error == "Reverted" do
+ if revert_reason == nil do
+ %Transaction{transaction | revert_reason: Chain.fetch_tx_revert_reason(transaction)}
+ else
+ transaction
+ end
+ else
+ transaction
+ end
+
render(conn, :gettxinfo, %{
- transaction: transaction,
+ transaction: transaction_updated,
block_height: Chain.block_height(),
logs: logs,
next_page_params: next_page_params(next_page, logs, params)
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 330b493d8c..ee831c26e0 100644
--- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex
+++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex
@@ -491,7 +491,8 @@ defmodule BlockScoutWeb.Etherscan do
"success" => true,
"timeStamp" => "1541018182",
"to" => "0x000000000000000000000000000000000000000d",
- "value" => "67612"
+ "value" => "67612",
+ revertReason: "No credit of that type"
}
}
@@ -630,6 +631,12 @@ defmodule BlockScoutWeb.Etherscan do
example: ~s("18")
}
+ @revert_reason_type %{
+ type: "revert_reason",
+ definition: "Revert reason of transaction.",
+ example: ~s("No credit of that type")
+ }
+
@logs_details %{
name: "Log Detail",
fields: %{
@@ -1031,7 +1038,8 @@ defmodule BlockScoutWeb.Etherscan do
logs: %{
type: "array",
array_type: @logs_details
- }
+ },
+ revertReason: @revert_reason_type
}
}
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
index ce01098d43..d54f71fb41 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
@@ -55,12 +55,16 @@
class: "dropdown-item #{tab_status("txs", @conn.request_path)}",
to: transaction_path(@conn, :index)
) %>
- <%= link(
- gettext("Pending"),
- class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
- "data-test": "pending_transactions_link",
- to: pending_transaction_path(@conn, :index)
- ) %>
+ <% json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments)%>
+ <% variant = Keyword.fetch!(json_rpc_named_arguments, :variant) %>
+ <%= if variant !== EthereumJSONRPC.Besu do %>
+ <%= link(
+ gettext("Pending"),
+ class: "dropdown-item #{tab_status("pending_transactions", @conn.request_path)}",
+ "data-test": "pending_transactions_link",
+ to: pending_transaction_path(@conn, :index)
+ ) %>
+ <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
index cca53f0865..5ee1fac85e 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex
@@ -53,6 +53,14 @@
+ <%= if status == {:error, "Reverted"} do %>
+
+ - <%= gettext "Revert reason" %>
+ -
+ <%= BlockScoutWeb.TransactionView.transaction_revert_reason(@transaction) %>
+
+
+ <% end %>
- <%= gettext "Block Number" %>
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 b9e8f7430d..4b81b11091 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
@@ -70,6 +70,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
"gasUsed" => "#{transaction.gas_used}",
"gasPrice" => "#{transaction.gas_price.value}",
"logs" => Enum.map(logs, &prepare_log/1),
+ "revertReason" => "#{transaction.revert_reason}",
"next_page_params" => next_page_params
}
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
index 1c01ab80a3..c128083916 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
@@ -237,6 +237,10 @@ defmodule BlockScoutWeb.TransactionView do
Chain.transaction_to_status(transaction)
end
+ def transaction_revert_reason(transaction) do
+ Chain.transaction_to_revert_reason(transaction)
+ end
+
def empty_exchange_rate?(exchange_rate) do
Token.null?(exchange_rate)
end
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index b4edc9e73c..ac3a6b36cb 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:196
+#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:247
+#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:72
+#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
@@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:336
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:333
+#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:251
+#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:249
+#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -636,7 +636,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@@ -666,7 +666,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
-#: lib/block_scout_web/templates/transaction/overview.html.eex:79
+#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@@ -737,7 +737,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -753,8 +753,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:115
-#: lib/block_scout_web/templates/transaction/overview.html.eex:119
+#: lib/block_scout_web/templates/transaction/overview.html.eex:123
+#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@@ -776,7 +776,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:248
+#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@@ -789,7 +789,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
-#: lib/block_scout_web/templates/transaction/overview.html.eex:84
+#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@@ -891,7 +891,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/views/transaction_view.ex:329
+#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@@ -906,7 +906,7 @@ msgstr ""
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
-#: lib/block_scout_web/views/transaction_view.ex:390
+#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@@ -922,7 +922,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
-#: lib/block_scout_web/views/transaction_view.ex:339
+#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:283
+#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
@@ -1158,9 +1158,9 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
-#: lib/block_scout_web/views/transaction_view.ex:246
-#: lib/block_scout_web/views/transaction_view.ex:280
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
+#: lib/block_scout_web/views/transaction_view.ex:250
+#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@@ -1196,19 +1196,19 @@ msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:109
+#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:393
+#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@@ -1254,14 +1254,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:145
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:101
+#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@@ -1518,7 +1518,7 @@ msgid "Type"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:122
+#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:277
+#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:273
+#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@@ -1799,8 +1799,8 @@ msgid "Transfers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:134
-#: lib/block_scout_web/templates/transaction/overview.html.eex:147
+#: lib/block_scout_web/templates/transaction/overview.html.eex:142
+#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
@@ -1838,7 +1838,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324
-#: lib/block_scout_web/views/transaction_view.ex:391
+#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@@ -1848,7 +1848,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331
-#: lib/block_scout_web/views/transaction_view.ex:392
+#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@@ -1881,26 +1881,26 @@ msgid "Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:232
+#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:328
+#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:214
+#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:327
+#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+msgid "Revert reason"
+msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index b4edc9e73c..ac3a6b36cb 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -13,7 +13,7 @@ msgstr[0] ""
msgstr[1] ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:196
+#: lib/block_scout_web/templates/transaction/overview.html.eex:204
msgid " Token Transfer"
msgstr ""
@@ -59,7 +59,7 @@ msgid "%{subnetwork} Explorer - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:247
+#: lib/block_scout_web/views/transaction_view.ex:251
msgid "(Awaiting internal transactions for status)"
msgstr ""
@@ -107,12 +107,12 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:81
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:71
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:75
msgid "Accounts"
msgstr ""
@@ -193,7 +193,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:72
+#: lib/block_scout_web/templates/transaction/overview.html.eex:80
msgid "Block Confirmations"
msgstr ""
@@ -213,7 +213,7 @@ msgid "Block Mined, awaiting import..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+#: lib/block_scout_web/templates/transaction/overview.html.eex:66
msgid "Block Number"
msgstr ""
@@ -355,12 +355,12 @@ msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:336
+#: lib/block_scout_web/views/transaction_view.ex:340
msgid "Contract Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:333
+#: lib/block_scout_web/views/transaction_view.ex:337
msgid "Contract Creation"
msgstr ""
@@ -621,12 +621,12 @@ msgid "Error trying to fetch balances."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:251
+#: lib/block_scout_web/views/transaction_view.ex:255
msgid "Error: %{reason}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/transaction_view.ex:249
+#: lib/block_scout_web/views/transaction_view.ex:253
msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
@@ -636,7 +636,7 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:95
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:99
msgid "Eth RPC"
msgstr ""
@@ -646,8 +646,8 @@ msgstr ""
#: lib/block_scout_web/templates/layout/app.html.eex:32
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:29
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
#: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether"
msgstr ""
@@ -666,7 +666,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:73
-#: lib/block_scout_web/templates/transaction/overview.html.eex:79
+#: lib/block_scout_web/templates/transaction/overview.html.eex:87
msgid "Nonce"
msgstr ""
@@ -737,7 +737,7 @@ msgid "Github"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:85
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:89
msgid "GraphQL"
msgstr ""
@@ -753,8 +753,8 @@ msgid "Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:115
-#: lib/block_scout_web/templates/transaction/overview.html.eex:119
+#: lib/block_scout_web/templates/transaction/overview.html.eex:123
+#: lib/block_scout_web/templates/transaction/overview.html.eex:127
msgid "Hex (Default)"
msgstr ""
@@ -776,7 +776,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:248
+#: lib/block_scout_web/views/transaction_view.ex:252
msgid "Success"
msgstr ""
@@ -789,7 +789,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21
#: lib/block_scout_web/templates/transaction/_tile.html.eex:32
-#: lib/block_scout_web/templates/transaction/overview.html.eex:84
+#: lib/block_scout_web/templates/transaction/overview.html.eex:92
msgid "TX Fee"
msgstr ""
@@ -891,7 +891,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:12
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:10
-#: lib/block_scout_web/views/transaction_view.ex:329
+#: lib/block_scout_web/views/transaction_view.ex:333
msgid "Token Transfer"
msgstr ""
@@ -906,7 +906,7 @@ msgstr ""
#: lib/block_scout_web/views/address_view.ex:325
#: lib/block_scout_web/views/tokens/instance/overview_view.ex:98
#: lib/block_scout_web/views/tokens/overview_view.ex:35
-#: lib/block_scout_web/views/transaction_view.ex:390
+#: lib/block_scout_web/views/transaction_view.ex:394
msgid "Token Transfers"
msgstr ""
@@ -922,7 +922,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/_logs.html.eex:18
-#: lib/block_scout_web/views/transaction_view.ex:339
+#: lib/block_scout_web/views/transaction_view.ex:343
msgid "Transaction"
msgstr ""
@@ -1005,7 +1005,7 @@ msgid "License ID"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:283
+#: lib/block_scout_web/templates/transaction/overview.html.eex:291
msgid "Limit"
msgstr ""
@@ -1158,9 +1158,9 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:59
-#: lib/block_scout_web/views/transaction_view.ex:246
-#: lib/block_scout_web/views/transaction_view.ex:280
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:62
+#: lib/block_scout_web/views/transaction_view.ex:250
+#: lib/block_scout_web/views/transaction_view.ex:284
msgid "Pending"
msgstr ""
@@ -1196,19 +1196,19 @@ msgid "Query"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:90
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:94
msgid "RPC"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:109
+#: lib/block_scout_web/templates/transaction/overview.html.eex:117
msgid "Raw Input"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24
#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7
-#: lib/block_scout_web/views/transaction_view.ex:393
+#: lib/block_scout_web/views/transaction_view.ex:397
msgid "Raw Trace"
msgstr ""
@@ -1254,14 +1254,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:141
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:158
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:145
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:162
msgid "Search"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
#: lib/block_scout_web/templates/layout/_topnav.html.eex:139
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:143
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
@@ -1488,7 +1488,7 @@ msgid "Transaction Inputs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:101
+#: lib/block_scout_web/templates/transaction/overview.html.eex:109
msgid "Transaction Speed"
msgstr ""
@@ -1518,7 +1518,7 @@ msgid "Type"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:122
+#: lib/block_scout_web/templates/transaction/overview.html.eex:130
msgid "UTF-8"
msgstr ""
@@ -1544,7 +1544,7 @@ msgid "Use the search box to find a hosted network, or select from the list of a
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:277
+#: lib/block_scout_web/templates/transaction/overview.html.eex:285
msgid "Used"
msgstr ""
@@ -1574,8 +1574,8 @@ msgid "Validator Info"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:181
-#: lib/block_scout_web/templates/transaction/overview.html.eex:255
+#: lib/block_scout_web/templates/transaction/overview.html.eex:189
+#: lib/block_scout_web/templates/transaction/overview.html.eex:263
msgid "Value"
msgstr ""
@@ -1752,7 +1752,7 @@ msgid "Decimals"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:273
+#: lib/block_scout_web/templates/transaction/overview.html.eex:281
msgid "Gas"
msgstr ""
@@ -1799,8 +1799,8 @@ msgid "Transfers"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:134
-#: lib/block_scout_web/templates/transaction/overview.html.eex:147
+#: lib/block_scout_web/templates/transaction/overview.html.eex:142
+#: lib/block_scout_web/templates/transaction/overview.html.eex:155
msgid "Copy Txn Input"
msgstr ""
@@ -1838,7 +1838,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11
#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6
#: lib/block_scout_web/views/address_view.ex:324
-#: lib/block_scout_web/views/transaction_view.ex:391
+#: lib/block_scout_web/views/transaction_view.ex:395
msgid "Internal Transactions"
msgstr ""
@@ -1848,7 +1848,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:331
-#: lib/block_scout_web/views/transaction_view.ex:392
+#: lib/block_scout_web/views/transaction_view.ex:396
msgid "Logs"
msgstr ""
@@ -1881,26 +1881,26 @@ msgid "Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:232
+#: lib/block_scout_web/templates/transaction/overview.html.eex:240
msgid " Token Burning"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:8
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:6
-#: lib/block_scout_web/views/transaction_view.ex:328
+#: lib/block_scout_web/views/transaction_view.ex:332
msgid "Token Burning"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/overview.html.eex:214
+#: lib/block_scout_web/templates/transaction/overview.html.eex:222
msgid " Token Minting"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:10
#: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:8
-#: lib/block_scout_web/views/transaction_view.ex:327
+#: lib/block_scout_web/views/transaction_view.ex:331
msgid "Token Minting"
msgstr ""
@@ -1908,3 +1908,8 @@ msgstr ""
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/transaction/overview.html.eex:58
+msgid "Revert reason"
+msgstr ""
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 2b3f6fff5e..d46071d215 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
@@ -1,6 +1,8 @@
defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
use BlockScoutWeb.ConnCase
+ import Mox
+
@moduletag capture_log: true
describe "gettxreceiptstatus" do
@@ -520,7 +522,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"index" => "#{log.index}"
}
],
- "next_page_params" => nil
+ "next_page_params" => nil,
+ "revertReason" => ""
}
schema =
@@ -576,6 +579,184 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response["status"] == "1"
assert response["message"] == "OK"
end
+
+ test "with a txhash with revert reason from DB", %{conn: conn} do
+ block = insert(:block, number: 100)
+
+ transaction =
+ :transaction
+ |> insert(revert_reason: "No credit of that type")
+ |> with_block(block)
+
+ insert(:address)
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == "No credit of that type"
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with empty revert reason from DB", %{conn: conn} do
+ block = insert(:block, number: 100)
+
+ transaction =
+ :transaction
+ |> insert(revert_reason: "")
+ |> with_block(block)
+
+ insert(:address)
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with revert reason from the archive node", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == "No credit of that type"
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+ end
+
+ test "with a txhash with empty revert reason from the archive node", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: ""}}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
+ end
+
+ test "with a txhash with empty revert reason from DB if eth_call doesn't return an error", %{conn: conn} do
+ block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
+
+ transaction =
+ :transaction
+ |> insert(
+ error: "Reverted",
+ status: :error,
+ block_hash: block.hash,
+ block_number: block.number,
+ cumulative_gas_used: 884_322,
+ gas_used: 106_025,
+ index: 0,
+ hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
+ )
+
+ insert(:address)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:ok}
+ end
+ )
+
+ params = %{
+ "module" => "transaction",
+ "action" => "gettxinfo",
+ "txhash" => "#{transaction.hash}"
+ }
+
+ assert response =
+ conn
+ |> get("/api", params)
+ |> json_response(200)
+
+ assert response["result"]["revertReason"] == ""
+ assert response["status"] == "1"
+ assert response["message"] == "OK"
end
defp resolve_schema(result \\ %{}) do
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
new file mode 100644
index 0000000000..4e834d78a8
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu.ex
@@ -0,0 +1,305 @@
+# credo:disable-for-this-file
+defmodule EthereumJSONRPC.Besu do
+ @moduledoc """
+ Ethereum JSONRPC methods that are only supported by [Besu](https://besu.hyperledger.org/en/stable/Reference/API-Methods).
+ """
+ require Logger
+ import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1]
+
+ alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces}
+ alias EthereumJSONRPC.{Transaction, Transactions}
+
+ @behaviour EthereumJSONRPC.Variant
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_beneficiaries(block_numbers, json_rpc_named_arguments)
+ when is_list(block_numbers) and is_list(json_rpc_named_arguments) do
+ id_to_params =
+ block_numbers
+ |> block_numbers_to_params_list()
+ |> id_to_params()
+
+ with {:ok, responses} <-
+ id_to_params
+ |> FetchedBeneficiaries.requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)}
+ end
+ end
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore
+
+ @doc """
+ Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Besu trace URL.
+ """
+ @impl EthereumJSONRPC.Variant
+ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do
+ id_to_params = id_to_params(block_numbers)
+
+ with {:ok, responses} <-
+ id_to_params
+ |> trace_replay_block_transactions_requests()
+ |> json_rpc(json_rpc_named_arguments) do
+ trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ end
+ end
+
+ @impl EthereumJSONRPC.Variant
+ def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do
+ id_to_params = id_to_params(transactions_params)
+
+ trace_replay_transaction_response =
+ id_to_params
+ |> trace_replay_transaction_requests()
+ |> json_rpc(json_rpc_named_arguments)
+
+ case trace_replay_transaction_response do
+ {:ok, responses} ->
+ case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do
+ {:ok, [first_trace]} ->
+ %{block_hash: block_hash} =
+ transactions_params
+ |> Enum.at(0)
+
+ {:ok,
+ [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]}
+
+ {:error, error} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+
+ {:error, :econnrefused} ->
+ {:error, :econnrefused}
+
+ {:error, [error]} ->
+ Logger.error(inspect(error))
+ {:error, error}
+ end
+ end
+
+ @doc """
+ Fetches the pending transactions from the Besu node.
+
+ *NOTE*: The pending transactions are local to the node that is contacted and may not be consistent across nodes based
+ on the transactions that each node has seen and how each node prioritizes collating transactions into the next block.
+ """
+ @impl EthereumJSONRPC.Variant
+ @spec fetch_pending_transactions(EthereumJSONRPC.json_rpc_named_arguments()) ::
+ {:ok, [Transaction.params()]} | {:error, reason :: term}
+ def fetch_pending_transactions(json_rpc_named_arguments) do
+ with {:ok, transactions} <-
+ %{id: 1, method: "txpool_besuTransactions", params: []}
+ |> request()
+ |> json_rpc(json_rpc_named_arguments) do
+ transactions_params =
+ transactions
+ |> Transactions.to_elixir()
+ |> Transactions.elixir_to_params()
+
+ {:ok, transactions_params}
+ end
+ end
+
+ defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do
+ Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)})
+ end
+
+ defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params))
+ |> Enum.reduce(
+ {:ok, []},
+ fn
+ {:ok, traces}, {:ok, acc_traces_list} ->
+ {:ok, [traces | acc_traces_list]}
+
+ {:ok, _}, {:error, _} = acc_error ->
+ acc_error
+
+ {:error, reason}, {:ok, _} ->
+ {:error, [reason]}
+
+ {:error, reason}, {:error, acc_reason} ->
+ {:error, [reason | acc_reason]}
+ end
+ )
+ |> case do
+ {:ok, traces_list} ->
+ traces =
+ traces_list
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {:ok, traces}
+
+ {:error, reverse_reasons} ->
+ reasons = Enum.reverse(reverse_reasons)
+ {:error, reasons}
+ end
+ end
+
+ defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params)
+ when is_list(results) and is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_traces =
+ results
+ |> Stream.with_index()
+ |> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} ->
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockNumber" => block_number,
+ "transactionHash" => transaction_hash,
+ "transactionIndex" => transaction_index,
+ "index" => index
+ })
+ end)
+ end)
+
+ {:ok, annotated_traces}
+ end
+
+ defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ block_number = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockNumber" => block_number
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, block_number} ->
+ trace_replay_block_transactions_request(%{id: id, block_number: block_number})
+ end)
+ end
+
+ defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do
+ request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]})
+ end
+
+ def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do
+ params =
+ traces
+ |> Traces.to_elixir()
+ |> Traces.elixir_to_params()
+
+ {:ok, params}
+ end
+ end
+
+ defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params)
+ when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params))
+ |> Enum.reduce(
+ {:ok, []},
+ fn
+ {:ok, traces}, {:ok, acc_traces_list} ->
+ {:ok, [traces | acc_traces_list]}
+
+ {:ok, _}, {:error, _} = acc_error ->
+ acc_error
+
+ {:error, reason}, {:ok, _} ->
+ {:error, [reason]}
+
+ {:error, reason}, {:error, acc_reason} ->
+ {:error, [reason | acc_reason]}
+ end
+ )
+ |> case do
+ {:ok, traces_list} ->
+ traces =
+ traces_list
+ |> Enum.reverse()
+ |> List.flatten()
+
+ {:ok, traces}
+
+ {:error, reverse_reasons} ->
+ reasons = Enum.reverse(reverse_reasons)
+ {:error, reasons}
+ end
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params)
+ when is_list(traces) and is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ first_trace =
+ traces
+ |> Stream.with_index()
+ |> Enum.map(fn {trace, index} ->
+ Map.merge(trace, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "index" => index,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+ end)
+ |> Enum.filter(fn trace ->
+ Map.get(trace, "index") == 0
+ end)
+
+ {:ok, first_trace}
+ end
+
+ defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params)
+ when is_map(id_to_params) do
+ %{
+ block_hash: block_hash,
+ block_number: block_number,
+ hash_data: transaction_hash,
+ transaction_index: transaction_index
+ } = Map.fetch!(id_to_params, id)
+
+ annotated_error =
+ Map.put(error, :data, %{
+ "blockHash" => block_hash,
+ "blockNumber" => block_number,
+ "transactionIndex" => transaction_index,
+ "transactionHash" => transaction_hash
+ })
+
+ {:error, annotated_error}
+ end
+
+ defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} ->
+ trace_replay_transaction_request(%{id: id, hash_data: hash_data})
+ end)
+ end
+
+ defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do
+ request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]})
+ end
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
new file mode 100644
index 0000000000..cb569cf9f2
--- /dev/null
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/besu/fetched_beneficiaries.ex
@@ -0,0 +1,196 @@
+defmodule EthereumJSONRPC.Besu.FetchedBeneficiaries do
+ @moduledoc """
+ Beneficiaries and errors from batch requests to `trace_block`.
+ """
+
+ import EthereumJSONRPC, only: [quantity_to_integer: 1]
+
+ @doc """
+ Converts `responses` to `EthereumJSONRPC.FetchedBeneficiaries.t()`.
+
+ responses - List with trace_block responses
+ id_to_params - Maps request id to query params
+
+ ## Examples
+ iex> EthereumJSONRPC.Besu.FetchedBeneficiaries.from_responses(
+ ...> [
+ ...> %{
+ ...> id: 0,
+ ...> result: [
+ ...> %{
+ ...> "action" => %{"author" => "0x1", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> },
+ ...> %{
+ ...> "action" => %{"author" => "0x2", "rewardType" => "external", "value" => "0x0"},
+ ...> "blockHash" => "0xFFF",
+ ...> "blockNumber" => 12,
+ ...> "result" => nil,
+ ...> "subtraces" => 0,
+ ...> "traceAddress" => [],
+ ...> "transactionHash" => nil,
+ ...> "transactionPosition" => nil,
+ ...> "type" => "reward"
+ ...> }
+ ...> ]
+ ...> }
+ ...> ],
+ ...> %{0 => %{block_quantity: "0xC"}}
+ ...> )
+ %EthereumJSONRPC.FetchedBeneficiaries{
+ errors: [],
+ params_set: #MapSet<[
+ %{
+ address_hash: "0x1",
+ address_type: :validator,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ },
+ %{
+ address_hash: "0x2",
+ address_type: :emission_funds,
+ block_hash: "0xFFF",
+ block_number: 12,
+ reward: "0x0"
+ }
+ ]>
+ }
+ """
+ def from_responses(responses, id_to_params) when is_list(responses) and is_map(id_to_params) do
+ responses
+ |> Enum.map(&response_to_params_set(&1, id_to_params))
+ |> Enum.reduce(
+ %EthereumJSONRPC.FetchedBeneficiaries{},
+ fn
+ {:ok, params_set}, %EthereumJSONRPC.FetchedBeneficiaries{params_set: acc_params_set} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)}
+
+ {:error, reason}, %EthereumJSONRPC.FetchedBeneficiaries{errors: errors} = acc ->
+ %EthereumJSONRPC.FetchedBeneficiaries{acc | errors: [reason | errors]}
+ end
+ )
+ end
+
+ @doc """
+ `trace_block` requests for `id_to_params`.
+ """
+ def requests(id_to_params) when is_map(id_to_params) do
+ Enum.map(id_to_params, fn {id, %{block_quantity: block_quantity}} ->
+ request(%{id: id, block_quantity: block_quantity})
+ end)
+ end
+
+ @spec response_to_params_set(%{id: id, result: nil}, %{id => %{block_quantity: block_quantity}}) ::
+ {:error, %{code: 404, message: String.t(), data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: nil}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ {:error, %{code: 404, message: "Not Found", data: %{block_quantity: block_quantity}}}
+ end
+
+ @spec response_to_params_set(%{id: id, result: list(map())}, %{id => %{block_quantity: block_quantity}}) ::
+ {:ok, MapSet.t(EthereumJSONRPC.FetchedBeneficiary.params())}
+ when id: non_neg_integer(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, result: traces}, id_to_params) when is_list(traces) and is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+ block_number = quantity_to_integer(block_quantity)
+ params_set = traces_to_params_set(traces, block_number)
+
+ {:ok, params_set}
+ end
+
+ @spec response_to_params_set(%{id: id, error: %{code: code, message: message}}, %{
+ id => %{block_quantity: block_quantity}
+ }) :: {:error, %{code: code, message: message, data: %{block_quantity: block_quantity}}}
+ when id: non_neg_integer(), code: integer(), message: String.t(), block_quantity: String.t()
+ defp response_to_params_set(%{id: id, error: error}, id_to_params) when is_map(id_to_params) do
+ %{block_quantity: block_quantity} = Map.fetch!(id_to_params, id)
+
+ annotated_error = Map.put(error, :data, %{block_quantity: block_quantity})
+
+ {:error, annotated_error}
+ end
+
+ defp request(%{id: id, block_quantity: block_quantity}) when is_integer(id) and is_binary(block_quantity) do
+ EthereumJSONRPC.request(%{id: id, method: "trace_block", params: [block_quantity]})
+ end
+
+ defp traces_to_params_set(traces, block_number) when is_list(traces) and is_integer(block_number) do
+ traces
+ |> Stream.filter(&(&1["type"] == "reward"))
+ |> Stream.with_index()
+ |> Enum.reduce(MapSet.new(), fn {trace, index}, acc ->
+ MapSet.union(acc, trace_to_params_set(trace, block_number, index))
+ end)
+ end
+
+ defp trace_to_params_set(
+ %{
+ "action" => %{
+ "rewardType" => reward_type,
+ "author" => address_hash_data,
+ "value" => reward_value
+ },
+ "blockHash" => block_hash,
+ "blockNumber" => block_number
+ },
+ block_number,
+ index
+ )
+ when is_integer(block_number) and reward_type in ~w(block external uncle) do
+ MapSet.new([
+ %{
+ address_hash: address_hash_data,
+ block_hash: block_hash,
+ block_number: block_number,
+ reward: reward_value,
+ address_type: get_address_type(reward_type, index)
+ }
+ ])
+ end
+
+ # Beneficiary's address type will depend on the responses' action.rewardType,
+ # which will vary depending on which network is being indexed
+ #
+ # On POA networks, rewardType will always be external and the type of the address being
+ # rewarded will depend on its position.
+ # First address will always be the validator's while the second will be the EmissionsFunds address
+ #
+ # On PoW networks, like Ethereum, the reward type will already specify the type for the
+ # address being rewarded
+ # The rewardType "block" will show the reward for the consensus block validator
+ # The rewardType "uncle" will show reward for validating an uncle block
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 0, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 1, do: :emission_funds
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 5, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 6, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 7, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 8, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 9, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 10, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 11, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 12, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 13, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 14, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 15, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 16, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 17, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 18, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 19, do: :validator
+ defp get_address_type(reward_type, index) when reward_type == "external" and index == 20, do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator
+ defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle
+ defp get_address_type(reward_type, _index) when reward_type == "emptyStep", do: :validator
+end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
index 03e8c8457c..05e32348f2 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex
@@ -38,7 +38,7 @@ defmodule EthereumJSONRPC.Geth do
end
@doc """
- Fetches the first trace from the Parity trace URL.
+ Fetches the first trace from the trace URL.
"""
@impl EthereumJSONRPC.Variant
def fetch_first_trace(_transactions_params, _json_rpc_named_arguments), do: :ignore
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
index 84a1b94702..8165651ad5 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex
@@ -1,3 +1,4 @@
+# credo:disable-for-this-file
defmodule EthereumJSONRPC.Parity do
@moduledoc """
Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/).
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
index 031b150384..54d812c4c4 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
@@ -9,7 +9,7 @@ defmodule EthereumJSONRPC.Transaction do
"""
require Logger
- import EthereumJSONRPC, only: [quantity_to_integer: 1]
+ import EthereumJSONRPC, only: [quantity_to_integer: 1, integer_to_quantity: 1, request: 1]
alias EthereumJSONRPC
@@ -313,6 +313,20 @@ defmodule EthereumJSONRPC.Transaction do
nil
end
+ def eth_call_request(id, block_number, data, to, from, gas, gas_price, value) do
+ block =
+ case block_number do
+ nil -> "latest"
+ block_number -> integer_to_quantity(block_number)
+ end
+
+ request(%{
+ id: id,
+ method: "eth_call",
+ params: [%{to: to, from: from, data: data, gas: gas, gas_price: gas_price, value: value}, block]
+ })
+ end
+
# double check that no new keys are being missed by requiring explicit match for passthrough
# `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct
# hash format
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 8424590e95..57d5030cf5 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -207,6 +207,11 @@ config :explorer, Explorer.Chain.Cache.Accounts,
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.PendingTransactions,
+ enabled:
+ if(System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu",
+ do: false,
+ else: true
+ ),
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs
new file mode 100644
index 0000000000..e014ac960c
--- /dev/null
+++ b/apps/explorer/config/dev/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs
new file mode 100644
index 0000000000..4b69e1363e
--- /dev/null
+++ b/apps/explorer/config/prod/besu.exs
@@ -0,0 +1,25 @@
+use Mix.Config
+
+config :explorer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.HTTP,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/config/test/besu.exs b/apps/explorer/config/test/besu.exs
new file mode 100644
index 0000000000..f5c234c7e1
--- /dev/null
+++ b/apps/explorer/config/test/besu.exs
@@ -0,0 +1,14 @@
+use Mix.Config
+
+config :explorer,
+ transport: EthereumJSONRPC.HTTP,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index 06e4dac6c9..7ffdc71aa3 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -28,6 +28,8 @@ defmodule Explorer.Chain do
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi}
+ alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction
+
alias Explorer.Counters.LastFetchedCounter
alias Explorer.Chain
@@ -72,6 +74,7 @@ defmodule Explorer.Chain do
alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter}
alias Explorer.Market.MarketHistoryCache
alias Explorer.{PagingOptions, Repo}
+ alias Explorer.SmartContract.Reader
alias Dataloader.Ecto, as: DataloaderEcto
@@ -2715,6 +2718,68 @@ defmodule Explorer.Chain do
def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error}
+ def transaction_to_revert_reason(transaction) do
+ %Transaction{revert_reason: revert_reason} = transaction
+
+ if revert_reason == nil do
+ fetch_tx_revert_reason(transaction)
+ else
+ revert_reason
+ end
+ end
+
+ def fetch_tx_revert_reason(
+ %Transaction{
+ block_number: block_number,
+ to_address_hash: to_address_hash,
+ from_address_hash: from_address_hash,
+ input: data,
+ gas: gas,
+ gas_price: gas_price,
+ value: value
+ } = transaction
+ ) do
+ json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
+
+ req =
+ EthereumJSONRPCTransaction.eth_call_request(
+ 0,
+ block_number,
+ data,
+ to_address_hash,
+ from_address_hash,
+ Decimal.to_integer(gas),
+ Wei.hex_format(gas_price),
+ Wei.hex_format(value)
+ )
+
+ data =
+ case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do
+ {:error, %{data: data}} ->
+ data
+
+ _ ->
+ ""
+ end
+
+ revert_reason_parts = String.split(data, "revert: ")
+
+ formatted_revert_reason =
+ if Enum.count(revert_reason_parts) > 1 do
+ Enum.at(revert_reason_parts, 1)
+ else
+ data
+ end
+
+ if byte_size(formatted_revert_reason) > 0 do
+ transaction
+ |> Changeset.change(%{revert_reason: formatted_revert_reason})
+ |> Repo.update()
+ end
+
+ formatted_revert_reason
+ end
+
@doc """
The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in
`unit`.
@@ -4273,6 +4338,56 @@ defmodule Explorer.Chain do
end
end
+ def combine_proxy_implementation_abi(address_hash, abi) when not is_nil(abi) do
+ implementation_method_abi =
+ abi
+ |> Enum.find(fn method ->
+ Map.get(method, "name") == "implementation"
+ end)
+
+ implementation_abi =
+ if implementation_method_abi do
+ implementation_address =
+ case Reader.query_contract(address_hash, abi, %{
+ "implementation" => []
+ }) do
+ %{"implementation" => {:ok, [result]}} -> result
+ _ -> nil
+ end
+
+ if implementation_address do
+ implementation_address_hash_string = "0x" <> Base.encode16(implementation_address, case: :lower)
+
+ case Chain.string_to_address_hash(implementation_address_hash_string) do
+ {:ok, implementation_address_hash} ->
+ implementation_smart_contract =
+ implementation_address_hash
+ |> Chain.address_hash_to_smart_contract()
+
+ if implementation_smart_contract do
+ implementation_smart_contract
+ |> Map.get(:abi)
+ else
+ []
+ end
+
+ _ ->
+ []
+ end
+ else
+ []
+ end
+ else
+ []
+ end
+
+ if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi
+ end
+
+ def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do
+ []
+ end
+
defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do
{:ok, to_address_hash} =
if Map.has_key?(first_trace, :to_address_hash) do
diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex
index 27899303f3..9592f0b2dd 100644
--- a/apps/explorer/lib/explorer/chain/log.ex
+++ b/apps/explorer/lib/explorer/chain/log.ex
@@ -6,8 +6,8 @@ defmodule Explorer.Chain.Log do
require Logger
alias ABI.{Event, FunctionSelector}
+ alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
- alias Explorer.Repo
@required_attrs ~w(address_hash data block_hash index transaction_hash)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
@@ -121,8 +121,11 @@ defmodule Explorer.Chain.Log do
"""
def decode(_log, %Transaction{to_address: nil}), do: {:error, :no_to_address}
- def decode(log, transaction = %Transaction{to_address: %{smart_contract: %{abi: abi}}}) when not is_nil(abi) do
- with {:ok, selector, mapping} <- find_and_decode(abi, log, transaction),
+ def decode(log, transaction = %Transaction{to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}}})
+ when not is_nil(abi) do
+ full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
+
+ with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping),
do: {:ok, identifier, text, mapping}
diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex
index 66127868c6..e4518c7349 100644
--- a/apps/explorer/lib/explorer/chain/transaction.ex
+++ b/apps/explorer/lib/explorer/chain/transaction.ex
@@ -11,6 +11,8 @@ defmodule Explorer.Chain.Transaction do
alias Ecto.Changeset
+ alias Explorer.{Chain, Repo}
+
alias Explorer.Chain.{
Address,
Block,
@@ -26,11 +28,10 @@ defmodule Explorer.Chain.Transaction do
}
alias Explorer.Chain.Transaction.{Fork, Status}
- alias Explorer.Repo
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index created_contract_code_indexed_at status
- to_address_hash)a
+ to_address_hash revert_reason)a
@required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
@@ -106,6 +107,7 @@ defmodule Explorer.Chain.Transaction do
* `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer`
+ * `revert_reason` - revert reason of transaction
| `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description |
|----------|----------------------------------|------------|-----------------|-------------------------------------------|-------------------------|-----------------------------------------------------------------------------------------------------|
@@ -129,6 +131,7 @@ defmodule Explorer.Chain.Transaction do
* `uncles` - uncle blocks where `forks` were collated
* `v` - The V field of the signature.
* `value` - wei transferred from `from_address` to `to_address`
+ * `revert_reason` - revert reason of transaction
"""
@type t :: %__MODULE__{
block: %Ecto.Association.NotLoaded{} | Block.t() | nil,
@@ -159,7 +162,8 @@ defmodule Explorer.Chain.Transaction do
to_address_hash: Hash.Address.t() | nil,
uncles: %Ecto.Association.NotLoaded{} | [Block.t()],
v: v(),
- value: Wei.t()
+ value: Wei.t(),
+ revert_reason: String.t()
}
@derive {Poison.Encoder,
@@ -199,6 +203,7 @@ defmodule Explorer.Chain.Transaction do
field(:status, Status)
field(:v, :decimal)
field(:value, Wei)
+ field(:revert_reason, :string)
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated
@@ -419,7 +424,7 @@ defmodule Explorer.Chain.Transaction do
candidates_query
|> Repo.all()
|> Enum.flat_map(fn candidate ->
- case do_decoded_input_data(data, [candidate.abi], hash) do
+ case do_decoded_input_data(data, [candidate.abi], nil, hash) do
{:ok, _, _, _} = decoded -> [decoded]
_ -> []
end
@@ -432,12 +437,18 @@ defmodule Explorer.Chain.Transaction do
{:error, :contract_not_verified, []}
end
- def decoded_input_data(%__MODULE__{input: %{bytes: data}, to_address: %{smart_contract: %{abi: abi}}, hash: hash}) do
- do_decoded_input_data(data, abi, hash)
+ def decoded_input_data(%__MODULE__{
+ input: %{bytes: data},
+ to_address: %{smart_contract: %{abi: abi, address_hash: address_hash}},
+ hash: hash
+ }) do
+ do_decoded_input_data(data, abi, address_hash, hash)
end
- defp do_decoded_input_data(data, abi, hash) do
- with {:ok, {selector, values}} <- find_and_decode(abi, data, hash),
+ defp do_decoded_input_data(data, abi, address_hash, hash) do
+ full_abi = Chain.combine_proxy_implementation_abi(address_hash, abi)
+
+ with {:ok, {selector, values}} <- find_and_decode(full_abi, data, hash),
{:ok, mapping} <- selector_mapping(selector, values, hash),
identifier <- Base.encode16(selector.method_id, case: :lower),
text <- function_call(selector.function, mapping),
diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex
index 9df3fea296..174e25b11a 100644
--- a/apps/explorer/lib/explorer/etherscan.ex
+++ b/apps/explorer/lib/explorer/etherscan.ex
@@ -348,6 +348,7 @@ defmodule Explorer.Etherscan do
status
to_address_hash
value
+ revert_reason
)a
@pending_transaction_fields ~w(
diff --git a/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs b/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs
new file mode 100644
index 0000000000..6f0ab6320e
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20200608075122_alter_transactions_add_error_reason.exs
@@ -0,0 +1,9 @@
+defmodule Explorer.Repo.Migrations.AlterTransactionsAddErrorReason do
+ use Ecto.Migration
+
+ def change do
+ alter table(:transactions) do
+ add(:revert_reason, :text)
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs
index 76774460c1..e374ab492c 100644
--- a/apps/explorer/test/explorer/chain_test.exs
+++ b/apps/explorer/test/explorer/chain_test.exs
@@ -5165,4 +5165,204 @@ defmodule Explorer.ChainTest do
}
end
end
+
+ describe "transaction_to_revert_reason/1" do
+ test "returns correct revert_reason from DB" do
+ transaction = insert(:transaction, revert_reason: "No credit of that type")
+ assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
+ end
+
+ test "returns correct revert_reason from the archive node" do
+ transaction =
+ insert(:transaction,
+ gas: 27319,
+ gas_price: "0x1b31d2900",
+ value: "0x86b3",
+ input: %Explorer.Chain.Data{bytes: <<1>>}
+ )
+ |> with_block(insert(:block, number: 1))
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn _json, [] ->
+ {:error, %{code: -32015, message: "VM execution error.", data: "revert: No credit of that type"}}
+ end
+ )
+
+ assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type"
+ end
+ end
+
+ describe "combine_proxy_implementation_abi/2" do
+ @proxy_abi [
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [%{"type" => "bool", "name" => ""}],
+ "name" => "upgradeTo",
+ "inputs" => [%{"type" => "address", "name" => "newImplementation"}],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "uint256", "name" => ""}],
+ "name" => "version",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "implementation",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "renounceOwnership",
+ "inputs" => [],
+ "constant" => false
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getOwner",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "view",
+ "payable" => false,
+ "outputs" => [%{"type" => "address", "name" => ""}],
+ "name" => "getProxyStorage",
+ "inputs" => [],
+ "constant" => true
+ },
+ %{
+ "type" => "function",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "outputs" => [],
+ "name" => "transferOwnership",
+ "inputs" => [%{"type" => "address", "name" => "_newOwner"}],
+ "constant" => false
+ },
+ %{
+ "type" => "constructor",
+ "stateMutability" => "nonpayable",
+ "payable" => false,
+ "inputs" => [
+ %{"type" => "address", "name" => "_proxyStorage"},
+ %{"type" => "address", "name" => "_implementationAddress"}
+ ]
+ },
+ %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false},
+ %{
+ "type" => "event",
+ "name" => "Upgraded",
+ "inputs" => [
+ %{"type" => "uint256", "name" => "version", "indexed" => false},
+ %{"type" => "address", "name" => "implementation", "indexed" => true}
+ ],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipRenounced",
+ "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}],
+ "anonymous" => false
+ },
+ %{
+ "type" => "event",
+ "name" => "OwnershipTransferred",
+ "inputs" => [
+ %{"type" => "address", "name" => "previousOwner", "indexed" => true},
+ %{"type" => "address", "name" => "newOwner", "indexed" => true}
+ ],
+ "anonymous" => false
+ }
+ ]
+
+ @implementation_abi [
+ %{
+ "constant" => false,
+ "inputs" => [%{"name" => "x", "type" => "uint256"}],
+ "name" => "set",
+ "outputs" => [],
+ "payable" => false,
+ "stateMutability" => "nonpayable",
+ "type" => "function"
+ },
+ %{
+ "constant" => true,
+ "inputs" => [],
+ "name" => "get",
+ "outputs" => [%{"name" => "", "type" => "uint256"}],
+ "payable" => false,
+ "stateMutability" => "view",
+ "type" => "function"
+ }
+ ]
+
+ test "returns empty [] abi if proxy abi is null" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == []
+ end
+
+ test "returns [] abi for unverified proxy" do
+ proxy_contract_address = insert(:contract_address)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == []
+ end
+
+ test "returns proxy abi if implementation is not verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+ assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi
+ end
+
+ test "returns proxy + implementation abi if implementation is verified" do
+ proxy_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi)
+
+ implementation_contract_address = insert(:contract_address)
+ insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi)
+
+ implementation_contract_address_hash_string =
+ Base.encode16(implementation_contract_address.hash.bytes, case: :lower)
+
+ expect(
+ EthereumJSONRPC.Mox,
+ :json_rpc,
+ fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
+ {:ok,
+ [
+ %{
+ id: id,
+ jsonrpc: "2.0",
+ result: "0x000000000000000000000000" <> implementation_contract_address_hash_string
+ }
+ ]}
+ end
+ )
+
+ combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi)
+
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false
+ assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true
+ assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
index 270684c8d1..8d092d66bf 100644
--- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
@@ -166,7 +166,7 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
}
"""
- version = "v0.1.3-nightly.2015.9.25+commit.4457170"
+ version = "v0.1.3+commit.028f561d"
response = CodeCompiler.run(name: name, compiler_version: version, code: code, optimize: optimize)
diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs
index 071f741a74..24b9edd909 100644
--- a/apps/indexer/config/config.exs
+++ b/apps/indexer/config/config.exs
@@ -38,6 +38,9 @@ config :indexer,
first_block: System.get_env("FIRST_BLOCK") || "0",
last_block: System.get_env("LAST_BLOCK") || ""
+config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor,
+ disabled?: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu"
+
# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true
# config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true
config :indexer, Indexer.Fetcher.StakingPools.Supervisor, disabled?: true
diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs
new file mode 100644
index 0000000000..dc61acea33
--- /dev/null
+++ b/apps/indexer/config/dev/besu.exs
@@ -0,0 +1,30 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ else: EthereumJSONRPC.IPC,
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545",
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545",
+ trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545"
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs
new file mode 100644
index 0000000000..aad6529e5c
--- /dev/null
+++ b/apps/indexer/config/prod/besu.exs
@@ -0,0 +1,29 @@
+use Mix.Config
+
+config :indexer,
+ block_interval: :timer.seconds(5),
+ json_rpc_named_arguments: [
+ transport:
+ if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http",
+ do: EthereumJSONRPC.HTTP,
+ else: EthereumJSONRPC.IPC
+ ),
+ transport_options: [
+ http: EthereumJSONRPC.HTTP.HTTPoison,
+ url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"),
+ method_to_url: [
+ eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"),
+ trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL")
+ ],
+ http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]]
+ ],
+ variant: EthereumJSONRPC.Besu
+ ],
+ subscribe_named_arguments: [
+ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && EthereumJSONRPC.WebSocket,
+ transport_options: [
+ web_socket: EthereumJSONRPC.WebSocket.WebSocketClient,
+ url: System.get_env("ETHEREUM_JSONRPC_WS_URL")
+ ]
+ ]
diff --git a/apps/indexer/config/test/besu.exs b/apps/indexer/config/test/besu.exs
new file mode 100644
index 0000000000..ceaac3a9b4
--- /dev/null
+++ b/apps/indexer/config/test/besu.exs
@@ -0,0 +1,8 @@
+use Mix.Config
+
+config :indexer,
+ json_rpc_named_arguments: [
+ transport: EthereumJSONRPC.Mox,
+ transport_options: [],
+ variant: EthereumJSONRPC.Besu
+ ]
diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
index e9d914bd8c..4ed5a85888 100644
--- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
+++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex
@@ -105,6 +105,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+ EthereumJSONRPC.Besu ->
+ EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
+
_ ->
fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments)
end
diff --git a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
index e61d54d6f8..12acf46a09 100644
--- a/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
+++ b/apps/indexer/test/indexer/fetcher/coin_balance_test.exs
@@ -440,8 +440,8 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
{:ok, responses}
end)
- bad_block_quantity = integer_to_quantity(bad_block_number)
- res_bad = eth_block_number_fake_response(bad_block_quantity)
+ good_block_quantity = integer_to_quantity(good_block_number)
+ res_good = eth_block_number_fake_response(good_block_quantity)
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
@@ -449,11 +449,11 @@ defmodule Indexer.Fetcher.CoinBalanceTest do
id: 0,
jsonrpc: "2.0",
method: "eth_getBlockByNumber",
- params: [bad_block_quantity, true]
+ params: [^good_block_quantity, true]
}
],
[] ->
- {:ok, [res_bad]}
+ {:ok, [res_good]}
end)
assert {:retry, [{^address_hash_bytes, ^bad_block_number}]} =