From 52a0b19d5e34745b818021c6345a3277999effb6 Mon Sep 17 00:00:00 2001 From: pasqu4le Date: Wed, 7 Aug 2019 18:35:59 +0200 Subject: [PATCH] Avoid importing internal_transactions of pending transactions Problem: While fetching the internal transactions for a block sometimes we receive some that are part of a pending transaction. By inserting them we also try to update the pending transaction's field, but this violates the `status` check constraint (rightfully). Solution: Filter out internal_transactions that are part of a pending transaction. --- CHANGELOG.md | 1 + .../import/runner/internal_transactions.ex | 28 ++++++++++++++++--- .../runner/internal_transactions_test.exs | 24 +++++++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ed41ca1f7..9c6abd2066 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#2456](https://github.com/poanetwork/blockscout/pull/2456) - fetch pending transactions for geth ### Fixes +- [#2523](https://github.com/poanetwork/blockscout/pull/2523) - Avoid importing internal_transactions of pending transactions - [#2503](https://github.com/poanetwork/blockscout/pull/2503) - Mitigate autocompletion library influence to page loading performance - [#2502](https://github.com/poanetwork/blockscout/pull/2502) - increase reward task timeout - [#2463](https://github.com/poanetwork/blockscout/pull/2463) - dark theme fixes diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 5e2e4e32f5..50529e8ce8 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -73,10 +73,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) + final_changes_list = reject_pending_transactions(ordered_changes_list, repo) + {:ok, internal_transactions} = Import.insert_changes_list( repo, - ordered_changes_list, + final_changes_list, conflict_target: [:transaction_hash, :index], for: InternalTransaction, on_conflict: on_conflict, @@ -156,6 +158,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do from( t in Transaction, where: t.hash in ^ordered_transaction_hashes, + where: not is_nil(t.block_hash), update: [ set: [ internal_transactions_indexed_at: ^timestamps.updated_at, @@ -180,10 +183,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do ] ) - transaction_count = Enum.count(ordered_transaction_hashes) - try do - {^transaction_count, result} = repo.update_all(query, [], timeout: timeout) + {_transaction_count, result} = repo.update_all(query, [], timeout: timeout) {:ok, result} rescue @@ -191,4 +192,23 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:error, %{exception: postgrex_error, transaction_hashes: ordered_transaction_hashes}} end end + + defp reject_pending_transactions(ordered_changes_list, repo) do + transaction_hashes = + ordered_changes_list + |> Enum.map(& &1.transaction_hash) + |> Enum.dedup() + + query = + from(t in Transaction, + where: t.hash in ^transaction_hashes, + where: is_nil(t.block_hash), + select: t.hash + ) + + pending_transactions = repo.all(query) + + ordered_changes_list + |> Enum.reject(fn %{transaction_hash: hash} -> Enum.member?(pending_transactions, hash) end) + end end diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index f5c080991e..66d62ae940 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -2,7 +2,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do use Explorer.DataCase alias Ecto.Multi - alias Explorer.Chain.{Data, Wei, Transaction} + alias Explorer.Chain.{Data, Wei, Transaction, InternalTransaction} alias Explorer.Chain.Import.Runner.InternalTransactions describe "run/1" do @@ -20,6 +20,28 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert :error == Repo.get(Transaction, transaction.hash).status end + + test "pending transactions don't get updated not its internal_transactions inserted" do + transaction = insert(:transaction) |> with_block(status: :ok) + pending = insert(:transaction) + + assert :ok == transaction.status + assert is_nil(pending.block_hash) + + index = 0 + + transaction_changes = make_internal_transaction_changes(transaction.hash, index, nil) + pending_changes = make_internal_transaction_changes(pending.hash, index, nil) + + assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes]) + + assert %InternalTransaction{} = + Repo.one(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash)) + + assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() + + assert is_nil(Repo.get(Transaction, pending.hash).block_hash) + end end defp run_internal_transactions(changes_list) when is_list(changes_list) do