diff --git a/apps/block_scout_web/assets/__tests__/pages/block.js b/apps/block_scout_web/assets/__tests__/pages/block.js new file mode 100644 index 0000000000..0f50e9cf7f --- /dev/null +++ b/apps/block_scout_web/assets/__tests__/pages/block.js @@ -0,0 +1,13 @@ +import { reducer, initialState } from '../../js/pages/block' + +test('RECEIVED_NEW_BLOCK', () => { + const action = { + type: 'RECEIVED_NEW_BLOCK', + msg: { + blockHtml: "test" + } + } + const output = reducer(initialState, action) + + expect(output.newBlock).toBe("test") +}) diff --git a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js index c1e8759859..ff763df87c 100644 --- a/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js +++ b/apps/block_scout_web/assets/js/lib/token_balance_dropdown.js @@ -16,4 +16,7 @@ const tokenBalanceDropdown = (element) => { }) } -$('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element)) +export function loadTokenBalanceDropdown () { + $('[data-token-balance-dropdown]').each((_index, element) => tokenBalanceDropdown(element)) +} +loadTokenBalanceDropdown() diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js index dd8116be1e..277d158d0c 100644 --- a/apps/block_scout_web/assets/js/pages/address.js +++ b/apps/block_scout_web/assets/js/pages/address.js @@ -5,6 +5,7 @@ import socket from '../socket' import router from '../router' import { batchChannel, initRedux } from '../utils' import { updateAllAges } from '../lib/from_now' +import { loadTokenBalanceDropdown } from '../lib/token_balance_dropdown' const BATCH_THRESHOLD = 10 @@ -14,6 +15,7 @@ export const initialState = { beyondPageOne: null, channelDisconnected: false, filter: null, + newInternalTransactions: [], newTransactions: [], balance: null, transactionCount: null @@ -42,6 +44,29 @@ export function reducer (state = initialState, action) { balance: action.msg.balance }) } + case 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH': { + if (state.channelDisconnected || state.beyondPageOne) return state + + const incomingInternalTransactions = humps.camelizeKeys(action.msgs) + .filter(({toAddressHash, fromAddressHash}) => ( + !state.filter || + (state.filter === 'to' && toAddressHash === state.addressHash) || + (state.filter === 'from' && fromAddressHash === state.addressHash) + )) + + if (!state.batchCountAccumulator && action.msgs.length < BATCH_THRESHOLD) { + return Object.assign({}, state, { + newInternalTransactions: [ + ...state.newInternalTransactions, + ...incomingInternalTransactions.map(({internalTransactionHtml}) => internalTransactionHtml) + ] + }) + } else { + return Object.assign({}, state, { + batchCountAccumulator: state.batchCountAccumulator + action.msgs.length + }) + } + } case 'RECEIVED_NEW_TRANSACTION_BATCH': { if (state.channelDisconnected || state.beyondPageOne) return state @@ -74,30 +99,44 @@ export function reducer (state = initialState, action) { router.when('/address/:addressHash').then((params) => initRedux(reducer, { main (store) { - const { addressHash, blockNumber } = params - const channel = socket.channel(`addresses:${addressHash}`, {}) - store.dispatch({ + const { addressHash } = params + const addressChannel = socket.channel(`addresses:${addressHash}`, {}) + const state = store.dispatch({ type: 'PAGE_LOAD', params, transactionCount: $('[data-selector="transaction-count"]').text() }) - channel.join() - channel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) - channel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg })) - if (!blockNumber) channel.on('transaction', batchChannel((msgs) => store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs }))) + addressChannel.join() + addressChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + addressChannel.on('balance', (msg) => store.dispatch({ type: 'RECEIVED_UPDATED_BALANCE', msg })) + if (!state.beyondPageOne) { + addressChannel.on('transaction', batchChannel((msgs) => + store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs }) + )) + + addressChannel.on('internal_transaction', batchChannel((msgs) => + store.dispatch({ type: 'RECEIVED_NEW_INTERNAL_TRANSACTION_BATCH', msgs }) + )) + } }, render (state, oldState) { + const $balance = $('[data-selector="balance-card"]') const $channelBatching = $('[data-selector="channel-batching-message"]') const $channelBatchingCount = $('[data-selector="channel-batching-count"]') const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') + const $emptyInternalTransactionsList = $('[data-selector="empty-internal-transactions-list"]') const $emptyTransactionsList = $('[data-selector="empty-transactions-list"]') - const $balance = $('[data-selector="balance-card"]') + const $internalTransactionsList = $('[data-selector="internal-transactions-list"]') const $transactionCount = $('[data-selector="transaction-count"]') const $transactionsList = $('[data-selector="transactions-list"]') + if ($emptyInternalTransactionsList.length && state.newInternalTransactions.length) window.location.reload() if ($emptyTransactionsList.length && state.newTransactions.length) window.location.reload() if (state.channelDisconnected) $channelDisconnected.show() - if (oldState.balance !== state.balance) $balance.empty().append(state.balance) + if (oldState.balance !== state.balance) { + $balance.empty().append(state.balance) + loadTokenBalanceDropdown() + } if (oldState.transactionCount !== state.transactionCount) $transactionCount.empty().append(numeral(state.transactionCount).format()) if (state.batchCountAccumulator) { $channelBatching.show() @@ -105,6 +144,9 @@ router.when('/address/:addressHash').then((params) => initRedux(reducer, { } else { $channelBatching.hide() } + if (oldState.newInternalTransactions !== state.newInternalTransactions && $internalTransactionsList.length) { + $internalTransactionsList.prepend(state.newInternalTransactions.slice(oldState.newInternalTransactions.length).reverse().join('')) + } if (oldState.newTransactions !== state.newTransactions && $transactionsList.length) { $transactionsList.prepend(state.newTransactions.slice(oldState.newTransactions.length).reverse().join('')) updateAllAges() diff --git a/apps/block_scout_web/assets/js/pages/block.js b/apps/block_scout_web/assets/js/pages/block.js index 2c32d9ffae..09e1899713 100644 --- a/apps/block_scout_web/assets/js/pages/block.js +++ b/apps/block_scout_web/assets/js/pages/block.js @@ -19,14 +19,12 @@ export function reducer (state = initialState, action) { }) } case 'CHANNEL_DISCONNECTED': { - if (state.beyondPageOne) return state - return Object.assign({}, state, { channelDisconnected: true }) } case 'RECEIVED_NEW_BLOCK': { - if (state.channelDisconnected || state.beyondPageOne) return state + if (state.channelDisconnected) return state return Object.assign({}, state, { newBlock: action.msg.blockHtml @@ -39,13 +37,15 @@ export function reducer (state = initialState, action) { router.when('/blocks', { exactPathMatch: true }).then(({ blockNumber }) => initRedux(reducer, { main (store) { - const blocksChannel = socket.channel(`blocks:new_block`, {}) - store.dispatch({ type: 'PAGE_LOAD', blockNumber }) - blocksChannel.join() - blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) - blocksChannel.on('new_block', (msg) => - store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }) - ) + const state = store.dispatch({ type: 'PAGE_LOAD', blockNumber }) + if (!state.beyondPageOne) { + const blocksChannel = socket.channel(`blocks:new_block`, {}) + blocksChannel.join() + blocksChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + blocksChannel.on('new_block', (msg) => + store.dispatch({ type: 'RECEIVED_NEW_BLOCK', msg: humps.camelizeKeys(msg) }) + ) + } }, render (state, oldState) { const $channelDisconnected = $('[data-selector="channel-disconnected-message"]') diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index 84183423e9..50f9fc89cb 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -28,8 +28,6 @@ export function reducer (state = initialState, action) { }) } case 'CHANNEL_DISCONNECTED': { - if (state.beyondPageOne) return state - return Object.assign({}, state, { channelDisconnected: true, batchCountAccumulator: 0 @@ -88,17 +86,19 @@ router.when('/tx/:transactionHash').then(() => initRedux(reducer, { router.when('/txs', { exactPathMatch: true }).then((params) => initRedux(reducer, { main (store) { const { index } = params - const transactionsChannel = socket.channel(`transactions:new_transaction`) - store.dispatch({ + const state = store.dispatch({ type: 'PAGE_LOAD', transactionCount: $('[data-selector="transaction-count"]').text(), index }) - transactionsChannel.join() - transactionsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) - transactionsChannel.on('new_transaction', batchChannel((msgs) => - store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) })) - ) + if (!state.beyondPageOne) { + const transactionsChannel = socket.channel(`transactions:new_transaction`) + transactionsChannel.join() + transactionsChannel.onError(() => store.dispatch({ type: 'CHANNEL_DISCONNECTED' })) + transactionsChannel.on('new_transaction', batchChannel((msgs) => + store.dispatch({ type: 'RECEIVED_NEW_TRANSACTION_BATCH', msgs: humps.camelizeKeys(msgs) })) + ) + } }, render (state, oldState) { const $channelBatching = $('[data-selector="channel-batching-message"]') diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index 87299c4bf0..9d68396390 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -18,6 +18,26 @@ defmodule BlockScoutWeb.Chain do @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} + def current_filter(%{paging_options: paging_options} = params) do + params + |> Map.get("filter") + |> case do + "to" -> [direction: :to, paging_options: paging_options] + "from" -> [direction: :from, paging_options: paging_options] + _ -> [paging_options: paging_options] + end + end + + def current_filter(params) do + params + |> Map.get("filter") + |> case do + "to" -> [direction: :to] + "from" -> [direction: :from] + _ -> [] + end + end + @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} def from_param(param) diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex index 4a177eee86..0bd9d484d9 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex @@ -4,10 +4,10 @@ defmodule BlockScoutWeb.AddressChannel do """ use BlockScoutWeb, :channel - alias BlockScoutWeb.{AddressTransactionView, AddressView} + alias BlockScoutWeb.{AddressInternalTransactionView, AddressView, TransactionView} alias Phoenix.View - intercept(["balance_update", "count", "transaction"]) + intercept(["balance_update", "count", "internal_transaction", "transaction"]) def join("addresses:" <> _address_hash, _params, socket) do {:ok, %{}, socket} @@ -40,13 +40,33 @@ defmodule BlockScoutWeb.AddressChannel do {:noreply, socket} end + def handle_out("internal_transaction", %{address: address, internal_transaction: internal_transaction}, socket) do + Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) + + rendered_internal_transaction = + View.render_to_string( + AddressInternalTransactionView, + "_internal_transaction.html", + address: address, + internal_transaction: internal_transaction + ) + + push(socket, "internal_transaction", %{ + to_address_hash: to_string(internal_transaction.to_address_hash), + from_address_hash: to_string(internal_transaction.from_address_hash), + internal_transaction_html: rendered_internal_transaction + }) + + {:noreply, socket} + end + def handle_out("transaction", %{address: address, transaction: transaction}, socket) do Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale) rendered = View.render_to_string( - AddressTransactionView, - "_transaction.html", + TransactionView, + "_tile.html", address: address, transaction: transaction ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex index cf70be275d..de65aa50aa 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex @@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.AddressController, only: [transaction_count: 1] - import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias Explorer.{Chain, Market} alias Explorer.ExchangeRates.Token @@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do not_found(conn) end end - - defp current_filter(%{paging_options: paging_options} = params) do - params - |> Map.get("filter") - |> case do - "to" -> [direction: :to, paging_options: paging_options] - "from" -> [direction: :from, paging_options: paging_options] - _ -> [paging_options: paging_options] - end - end - - defp current_filter(params) do - params - |> Map.get("filter") - |> case do - "to" -> [direction: :to] - "from" -> [direction: :from] - _ -> [] - end - end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 5c406dcc63..5bf5cccc43 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressTransactionController do use BlockScoutWeb, :controller import BlockScoutWeb.AddressController, only: [transaction_count: 1] - import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] + import BlockScoutWeb.Chain, only: [current_filter: 1, paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias Explorer.{Chain, Market} alias Explorer.ExchangeRates.Token @@ -46,24 +46,4 @@ defmodule BlockScoutWeb.AddressTransactionController do not_found(conn) end end - - defp current_filter(%{paging_options: paging_options} = params) do - params - |> Map.get("filter") - |> case do - "to" -> [direction: :to, paging_options: paging_options] - "from" -> [direction: :from, paging_options: paging_options] - _ -> [paging_options: paging_options] - end - end - - defp current_filter(params) do - params - |> Map.get("filter") - |> case do - "to" -> [direction: :to] - "from" -> [direction: :from] - _ -> [] - end - end end diff --git a/apps/block_scout_web/lib/block_scout_web/event_handler.ex b/apps/block_scout_web/lib/block_scout_web/event_handler.ex index a66107d656..5b7bf51118 100644 --- a/apps/block_scout_web/lib/block_scout_web/event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/event_handler.ex @@ -19,6 +19,7 @@ defmodule BlockScoutWeb.EventHandler do Chain.subscribe_to_events(:addresses) Chain.subscribe_to_events(:blocks) Chain.subscribe_to_events(:exchange_rate) + Chain.subscribe_to_events(:internal_transactions) Chain.subscribe_to_events(:transactions) {:ok, []} end diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 2546a352dc..849daaa2c7 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.Notifier do """ alias Explorer.{Chain, Market, Repo} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, InternalTransaction} alias Explorer.ExchangeRates.Token alias BlockScoutWeb.Endpoint @@ -36,6 +36,12 @@ defmodule BlockScoutWeb.Notifier do }) end + def handle_event({:chain_event, :internal_transactions, internal_transactions}) do + internal_transactions + |> Stream.map(&(InternalTransaction |> Repo.get(&1.id) |> Repo.preload([:from_address, :to_address]))) + |> Enum.each(&broadcast_internal_transaction/1) + end + def handle_event({:chain_event, :transactions, transaction_hashes}) do transaction_hashes |> Chain.hashes_to_transactions( @@ -65,6 +71,24 @@ defmodule BlockScoutWeb.Notifier do }) end + defp broadcast_internal_transaction(internal_transaction) do + Endpoint.broadcast("internal_transactions:new_internal_transaction", "new_internal_transaction", %{ + internal_transaction: internal_transaction + }) + + Endpoint.broadcast("addresses:#{internal_transaction.from_address_hash}", "internal_transaction", %{ + address: internal_transaction.from_address, + internal_transaction: internal_transaction + }) + + if internal_transaction.to_address_hash != internal_transaction.from_address_hash do + Endpoint.broadcast("addresses:#{internal_transaction.to_address_hash}", "internal_transaction", %{ + address: internal_transaction.to_address, + internal_transaction: internal_transaction + }) + end + end + defp broadcast_transaction(transaction) do Endpoint.broadcast("transactions:new_transaction", "new_transaction", %{ transaction: transaction @@ -75,7 +99,7 @@ defmodule BlockScoutWeb.Notifier do transaction: transaction }) - if transaction.to_address_hash && transaction.to_address_hash != transaction.from_address_hash do + if transaction.to_address_hash != transaction.from_address_hash do Endpoint.broadcast("addresses:#{transaction.to_address_hash}", "transaction", %{ address: transaction.to_address, transaction: transaction diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex index 3471ddc699..4d1ed942ae 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_balance_card.html.eex @@ -9,7 +9,7 @@ data-usd-exchange-rate="<%= @exchange_rate.usd_value %>"> -
+
+
+ +
+
+ +

<%= gettext "Internal Transactions" %>

<%= if Enum.count(@internal_transactions) > 0 do %> - <%= for internal_transaction <- @internal_transactions do %> - <%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %> - <% end %> + + <%= for internal_transaction <- @internal_transactions do %> + <%= render "_internal_transaction.html", address: @address, internal_transaction: internal_transaction %> + <% end %> + <% else %>
- <%= gettext "There are no internal transactions for this address." %> + <%= gettext "There are no internal transactions for this address." %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex index c36e772b95..beef796349 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_transaction_view.ex @@ -1,7 +1,3 @@ defmodule BlockScoutWeb.BlockTransactionView do use BlockScoutWeb, :view - - alias BlockScoutWeb.BlockView - - defdelegate formatted_timestamp(block), to: BlockView end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index bb761997c4..b050ec65ab 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -134,7 +134,7 @@ msgstr "" msgid "Address" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:105 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:115 #: lib/block_scout_web/templates/address_transaction/index.html.eex:116 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -150,7 +150,7 @@ msgstr "" msgid "Success" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:104 #: lib/block_scout_web/templates/address_transaction/index.html.eex:105 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_transaction_view.ex:9 @@ -305,7 +305,7 @@ msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:117 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:127 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:60 @@ -354,7 +354,7 @@ msgstr "" msgid "Wei" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:98 #: lib/block_scout_web/templates/address_transaction/index.html.eex:99 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 @@ -476,7 +476,7 @@ msgstr "" msgid "There are no Transactions" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:130 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:142 #: lib/block_scout_web/templates/address_transaction/index.html.eex:143 #: lib/block_scout_web/templates/block/index.html.eex:15 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50 @@ -606,7 +606,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:124 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:136 msgid "There are no internal transactions for this address." msgstr "" @@ -1000,3 +1000,13 @@ msgstr "" #: lib/block_scout_web/views/transaction_view.ex:42 msgid "Max of" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 +msgid "Connection Lost, click to load newer internal transactions" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83 +msgid "More internal transactions have come in" +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 484da93917..eb92b7aea7 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 @@ -146,7 +146,7 @@ msgstr "%{count} transactions in this block" msgid "Address" msgstr "Address" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:105 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:115 #: lib/block_scout_web/templates/address_transaction/index.html.eex:116 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 @@ -162,7 +162,7 @@ msgstr "Overview" msgid "Success" msgstr "Success" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:94 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:104 #: lib/block_scout_web/templates/address_transaction/index.html.eex:105 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_transaction_view.ex:9 @@ -317,7 +317,7 @@ msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:60 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:117 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:127 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_transaction/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:60 @@ -366,7 +366,7 @@ msgstr "" msgid "Wei" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:98 #: lib/block_scout_web/templates/address_transaction/index.html.eex:99 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 @@ -488,7 +488,7 @@ msgstr "" msgid "There are no Transactions" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:130 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:142 #: lib/block_scout_web/templates/address_transaction/index.html.eex:143 #: lib/block_scout_web/templates/block/index.html.eex:15 #: lib/block_scout_web/templates/block_transaction/index.html.eex:50 @@ -618,7 +618,7 @@ msgid "QR Code" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:124 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:136 msgid "There are no internal transactions for this address." msgstr "" @@ -1012,3 +1012,13 @@ msgstr "" #: lib/block_scout_web/views/transaction_view.ex:42 msgid "Max of" msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:88 +msgid "Connection Lost, click to load newer internal transactions" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:83 +msgid "More internal transactions have come in" +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/chain_test.exs b/apps/block_scout_web/test/block_scout_web/chain_test.exs index b951820a87..93a7757456 100644 --- a/apps/block_scout_web/test/block_scout_web/chain_test.exs +++ b/apps/block_scout_web/test/block_scout_web/chain_test.exs @@ -4,6 +4,24 @@ defmodule BlockScoutWeb.ChainTest do alias Explorer.Chain.{Address, Block, Transaction} alias BlockScoutWeb.Chain + describe "current_filter/1" do + test "sets direction based on to filter" do + assert [direction: :to] = Chain.current_filter(%{"filter" => "to"}) + end + + test "sets direction based on from filter" do + assert [direction: :from] = Chain.current_filter(%{"filter" => "from"}) + end + + test "no direction set" do + assert [] = Chain.current_filter(%{}) + end + + test "no direction set with paging_options" do + assert [paging_options: "test"] = Chain.current_filter(%{paging_options: "test"}) + end + end + describe "from_param/1" do test "finds a block by block number with a valid block number" do %Block{number: number} = insert(:block, number: 37) diff --git a/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs index b4c43f0899..8f8b6da85a 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs @@ -110,5 +110,75 @@ defmodule BlockScoutWeb.AddressChannelTest do 100 -> assert true end end + + test "notified of new_internal_transaction for matching from_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + internal_transaction = insert(:internal_transaction, transaction: transaction, from_address: address, index: 0) + + Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} -> + assert payload.address.hash == address.hash + assert payload.internal_transaction.id == internal_transaction.id + after + 5_000 -> + assert false, "Expected message received nothing." + end + end + + test "notified of new_internal_transaction for matching to_address", %{address: address, topic: topic} do + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + internal_transaction = insert(:internal_transaction, transaction: transaction, to_address: address, index: 0) + + Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} -> + assert payload.address.hash == address.hash + assert payload.internal_transaction.id == internal_transaction.id + after + 5_000 -> + assert false, "Expected message received nothing." + end + end + + test "not notified twice of new_internal_transaction if to and from address are equal", %{ + address: address, + topic: topic + } do + transaction = + :transaction + |> insert(from_address: address, to_address: address) + |> with_block() + + internal_transaction = + insert(:internal_transaction, transaction: transaction, from_address: address, to_address: address, index: 0) + + Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "internal_transaction", payload: payload} -> + assert payload.address.hash == address.hash + assert payload.internal_transaction.id == internal_transaction.id + after + 5_000 -> + assert false, "Expected message received nothing." + end + + receive do + _ -> assert false, "Received duplicate broadcast." + after + 100 -> assert true + end + end end end diff --git a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs index d418a419d1..49f02d8182 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/exchange_rate_channel_test.exs @@ -7,6 +7,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do alias Explorer.ExchangeRates alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Source.TestSource + alias Explorer.Market setup :verify_on_exit! @@ -25,7 +26,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do market_cap_usd: Decimal.new("1000000.0"), name: "test", symbol: Explorer.coin(), - usd_value: Decimal.new("1.0"), + usd_value: Decimal.new("2.5"), volume_24h_usd: Decimal.new("1000.0") } @@ -36,20 +37,55 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do {:ok, %{token: token}} end - test "subscribed user is notified of new_rate event", %{token: token} do - ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) + describe "new_rate" do + test "subscribed user is notified", %{token: token} do + ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) - topic = "exchange_rate:new_rate" - @endpoint.subscribe(topic) + topic = "exchange_rate:new_rate" + @endpoint.subscribe(topic) - Notifier.handle_event({:chain_event, :exchange_rate}) + Notifier.handle_event({:chain_event, :exchange_rate}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> - assert payload.exchange_rate == token - after - 5_000 -> - assert false, "Expected message received nothing." + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> + assert payload.exchange_rate == token + assert payload.market_history_data == [] + after + 5_000 -> + assert false, "Expected message received nothing." + end + end + + test "subscribed user is notified with market history", %{token: token} do + ExchangeRates.handle_info({nil, {:ok, [token]}}, %{}) + + today = Date.utc_today() + + old_records = + for i <- 1..29 do + %{ + date: Timex.shift(today, days: i * -1), + closing_price: Decimal.new(1) + } + end + + records = [%{date: today, closing_price: token.usd_value} | old_records] + + Market.bulk_insert_history(records) + + topic = "exchange_rate:new_rate" + @endpoint.subscribe(topic) + + Notifier.handle_event({:chain_event, :exchange_rate}) + + receive do + %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_rate", payload: payload} -> + assert payload.exchange_rate == token + assert payload.market_history_data == records + after + 5_000 -> + assert false, "Expected message received nothing." + end end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index 3cd3cee7c8..b9a49d75cc 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -6,6 +6,14 @@ defmodule BlockScoutWeb.SmartContractControllerTest do setup :verify_on_exit! describe "GET index/3" do + test "error for invalid address", %{conn: conn} do + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, :en, hash: "0x00") + + conn = get(conn, path) + + assert conn.status == 404 + end + test "only responds to ajax requests", %{conn: conn} do smart_contract = insert(:smart_contract) @@ -36,6 +44,22 @@ defmodule BlockScoutWeb.SmartContractControllerTest do end describe "GET show/3" do + test "error for invalid address", %{conn: conn} do + path = + smart_contract_path( + BlockScoutWeb.Endpoint, + :show, + :en, + "0x00", + function_name: "get", + args: [] + ) + + conn = get(conn, path) + + assert conn.status == 404 + end + test "only responds to ajax requests", %{conn: conn} do smart_contract = insert(:smart_contract) diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex index 601e5c148a..1d6c4aef9e 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex @@ -31,15 +31,19 @@ defmodule BlockScoutWeb.AddressPage do css("[data-test='address_detail_hash']", text: to_string(address_hash)) end + def internal_transaction(%InternalTransaction{id: id}) do + css("[data-test='internal_transaction'][data-internal-transaction-id='#{id}']") + end + def internal_transactions(count: count) do css("[data-test='internal_transaction']", count: count) end - def internal_transaction_address_link(%InternalTransaction{id: id, to_address_hash: address_hash}, :to) do + def internal_transaction_address_link(%InternalTransaction{id: id, from_address_hash: address_hash}, :from) do css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") end - def internal_transaction_address_link(%InternalTransaction{id: id, from_address_hash: address_hash}, :from) do + def internal_transaction_address_link(%InternalTransaction{id: id, to_address_hash: address_hash}, :to) do css("[data-internal-transaction-id='#{id}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") end @@ -55,11 +59,11 @@ defmodule BlockScoutWeb.AddressPage do css("[data-transaction-hash='#{transaction_hash}']") end - def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do + def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") end - def transaction_address_link(%Transaction{hash: hash, from_address_hash: address_hash}, :from) do + def transaction_address_link(%Transaction{hash: hash, to_address_hash: address_hash}, :to) do css("[data-transaction-hash='#{hash}'] [data-test='address_hash_link'] [data-address-hash='#{address_hash}']") end @@ -91,8 +95,4 @@ defmodule BlockScoutWeb.AddressPage do def token_transfers_expansion(%Transaction{hash: transaction_hash}) do css("[data-transaction-hash='#{transaction_hash}'] [data-test='token_transfers_expansion']") end - - def transaction_type do - css("[data-test='transaction_type']") - end end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex index 70d192881d..9ff557eabb 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/chain_page.ex @@ -15,10 +15,6 @@ defmodule BlockScoutWeb.ChainPage do css("[data-test='contract-creation'] [data-address-hash='#{hash}']") end - def non_loaded_transaction_count(count) do - css("[data-selector='channel-batching-count']", text: count) - end - def search(session, text) do session |> fill_in(css("[data-test='search_input']"), with: text) @@ -37,14 +33,6 @@ defmodule BlockScoutWeb.ChainPage do css("[data-test='chain_transaction']", count: count) end - def transaction(%Transaction{hash: transaction_hash}) do - css("[data-transaction-hash='#{transaction_hash}']") - end - - def transaction_status(%Transaction{hash: transaction_hash}) do - css("[data-transaction-hash='#{transaction_hash}'] [data-test='transaction_status']") - end - def visit_page(session) do visit(session, "/") end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex index 479738a77d..b3de0908ab 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_list_page.ex @@ -19,10 +19,6 @@ defmodule BlockScoutWeb.TransactionListPage do css("[data-transaction-hash='#{hash}'] [data-test='transaction_type']", text: "Contract Creation") end - def non_loaded_transaction_count(count) do - css("[data-selector='channel-batching-count']", text: count) - end - def transaction(%Transaction{hash: transaction_hash}) do css("[data-transaction-hash='#{transaction_hash}']") end diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex index 3b59ceae12..5a6ccfa9dc 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/transaction_page.ex @@ -18,8 +18,4 @@ defmodule BlockScoutWeb.TransactionPage do def visit_page(session, %Transaction{hash: transaction_hash}) do visit(session, "/tx/#{transaction_hash}") end - - def visit_page(session, transaction_hash = %Hash{}) do - visit(session, "/tx/#{transaction_hash}") - end end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs index b5ca4a5a3e..bbd3b1a1ec 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do alias Explorer.Chain.Wei alias Explorer.Factory - alias BlockScoutWeb.{AddressPage, AddressView} + alias BlockScoutWeb.{AddressPage, AddressView, Notifier} setup do block = insert(:block) @@ -169,6 +169,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do session |> AddressPage.visit_page(addresses.lincoln) |> assert_has(AddressPage.transaction_address_link(transactions.from_lincoln, :to)) + |> refute_has(AddressPage.transaction_address_link(transactions.from_lincoln, :from)) end end @@ -216,6 +217,28 @@ defmodule BlockScoutWeb.ViewingAddressesTest do |> AddressPage.visit_page(addresses.lincoln) |> AddressPage.click_internal_transactions() |> assert_has(AddressPage.internal_transaction_address_link(internal_transaction, :from)) + |> refute_has(AddressPage.internal_transaction_address_link(internal_transaction, :to)) + end + + test "viewing new internal transactions via live update", %{addresses: addresses, session: session} do + transaction = + :transaction + |> insert(from_address: addresses.lincoln) + |> with_block() + + session + |> AddressPage.visit_page(addresses.lincoln) + |> AddressPage.click_internal_transactions() + |> assert_has(AddressPage.internal_transactions(count: 2)) + + internal_transaction = + insert(:internal_transaction, transaction: transaction, index: 0, from_address: addresses.lincoln) + + Notifier.handle_event({:chain_event, :internal_transactions, [internal_transaction]}) + + session + |> assert_has(AddressPage.internal_transactions(count: 3)) + |> assert_has(AddressPage.internal_transaction(internal_transaction)) end end diff --git a/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs new file mode 100644 index 0000000000..0ce111afa9 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/block_view_test.exs @@ -0,0 +1,29 @@ +defmodule BlockScoutWeb.BlockViewTest do + use BlockScoutWeb.ConnCase, async: true + + alias BlockScoutWeb.BlockView + alias Explorer.Repo + + describe "average_gas_price/1" do + test "returns an average of the gas prices for a block's transactions with the unit value" do + block = insert(:block) + + Enum.each(1..10, fn index -> + :transaction + |> insert(gas_price: 10_000_000_000 * index) + |> with_block(block) + end) + + assert "55 Gwei" == BlockView.average_gas_price(Repo.preload(block, [:transactions])) + end + end + + describe "formatted_timestamp/1" do + test "returns a formatted timestamp string for a block" do + block = insert(:block) + + assert Timex.format!(block.timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) == + BlockView.formatted_timestamp(block) + end + end +end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 63c21a5b2a..2ac103b84b 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -48,7 +48,8 @@ defmodule Explorer.Chain do @typedoc """ Event type where data is broadcasted whenever data is inserted from chain indexing. """ - @type chain_event :: :addresses | :balances | :blocks | :exchange_rate | :logs | :transactions + @type chain_event :: + :addresses | :balances | :blocks | :exchange_rate | :internal_transactions | :logs | :transactions @type direction :: :from | :to @@ -1212,7 +1213,7 @@ defmodule Explorer.Chain do """ @spec subscribe_to_events(chain_event()) :: :ok def subscribe_to_events(event_type) - when event_type in ~w(addresses balances blocks exchange_rate logs transactions)a do + when event_type in ~w(addresses balances blocks exchange_rate internal_transactions logs transactions)a do Registry.register(Registry.ChainEvents, event_type, []) :ok end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index a09de9aa5b..1644a088d2 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -209,7 +209,8 @@ defmodule Explorer.Chain.Import do end defp broadcast_events(data) do - for {event_type, event_data} <- data, event_type in ~w(addresses balances blocks logs transactions)a do + for {event_type, event_data} <- data, + event_type in ~w(addresses balances blocks internal_transactions logs transactions)a do broadcast_event_data(event_type, event_data) end end @@ -666,7 +667,7 @@ defmodule Explorer.Chain.Import do conflict_target: [:transaction_hash, :index], for: InternalTransaction, on_conflict: :replace_all, - returning: [:index, :transaction_hash], + returning: [:id, :index, :transaction_hash], timeout: timeout, timestamps: timestamps ) @@ -674,7 +675,7 @@ defmodule Explorer.Chain.Import do {:ok, for( internal_transaction <- internal_transactions, - do: Map.take(internal_transaction, [:index, :transaction_hash]) + do: Map.take(internal_transaction, [:id, :index, :transaction_hash]) )} end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 4440845207..7c691c6d59 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -417,6 +417,12 @@ defmodule Explorer.Chain.ImportTest do assert_received {:chain_event, :blocks, [%Block{}]} end + test "publishes internal_transaction data to subscribers on insert" do + Chain.subscribe_to_events(:internal_transactions) + Import.all(@import_data) + assert_received {:chain_event, :internal_transactions, [%{id: _}, %{id: _}]} + end + test "publishes log data to subscribers on insert" do Chain.subscribe_to_events(:logs) Import.all(@import_data)