From 1a17658866e1dc3cef2870fdbeee174732a22aee Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 07:24:55 -0600 Subject: [PATCH 1/6] Increase DB transaction timeout to 4 minutes This prevents DB timeouts against Eth Mainnet for stretches of 20 minutes when testing with block concurrency of 5 and block batch size of 5 while production is simultaneously running at 12x12. --- apps/explorer/lib/explorer/chain/import.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index ac9295fca3..00da6b6778 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -60,7 +60,7 @@ defmodule Explorer.Chain.Import do @type timestamps :: %{inserted_at: DateTime.t(), updated_at: DateTime.t()} # milliseconds - @transaction_timeout 120_000 + @transaction_timeout 240_000 @imported_table_rows @runners |> Stream.map(&Map.put(&1.imported_table_row(), :key, &1.option_key())) From 81179f9459fc6a80944d8490a331c9160d85ecdd Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 07:44:13 -0600 Subject: [PATCH 2/6] Use :timer functions to make timeout values clearer --- .../channels/address_channel_test.exs | 22 +++++++++++-------- .../channels/block_channel_test.exs | 4 ++-- .../channels/exchange_rate_channel_test.exs | 4 ++-- .../channels/transaction_channel_test.exs | 6 ++--- apps/explorer/config/config.exs | 2 +- apps/explorer/config/dev.exs | 2 +- apps/explorer/config/dev/ganache.exs | 2 +- apps/explorer/config/dev/geth.exs | 2 +- apps/explorer/config/dev/parity.exs | 2 +- apps/explorer/config/prod.exs | 2 +- apps/explorer/config/prod/ganache.exs | 2 +- apps/explorer/config/prod/geth.exs | 2 +- apps/explorer/config/prod/parity.exs | 2 +- apps/explorer/config/test.exs | 2 +- apps/explorer/lib/explorer/chain/import.ex | 2 +- apps/indexer/config/dev/ganache.exs | 4 ++-- apps/indexer/config/dev/geth.exs | 4 ++-- apps/indexer/config/dev/parity.exs | 4 ++-- apps/indexer/config/prod/ganache.exs | 4 ++-- apps/indexer/config/prod/geth.exs | 4 ++-- apps/indexer/config/prod/parity.exs | 4 ++-- .../test/indexer/block/fetcher_test.exs | 2 +- apps/indexer/test/indexer/sequence_test.exs | 6 ++--- 23 files changed, 47 insertions(+), 43 deletions(-) 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 2e59c8435c..e6c6d623a6 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 @@ -17,7 +17,7 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :addresses, :realtime, [address]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "count", payload: %{count: _}}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "count", payload: %{count: _}}, :timer.seconds(5) end describe "user subscribed to address" do @@ -36,7 +36,9 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :addresses, :realtime, [address_with_balance]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "balance_update", payload: payload}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "balance_update", payload: payload}, + :timer.seconds(5) + assert payload.address.hash == address_with_balance.hash end @@ -54,7 +56,9 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [pending.hash]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload}, + :timer.seconds(5) + assert payload.address.hash == address.hash assert payload.transaction.hash == pending.hash end @@ -67,7 +71,7 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) assert payload.address.hash == address.hash assert payload.transaction.hash == transaction.hash end @@ -80,7 +84,7 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) assert payload.address.hash == address.hash assert payload.transaction.hash == transaction.hash end @@ -93,7 +97,7 @@ defmodule BlockScoutWeb.AddressChannelTest do Notifier.handle_event({:chain_event, :transactions, :realtime, [transaction.hash]}) - assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, 5_000 + assert_receive %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload}, :timer.seconds(5) assert payload.address.hash == address.hash assert payload.transaction.hash == transaction.hash @@ -118,7 +122,7 @@ defmodule BlockScoutWeb.AddressChannelTest do internal_transaction: %{transaction_hash: transaction_hash, index: index} } }, - 5_000 + :timer.seconds(5) assert address_hash == address.hash assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} @@ -142,7 +146,7 @@ defmodule BlockScoutWeb.AddressChannelTest do internal_transaction: %{transaction_hash: transaction_hash, index: index} } }, - 5_000 + :timer.seconds(5) assert address_hash == address.hash assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} @@ -170,7 +174,7 @@ defmodule BlockScoutWeb.AddressChannelTest do internal_transaction: %{transaction_hash: transaction_hash, index: index} } }, - 5_000 + :timer.seconds(5) assert address_hash == address.hash assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} diff --git a/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs index c5848597e8..dd1d281a9a 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/block_channel_test.exs @@ -15,7 +15,7 @@ defmodule BlockScoutWeb.BlockChannelTest do %Phoenix.Socket.Broadcast{topic: ^topic, event: "new_block", payload: %{block: _}} -> assert true after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end @@ -30,7 +30,7 @@ defmodule BlockScoutWeb.BlockChannelTest do %Phoenix.Socket.Broadcast{topic: ^topic, event: "index_status", payload: %{}} -> assert true after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." 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 704f4170b3..7c495e6bfc 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 @@ -52,7 +52,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do assert payload.exchange_rate == token assert payload.market_history_data == [] after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end @@ -84,7 +84,7 @@ defmodule BlockScoutWeb.ExchangeRateChannelTest do assert payload.exchange_rate == token assert payload.market_history_data == records after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end diff --git a/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs index 2488855188..519658875b 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/transaction_channel_test.exs @@ -19,7 +19,7 @@ defmodule BlockScoutWeb.TransactionChannelTest do %Phoenix.Socket.Broadcast{topic: ^topic, event: "transaction", payload: payload} -> assert payload.transaction.hash == transaction.hash after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end @@ -36,7 +36,7 @@ defmodule BlockScoutWeb.TransactionChannelTest do %Phoenix.Socket.Broadcast{topic: ^topic, event: "pending_transaction", payload: payload} -> assert payload.transaction.hash == pending.hash after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end @@ -56,7 +56,7 @@ defmodule BlockScoutWeb.TransactionChannelTest do %Phoenix.Socket.Broadcast{topic: ^topic, event: "collated", payload: %{}} -> assert true after - 5_000 -> + :timer.seconds(5) -> assert false, "Expected message received nothing." end end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index d5ccdaa619..d40551bbfc 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -21,7 +21,7 @@ config :explorer, Explorer.Counters.BlockValidationCounter, enabled: true, enabl config :explorer, Explorer.ExchangeRates, enabled: true, store: :ets -config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 +config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: :timer.seconds(2) config :explorer, Explorer.Market.History.Cataloger, enabled: true diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 34e1a357af..95019470fc 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -5,7 +5,7 @@ config :explorer, Explorer.Repo, database: "explorer_dev", hostname: "localhost", pool_size: 20, - timeout: 80_000 + timeout: :timer.seconds(80) config :explorer, Explorer.Tracer, env: "dev", disabled?: true diff --git a/apps/explorer/config/dev/ganache.exs b/apps/explorer/config/dev/ganache.exs index f9ed96fb0e..fec954d325 100644 --- a/apps/explorer/config/dev/ganache.exs +++ b/apps/explorer/config/dev/ganache.exs @@ -6,7 +6,7 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Ganache ], diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index 6bdba9a0b0..5d57d1a3f2 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -6,7 +6,7 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/explorer/config/dev/parity.exs b/apps/explorer/config/dev/parity.exs index d503892fe0..7f92c092cc 100644 --- a/apps/explorer/config/dev/parity.exs +++ b/apps/explorer/config/dev/parity.exs @@ -11,7 +11,7 @@ config :explorer, eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Parity ], diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 29a377d219..0957ffa1f7 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -6,7 +6,7 @@ config :explorer, Explorer.Repo, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), prepare: :unnamed, - timeout: 60_000 + timeout: :timer.seconds(60) config :explorer, Explorer.Tracer, env: "production", disabled?: true diff --git a/apps/explorer/config/prod/ganache.exs b/apps/explorer/config/prod/ganache.exs index f9ed96fb0e..fec954d325 100644 --- a/apps/explorer/config/prod/ganache.exs +++ b/apps/explorer/config/prod/ganache.exs @@ -6,7 +6,7 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Ganache ], diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 6bdba9a0b0..5d57d1a3f2 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -6,7 +6,7 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/explorer/config/prod/parity.exs b/apps/explorer/config/prod/parity.exs index 3e5c8ca64d..68c1d14fdf 100644 --- a/apps/explorer/config/prod/parity.exs +++ b/apps/explorer/config/prod/parity.exs @@ -11,7 +11,7 @@ config :explorer, eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Parity ], diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 194b2f2ccc..aa8bb02dd1 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -9,7 +9,7 @@ config :explorer, Explorer.Repo, hostname: "localhost", pool: Ecto.Adapters.SQL.Sandbox, # Default of `5_000` was too low for `BlockFetcher` test - ownership_timeout: 60_000 + ownership_timeout: :timer.minutes(1) config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 00da6b6778..50964e8751 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -60,7 +60,7 @@ defmodule Explorer.Chain.Import do @type timestamps :: %{inserted_at: DateTime.t(), updated_at: DateTime.t()} # milliseconds - @transaction_timeout 240_000 + @transaction_timeout :timer.minutes(4) @imported_table_rows @runners |> Stream.map(&Map.put(&1.imported_table_row(), :key, &1.option_key())) diff --git a/apps/indexer/config/dev/ganache.exs b/apps/indexer/config/dev/ganache.exs index 174ba47d4f..337c2317c6 100644 --- a/apps/indexer/config/dev/ganache.exs +++ b/apps/indexer/config/dev/ganache.exs @@ -1,13 +1,13 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Ganache ], diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index 4950dccd3f..cf1113119d 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -1,13 +1,13 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/indexer/config/dev/parity.exs b/apps/indexer/config/dev/parity.exs index e903be4141..b63f1d8ccf 100644 --- a/apps/indexer/config/dev/parity.exs +++ b/apps/indexer/config/dev/parity.exs @@ -1,7 +1,7 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ @@ -12,7 +12,7 @@ config :indexer, trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Parity ], diff --git a/apps/indexer/config/prod/ganache.exs b/apps/indexer/config/prod/ganache.exs index 174ba47d4f..337c2317c6 100644 --- a/apps/indexer/config/prod/ganache.exs +++ b/apps/indexer/config/prod/ganache.exs @@ -1,13 +1,13 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Ganache ], diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index 4950dccd3f..cf1113119d 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -1,13 +1,13 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/indexer/config/prod/parity.exs b/apps/indexer/config/prod/parity.exs index 463dc3f079..8c12ff4485 100644 --- a/apps/indexer/config/prod/parity.exs +++ b/apps/indexer/config/prod/parity.exs @@ -1,7 +1,7 @@ use Mix.Config config :indexer, - block_interval: 5_000, + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: EthereumJSONRPC.HTTP, transport_options: [ @@ -12,7 +12,7 @@ config :indexer, trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Parity ], diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 0328abf028..2529df75e0 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -611,7 +611,7 @@ defmodule Indexer.Block.FetcherTest do end defp wait_for_tasks(buffered_task) do - wait_until(10_000, fn -> + wait_until(:timer.seconds(10), fn -> counts = BufferedTask.debug_count(buffered_task) counts.buffer == 0 and counts.tasks == 0 end) diff --git a/apps/indexer/test/indexer/sequence_test.exs b/apps/indexer/test/indexer/sequence_test.exs index b7ed8f9ab3..49933c85fb 100644 --- a/apps/indexer/test/indexer/sequence_test.exs +++ b/apps/indexer/test/indexer/sequence_test.exs @@ -16,7 +16,7 @@ defmodule Indexer.SequenceTest do {child_pid, child_ref} = spawn_monitor(fn -> Sequence.start_link(first: 1, step: -1) - Process.sleep(5_000) + Process.sleep(:timer.seconds(5)) end) assert_receive {:DOWN, ^child_ref, :process, ^child_pid, @@ -27,7 +27,7 @@ defmodule Indexer.SequenceTest do {child_pid, child_ref} = spawn_monitor(fn -> Sequence.start_link(step: -1) - Process.sleep(5_000) + Process.sleep(:timer.seconds(5)) end) assert_receive {:DOWN, ^child_ref, :process, ^child_pid, "either :ranges or :first must be set"} @@ -47,7 +47,7 @@ defmodule Indexer.SequenceTest do {child_pid, child_ref} = spawn_monitor(fn -> Sequence.start_link(ranges: [1..0], first: 1, step: -1) - Process.sleep(5_000) + Process.sleep(:timer.seconds(5)) end) assert_receive {:DOWN, ^child_ref, :process, ^child_pid, From 786f89164ba03bff6138a562f795f7d1bd9835aa Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 14:13:26 -0600 Subject: [PATCH 3/6] Failing regression test for #1262 --- .../chain/import/runner/blocks_test.exs | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 apps/explorer/test/explorer/chain/import/runner/blocks_test.exs diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs new file mode 100644 index 0000000000..cba86e999a --- /dev/null +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -0,0 +1,84 @@ +defmodule Explorer.Chain.Import.Runner.BlocksTest do + use Explorer.DataCase + + import Ecto.Query, only: [from: 2, select: 2, where: 2] + + alias Ecto.Multi + alias Explorer.Chain.Import.Runner.{Blocks, Transaction} + alias Explorer.Chain.{Block, Transaction} + alias Explorer.Repo + + describe "run/1" do + setup do + block = insert(:block, consensus: true) + + transaction = + :transaction + |> insert() + |> with_block(block) + + %{consensus_block: block, transaction: transaction} + end + + test "derive_transaction_forks replaces hash on conflicting (uncle_hash, index)", %{ + consensus_block: %Block{hash: block_hash, miner_hash: miner_hash, number: block_number}, + transaction: transaction + } do + block_params = + params_for(:block, hash: block_hash, miner_hash: miner_hash, number: block_number, consensus: false) + + %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, block_params) + changes_list = [block_changes] + + timestamp = DateTime.utc_now() + options = %{timestamps: %{inserted_at: timestamp, updated_at: timestamp}} + + assert Repo.aggregate(from(transaction in Transaction, where: is_nil(transaction.block_number)), :count, :hash) == + 0 + + assert count(Transaction.Fork) == 0 + + # re-org consensus_block to uncle + + assert {:ok, %{derive_transaction_forks: [_]}} = + Multi.new() + |> Blocks.run(changes_list, options) + |> Repo.transaction() + + assert Repo.aggregate(where(Block, consensus: false), :count, :number) == 1 + + assert Repo.aggregate(from(transaction in Transaction, where: is_nil(transaction.block_number)), :count, :hash) == + 1 + + assert count(Transaction.Fork) == 1 + + non_consensus_transaction = Repo.get(Transaction, transaction.hash) + non_consensus_block = Repo.get(Block, block_hash) + + # Make it consensus again + new_consensus_block = + non_consensus_block + |> Block.changeset(%{consensus: true}) + |> Repo.update!() + + with_block(non_consensus_transaction, new_consensus_block) + + ctid = Repo.one!(from(transaction_fork in Transaction.Fork, select: "ctid")) + + assert Repo.aggregate(from(transaction in Transaction, where: is_nil(transaction.block_number)), :count, :hash) == + 0 + + assert {:ok, %{derive_transaction_forks: []}} = + Multi.new() + |> Blocks.run(changes_list, options) + |> Repo.transaction() + + assert Repo.one!(from(transaction_fork in Transaction.Fork, select: "ctid")) == ctid, + "Tuple was written even though it is not distinct" + end + end + + defp count(schema) do + Repo.one!(select(schema, fragment("COUNT(*)"))) + end +end From 1579db1aec4ffe532040adbb5fdaa44edc134b21 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 14:14:40 -0600 Subject: [PATCH 4/6] Add ON CONFLICT to derive_transaction_forks Fixes #1262 --- apps/explorer/lib/explorer/chain/import/runner/blocks.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 9f24d0be7b..5917ae9968 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -133,6 +133,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do insert_sql = """ INSERT INTO transaction_forks (uncle_hash, index, hash, inserted_at, updated_at) #{select_sql} + ON CONFLICT (uncle_hash, index) + DO UPDATE SET hash = EXCLUDED.hash + WHERE EXCLUDED.hash <> transaction_forks.hash RETURNING uncle_hash, hash """ From 07cb5439482d46d55f2196e047d80fdb7bcf544d Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 14:44:08 -0600 Subject: [PATCH 5/6] Failing regression test for #1266 When the max block number exceeds the search range by more than 1 it causes extra ranges to be calculated and they depend on the max block number instead of the search range. --- apps/explorer/test/explorer/chain_test.exs | 111 +++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index b49bfd844e..83f4bf3883 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -2191,6 +2191,117 @@ defmodule Explorer.ChainTest do end end + describe "missing_block_number_ranges/1" do + # 0000 + test "0..0 without blocks" do + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0001 + test "0..0 with block 3" do + insert(:block, number: 3) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0010 + test "0..0 with block 2" do + insert(:block, number: 2) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0011 + test "0..0 with blocks 2,3" do + Enum.each([2, 3], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0100 + test "0..0 with block 1" do + insert(:block, number: 1) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0101 + test "0..0 with blocks 1,3" do + Enum.each([1, 3], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 0111 + test "0..0 with blocks 1..3" do + Enum.each(1..3, &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [0..0] + end + + # 1000 + test "0..0 with block 0" do + insert(:block, number: 0) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1001 + test "0..0 with blocks 0,3" do + Enum.each([0, 3], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1010 + test "0..0 with blocks 0,2" do + Enum.each([0, 2], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1011 + test "0..0 with blocks 0,2,3" do + Enum.each([0, 2, 3], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1100 + test "0..0 with blocks 0..1" do + Enum.each(0..1, &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1101 + test "0..0 with blocks 0,1,3" do + Enum.each([0, 1, 3], &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1110 + test "0..0 with blocks 0..2" do + Enum.each(0..2, &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + # 1111 + test "0..0 with blocks 0..3" do + Enum.each(0..2, &insert(:block, number: &1)) + + assert Chain.missing_block_number_ranges(0..0) == [] + end + + test "0..2 with block 1" do + insert(:block, number: 1) + + assert Chain.missing_block_number_ranges(0..2) == [0..0, 2..2] + end + end + describe "recent_collated_transactions/1" do test "with no collated transactions it returns an empty list" do assert [] == Explorer.Chain.recent_collated_transactions() From 2a4223c2c6fa0bfaf2d601e794c5c4438f2f0a5d Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 20 Dec 2018 21:37:07 -0600 Subject: [PATCH 6/6] Allow block numbers to exceed missing block search range by > 1 Fixes #1266 Issue #1266 was triggered when the recorded block number exceeded the reported max block number as can happen temporary when the node that gives the latest block number is farther behind than the node that gave the block data. The previous `Explorer.Chain.missing_block_number_ranges` query added in #1230 did not account for the max block number in the database exceeding the range max. When this occurs, an errant range was produced by the #1230 query because it chose the greatest of the range and the max block when trying to find the end point for the gap search. The testing did not catch #1266 because the doctests only used block numbers that exceeded the search range, by 1 and the bug did not show up until it was 2 or more. The new query is split into 4 subqueries: 1. missing prefix when range min < min(block.number) 2. missing infix when the blocks aren't in the range at all 3. missing suffix when max(block.number) < range max 4. gaps between existent block numbers This version is also more efficient as there is only a single sort of the outer `UNION ALL` result instead of a sort of real blocks and fake end points for the range. It is able to move the `UNION ALL` up because it doesn't need to produce fake end cap blocks. This positioning of the `UNION ALL` and `ORDER BY` on the outer most query allows for all 4 queries to use `one_consensus_block_at_height` index and either a limit (for `min`/`max`) or a window aggregate (for `lag`), which has minimal, single row cost. --- apps/explorer/lib/explorer/chain.ex | 101 +++++++++++++--------------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6bf306c0ce..ce4b897681 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1245,76 +1245,69 @@ defmodule Explorer.Chain do def missing_block_number_ranges(range) def missing_block_number_ranges(range_start..range_end) do - # subquery so we can check for NULL in `range_min_query`, which happens for empty table - min_query = from(block in Block, select: %{number: min(block.number)}, where: block.consensus == true) - # this acts a fake found block, so it has to before the range of blocks we actually care to check - before_range_min = min(range_start, range_end) - 1 + range_min = min(range_start, range_end) + range_max = max(range_start, range_end) - range_min_query = - from(min_block in subquery(min_query), - select: %{ - # `LEAST` ignores `NULL`, so it picks the fake range when there is no `min_block.number` - # because `blocks` is empty - number: fragment("LEAST(?, ?)", min_block.number, ^before_range_min) - }, - # `blocks` is empty - # same number will not be returned by `number_query` - where: is_nil(min_block.number) or min_block.number != ^before_range_min + missing_prefix_query = + from(block in Block, + select: %{min: type(^range_min, block.number), max: min(block.number) - 1}, + where: block.consensus == true, + having: ^range_min < min(block.number) and min(block.number) < ^range_max ) - number_query = from(block in Block, select: %{number: block.number}, where: block.consensus == true) - - # subquery so we can check for NULL in `range_max_query`, which happens for empty table - max_query = from(block in Block, select: %{number: max(block.number)}, where: block.consensus == true) - # this acts a fake found block, so it has to after the range of blocks we actually care to check - after_range_max = max(range_start, range_end) + 1 - - range_max_query = - from(max_block in subquery(max_query), - select: %{ - # `GREATEST` ignores `NULL`, so it picks the fake range when there is no `max_block.number` - # because `blocks` is empty - number: fragment("GREATEST(?, ?)", max_block.number, ^after_range_max) - }, - # blocks is empty - # same number will not be returned by `number_query` - where: is_nil(max_block.number) or max_block.number != ^after_range_max + missing_suffix_query = + from(block in Block, + select: %{min: max(block.number) + 1, max: type(^range_max, block.number)}, + where: block.consensus == true, + having: ^range_min < max(block.number) and max(block.number) < ^range_max ) - # The actual blocks and a boundary of fake found blocks outside of `range_start..range_end` so that there is always - # a `lag` block - search_range_query = - number_query - |> union_all(^range_min_query) - |> union_all(^range_max_query) + missing_infix_query = + from(block in Block, + select: %{min: type(^range_min, block.number), max: type(^range_max, block.number)}, + where: block.consensus == true, + having: + (is_nil(min(block.number)) and is_nil(max(block.number))) or + (^range_max < min(block.number) or max(block.number) < ^range_min) + ) # Gaps and Islands is the term-of-art for finding the runs of missing (gaps) and existing (islands) data. If you # Google for `sql missing ranges` you won't find much, but `sql gaps and islands` will get a lot of hits. - island_query = - from( - search_block in subquery(search_range_query), - windows: [w: [order_by: search_block.number]], - select: %{last_number: search_block.number |> lag() |> over(:w), next_number: search_block.number} + land_query = + from(block in Block, + where: block.consensus == true and ^range_min <= block.number and block.number <= ^range_max, + windows: [w: [order_by: block.number]], + select: %{last_number: block.number |> lag() |> over(:w), next_number: block.number} ) gap_query = from( - island in subquery(island_query), - where: island.last_number != island.next_number - 1, - select: %Range{first: island.last_number + 1, last: island.next_number - 1}, - order_by: island.last_number + coastline in subquery(land_query), + where: coastline.last_number != coastline.next_number - 1, + select: %{min: coastline.last_number + 1, max: coastline.next_number - 1} ) - ascending = Repo.all(gap_query, timeout: :infinity) + missing_query = + missing_prefix_query + |> union_all(^missing_infix_query) + |> union_all(^gap_query) + |> union_all(^missing_suffix_query) + + {first, last, direction} = + if range_start <= range_end do + {:min, :max, :asc} + else + {:max, :min, :desc} + end - if range_start <= range_end do - ascending - else - ascending - |> Enum.reverse() - |> Enum.map(fn first..last -> last..first end) - end + ordered_missing_query = + from(missing_range in subquery(missing_query), + select: %Range{first: field(missing_range, ^first), last: field(missing_range, ^last)}, + order_by: [{^direction, field(missing_range, ^first)}] + ) + + Repo.all(ordered_missing_query, timeout: :infinity) end @doc """