From 95e3774e9ab0a304556d49f632a794c3202c2ed6 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 11:01:43 -0500 Subject: [PATCH 01/12] Switch internal_transactions to composite primary key * Drop id. * Make (transaaction_hash, index) the primary key. --- .../internal_transaction/_tile.html.eex | 2 +- .../channels/address_channel_test.exs | 143 +++++++----------- ...s_internal_transaction_controller_test.exs | 18 ++- ...n_internal_transaction_controller_test.exs | 7 +- ...saction_token_transfer_controller_test.exs | 14 +- .../features/pages/address_page.ex | 28 +++- apps/explorer/lib/explorer/chain.ex | 4 +- .../chain/import/internal_transactions.ex | 2 +- .../explorer/chain/internal_transaction.ex | 10 +- ...nal_transactions_composite_primary_key.exs | 22 +++ .../test/explorer/chain/import_test.exs | 4 +- apps/explorer/test/explorer/chain_test.exs | 124 +++++++++------ 12 files changed, 219 insertions(+), 159 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20181024141113_internal_transactions_composite_primary_key.exs diff --git a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex index 4d88f14ceb..b2c6940561 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/internal_transaction/_tile.html.eex @@ -1,4 +1,4 @@ -
+
<%= gettext("Internal Transaction") %> 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 aad8a9246f..1ab7489231 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 @@ -11,13 +11,7 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :addresses, :realtime, [address]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "count", payload: %{count: _}} -> - assert true - after - 5_000 -> - assert false, "Expected message received nothing." - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "count", payload: %{count: _}}, 5_000 end describe "user subscribed to address" do @@ -32,23 +26,14 @@ defmodule BlockScoutWeb.AddressChannelTest do address_with_balance = %{address | fetched_coin_balance: 1} Notifier.handle_event({:chain_event, :addresses, :realtime, [address_with_balance]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "balance_update", payload: payload} -> - assert payload.address.hash == address_with_balance.hash - after - 5_000 -> - assert false, "Expected message received nothing." - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "balance_update", payload: payload}, 5_000 + assert payload.address.hash == address_with_balance.hash end test "not notified of balance_update if fetched_coin_balance is nil", %{address: address} do Notifier.handle_event({:chain_event, :addresses, :realtime, [address]}) - receive do - _ -> assert false, "Message was broadcast for nil fetched_coin_balance." - after - 100 -> assert true - end + refute_receive _, 100, "Message was broadcast for nil fetched_coin_balance." end test "notified of new_pending_transaction for matching from_address", %{address: address, topic: topic} do @@ -56,14 +41,9 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [pending.hash]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload} -> - assert payload.address.hash == address.hash - assert payload.transaction.hash == pending.hash - after - 5_000 -> - assert false, "Expected message received nothing." - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload}, 5_000 + assert payload.address.hash == address.hash + assert payload.transaction.hash == pending.hash end test "notified of new_transaction for matching from_address", %{address: address, topic: topic} do @@ -74,14 +54,9 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload} -> - assert payload.address.hash == address.hash - assert payload.transaction.hash == transaction.hash - after - 5_000 -> - assert false, "Expected message received nothing." - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash end test "notified of new_transaction for matching to_address", %{address: address, topic: topic} do @@ -92,14 +67,9 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload} -> - assert payload.address.hash == address.hash - assert payload.transaction.hash == transaction.hash - after - 5_000 -> - assert false, "Expected message received nothing." - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash end test "not notified twice of new_transaction if to and from address are equal", %{address: address, topic: topic} do @@ -110,20 +80,11 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - receive do - %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload} -> - assert payload.address.hash == address.hash - assert payload.transaction.hash == transaction.hash - after - 5_000 -> - assert false, "Expected message received nothing." - end - - receive do - _ -> assert false, "Received duplicate broadcast." - after - 100 -> assert true - end + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert payload.address.hash == address.hash + assert payload.transaction.hash == transaction.hash + + refute_receive _, 100, "Received duplicate broadcast." end test "notified of new_internal_transaction for matching from_address", %{address: address, topic: topic} do @@ -136,14 +97,18 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [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 + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + 5_000 + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} end test "notified of new_internal_transaction for matching to_address", %{address: address, topic: topic} do @@ -156,14 +121,18 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [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 + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + 5_000 + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} end test "not notified twice of new_internal_transaction if to and from address are equal", %{ @@ -180,20 +149,20 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [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 + assert_receive %Phoenix.Socket.Broadcast{ + topic: ^topic, + event: "internal_transaction", + payload: %{ + address: %{hash: address_hash}, + internal_transaction: %{transaction_hash: transaction_hash, index: index} + } + }, + 5_000 + + assert address_hash == address.hash + assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + + refute_receive _, 100, "Received duplicate broadcast." end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs index 6d2588ad5f..19d03d9cdd 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs @@ -50,12 +50,18 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do path = address_internal_transaction_path(conn, :index, address) conn = get(conn, path) - actual_transaction_ids = - conn.assigns.internal_transactions - |> Enum.map(fn internal_transaction -> internal_transaction.id end) - - assert Enum.member?(actual_transaction_ids, from_internal_transaction.id) - assert Enum.member?(actual_transaction_ids, to_internal_transaction.id) + actual_internal_transaction_primary_keys = + Enum.map(conn.assigns.internal_transactions, &{&1.transaction_hash, &1.index}) + + assert Enum.member?( + actual_internal_transaction_primary_keys, + {from_internal_transaction.transaction_hash, from_internal_transaction.index} + ) + + assert Enum.member?( + actual_internal_transaction_primary_keys, + {to_internal_transaction.transaction_hash, to_internal_transaction.index} + ) end test "includes USD exchange rate value for address in assigns", %{conn: conn} do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs index 1a1899c82b..0e6c8d7009 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -59,13 +59,12 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do conn = get(conn, path) - actual_internal_transaction_ids = - conn.assigns.internal_transactions - |> Enum.map(fn it -> it.id end) + actual_internal_transaction_primary_keys = + Enum.map(conn.assigns.internal_transactions, &{&1.transaction_hash, &1.index}) assert html_response(conn, 200) - assert Enum.member?(actual_internal_transaction_ids, expected_internal_transaction.id) + assert {expected_internal_transaction.transaction_hash, expected_internal_transaction.index} in actual_internal_transaction_primary_keys end test "includes USD exchange rate value for address in assigns", %{conn: conn} do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs index d204099dad..01f51a2b8d 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs @@ -12,7 +12,10 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do conn = get(conn, transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)) - assert List.first(conn.assigns.transaction.token_transfers).id == token_transfer.id + assigned_token_transfer = List.first(conn.assigns.transaction.token_transfers) + + assert {assigned_token_transfer.transaction_hash, assigned_token_transfer.log_index} == + {token_transfer.transaction_hash, token_transfer.log_index} end test "with missing transaction", %{conn: conn} do @@ -53,13 +56,16 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do conn = get(conn, path) - actual_token_transfer_ids = + actual_token_transfer_primary_keys = conn.assigns.token_transfers - |> Enum.map(fn it -> it.id end) + |> Enum.map(&{&1.transaction_hash, &1.log_index}) assert html_response(conn, 200) - assert Enum.member?(actual_token_transfer_ids, expected_token_transfer.id) + assert Enum.member?( + actual_token_transfer_primary_keys, + {expected_token_transfer.transaction_hash, expected_token_transfer.log_index} + ) end test "includes USD exchange rate value for address in assigns", %{conn: conn} do 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 41c17024eb..2acba3aa77 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 @@ -75,20 +75,36 @@ 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}']") + def internal_transaction(%InternalTransaction{transaction_hash: transaction_hash, index: index}) do + css( + "[data-test='internal_transaction']" <> + "[data-internal-transaction-transaction-hash='#{transaction_hash}']" <> + "[data-internal-transaction-index='#{index}']" + ) end def internal_transactions(count: count) do css("[data-test='internal_transaction']", count: count) end - 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}']") + def internal_transaction_address_link( + %InternalTransaction{transaction_hash: transaction_hash, index: index, from_address_hash: address_hash}, + :from + ) do + css( + "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + " [data-test='address_hash_link']" <> " [data-address-hash='#{address_hash}']" + ) end - 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}']") + def internal_transaction_address_link( + %InternalTransaction{transaction_hash: transaction_hash, index: index, to_address_hash: address_hash}, + :to + ) do + css( + "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + " [data-test='address_hash_link']" <> " [data-address-hash='#{address_hash}']" + ) end def pending_transaction(%Transaction{hash: transaction_hash}), do: pending_transaction(transaction_hash) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 400cd16232..5cc07d3332 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -883,7 +883,7 @@ defmodule Explorer.Chain do """ def internal_transaction_count do - Repo.aggregate(InternalTransaction, :count, :id) + Repo.one!(from(it in "internal_transactions", select: fragment("COUNT(*)"))) end @doc """ @@ -1894,7 +1894,7 @@ defmodule Explorer.Chain do internal_transaction.type != ^:call or fragment( """ - (SELECT COUNT(sibling.id) + (SELECT COUNT(sibling.*) FROM internal_transactions AS sibling WHERE sibling.transaction_hash = ? LIMIT 2 diff --git a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex index fae31ac669..2b21a5b38b 100644 --- a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex @@ -69,7 +69,7 @@ defmodule Explorer.Chain.Import.InternalTransactions do conflict_target: [:transaction_hash, :index], for: InternalTransaction, on_conflict: on_conflict, - returning: [:id, :index, :transaction_hash], + returning: [:transaction_hash, :index], timeout: timeout, timestamps: timestamps ) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 52dc47d858..dfce9c8f78 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -49,13 +49,14 @@ defmodule Explorer.Chain.InternalTransaction do value: Wei.t() } + @primary_key false schema "internal_transactions" do field(:call_type, CallType) field(:created_contract_code, Data) field(:error, :string) field(:gas, :decimal) field(:gas_used, :decimal) - field(:index, :integer) + field(:index, :integer, primary_key: true) field(:init, Data) field(:input, Data) field(:output, Data) @@ -91,7 +92,12 @@ defmodule Explorer.Chain.InternalTransaction do type: Hash.Address ) - belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) + belongs_to(:transaction, Transaction, + foreign_key: :transaction_hash, + primary_key: true, + references: :hash, + type: Hash.Full + ) end @doc """ diff --git a/apps/explorer/priv/repo/migrations/20181024141113_internal_transactions_composite_primary_key.exs b/apps/explorer/priv/repo/migrations/20181024141113_internal_transactions_composite_primary_key.exs new file mode 100644 index 0000000000..be09d23c93 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181024141113_internal_transactions_composite_primary_key.exs @@ -0,0 +1,22 @@ +defmodule Explorer.Repo.Migrations.InternalTransactionsCompositePrimaryKey do + use Ecto.Migration + + def up do + # Remove old id + alter table(:internal_transactions) do + remove(:id) + end + + # Don't use `modify` as it requires restating the whole column description + execute("ALTER TABLE internal_transactions ADD PRIMARY KEY (transaction_hash, index)") + end + + def down do + execute("ALTER TABLE internal_transactions DROP CONSTRAINT internal_transactions_pkey") + + # Add back old id + alter table(:internal_transactions) do + add(:id, :bigserial, primary_key: true) + end + end +end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 21b493ca76..ab4f51aeb5 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -429,7 +429,9 @@ defmodule Explorer.Chain.ImportTest do 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, :realtime, [%{id: _}, %{id: _}]} + + assert_received {:chain_event, :internal_transactions, :realtime, + [%{transaction_hash: _, index: _}, %{transaction_hash: _, index: _}]} end test "publishes log data to subscribers on insert" do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 9bb6242fbb..1f460777b0 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1270,7 +1270,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - %InternalTransaction{id: first_id} = + %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = insert(:internal_transaction, index: 1, transaction: transaction, @@ -1279,7 +1279,7 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - %InternalTransaction{id: second_id} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert(:internal_transaction, index: 2, transaction: transaction, @@ -1291,10 +1291,10 @@ defmodule Explorer.ChainTest do result = address |> Chain.address_to_internal_transactions() - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) - assert Enum.member?(result, first_id) - assert Enum.member?(result, second_id) + assert Enum.member?(result, {first_transaction_hash, first_index}) + assert Enum.member?(result, {second_transaction_hash, second_index}) end test "loads associations in necessity_by_association" do @@ -1359,7 +1359,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - %InternalTransaction{id: first_pending} = + %InternalTransaction{transaction_hash: first_pending_transaction_hash, index: first_pending_index} = insert( :internal_transaction, transaction: pending_transaction, @@ -1369,7 +1369,7 @@ defmodule Explorer.ChainTest do transaction_index: pending_transaction.index ) - %InternalTransaction{id: second_pending} = + %InternalTransaction{transaction_hash: second_pending_transaction_hash, index: second_pending_index} = insert( :internal_transaction, transaction: pending_transaction, @@ -1386,7 +1386,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(a_block) - %InternalTransaction{id: first} = + %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = insert( :internal_transaction, transaction: first_a_transaction, @@ -1396,7 +1396,7 @@ defmodule Explorer.ChainTest do transaction_index: first_a_transaction.index ) - %InternalTransaction{id: second} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert( :internal_transaction, transaction: first_a_transaction, @@ -1411,7 +1411,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(a_block) - %InternalTransaction{id: third} = + %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = insert( :internal_transaction, transaction: second_a_transaction, @@ -1421,7 +1421,7 @@ defmodule Explorer.ChainTest do transaction_index: second_a_transaction.index ) - %InternalTransaction{id: fourth} = + %InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} = insert( :internal_transaction, transaction: second_a_transaction, @@ -1438,7 +1438,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(b_block) - %InternalTransaction{id: fifth} = + %InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} = insert( :internal_transaction, transaction: first_b_transaction, @@ -1448,7 +1448,7 @@ defmodule Explorer.ChainTest do transaction_index: first_b_transaction.index ) - %InternalTransaction{id: sixth} = + %InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} = insert( :internal_transaction, transaction: first_b_transaction, @@ -1461,9 +1461,18 @@ defmodule Explorer.ChainTest do result = address |> Chain.address_to_internal_transactions() - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) - assert [second_pending, first_pending, sixth, fifth, fourth, third, second, first] == result + assert [ + {second_pending_transaction_hash, second_pending_index}, + {first_pending_transaction_hash, first_pending_index}, + {sixth_transaction_hash, sixth_index}, + {fifth_transaction_hash, fifth_index}, + {fourth_transaction_hash, fourth_index}, + {third_transaction_hash, third_index}, + {second_transaction_hash, second_index}, + {first_transaction_hash, first_index} + ] == result end test "pages by {block_number, transaction_index, index}" do @@ -1492,7 +1501,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(a_block) - %InternalTransaction{id: first} = + %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = insert( :internal_transaction, transaction: first_a_transaction, @@ -1502,7 +1511,7 @@ defmodule Explorer.ChainTest do transaction_index: first_a_transaction.index ) - %InternalTransaction{id: second} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert( :internal_transaction, transaction: first_a_transaction, @@ -1517,7 +1526,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(a_block) - %InternalTransaction{id: third} = + %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = insert( :internal_transaction, transaction: second_a_transaction, @@ -1527,7 +1536,7 @@ defmodule Explorer.ChainTest do transaction_index: second_a_transaction.index ) - %InternalTransaction{id: fourth} = + %InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} = insert( :internal_transaction, transaction: second_a_transaction, @@ -1544,7 +1553,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(b_block) - %InternalTransaction{id: fifth} = + %InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} = insert( :internal_transaction, transaction: first_b_transaction, @@ -1554,7 +1563,7 @@ defmodule Explorer.ChainTest do transaction_index: first_b_transaction.index ) - %InternalTransaction{id: sixth} = + %InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} = insert( :internal_transaction, transaction: first_b_transaction, @@ -1566,28 +1575,45 @@ defmodule Explorer.ChainTest do # When paged, internal transactions need an associated block number, so `second_pending` and `first_pending` are # excluded. - assert [sixth, fifth, fourth, third, second, first] == + assert [ + {sixth_transaction_hash, sixth_index}, + {fifth_transaction_hash, fifth_index}, + {fourth_transaction_hash, fourth_index}, + {third_transaction_hash, third_index}, + {second_transaction_hash, second_index}, + {first_transaction_hash, first_index} + ] == address |> Chain.address_to_internal_transactions( paging_options: %PagingOptions{key: {6001, 3, 2}, page_size: 8} ) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) # block number ==, transaction index ==, internal transaction index < - assert [fourth, third, second, first] == + assert [ + {fourth_transaction_hash, fourth_index}, + {third_transaction_hash, third_index}, + {second_transaction_hash, second_index}, + {first_transaction_hash, first_index} + ] == address |> Chain.address_to_internal_transactions( paging_options: %PagingOptions{key: {6000, 0, 1}, page_size: 8} ) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) # block number ==, transaction index < - assert [fourth, third, second, first] == + assert [ + {fourth_transaction_hash, fourth_index}, + {third_transaction_hash, third_index}, + {second_transaction_hash, second_index}, + {first_transaction_hash, first_index} + ] == address |> Chain.address_to_internal_transactions( paging_options: %PagingOptions{key: {6000, -1, -1}, page_size: 8} ) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) # block number < assert [] == @@ -1595,7 +1621,7 @@ defmodule Explorer.ChainTest do |> Chain.address_to_internal_transactions( paging_options: %PagingOptions{key: {2000, -1, -1}, page_size: 8} ) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) end test "excludes internal transactions of type `call` when they are alone in the parent transaction" do @@ -1637,7 +1663,7 @@ defmodule Explorer.ChainTest do actual = Enum.at(Chain.address_to_internal_transactions(address), 0) - assert actual.id == expected.id + assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} end end @@ -1708,7 +1734,15 @@ defmodule Explorer.ChainTest do results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction) assert 2 == length(results) - assert Enum.all?(results, &(&1.id in [first.id, second.id])) + + assert Enum.all?( + results, + &({&1.transaction_hash, &1.index} in [ + {first.transaction_hash, first.index}, + {second.transaction_hash, second.index} + ]) + ) + assert internal_transaction.transaction.block.number == block.number end @@ -1781,7 +1815,7 @@ defmodule Explorer.ChainTest do actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) - assert actual.id == expected.id + assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} end test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do @@ -1801,7 +1835,7 @@ defmodule Explorer.ChainTest do actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) - assert actual.id == expected.id + assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} end test "includes internal transactions of type `suicide` even when they are alone in the parent transaction" do @@ -1822,7 +1856,7 @@ defmodule Explorer.ChainTest do actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0) - assert actual.id == expected.id + assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} end test "returns the internal transactions in ascending index order" do @@ -1831,7 +1865,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block() - %InternalTransaction{id: first_id} = + %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = insert(:internal_transaction, transaction: transaction, index: 0, @@ -1839,7 +1873,7 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - %InternalTransaction{id: second_id} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert(:internal_transaction, transaction: transaction, index: 1, @@ -1850,9 +1884,9 @@ defmodule Explorer.ChainTest do result = transaction |> Chain.transaction_to_internal_transactions() - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) - assert [first_id, second_id] == result + assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] == result end test "pages by index" do @@ -1861,7 +1895,7 @@ defmodule Explorer.ChainTest do |> insert() |> with_block() - %InternalTransaction{id: first_id} = + %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = insert(:internal_transaction, transaction: transaction, index: 0, @@ -1869,7 +1903,7 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - %InternalTransaction{id: second_id} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert(:internal_transaction, transaction: transaction, index: 1, @@ -1877,20 +1911,20 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - assert [^first_id, ^second_id] = + assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] == transaction |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2}) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) - assert [^first_id] = + assert [{first_transaction_hash, first_index}] == transaction |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1}) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) - assert [^second_id] = + assert [{second_transaction_hash, second_index}] == transaction |> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, page_size: 2}) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.index}) end end From ac2bf31856bfccf884849653aca8907b7814f334 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 11:30:45 -0500 Subject: [PATCH 02/12] Don't replace_all fields of internal_transactions on_conflict Fixes #978 Replace all field except the primary key and timestamps. For the primary key fields (transaction_hash, index), leave them as is as they are the conflict target and have no changed by definition. For the timestamps that the least inserted_at and the greatest updated_at to preserve the bounds of the timeline. --- .../chain/import/internal_transactions.ex | 32 ++++++++++++++++++- .../explorer/chain/internal_transaction.ex | 8 +++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex index 2b21a5b38b..059a1284bb 100644 --- a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex @@ -58,7 +58,7 @@ defmodule Explorer.Chain.Import.InternalTransactions do | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get(options, :on_conflict, :replace_all) + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) @@ -81,6 +81,36 @@ defmodule Explorer.Chain.Import.InternalTransactions do )} end + defp default_on_conflict do + from( + internal_transaction in InternalTransaction, + update: [ + set: [ + block_number: fragment("EXCLUDED.block_number"), + call_type: fragment("EXCLUDED.call_type"), + created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), + created_contract_code: fragment("EXCLUDED.created_contract_code"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_used: fragment("EXCLUDED.gas_used"), + # Don't update `index` as it is part of the composite primary key and used for the conflict target + init: fragment("EXCLUDED.init"), + input: fragment("EXCLUDED.input"), + output: fragment("EXCLUDED.output"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + trace_address: fragment("EXCLUDED.trace_address"), + # Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target + transaction_index: fragment("EXCLUDED.transaction_index"), + type: fragment("EXCLUDED.type"), + value: fragment("EXCLUDED.value"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", internal_transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", internal_transaction.updated_at) + ] + ] + ) + end + defp update_transactions(internal_transactions, %{ timeout: timeout, timestamps: timestamps diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index dfce9c8f78..ca392e567f 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -7,6 +7,7 @@ defmodule Explorer.Chain.InternalTransaction do alias Explorer.Chain.InternalTransaction.{CallType, Type} @typedoc """ + * `block_number` - the `t:Explorer.Chain.Block.t/0` `number` that the `transaction` is collated into. * `call_type` - the type of call. `nil` when `type` is not `:call`. * `created_contract_code` - the code of the contract that was created when `type` is `:create`. * `error` - error message when `:call` or `:create` `type` errors @@ -23,13 +24,15 @@ defmodule Explorer.Chain.InternalTransaction do * `trace_address` - list of traces * `transaction` - transaction in which this transaction occurred * `transaction_hash` - foreign key for `transaction` + * `transaction_index` - the `t:Explorer.Chain.Transaction.t/0` `index` of `transaction` in `block_number`. * `type` - type of internal transaction * `value` - value of transferred from `from_address` to `to_address` """ @type t :: %__MODULE__{ + block_number: Explorer.Chain.Block.block_number() | nil, call_type: CallType.t() | nil, created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, - created_contract_address_hash: Explorer.Chain.Hash.t() | nil, + created_contract_address_hash: Hash.t() | nil, created_contract_code: Data.t() | nil, error: String.t(), from_address: %Ecto.Association.NotLoaded{} | Address.t(), @@ -44,7 +47,8 @@ defmodule Explorer.Chain.InternalTransaction do to_address_hash: Hash.Address.t() | nil, trace_address: [non_neg_integer()], transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - transaction_hash: Explorer.Chain.Hash.t(), + transaction_hash: Hash.t(), + transaction_index: Transaction.transaction_index() | nil, type: Type.t(), value: Wei.t() } From f757e1100ff5ade36ea779214f668ab9dfcf184e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 11:43:47 -0500 Subject: [PATCH 03/12] Port #978 fix to blocks Similar to internal_transactions, by default don't update the conflict target (hash) and take the outer most values for timestamps. --- .../lib/explorer/chain/import/blocks.ex | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import/blocks.ex b/apps/explorer/lib/explorer/chain/import/blocks.ex index a1fa43d395..b6de2f2b6a 100644 --- a/apps/explorer/lib/explorer/chain/import/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/blocks.ex @@ -170,7 +170,7 @@ defmodule Explorer.Chain.Import.Blocks do @spec insert([map()], %{required(:timeout) => timeout, required(:timestamps) => Import.timestamps()}) :: {:ok, [Block.t()]} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get(options, :on_conflict, :replace_all) + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.number, &1.hash}) @@ -186,6 +186,30 @@ defmodule Explorer.Chain.Import.Blocks do ) end + defp default_on_conflict do + from( + block in Block, + update: [ + set: [ + consensus: fragment("EXCLUDED.consensus"), + difficulty: fragment("EXCLUDED.difficulty"), + gas_limit: fragment("EXCLUDED.gas_limit"), + gas_used: fragment("EXCLUDED.gas_used"), + miner_hash: fragment("EXCLUDED.miner_hash"), + nonce: fragment("EXCLUDED.nonce"), + number: fragment("EXCLUDED.number"), + parent_hash: fragment("EXCLUDED.parent_hash"), + size: fragment("EXCLUDED.size"), + timestamp: fragment("EXCLUDED.timestamp"), + total_difficulty: fragment("EXCLUDED.total_difficulty"), + # Don't update `hash` as it is used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", block.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", block.updated_at) + ] + ] + ) + end + defp lose_consensus(blocks_changes, %{timeout: timeout, timestamps: %{updated_at: updated_at}}) when is_list(blocks_changes) do ordered_consensus_block_number = From 96b97f0dc1f3daffb87ffb3b189c26ed62257372 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 12:04:45 -0500 Subject: [PATCH 04/12] Switch logs to composite primary key * Drop id. * Make (transaaction_hash, index) the primary key. This ports the change from `internal_transactions`. --- apps/explorer/lib/explorer/chain.ex | 2 +- apps/explorer/lib/explorer/chain/log.ex | 11 ++++++++-- ...81024164623_logs_composite_primary_key.exs | 22 +++++++++++++++++++ apps/explorer/test/explorer/chain_test.exs | 4 ++-- .../test/indexer/block/fetcher_test.exs | 2 +- 5 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20181024164623_logs_composite_primary_key.exs diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 5cc07d3332..ca1fad5984 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1167,7 +1167,7 @@ defmodule Explorer.Chain do """ def log_count do - Repo.aggregate(Log, :count, :id) + Repo.one!(from(log in "logs", select: fragment("COUNT(*)"))) end @doc """ diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index f61e8f4e37..20008fb7e0 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -35,11 +35,12 @@ defmodule Explorer.Chain.Log do type: String.t() | nil } + @primary_key false schema "logs" do field(:data, Data) field(:first_topic, :string) field(:fourth_topic, :string) - field(:index, :integer) + field(:index, :integer, primary_key: true) field(:second_topic, :string) field(:third_topic, :string) field(:type, :string) @@ -47,7 +48,13 @@ defmodule Explorer.Chain.Log do timestamps() belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) - belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) + + belongs_to(:transaction, Transaction, + foreign_key: :transaction_hash, + primary_key: true, + references: :hash, + type: Hash.Full + ) end @doc """ diff --git a/apps/explorer/priv/repo/migrations/20181024164623_logs_composite_primary_key.exs b/apps/explorer/priv/repo/migrations/20181024164623_logs_composite_primary_key.exs new file mode 100644 index 0000000000..702f79eec1 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181024164623_logs_composite_primary_key.exs @@ -0,0 +1,22 @@ +defmodule Explorer.Repo.Migrations.LogsCompositePrimaryKey do + use Ecto.Migration + + def up do + # Remove old id + alter table(:logs) do + remove(:id) + end + + # Don't use `modify` as it requires restating the whole column description + execute("ALTER TABLE logs ADD PRIMARY KEY (transaction_hash, index)") + end + + def down do + execute("ALTER TABLE logs DROP CONSTRAINT logs_pkey") + + # Add back old id + alter table(:logs) do + add(:id, :bigserial, primary_key: true) + end + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 1f460777b0..a2ff28c95c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1941,9 +1941,9 @@ defmodule Explorer.ChainTest do |> insert() |> with_block() - %Log{id: id} = insert(:log, transaction: transaction) + %Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction) - assert [%Log{id: ^id}] = Chain.transaction_to_logs(transaction) + assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction) end test "with logs can be paginated" do diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index a35d5fa776..60526035f8 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -564,7 +564,7 @@ defmodule Indexer.Block.FetcherTest do assert Repo.aggregate(Chain.Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 2 - assert Repo.aggregate(Log, :count, :id) == 1 + assert Chain.log_count() == 1 assert Repo.aggregate(Transaction, :count, :hash) == 1 first_address = Repo.get!(Address, first_address_hash) From 92264a59e229619235b48a23592c05d998fd8841 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 12:16:08 -0500 Subject: [PATCH 05/12] Don't replace_all fields of logs on_conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all field except the primary key and timestamps.  For the primary key fields (transaction_hash, index), leave them as is as they are the conflict target and have no changed by definition.  For the timestamps that the least inserted_at and the greatest updated_at to preserve the bounds of the timeline. This is a port of the change made to internal_transactions. --- .../lib/explorer/chain/import/logs.ex | 25 ++++++++++++++++++- apps/explorer/lib/explorer/chain/log.ex | 16 ++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/logs.ex b/apps/explorer/lib/explorer/chain/import/logs.ex index d5eb6223ef..4c4d210187 100644 --- a/apps/explorer/lib/explorer/chain/import/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/logs.ex @@ -8,6 +8,8 @@ defmodule Explorer.Chain.Import.Logs do alias Ecto.{Changeset, Multi} alias Explorer.Chain.{Import, Log} + import Ecto.Query, only: [from: 2] + @behaviour Import.Runner # milliseconds @@ -46,7 +48,7 @@ defmodule Explorer.Chain.Import.Logs do {:ok, [Log.t()]} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get(options, :on_conflict, :replace_all) + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) @@ -62,4 +64,25 @@ defmodule Explorer.Chain.Import.Logs do timestamps: timestamps ) end + + defp default_on_conflict do + from( + log in Log, + update: [ + set: [ + address_hash: fragment("EXCLUDED.address_hash"), + data: fragment("EXCLUDED.data"), + first_topic: fragment("EXCLUDED.first_topic"), + second_topic: fragment("EXCLUDED.second_topic"), + third_topic: fragment("EXCLUDED.third_topic"), + fourth_topic: fragment("EXCLUDED.fourth_topic"), + # Don't update `index` as it is part of the composite primary key and used for the conflict target + type: fragment("EXCLUDED.type"), + # Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", log.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", log.updated_at) + ] + ] + ) + end end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 20008fb7e0..9dcb31405a 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -13,12 +13,12 @@ defmodule Explorer.Chain.Log do * `address_hash` - foreign key for `address` * `data` - non-indexed log parameters. * `first_topic` - `topics[0]` - * `fourth_topic` - `topics[3]` - * `index` - index of the log entry in all logs for the `transaction` * `second_topic` - `topics[1]` + * `third_topic` - `topics[2]` + * `fourth_topic` - `topics[3]` * `transaction` - transaction for which `log` is * `transaction_hash` - foreign key for `transaction`. - * `third_topic` - `topics[2]` + * `index` - index of the log entry in all logs for the `transaction` * `type` - type of event. *Parity-only* """ @type t :: %__MODULE__{ @@ -26,12 +26,12 @@ defmodule Explorer.Chain.Log do address_hash: Hash.Address.t(), data: Data.t(), first_topic: String.t(), - fourth_topic: String.t(), - index: non_neg_integer(), second_topic: String.t(), + third_topic: String.t(), + fourth_topic: String.t(), transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction_hash: Hash.Full.t(), - third_topic: String.t(), + index: non_neg_integer(), type: String.t() | nil } @@ -39,10 +39,10 @@ defmodule Explorer.Chain.Log do schema "logs" do field(:data, Data) field(:first_topic, :string) - field(:fourth_topic, :string) - field(:index, :integer, primary_key: true) field(:second_topic, :string) field(:third_topic, :string) + field(:fourth_topic, :string) + field(:index, :integer, primary_key: true) field(:type, :string) timestamps() From 7f7cdf753d7ace2714d560121a8dbfc62c0568d9 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 12:55:40 -0500 Subject: [PATCH 06/12] Switch token_transfers to composite primary key * Drop id. * Make (transaaction_hash, log_index) the primary key. This ports the change from `internal_transactions`. --- apps/explorer/lib/explorer/chain.ex | 2 +- .../lib/explorer/chain/token_transfer.ex | 12 +++++-- ..._token_transfers_composite_primary_key.exs | 22 ++++++++++++ .../explorer/chain/token_transfer_test.exs | 15 ++++---- apps/explorer/test/explorer/chain_test.exs | 36 +++++++++++++------ 5 files changed, 67 insertions(+), 20 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20181024172010_token_transfers_composite_primary_key.exs diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index ca1fad5984..9be85c9c6f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1967,7 +1967,7 @@ defmodule Explorer.Chain do left_join: tf in TokenTransfer, on: tf.transaction_hash == l.transaction_hash and tf.log_index == l.index, where: l.first_topic == unquote(TokenTransfer.constant()), - where: is_nil(tf.id), + where: is_nil(tf.transaction_hash) and is_nil(tf.log_index), select: t.block_number, distinct: t.block_number ) diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 3e5ae71b4e..1d454e1809 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -62,9 +62,10 @@ defmodule Explorer.Chain.TokenTransfer do @constant "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @primary_key false schema "token_transfers" do field(:amount, :decimal) - field(:log_index, :integer) + field(:log_index, :integer, primary_key: true) field(:token_id, :decimal) belongs_to(:from_address, Address, foreign_key: :from_address_hash, references: :hash, type: Hash.Address) @@ -78,7 +79,12 @@ defmodule Explorer.Chain.TokenTransfer do type: Hash.Address ) - belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) + belongs_to(:transaction, Transaction, + foreign_key: :transaction_hash, + primary_key: true, + references: :hash, + type: Hash.Full + ) has_one(:token, through: [:token_contract_address, :token]) @@ -195,7 +201,7 @@ defmodule Explorer.Chain.TokenTransfer do tt in TokenTransfer, join: t in Token, on: tt.token_contract_address_hash == t.contract_address_hash, - select: {tt.token_contract_address_hash, count(tt.id)}, + select: {tt.token_contract_address_hash, fragment("COUNT(*)")}, group_by: tt.token_contract_address_hash ) diff --git a/apps/explorer/priv/repo/migrations/20181024172010_token_transfers_composite_primary_key.exs b/apps/explorer/priv/repo/migrations/20181024172010_token_transfers_composite_primary_key.exs new file mode 100644 index 0000000000..a973647898 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181024172010_token_transfers_composite_primary_key.exs @@ -0,0 +1,22 @@ +defmodule Explorer.Repo.Migrations.TokenTransfersCompositePrimaryKey do + use Ecto.Migration + + def up do + # Remove old id + alter table(:token_transfers) do + remove(:id) + end + + # Don't use `modify` as it requires restating the whole column description + execute("ALTER TABLE token_transfers ADD PRIMARY KEY (transaction_hash, log_index)") + end + + def down do + execute("ALTER TABLE token_transfers DROP CONSTRAINT token_transfers_pkey") + + # Add back old id + alter table(:token_transfers) do + add(:id, :bigserial, primary_key: true) + end + end +end diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index 040884afd8..a48326cc8c 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -50,12 +50,15 @@ defmodule Explorer.Chain.TokenTransferTest do token: token ) - transfers_ids = + transfers_primary_keys = token_contract_address.hash |> TokenTransfer.fetch_token_transfers_from_token_hash([]) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.log_index}) - assert transfers_ids == [another_transfer.id, token_transfer.id] + assert transfers_primary_keys == [ + {another_transfer.transaction_hash, another_transfer.log_index}, + {token_transfer.transaction_hash, token_transfer.log_index} + ] end test "when there isn't token transfers won't show anything" do @@ -101,14 +104,14 @@ defmodule Explorer.Chain.TokenTransferTest do paging_options = %PagingOptions{key: first_page.inserted_at, page_size: 1} - token_transfers_ids_paginated = + token_transfers_primary_keys_paginated = TokenTransfer.fetch_token_transfers_from_token_hash( token_contract_address.hash, paging_options: paging_options ) - |> Enum.map(& &1.id) + |> Enum.map(&{&1.transaction_hash, &1.log_index}) - assert token_transfers_ids_paginated == [second_page.id] + assert token_transfers_primary_keys_paginated == [{second_page.transaction_hash, second_page.log_index}] end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index a2ff28c95c..21ddaa3136 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -235,7 +235,10 @@ defmodule Explorer.ChainTest do insert(:token_transfer, to_address: build(:address), transaction: transaction) transaction = Chain.address_to_transactions(address) |> List.first() - assert transaction.token_transfers |> Enum.map(& &1.id) == [token_transfer.id] + + assert transaction.token_transfers |> Enum.map(&{&1.transaction_hash, &1.log_index}) == [ + {token_transfer.transaction_hash, token_transfer.log_index} + ] end test "returns just the token transfers related to the given contract address" do @@ -250,7 +253,10 @@ defmodule Explorer.ChainTest do insert(:token_transfer, to_address: build(:address), transaction: transaction) transaction = Chain.address_to_transactions(contract_address) |> List.first() - assert Enum.map(transaction.token_transfers, & &1.id) == [token_transfer.id] + + assert Enum.map(transaction.token_transfers, &{&1.transaction_hash, &1.log_index}) == [ + {token_transfer.transaction_hash, token_transfer.log_index} + ] end test "returns all token transfers when the given address is the token contract address" do @@ -572,12 +578,17 @@ defmodule Explorer.ChainTest do |> insert() |> with_block() - %TokenTransfer{id: token_transfer_id, token_contract_address_hash: token_contract_address_hash} = - insert(:token_transfer, to_address: address, transaction: transaction) + %TokenTransfer{ + transaction_hash: token_transfer_transaction_hash, + log_index: token_transfer_log_index, + token_contract_address_hash: token_contract_address_hash + } = insert(:token_transfer, to_address: address, transaction: transaction) assert token_contract_address_hash |> Chain.fetch_token_transfers_from_token_hash() - |> Enum.map(& &1.id) == [token_transfer_id] + |> Enum.map(&{&1.transaction_hash, &1.log_index}) == [ + {token_transfer_transaction_hash, token_transfer_log_index} + ] end end @@ -778,7 +789,7 @@ defmodule Explorer.ChainTest do |> insert_list(:transaction) |> with_block() - %TokenTransfer{id: id1} = + %TokenTransfer{transaction_hash: transaction_hash1, log_index: log_index1} = insert( :token_transfer, to_address: address, @@ -787,7 +798,7 @@ defmodule Explorer.ChainTest do token: token ) - %TokenTransfer{id: id2} = + %TokenTransfer{transaction_hash: transaction_hash2, log_index: log_index2} = insert( :token_transfer, to_address: address, @@ -799,7 +810,10 @@ defmodule Explorer.ChainTest do fetched_transactions = Explorer.Chain.hashes_to_transactions([transaction1.hash, transaction2.hash]) assert Enum.all?(fetched_transactions, fn transaction -> - hd(transaction.token_transfers).id in [id1, id2] + %TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} = + hd(transaction.token_transfers) + + {transaction_hash, log_index} in [{transaction_hash1, log_index1}, {transaction_hash2, log_index2}] end) end end @@ -2004,9 +2018,11 @@ defmodule Explorer.ChainTest do |> insert() |> with_block() - %TokenTransfer{id: id} = insert(:token_transfer, transaction: transaction) + %TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} = + insert(:token_transfer, transaction: transaction) - assert [%TokenTransfer{id: ^id}] = Chain.transaction_to_token_transfers(transaction) + assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] = + Chain.transaction_to_token_transfers(transaction) end test "token transfers necessity_by_association loads associations" do From e4aa6ff5a7d4e08fe0b81d38fe23748e5636cd4b Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 13:04:50 -0500 Subject: [PATCH 07/12] Don't replace_all fields of token_transfers on_conflict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all field except the primary key and timestamps.  For the primary key fields (transaction_hash, log_index), leave them as is as they are the conflict target and have not changed by definition.  For the timestamps that the least inserted_at and the greatest updated_at to preserve the bounds of the timeline. This is a port of the change made to internal_transactions. --- .../explorer/chain/import/token_transfers.ex | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/token_transfers.ex index 860fd10c78..bbc2129e5f 100644 --- a/apps/explorer/lib/explorer/chain/import/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/token_transfers.ex @@ -5,6 +5,8 @@ defmodule Explorer.Chain.Import.TokenTransfers do require Ecto.Query + import Ecto.Query, only: [from: 2] + alias Ecto.{Changeset, Multi} alias Explorer.Chain.{Import, TokenTransfer} @@ -46,7 +48,7 @@ defmodule Explorer.Chain.Import.TokenTransfers do {:ok, [TokenTransfer.t()]} | {:error, [Changeset.t()]} def insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get(options, :on_conflict, :replace_all) + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.log_index}) @@ -62,4 +64,23 @@ defmodule Explorer.Chain.Import.TokenTransfers do timestamps: timestamps ) end + + defp default_on_conflict do + from( + token_transfer in TokenTransfer, + update: [ + set: [ + # Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target + # Don't update `log_index` as it is part of the composite primary key and used for the conflict target + amount: fragment("EXCLUDED.amount"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), + token_id: fragment("EXCLUDED.token_id"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) + ] + ] + ) + end end From 41ea1f55e71b12c40b373d53f2206aeda4a24744 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 13:32:20 -0500 Subject: [PATCH 08/12] Don't use :replace_all for transactions --- .../lib/explorer/chain/import/transactions.ex | 51 +++++++++++++++++-- .../test/explorer/chain/import_test.exs | 22 +++----- apps/explorer/test/explorer/chain_test.exs | 1 - apps/indexer/lib/indexer/block/fetcher.ex | 2 +- 4 files changed, 53 insertions(+), 23 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/transactions.ex b/apps/explorer/lib/explorer/chain/import/transactions.ex index f718639128..9f3e7bef28 100644 --- a/apps/explorer/lib/explorer/chain/import/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/transactions.ex @@ -5,6 +5,8 @@ defmodule Explorer.Chain.Import.Transactions do require Ecto.Query + import Ecto.Query, only: [from: 2] + alias Ecto.Multi alias Explorer.Chain.{Hash, Import, Transaction} @@ -31,11 +33,16 @@ defmodule Explorer.Chain.Import.Transactions do @impl Import.Runner def run(multi, changes_list, options) when is_map(options) do - %{timestamps: timestamps, transactions: %{on_conflict: on_conflict} = transactions_options} = options - timeout = transactions_options[:timeout] || @timeout + %{timestamps: timestamps, transactions: transactions_options} = options + + insert_options = + transactions_options + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put_new(:timestamps, timestamps) Multi.run(multi, :transactions, fn _ -> - insert(changes_list, %{on_conflict: on_conflict, timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @@ -43,12 +50,14 @@ defmodule Explorer.Chain.Import.Transactions do def timeout, do: @timeout @spec insert([map()], %{ - required(:on_conflict) => Import.Runner.on_conflict(), + optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: {:ok, [Hash.t()]} - defp insert(changes_list, %{on_conflict: on_conflict, timeout: timeout, timestamps: timestamps}) + defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) @@ -65,4 +74,36 @@ defmodule Explorer.Chain.Import.Transactions do {:ok, for(transaction <- transactions, do: transaction.hash)} end + + defp default_on_conflict do + from( + transaction in Transaction, + update: [ + set: [ + block_hash: fragment("EXCLUDED.block_hash"), + block_number: fragment("EXCLUDED.block_number"), + created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), + cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_price: fragment("EXCLUDED.gas_price"), + gas_used: fragment("EXCLUDED.gas_used"), + index: fragment("EXCLUDED.index"), + internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"), + input: fragment("EXCLUDED.input"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + status: fragment("EXCLUDED.status"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + v: fragment("EXCLUDED.v"), + value: fragment("EXCLUDED.value"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) + ] + ] + ) + end end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index ab4f51aeb5..c816fd25b3 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -95,7 +95,6 @@ defmodule Explorer.Chain.ImportTest do timeout: 5 }, transactions: %{ - on_conflict: :replace_all, params: [ %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", @@ -570,8 +569,7 @@ defmodule Explorer.Chain.ImportTest do v: 0, value: 0 } - ], - on_conflict: :replace_all + ] }, internal_transactions: %{ params: [ @@ -662,8 +660,7 @@ defmodule Explorer.Chain.ImportTest do v: 0, value: 0 } - ], - on_conflict: :replace_all + ] }, internal_transactions: %{ params: [ @@ -733,7 +730,6 @@ defmodule Explorer.Chain.ImportTest do timeout: 5 }, transactions: %{ - on_conflict: :replace_all, params: [ %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", @@ -880,7 +876,6 @@ defmodule Explorer.Chain.ImportTest do }, broadcast: false, transactions: %{ - on_conflict: :replace_all, params: [ %{ block_hash: "0x1f8cde8bd326702c49e065d56b08bdc82caa0c4820d914e27026c9c68ca1cf09", @@ -1040,8 +1035,7 @@ defmodule Explorer.Chain.ImportTest do v: 0, value: 0 } - ], - on_conflict: :replace_all + ] }, transaction_forks: %{ params: [ @@ -1230,8 +1224,7 @@ defmodule Explorer.Chain.ImportTest do value: 0, status: :ok } - ], - on_conflict: :replace_all + ] } }) @@ -1343,8 +1336,7 @@ defmodule Explorer.Chain.ImportTest do value: 0, status: :ok } - ], - on_conflict: :replace_all + ] } }) @@ -1500,7 +1492,6 @@ defmodule Explorer.Chain.ImportTest do cumulative_gas_used: 0 ) ], - on_conflict: :replace_all, timeout: 1 }, transaction_forks: %{ @@ -1713,8 +1704,7 @@ defmodule Explorer.Chain.ImportTest do value: 0, status: :error } - ], - on_conflict: :replace_all + ] }, internal_transactions: %{ params: [ diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 21ddaa3136..199175defa 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -903,7 +903,6 @@ defmodule Explorer.ChainTest do ] }, transactions: %{ - on_conflict: :replace_all, params: [ %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 76fccbdfa2..07f35ee5c0 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -132,7 +132,7 @@ defmodule Indexer.Block.Fetcher do logs: %{params: logs}, token_transfers: %{params: token_transfers}, tokens: %{on_conflict: :nothing, params: tokens}, - transactions: %{params: transactions_with_receipts, on_conflict: :replace_all} + transactions: %{params: transactions_with_receipts} } ) do {:ok, {inserted, next}} From 806b623fb45cc13f704ae59f8d061fc43828ac9e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 14:12:17 -0500 Subject: [PATCH 09/12] Ensure that `on_conflict` can pass to `insert` from `run` --- .../chain/import/address/coin_balances.ex | 13 +++++++---- .../chain/import/address/token_balances.ex | 13 +++++++---- .../lib/explorer/chain/import/addresses.ex | 13 +++++++---- .../import/block/second_degree_relations.ex | 10 +++++--- .../lib/explorer/chain/import/blocks.ex | 22 ++++++++++++------ .../chain/import/internal_transactions.ex | 23 ++++++++++++++----- .../lib/explorer/chain/import/logs.ex | 18 +++++++++++---- .../explorer/chain/import/token_transfers.ex | 12 ++++++---- .../lib/explorer/chain/import/tokens.ex | 12 ++++++---- .../chain/import/transaction/forks.ex | 13 +++++++---- .../lib/explorer/chain/import/transactions.ex | 9 ++++---- 11 files changed, 108 insertions(+), 50 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import/address/coin_balances.ex b/apps/explorer/lib/explorer/chain/import/address/coin_balances.ex index 8a2b98f679..f9b5e3aeac 100644 --- a/apps/explorer/lib/explorer/chain/import/address/coin_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/address/coin_balances.ex @@ -35,12 +35,16 @@ defmodule Explorer.Chain.Import.Address.CoinBalances do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_coin_balances, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @@ -56,6 +60,7 @@ defmodule Explorer.Chain.Import.Address.CoinBalances do } ], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, required(:timestamps) => Import.timestamps() } diff --git a/apps/explorer/lib/explorer/chain/import/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/address/token_balances.ex index e53d1061cb..207bbeb81b 100644 --- a/apps/explorer/lib/explorer/chain/import/address/token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/address/token_balances.ex @@ -33,12 +33,16 @@ defmodule Explorer.Chain.Import.Address.TokenBalances do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_token_balances, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @@ -46,6 +50,7 @@ defmodule Explorer.Chain.Import.Address.TokenBalances do def timeout, do: @timeout @spec insert([map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout(), required(:timestamps) => Import.timestamps() }) :: diff --git a/apps/explorer/lib/explorer/chain/import/addresses.ex b/apps/explorer/lib/explorer/chain/import/addresses.ex index 3616a47ed3..ae6c92e887 100644 --- a/apps/explorer/lib/explorer/chain/import/addresses.ex +++ b/apps/explorer/lib/explorer/chain/import/addresses.ex @@ -32,12 +32,16 @@ defmodule Explorer.Chain.Import.Addresses do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - timeout = options[:addresses][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :addresses, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @@ -47,6 +51,7 @@ defmodule Explorer.Chain.Import.Addresses do ## Private Functions @spec insert([%{hash: Hash.Address.t()}], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: {:ok, [Address.t()]} diff --git a/apps/explorer/lib/explorer/chain/import/block/second_degree_relations.ex b/apps/explorer/lib/explorer/chain/import/block/second_degree_relations.ex index 8572772535..3722d8a3cc 100644 --- a/apps/explorer/lib/explorer/chain/import/block/second_degree_relations.ex +++ b/apps/explorer/lib/explorer/chain/import/block/second_degree_relations.ex @@ -34,17 +34,21 @@ defmodule Explorer.Chain.Import.Block.SecondDegreeRelations do @impl Import.Runner def run(multi, changes_list, options) when is_map(options) do - timeout = options[:block_second_degree_relations][:timeout] || @timeout + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) Multi.run(multi, :block_second_degree_relations, fn _ -> - insert(changes_list, %{timeout: timeout}) + insert(changes_list, insert_options) end) end @impl Import.Runner def timeout, do: @timeout - @spec insert([map()], %{required(:timeout) => timeout}) :: + @spec insert([map()], %{optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout}) :: {:ok, %{nephew_hash: Hash.Full.t(), uncle_hash: Hash.Full.t()}} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) diff --git a/apps/explorer/lib/explorer/chain/import/blocks.ex b/apps/explorer/lib/explorer/chain/import/blocks.ex index b6de2f2b6a..7b95e6c8e5 100644 --- a/apps/explorer/lib/explorer/chain/import/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/blocks.ex @@ -34,9 +34,14 @@ defmodule Explorer.Chain.Import.Blocks do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - blocks_timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + where_forked = where_forked(changes_list) multi @@ -56,10 +61,10 @@ defmodule Explorer.Chain.Import.Blocks do }) end) |> Multi.run(:lose_consenus, fn _ -> - lose_consensus(changes_list, %{timeout: blocks_timeout, timestamps: timestamps}) + lose_consensus(changes_list, insert_options) end) |> Multi.run(:blocks, fn _ -> - insert(changes_list, %{timeout: blocks_timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) |> Multi.run(:uncle_fetched_block_second_degree_relations, fn %{blocks: blocks} when is_list(blocks) -> update_block_second_degree_relations( @@ -167,8 +172,11 @@ defmodule Explorer.Chain.Import.Blocks do end end - @spec insert([map()], %{required(:timeout) => timeout, required(:timestamps) => Import.timestamps()}) :: - {:ok, [Block.t()]} | {:error, [Changeset.t()]} + @spec insert([map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: {:ok, [Block.t()]} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) diff --git a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex index 059a1284bb..cf80852831 100644 --- a/apps/explorer/lib/explorer/chain/import/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/internal_transactions.ex @@ -35,25 +35,36 @@ defmodule Explorer.Chain.Import.InternalTransactions do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - internal_transactions_timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) when is_map(options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + transactions_timeout = options[Import.Transactions.option_key()][:timeout] || Import.Transactions.timeout() + update_transactions_options = %{timeout: transactions_timeout, timestamps: timestamps} + multi |> Multi.run(:internal_transactions, fn _ -> - insert(changes_list, %{timeout: internal_transactions_timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) |> Multi.run(:internal_transactions_indexed_at_transactions, fn %{internal_transactions: internal_transactions} when is_list(internal_transactions) -> - update_transactions(internal_transactions, %{timeout: transactions_timeout, timestamps: timestamps}) + update_transactions(internal_transactions, update_transactions_options) end) end @impl Import.Runner def timeout, do: @timeout - @spec insert([map], %{required(:timeout) => timeout, required(:timestamps) => Import.timestamps()}) :: + @spec insert([map], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: {:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) diff --git a/apps/explorer/lib/explorer/chain/import/logs.ex b/apps/explorer/lib/explorer/chain/import/logs.ex index 4c4d210187..077bfd9c1e 100644 --- a/apps/explorer/lib/explorer/chain/import/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/logs.ex @@ -32,19 +32,27 @@ defmodule Explorer.Chain.Import.Logs do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :logs, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @impl Import.Runner def timeout, do: @timeout - @spec insert([map()], %{required(:timeout) => timeout, required(:timestamps) => Import.timestamps()}) :: + @spec insert([map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: {:ok, [Log.t()]} | {:error, [Changeset.t()]} defp insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do diff --git a/apps/explorer/lib/explorer/chain/import/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/token_transfers.ex index bbc2129e5f..5fc622223e 100644 --- a/apps/explorer/lib/explorer/chain/import/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/token_transfers.ex @@ -32,12 +32,16 @@ defmodule Explorer.Chain.Import.TokenTransfers do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - timestamps = Map.fetch!(options, :timestamps) - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :token_transfers, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end diff --git a/apps/explorer/lib/explorer/chain/import/tokens.ex b/apps/explorer/lib/explorer/chain/import/tokens.ex index f24a0a5af3..6b474aa8c7 100644 --- a/apps/explorer/lib/explorer/chain/import/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/tokens.ex @@ -30,12 +30,16 @@ defmodule Explorer.Chain.Import.Tokens do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - %{timestamps: timestamps, tokens: %{on_conflict: on_conflict}} = options - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :tokens, fn _ -> - insert(changes_list, %{on_conflict: on_conflict, timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end diff --git a/apps/explorer/lib/explorer/chain/import/transaction/forks.ex b/apps/explorer/lib/explorer/chain/import/transaction/forks.ex index 2d78b38e6f..ce2ef782f7 100644 --- a/apps/explorer/lib/explorer/chain/import/transaction/forks.ex +++ b/apps/explorer/lib/explorer/chain/import/transaction/forks.ex @@ -34,12 +34,16 @@ defmodule Explorer.Chain.Import.Transaction.Forks do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - %{timestamps: timestamps} = options - timeout = options[option_key()][:timeout] || @timeout + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :transaction_forks, fn _ -> - insert(changes_list, %{timeout: timeout, timestamps: timestamps}) + insert(changes_list, insert_options) end) end @@ -47,6 +51,7 @@ defmodule Explorer.Chain.Import.Transaction.Forks do def timeout, do: @timeout @spec insert([map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: {:ok, [%{uncle_hash: Hash.t(), hash: Hash.t()}]} diff --git a/apps/explorer/lib/explorer/chain/import/transactions.ex b/apps/explorer/lib/explorer/chain/import/transactions.ex index 9f3e7bef28..a1d788e6ae 100644 --- a/apps/explorer/lib/explorer/chain/import/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/transactions.ex @@ -32,14 +32,13 @@ defmodule Explorer.Chain.Import.Transactions do end @impl Import.Runner - def run(multi, changes_list, options) when is_map(options) do - %{timestamps: timestamps, transactions: transactions_options} = options - + def run(multi, changes_list, %{timestamps: timestamps} = options) do insert_options = - transactions_options + options + |> Map.get(option_key(), %{}) |> Map.take(~w(on_conflict timeout)a) |> Map.put_new(:timeout, @timeout) - |> Map.put_new(:timestamps, timestamps) + |> Map.put(:timestamps, timestamps) Multi.run(multi, :transactions, fn _ -> insert(changes_list, insert_options) From e690c3219bd63ae79ff228288e59c960481ee67b Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 14:22:59 -0500 Subject: [PATCH 10/12] Don't use :replace_all with tokens --- apps/explorer/lib/explorer/chain.ex | 2 +- .../lib/explorer/chain/import/tokens.ex | 54 ++++++++++++------- .../test/explorer/chain/import_test.exs | 1 - 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9be85c9c6f..cb387c5e23 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -2038,7 +2038,7 @@ defmodule Explorer.Chain do token_changeset = Token.changeset(token, params) address_name_changeset = Address.Name.changeset(%Address.Name{}, Map.put(params, :address_hash, address_hash)) - token_opts = [on_conflict: :replace_all, conflict_target: :contract_address_hash] + token_opts = [on_conflict: Import.Tokens.default_on_conflict(), conflict_target: :contract_address_hash] address_name_opts = [on_conflict: :nothing, conflict_target: [:address_hash, :name]] insert_result = diff --git a/apps/explorer/lib/explorer/chain/import/tokens.ex b/apps/explorer/lib/explorer/chain/import/tokens.ex index 6b474aa8c7..b400224aed 100644 --- a/apps/explorer/lib/explorer/chain/import/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/tokens.ex @@ -5,6 +5,8 @@ defmodule Explorer.Chain.Import.Tokens do require Ecto.Query + import Ecto.Query, only: [from: 2] + alias Ecto.Multi alias Explorer.Chain.{Import, Token} @@ -50,28 +52,40 @@ defmodule Explorer.Chain.Import.Tokens do required(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout(), required(:timestamps) => Import.timestamps() - }) :: - {:ok, [Token.t()]} - | {:error, {:required, :on_conflict}} + }) :: {:ok, [Token.t()]} def insert(changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - case options do - %{on_conflict: on_conflict} -> - # order so that row ShareLocks are grabbed in a consistent order - ordered_changes_list = Enum.sort_by(changes_list, & &1.contract_address_hash) + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, & &1.contract_address_hash) - {:ok, _} = - Import.insert_changes_list( - ordered_changes_list, - conflict_target: :contract_address_hash, - on_conflict: on_conflict, - for: Token, - returning: true, - timeout: timeout, - timestamps: timestamps - ) + {:ok, _} = + Import.insert_changes_list( + ordered_changes_list, + conflict_target: :contract_address_hash, + on_conflict: on_conflict, + for: Token, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end - _ -> - {:error, {:required, :on_conflict}} - end + def default_on_conflict do + from( + token in Token, + update: [ + set: [ + name: fragment("EXCLUDED.name"), + symbol: fragment("EXCLUDED.symbol"), + total_supply: fragment("EXCLUDED.total_supply"), + decimals: fragment("EXCLUDED.decimals"), + type: fragment("EXCLUDED.type"), + cataloged: fragment("EXCLUDED.cataloged"), + # Don't update `contract_address_hash` as it is the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) + ] + ] + ) end end diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index c816fd25b3..ea4a485f6f 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -1476,7 +1476,6 @@ defmodule Explorer.Chain.ImportTest do }, tokens: %{ params: [params_for(:token, contract_address_hash: token_contract_address_hash)], - on_conflict: :replace_all, timeout: 1 }, transactions: %{ From 21ace095babefc1789248b99422e353d0e85625c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 24 Oct 2018 15:05:29 -0500 Subject: [PATCH 11/12] Fix divide-by-zero error in indexed_ratio Dividing by `max_block_number` assumes it is 1-based, but it is 0-based, so add 1. --- .../features/viewing_app_test.exs | 24 +++++++++++++------ apps/explorer/lib/explorer/chain.ex | 4 ++-- apps/explorer/test/explorer/chain_test.exs | 4 ++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs index 54311ecead..960de0049b 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs @@ -7,50 +7,58 @@ defmodule BlockScoutWeb.ViewingAppTest do describe "loading bar when indexing" do test "shows blocks indexed percentage", %{session: session} do - for index <- 6..10 do + for index <- 5..9 do insert(:block, number: index) end + assert Explorer.Chain.indexed_ratio() == 0.5 + session |> AppPage.visit_page() |> assert_has(AppPage.indexed_status("50% Blocks Indexed")) end test "shows tokens loading", %{session: session} do - for index <- 1..10 do + for index <- 0..9 do insert(:block, number: index) end + assert Explorer.Chain.indexed_ratio() == 1.0 + session |> AppPage.visit_page() |> assert_has(AppPage.indexed_status("Indexing Tokens")) end test "live updates blocks indexed percentage", %{session: session} do - for index <- 6..10 do + for index <- 5..9 do insert(:block, number: index) end + assert Explorer.Chain.indexed_ratio() == 0.5 + session |> AppPage.visit_page() |> assert_has(AppPage.indexed_status("50% Blocks Indexed")) - insert(:block, number: 5) + insert(:block, number: 4) Notifier.handle_event({:chain_event, :blocks, :catchup, []}) assert_has(session, AppPage.indexed_status("60% Blocks Indexed")) end test "live updates when blocks are fully indexed", %{session: session} do - for index <- 2..10 do + for index <- 1..9 do insert(:block, number: index) end + assert Explorer.Chain.indexed_ratio() == 0.9 + session |> AppPage.visit_page() |> assert_has(AppPage.indexed_status("90% Blocks Indexed")) - insert(:block, number: 1) + insert(:block, number: 0) Notifier.handle_event({:chain_event, :blocks, :catchup, []}) assert_has(session, AppPage.indexed_status("Indexing Tokens")) @@ -58,10 +66,12 @@ defmodule BlockScoutWeb.ViewingAppTest do test "live removes message when chain is indexed", %{session: session} do [block | _] = - for index <- 1..10 do + for index <- 0..9 do insert(:block, number: index) end + assert Explorer.Chain.indexed_ratio() == 1.0 + session |> AppPage.visit_page() |> assert_has(AppPage.indexed_status("Indexing Tokens")) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index cb387c5e23..9b7a7f241d 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -842,7 +842,7 @@ defmodule Explorer.Chain do @doc """ The percentage of indexed blocks on the chain. - iex> for index <- 6..10 do + iex> for index <- 5..9 do ...> insert(:block, number: index) ...> end iex> Explorer.Chain.indexed_ratio() @@ -859,7 +859,7 @@ defmodule Explorer.Chain do with {:ok, min_block_number} <- min_block_number(), {:ok, max_block_number} <- max_block_number() do indexed_blocks = max_block_number - min_block_number + 1 - indexed_blocks / max_block_number + indexed_blocks / (max_block_number + 1) else {:error, _} -> 0 end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 199175defa..2f4e7c4854 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -820,7 +820,7 @@ defmodule Explorer.ChainTest do describe "indexed_ratio/0" do test "returns indexed ratio" do - for index <- 6..10 do + for index <- 5..9 do insert(:block, number: index) end @@ -832,7 +832,7 @@ defmodule Explorer.ChainTest do end test "returns 1.0 if fully indexed blocks" do - for index <- 1..10 do + for index <- 0..9 do insert(:block, number: index) end From 65d59843e43476f7086a4efab234f8f7ef6888aa Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 25 Oct 2018 09:36:09 -0500 Subject: [PATCH 12/12] Prevent block number collision in test --- .../test/block_scout_web/features/viewing_transactions_test.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs index db14b900d9..a485f86957 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs @@ -9,7 +9,6 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do setup do block = insert(:block, %{ - number: 555, timestamp: Timex.now() |> Timex.shift(hours: -2), gas_used: 123_987 })