diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 5a221e3986..1d1eea4a44 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.Import do Bulk importing of data into `Explorer.Repo` """ - import Ecto.Query, only: [from: 2] + import Ecto.Query, only: [from: 2, update: 2] alias Ecto.{Changeset, Multi} @@ -380,6 +380,12 @@ defmodule Explorer.Chain.Import do blocks_timeout = options[:blocks][:timeout] || @insert_blocks_timeout multi + |> Multi.run(:fork_transactions, fn _ -> + fork_transactions(blocks_changes, %{ + timeout: options[:transactions][:timeout] || @insert_transactions_timeout, + timestamps: timestamps + }) + end) |> Multi.run(:lose_consenus, fn _ -> lose_consensus(blocks_changes, %{timeout: blocks_timeout, timestamps: timestamps}) end) @@ -974,6 +980,49 @@ defmodule Explorer.Chain.Import do {:ok, inserted} end + defp fork_transactions(blocks_changes, %{ + timeout: timeout, + timestamps: %{updated_at: updated_at} + }) + when is_list(blocks_changes) do + query = + Transaction + |> where_forked(blocks_changes) + |> update( + set: [ + block_hash: nil, + block_number: nil, + gas_used: nil, + cumulative_gas_used: nil, + index: nil, + internal_transactions_indexed_at: nil, + status: nil, + updated_at: ^updated_at + ] + ) + + try do + {_, result} = Repo.update_all(query, [], timeout: timeout, returning: [:hash]) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, blocks_changes: blocks_changes}} + end + end + + defp where_forked(query, blocks_changes) when is_list(blocks_changes) do + Enum.reduce(blocks_changes, query, fn %{consensus: consensus, hash: hash, number: number}, acc -> + case consensus do + false -> + from(transaction in acc, or_where: transaction.block_hash == ^hash and transaction.block_number == ^number) + + true -> + from(transaction in acc, or_where: transaction.block_hash != ^hash and transaction.block_number == ^number) + end + end) + end + defp lose_consensus(blocks_changes, %{timeout: timeout, timestamps: %{updated_at: updated_at}}) when is_list(blocks_changes) do ordered_block_number = diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index cf33067f77..45cf5bba10 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -1235,7 +1235,121 @@ defmodule Explorer.Chain.ImportTest do timestamp: timestamp, total_difficulty: ^total_difficulty_before } = Repo.get(Block, block_hash_before) + assert DateTime.compare(timestamp, timestamp_before) == :eq end + + test "reorganizations nils transaction receipt fields for transactions that end up in non-consensus blocks" do + block_number = 0 + + miner_hash_before = address_hash() + from_address_hash_before = address_hash() + block_hash_before = block_hash() + index_before = 0 + + transaction_hash = transaction_hash() + + assert {:ok, _} = + Import.all(%{ + addresses: %{ + params: [ + %{hash: miner_hash_before}, + %{hash: from_address_hash_before} + ] + }, + blocks: %{ + params: [ + %{ + consensus: true, + difficulty: 0, + gas_limit: 0, + gas_used: 0, + hash: block_hash_before, + miner_hash: miner_hash_before, + nonce: 0, + number: block_number, + parent_hash: block_hash(), + size: 0, + timestamp: Timex.parse!("2019-01-01T01:00:00Z", "{ISO:Extended:Z}"), + total_difficulty: 0 + } + ] + }, + transactions: %{ + params: [ + %{ + block_hash: block_hash_before, + block_number: block_number, + from_address_hash: from_address_hash_before, + gas: 21_000, + gas_price: 1, + gas_used: 21_000, + cumulative_gas_used: 21_000, + hash: transaction_hash, + index: index_before, + input: "0x", + nonce: 0, + r: 0, + s: 0, + v: 0, + value: 0, + status: :ok + } + ], + on_conflict: :replace_all + } + }) + + %Block{consensus: true, number: ^block_number} = Repo.get(Block, block_hash_before) + transaction_before = Repo.get!(Transaction, transaction_hash) + + refute transaction_before.block_hash == nil + refute transaction_before.block_number == nil + refute transaction_before.gas_used == nil + refute transaction_before.cumulative_gas_used == nil + refute transaction_before.index == nil + refute transaction_before.status == nil + + miner_hash_after = address_hash() + from_address_hash_after = address_hash() + block_hash_after = block_hash() + + assert {:ok, _} = + Import.all(%{ + addresses: %{ + params: [ + %{hash: miner_hash_after}, + %{hash: from_address_hash_after} + ] + }, + blocks: %{ + params: [ + %{ + consensus: true, + difficulty: 1, + gas_limit: 1, + gas_used: 1, + hash: block_hash_after, + miner_hash: miner_hash_after, + nonce: 1, + number: block_number, + parent_hash: block_hash(), + size: 1, + timestamp: Timex.parse!("2019-01-01T02:00:00Z", "{ISO:Extended:Z}"), + total_difficulty: 1 + } + ] + } + }) + + transaction_after = Repo.get!(Transaction, transaction_hash) + + assert transaction_after.block_hash == nil + assert transaction_after.block_number == nil + assert transaction_after.gas_used == nil + assert transaction_after.cumulative_gas_used == nil + assert transaction_after.index == nil + assert transaction_after.status == nil + end end end