Merge pull request #2886 from poanetwork/pp-pending-blocks-ops

pending blocks ops
pull/2936/head
Victor Baranov 5 years ago committed by GitHub
commit d498a34c76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      CHANGELOG.md
  2. 2
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  3. 28
      apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs
  4. 6
      apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs
  5. 56
      apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs
  6. 12
      apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs
  7. 4
      apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs
  8. 59
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  9. 2
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  10. 30
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
  11. 18
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  12. 28
      apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs
  13. 42
      apps/block_scout_web/test/block_scout_web/controllers/transaction_log_controller_test.exs
  14. 14
      apps/block_scout_web/test/block_scout_web/controllers/transaction_token_transfer_controller_test.exs
  15. 28
      apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs
  16. 19
      apps/block_scout_web/test/block_scout_web/features/viewing_app_test.exs
  17. 7
      apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs
  18. 12
      apps/block_scout_web/test/block_scout_web/features/viewing_transactions_test.exs
  19. 22
      apps/block_scout_web/test/block_scout_web/schema/query/node_test.exs
  20. 63
      apps/block_scout_web/test/block_scout_web/schema/query/transaction_test.exs
  21. 6
      apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
  22. 4
      apps/block_scout_web/test/block_scout_web/views/transaction_view_test.exs
  23. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
  24. 4
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
  25. 208
      apps/explorer/lib/explorer/chain.ex
  26. 9
      apps/explorer/lib/explorer/chain/block.ex
  27. 3
      apps/explorer/lib/explorer/chain/import.ex
  28. 121
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  29. 293
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  30. 82
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions_indexed_at_blocks.ex
  31. 4
      apps/explorer/lib/explorer/chain/import/runner/logs.ex
  32. 4
      apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex
  33. 54
      apps/explorer/lib/explorer/chain/import/runner/transactions.ex
  34. 2
      apps/explorer/lib/explorer/chain/import/stage/block_following.ex
  35. 27
      apps/explorer/lib/explorer/chain/import/stage/block_pending.ex
  36. 82
      apps/explorer/lib/explorer/chain/internal_transaction.ex
  37. 19
      apps/explorer/lib/explorer/chain/log.ex
  38. 87
      apps/explorer/lib/explorer/chain/pending_block_operation.ex
  39. 13
      apps/explorer/lib/explorer/chain/token_transfer.ex
  40. 7
      apps/explorer/lib/explorer/chain/transaction.ex
  41. 7
      apps/explorer/lib/explorer/etherscan.ex
  42. 2
      apps/explorer/lib/explorer/etherscan/logs.ex
  43. 6
      apps/explorer/lib/explorer/graphql.ex
  44. 14
      apps/explorer/priv/repo/migrations/20191018120546_create_pending_block_operations.exs
  45. 122
      apps/explorer/priv/repo/migrations/20191018140054_add_pending_internal_txs_operation.exs
  46. 43
      apps/explorer/priv/repo/migrations/20191121064805_add_block_hash_and_block_index_to_logs.exs
  47. 40
      apps/explorer/priv/repo/migrations/20191122062035_add_block_hash_to_token_transfers.exs
  48. 7
      apps/explorer/priv/repo/migrations/20191218120138_logs_block_number_index_index.exs
  49. 9
      apps/explorer/priv/repo/migrations/20191220113006_pending_block_operations_block_hash_partial_index.exs
  50. 59
      apps/explorer/priv/repo/migrations/scripts/20181108205650_large_additional_internal_transaction_constraints.sql
  51. 4
      apps/explorer/test/explorer/chain/cache/pending_transactions_test.exs
  52. 1
      apps/explorer/test/explorer/chain/cache/uncles_test.exs
  53. 74
      apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
  54. 107
      apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
  55. 112
      apps/explorer/test/explorer/chain/import_test.exs
  56. 4
      apps/explorer/test/explorer/chain/internal_transaction_test.exs
  57. 10
      apps/explorer/test/explorer/chain/log_test.exs
  58. 232
      apps/explorer/test/explorer/chain_test.exs
  59. 135
      apps/explorer/test/explorer/etherscan_test.exs
  60. 75
      apps/explorer/test/explorer/graphql_test.exs
  61. 8
      apps/explorer/test/explorer/repo_test.exs
  62. 29
      apps/explorer/test/support/factory.ex
  63. 12
      apps/indexer/lib/indexer/block/catchup/fetcher.ex
  64. 31
      apps/indexer/lib/indexer/block/fetcher.ex
  65. 12
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  66. 193
      apps/indexer/lib/indexer/fetcher/internal_transaction.ex
  67. 45
      apps/indexer/lib/indexer/pending_ops_cleaner.ex
  68. 5
      apps/indexer/lib/indexer/supervisor.ex
  69. 3
      apps/indexer/lib/indexer/transform/token_transfers.ex
  70. 9
      apps/indexer/test/indexer/block/fetcher_test.exs
  71. 236
      apps/indexer/test/indexer/fetcher/internal_transaction_test.exs
  72. 34
      apps/indexer/test/indexer/pending_ops_cleaner_test.exs
  73. 12
      apps/indexer/test/indexer/transform/token_transfers_test.exs

@ -1,10 +1,14 @@
## Current ## Current
### Features ### Features
- [#2835](https://github.com/poanetwork/blockscout/pull/2835), [#2871](https://github.com/poanetwork/blockscout/pull/2871), [#2872](https://github.com/poanetwork/blockscout/pull/2872), [#2886](https://github.com/poanetwork/blockscout/pull/2886), [#2925](https://github.com/poanetwork/blockscout/pull/2925) - Add "block_hash" to logs, token_transfers and internal transactions and "pending blocks operations" approach
- [#2926](https://github.com/poanetwork/blockscout/pull/2926) - API endpoint: sum balances except burnt address - [#2926](https://github.com/poanetwork/blockscout/pull/2926) - API endpoint: sum balances except burnt address
- [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action - [#2918](https://github.com/poanetwork/blockscout/pull/2918) - Add tokenID for tokentx API action explicitly
### Fixes ### Fixes
- [#2932](https://github.com/poanetwork/blockscout/pull/2932) - fix duplicate websocket connection
- [#2928](https://github.com/poanetwork/blockscout/pull/2928) - Speedup pending block ops int txs to fetch query
- [#2924](https://github.com/poanetwork/blockscout/pull/2924) - Speedup address to logs query
- [#2915](https://github.com/poanetwork/blockscout/pull/2915) - Speedup of blocks_without_reward_query - [#2915](https://github.com/poanetwork/blockscout/pull/2915) - Speedup of blocks_without_reward_query
- [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query - [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query
- [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache - [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache
@ -12,7 +16,6 @@
- [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places - [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places
- [#2899](https://github.com/poanetwork/blockscout/pull/2899) - fix empty buffered task - [#2899](https://github.com/poanetwork/blockscout/pull/2899) - fix empty buffered task
- [#2887](https://github.com/poanetwork/blockscout/pull/2887) - increase chart loading speed - [#2887](https://github.com/poanetwork/blockscout/pull/2887) - increase chart loading speed
- [#2932](https://github.com/poanetwork/blockscout/pull/2932) - fix duplicate websocket connection
### Chore ### Chore
- [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests - [#2896](https://github.com/poanetwork/blockscout/pull/2896) - Disable Parity websockets tests

@ -95,7 +95,7 @@ defmodule BlockScoutWeb.Notifier do
def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do
internal_transactions internal_transactions
|> Stream.map( |> Stream.map(
&(InternalTransaction &(InternalTransaction.where_nonpending_block()
|> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index) |> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index)
|> Repo.preload([:from_address, :to_address, transaction: :block])) |> Repo.preload([:from_address, :to_address, transaction: :block]))
) )

@ -134,7 +134,15 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> insert(from_address: address) |> insert(from_address: address)
|> with_block() |> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, from_address: address, index: 0) internal_transaction =
insert(
:internal_transaction,
transaction: transaction,
from_address: address,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]})
@ -158,7 +166,14 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> insert(to_address: address) |> insert(to_address: address)
|> with_block() |> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, to_address: address, index: 0) internal_transaction =
insert(:internal_transaction,
transaction: transaction,
to_address: address,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]})
@ -186,7 +201,14 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> with_block() |> with_block()
internal_transaction = internal_transaction =
insert(:internal_transaction, transaction: transaction, from_address: address, to_address: address, index: 0) insert(:internal_transaction,
transaction: transaction,
from_address: address,
to_address: address,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]})

@ -35,13 +35,15 @@ defmodule BlockScoutWeb.AddressContractControllerTest do
test "successfully renders the page when the address is a contract", %{conn: conn} do test "successfully renders the page when the address is a contract", %{conn: conn} do
address = insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil) address = insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil)
transaction = insert(:transaction, from_address: address) transaction = insert(:transaction, from_address: address) |> with_block()
insert( insert(
:internal_transaction_create, :internal_transaction_create,
index: 0, index: 0,
transaction: transaction, transaction: transaction,
created_contract_address: address created_contract_address: address,
block_hash: transaction.block_hash,
block_index: 0
) )
conn = get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, address)) conn = get(conn, address_contract_path(BlockScoutWeb.Endpoint, :index, address))

@ -44,7 +44,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
) )
to_internal_transaction = to_internal_transaction =
@ -53,7 +55,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 2
) )
path = address_internal_transaction_path(conn, :index, address, %{"type" => "JSON"}) path = address_internal_transaction_path(conn, :index, address, %{"type" => "JSON"})
@ -83,7 +87,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
) )
to_internal_transaction = to_internal_transaction =
@ -92,7 +98,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 2
) )
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "from", "type" => "JSON"}) path = address_internal_transaction_path(conn, :index, address, %{"filter" => "from", "type" => "JSON"})
@ -125,7 +133,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
) )
to_internal_transaction = to_internal_transaction =
@ -134,7 +144,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 2
) )
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"}) path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"})
@ -167,7 +179,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
) )
to_internal_transaction = to_internal_transaction =
@ -177,7 +191,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
created_contract_address: address, created_contract_address: address,
index: 2, index: 2,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 2
) )
path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"}) path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"})
@ -226,7 +242,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: index, index: index,
block_number: transaction_1.block_number, block_number: transaction_1.block_number,
transaction_index: transaction_1.index transaction_index: transaction_1.index,
block_hash: a_block.hash,
block_index: index
) )
end) end)
@ -239,7 +257,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: index, index: index,
block_number: transaction_2.block_number, block_number: transaction_2.block_number,
transaction_index: transaction_2.index transaction_index: transaction_2.index,
block_hash: a_block.hash,
block_index: 20 + index
) )
end) end)
@ -252,7 +272,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: index, index: index,
block_number: transaction_3.block_number, block_number: transaction_3.block_number,
transaction_index: transaction_3.index transaction_index: transaction_3.index,
block_hash: b_block.hash,
block_index: index
) )
end) end)
@ -265,7 +287,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: 11, index: 11,
block_number: transaction_3.block_number, block_number: transaction_3.block_number,
transaction_index: transaction_3.index transaction_index: transaction_3.index,
block_hash: b_block.hash,
block_index: 11
) )
conn = conn =
@ -304,7 +328,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address, from_address: address,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: index
) )
end) end)
@ -335,7 +361,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
:internal_transaction, :internal_transaction,
transaction: transaction, transaction: transaction,
from_address: address, from_address: address,
index: index index: index,
block_hash: transaction.block_hash,
block_index: index
) )
end) end)

@ -21,13 +21,15 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
test "successfully renders the page when the address is a contract", %{conn: conn} do test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address) contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert( insert(
:internal_transaction_create, :internal_transaction_create,
index: 0, index: 0,
transaction: transaction, transaction: transaction,
created_contract_address: contract_address created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
) )
insert(:smart_contract, address_hash: contract_address.hash) insert(:smart_contract, address_hash: contract_address.hash)
@ -42,13 +44,15 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
test "returns not found for an unverified contract", %{conn: conn} do test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address) contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address) transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert( insert(
:internal_transaction_create, :internal_transaction_create,
index: 0, index: 0,
transaction: transaction, transaction: transaction,
created_contract_address: contract_address created_contract_address: contract_address,
block_hash: transaction.block_hash,
block_index: 0
) )
conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, contract_address.hash)) conn = get(conn, address_read_contract_path(BlockScoutWeb.Endpoint, :index, contract_address.hash))

@ -126,7 +126,9 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
index: 0, index: 0,
created_contract_address: address, created_contract_address: address,
to_address: nil, to_address: nil,
transaction: transaction transaction: transaction,
block_hash: block.hash,
block_index: 0
) )
conn = get(conn, address_transaction_path(conn, :index, address), %{"type" => "JSON"}) conn = get(conn, address_transaction_path(conn, :index, address), %{"type" => "JSON"})

@ -1456,7 +1456,13 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction = internal_transaction =
:internal_transaction_create :internal_transaction_create
|> insert(transaction: transaction, index: 0, from_address: address) |> insert(
transaction: transaction,
index: 0,
from_address: address,
block_hash: transaction.block_hash,
block_index: 0
)
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
params = %{ params = %{
@ -1505,7 +1511,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
type: :reward, type: :reward,
error: "some error" error: "some error",
block_hash: transaction.block_hash,
block_index: 0
] ]
insert(:internal_transaction_create, internal_transaction_details) insert(:internal_transaction_create, internal_transaction_details)
@ -1535,7 +1543,12 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> with_block() |> with_block()
for index <- 0..2 do for index <- 0..2 do
insert(:internal_transaction_create, transaction: transaction, index: index) insert(:internal_transaction_create,
transaction: transaction,
index: index,
block_hash: transaction.block_hash,
block_index: index
)
end end
params = %{ params = %{
@ -1609,7 +1622,13 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction = internal_transaction =
:internal_transaction_create :internal_transaction_create
|> insert(transaction: transaction, index: 0, from_address: address) |> insert(
transaction: transaction,
index: 0,
from_address: address,
block_hash: transaction.block_hash,
block_index: 0
)
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
params = %{ params = %{
@ -1661,7 +1680,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
type: :reward, type: :reward,
error: "some error" error: "some error",
block_hash: transaction.block_hash,
block_index: 0
] ]
insert(:internal_transaction_create, internal_transaction_details) insert(:internal_transaction_create, internal_transaction_details)
@ -1696,7 +1717,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction_details = %{ internal_transaction_details = %{
from_address: address, from_address: address,
transaction: transaction, transaction: transaction,
index: index index: index,
block_hash: transaction.block_hash,
block_index: index
} }
insert(:internal_transaction_create, internal_transaction_details) insert(:internal_transaction_create, internal_transaction_details)
@ -1790,7 +1813,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
insert(:token_transfer, %{ insert(:token_transfer, %{
token_contract_address: token_address, token_contract_address: token_address,
token_id: 666, token_id: 666,
transaction: transaction transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
}) })
{:ok, _} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) {:ok, _} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash)
@ -1820,7 +1845,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> insert() |> insert()
|> with_block() |> with_block()
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer =
insert(:token_transfer, block: transaction.block, transaction: transaction, block_number: block.number)
{:ok, token} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) {:ok, token} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash)
params = %{ params = %{
@ -1897,8 +1924,20 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> insert() |> insert()
|> with_block() |> with_block()
insert(:token_transfer, from_address: address, transaction: transaction) insert(:token_transfer,
insert(:token_transfer, from_address: address, token_contract_address: contract_address, transaction: transaction) from_address: address,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
insert(:token_transfer,
from_address: address,
token_contract_address: contract_address,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
params = %{ params = %{
"module" => "account", "module" => "account",

@ -524,6 +524,8 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
created_contract_address: created_contract_address, created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode, created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )

@ -76,7 +76,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0) block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block) transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101") insert(:log, block: block, address: address, transaction: transaction, data: "0x010101")
params = params(api_params, [%{"address" => to_string(address.hash)}]) params = params(api_params, [%{"address" => to_string(address.hash)}])
@ -94,7 +94,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0) block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block) transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01") insert(:log, block: block, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}]) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}])
@ -112,8 +112,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0) block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block) transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01") insert(:log, address: address, block: block, transaction: transaction, data: "0x010101", first_topic: "0x01")
insert(:log, address: address, transaction: transaction, data: "0x020202", first_topic: "0x00") insert(:log, address: address, block: block, transaction: transaction, data: "0x020202", first_topic: "0x00")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}]) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}])
@ -127,14 +127,15 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
test "paginates logs", %{conn: conn, api_params: api_params} do test "paginates logs", %{conn: conn, api_params: api_params} do
contract_address = insert(:contract_address) contract_address = insert(:contract_address)
block = insert(:block)
transaction = transaction =
:transaction :transaction
|> insert(to_address: contract_address) |> insert(to_address: contract_address)
|> with_block() |> with_block(block)
inserted_records = inserted_records =
insert_list(2000, :log, address: contract_address, transaction: transaction, first_topic: "0x01") insert_list(2000, :log, block: block, address: contract_address, transaction: transaction, first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}]) params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}])
@ -191,10 +192,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction, transaction: transaction,
data: "0x010101", data: "0x010101",
first_topic: "0x01", first_topic: "0x01",
second_topic: "0x02" second_topic: "0x02",
block: block
) )
insert(:log, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01") insert(:log, block: block, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01")
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}]) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}])
@ -219,7 +221,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction, transaction: transaction,
data: "0x010101", data: "0x010101",
first_topic: "0x01", first_topic: "0x01",
second_topic: "0x02" second_topic: "0x02",
block: block
) )
insert(:log, insert(:log,
@ -227,7 +230,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction, transaction: transaction,
data: "0x020202", data: "0x020202",
first_topic: "0x01", first_topic: "0x01",
second_topic: "0x03" second_topic: "0x03",
block: block
) )
params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}]) params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}])
@ -341,11 +345,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction2 = insert(:transaction, from_address: address) |> with_block(block2) transaction2 = insert(:transaction, from_address: address) |> with_block(block2)
transaction3 = insert(:transaction, from_address: address) |> with_block(block3) transaction3 = insert(:transaction, from_address: address) |> with_block(block3)
insert(:log, address: address, transaction: transaction1, data: "0x010101") insert(:log, block: block1, address: address, transaction: transaction1, data: "0x010101")
insert(:log, address: address, transaction: transaction2, data: "0x020202") insert(:log, block: block2, address: address, transaction: transaction2, data: "0x020202")
insert(:log, address: address, transaction: transaction3, data: "0x030303") insert(:log, block: block3, address: address, transaction: transaction3, data: "0x030303")
changeset = Ecto.Changeset.change(block3, %{consensus: false}) changeset = Ecto.Changeset.change(block3, %{consensus: false})

@ -243,8 +243,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
transaction_details = [ transaction_details = [
status: :error, status: :error,
error: error, error: error
internal_transactions_indexed_at: DateTime.utc_now()
] ]
transaction = transaction =
@ -256,7 +255,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
type: :reward, type: :reward,
error: error error: error,
block_hash: transaction.block_hash,
block_index: 0
] ]
insert(:internal_transaction, internal_transaction_details) insert(:internal_transaction, internal_transaction_details)
@ -285,8 +286,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
test "with a txhash with failed status but awaiting internal transactions", %{conn: conn} do test "with a txhash with failed status but awaiting internal transactions", %{conn: conn} do
transaction_details = [ transaction_details = [
status: :error, status: :error,
error: nil, error: nil
internal_transactions_indexed_at: nil
] ]
transaction = transaction =
@ -411,7 +411,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
address: address, address: address,
transaction: transaction, transaction: transaction,
first_topic: "first topic", first_topic: "first topic",
second_topic: "second topic" second_topic: "second topic",
block: block,
block_number: block.number
) )
end) end)
@ -486,7 +488,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
address: address, address: address,
transaction: transaction, transaction: transaction,
first_topic: "first topic", first_topic: "first topic",
second_topic: "second topic" second_topic: "second topic",
block: block,
block_number: block.number
) )
params = %{ params = %{

@ -44,14 +44,18 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 0
) )
insert(:internal_transaction, insert(:internal_transaction,
transaction: transaction, transaction: transaction,
index: 1, index: 1,
transaction_index: transaction.index, transaction_index: transaction.index,
block_number: transaction.block_number block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1
) )
path = transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash) path = transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash)
@ -90,7 +94,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 0
) )
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
@ -118,7 +124,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 0
) )
second_page_indexes = second_page_indexes =
@ -128,7 +136,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: index
) )
end) end)
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
@ -161,7 +171,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: index
) )
end) end)
@ -190,7 +202,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do
transaction: transaction, transaction: transaction,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: index
) )
end) end)

@ -29,7 +29,13 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block() |> with_block()
address = insert(:address) address = insert(:address)
insert(:log, address: address, transaction: transaction)
insert(:log,
address: address,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"})
@ -46,7 +52,13 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block() |> with_block()
address = insert(:address) address = insert(:address)
insert(:log, address: address, transaction: transaction)
insert(:log,
address: address,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"})
@ -73,11 +85,24 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> insert() |> insert()
|> with_block() |> with_block()
log = insert(:log, transaction: transaction, index: 1) log =
insert(:log,
transaction: transaction,
index: 1,
block: transaction.block,
block_number: transaction.block_number
)
second_page_indexes = second_page_indexes =
2..51 2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end) |> Enum.map(fn index ->
insert(:log,
transaction: transaction,
index: index,
block: transaction.block,
block_number: transaction.block_number
)
end)
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
conn = conn =
@ -98,7 +123,14 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block() |> with_block()
1..60 1..60
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end) |> Enum.map(fn index ->
insert(:log,
transaction: transaction,
index: index,
block: transaction.block,
block_number: transaction.block_number
)
end)
conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"}) conn = get(conn, transaction_log_path(conn, :index, transaction), %{type: "JSON"})

@ -46,11 +46,11 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
end end
test "includes token transfers for the transaction", %{conn: conn} do test "includes token transfers for the transaction", %{conn: conn} do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
insert(:token_transfer, transaction: transaction) insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
insert(:token_transfer, transaction: transaction) insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
path = transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash) path = transaction_token_transfer_path(BlockScoutWeb.Endpoint, :index, transaction.hash)
@ -106,7 +106,9 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
insert( insert(
:token_transfer, :token_transfer,
transaction: transaction, transaction: transaction,
log_index: log_index log_index: log_index,
block: transaction.block,
block_number: transaction.block_number
) )
end) end)
@ -129,7 +131,9 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
insert( insert(
:token_transfer, :token_transfer,
transaction: transaction, transaction: transaction,
log_index: log_index log_index: log_index,
block_number: transaction.block_number,
block: transaction.block
) )
end) end)

@ -80,7 +80,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
test "see the contract creator and transaction links", %{session: session} do test "see the contract creator and transaction links", %{session: session} do
address = insert(:address) address = insert(:address)
contract = insert(:contract_address) contract = insert(:contract_address)
transaction = insert(:transaction, from_address: address, created_contract_address: contract) transaction = insert(:transaction, from_address: address, created_contract_address: contract) |> with_block()
internal_transaction = internal_transaction =
insert( insert(
@ -88,7 +88,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 1, index: 1,
transaction: transaction, transaction: transaction,
from_address: address, from_address: address,
created_contract_address: contract created_contract_address: contract,
block_hash: transaction.block_hash,
block_index: 1
) )
address_hash = AddressView.trimmed_hash(address.hash) address_hash = AddressView.trimmed_hash(address.hash)
@ -102,7 +104,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
test "see the contract creator and transaction links even when the creator is another contract", %{session: session} do test "see the contract creator and transaction links even when the creator is another contract", %{session: session} do
lincoln = insert(:address) lincoln = insert(:address)
contract = insert(:contract_address) contract = insert(:contract_address)
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
another_contract = insert(:contract_address) another_contract = insert(:contract_address)
insert( insert(
@ -112,7 +114,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln, from_address: lincoln,
to_address: contract, to_address: contract,
created_contract_address: contract, created_contract_address: contract,
type: :call type: :call,
block_hash: transaction.block_hash,
block_index: 1
) )
internal_transaction = internal_transaction =
@ -121,7 +125,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 2, index: 2,
transaction: transaction, transaction: transaction,
from_address: contract, from_address: contract,
created_contract_address: another_contract created_contract_address: another_contract,
block_hash: transaction.block_hash,
block_index: 2
) )
contract_hash = AddressView.trimmed_hash(contract.hash) contract_hash = AddressView.trimmed_hash(contract.hash)
@ -208,7 +214,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: 7000, block_number: 7000,
transaction_index: 1 transaction_index: 1,
block_hash: transaction.block_hash,
block_index: 1
) )
insert(:internal_transaction, insert(:internal_transaction,
@ -216,7 +224,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: address, from_address: address,
index: 2, index: 2,
block_number: 8000, block_number: 8000,
transaction_index: 2 transaction_index: 2,
block_hash: transaction.block_hash,
block_index: 2
) )
{:ok, %{internal_transaction_lincoln_to_address: internal_transaction_lincoln_to_address}} {:ok, %{internal_transaction_lincoln_to_address: internal_transaction_lincoln_to_address}}
@ -251,7 +261,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 2, index: 2,
from_address: addresses.lincoln, from_address: addresses.lincoln,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 2
) )
Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]})

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.ViewingAppTest do
alias BlockScoutWeb.Counters.BlocksIndexedCounter alias BlockScoutWeb.Counters.BlocksIndexedCounter
alias Explorer.Counters.AddressesCounter alias Explorer.Counters.AddressesCounter
alias Explorer.{Repo} alias Explorer.{Repo}
alias Explorer.Chain.{Transaction} alias Explorer.Chain.PendingBlockOperation
setup do setup do
start_supervised!(AddressesCounter) start_supervised!(AddressesCounter)
@ -29,6 +29,8 @@ defmodule BlockScoutWeb.ViewingAppTest do
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session session
|> AppPage.visit_page() |> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("50% Blocks Indexed")) |> assert_has(AppPage.indexed_status("50% Blocks Indexed"))
@ -46,6 +48,8 @@ defmodule BlockScoutWeb.ViewingAppTest do
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), 1) == :eq assert Decimal.cmp(Explorer.Chain.indexed_ratio(), 1) == :eq
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session session
|> AppPage.visit_page() |> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("Indexing Tokens")) |> assert_has(AppPage.indexed_status("Indexing Tokens"))
@ -65,6 +69,8 @@ defmodule BlockScoutWeb.ViewingAppTest do
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session session
|> AppPage.visit_page() |> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("50% Blocks Indexed")) |> assert_has(AppPage.indexed_status("50% Blocks Indexed"))
@ -90,6 +96,8 @@ defmodule BlockScoutWeb.ViewingAppTest do
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.9)) == :eq assert Decimal.cmp(Explorer.Chain.indexed_ratio(), Decimal.from_float(0.9)) == :eq
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session session
|> AppPage.visit_page() |> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("90% Blocks Indexed")) |> assert_has(AppPage.indexed_status("90% Blocks Indexed"))
@ -111,6 +119,10 @@ defmodule BlockScoutWeb.ViewingAppTest do
|> insert() |> insert()
|> with_block(block) |> with_block(block)
block_hash = block.hash
insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true)
BlocksIndexedCounter.calculate_blocks_indexed() BlocksIndexedCounter.calculate_blocks_indexed()
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), 1) == :eq assert Decimal.cmp(Explorer.Chain.indexed_ratio(), 1) == :eq
@ -119,7 +131,10 @@ defmodule BlockScoutWeb.ViewingAppTest do
|> AppPage.visit_page() |> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("Indexing Tokens")) |> assert_has(AppPage.indexed_status("Indexing Tokens"))
Repo.update_all(Transaction, set: [internal_transactions_indexed_at: DateTime.utc_now()]) Repo.update_all(
from(p in PendingBlockOperation, where: p.block_hash == ^block_hash),
set: [fetch_internal_transactions: false]
)
BlocksIndexedCounter.calculate_blocks_indexed() BlocksIndexedCounter.calculate_blocks_indexed()

@ -59,7 +59,12 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
internal_transaction = internal_transaction =
:internal_transaction_create :internal_transaction_create
|> insert(transaction: transaction, index: 0) |> insert(
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 1
)
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
session session

@ -44,9 +44,15 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do
) )
|> with_block(block, gas_used: Decimal.new(1_230_000_000_000_123_000), status: :ok) |> with_block(block, gas_used: Decimal.new(1_230_000_000_000_123_000), status: :ok)
insert(:log, address: lincoln, index: 0, transaction: transaction) insert(:log, address: lincoln, index: 0, transaction: transaction, block: block, block_number: block.number)
internal = insert(:internal_transaction, index: 0, transaction: transaction) internal =
insert(:internal_transaction,
index: 0,
transaction: transaction,
block_hash: transaction.block_hash,
block_index: 0
)
{:ok, {:ok,
%{ %{
@ -95,7 +101,7 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
:internal_transaction_create :internal_transaction_create
|> insert(transaction: transaction, index: 0) |> insert(transaction: transaction, index: 0, block_hash: transaction.block_hash, block_index: 0)
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
session session

@ -58,9 +58,15 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
end end
test "with valid argument 'id' for an internal transaction", %{conn: conn} do test "with valid argument 'id' for an internal transaction", %{conn: conn} do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, index: 0) internal_transaction =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
query = """ query = """
query($id: ID!) { query($id: ID!) {
@ -96,9 +102,15 @@ defmodule BlockScoutWeb.Schema.Query.NodeTest do
end end
test "with 'id' for non-existent internal transaction", %{conn: conn} do test "with 'id' for non-existent internal transaction", %{conn: conn} do
transaction = build(:transaction) transaction = insert(:transaction) |> with_block()
internal_transaction = build(:internal_transaction, transaction: transaction, index: 0) internal_transaction =
build(:internal_transaction,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
query = """ query = """
query($id: ID!) { query($id: ID!) {

@ -134,7 +134,9 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
from_address: address, from_address: address,
call_type: :call call_type: :call,
block_hash: transaction.block_hash,
block_index: 0
} }
internal_transaction = internal_transaction =
@ -259,11 +261,28 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
end end
test "internal transactions are ordered by ascending index", %{conn: conn} do test "internal transactions are ordered by ascending index", %{conn: conn} do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
insert(:internal_transaction,
transaction: transaction,
index: 2,
block_hash: transaction.block_hash,
block_index: 2
)
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:internal_transaction, transaction: transaction, index: 2) insert(:internal_transaction,
insert(:internal_transaction, transaction: transaction, index: 0) transaction: transaction,
insert(:internal_transaction, transaction: transaction, index: 1) index: 1,
block_hash: transaction.block_hash,
block_index: 1
)
query = """ query = """
query ($hash: FullHash!, $first: Int!) { query ($hash: FullHash!, $first: Int!) {
@ -370,11 +389,28 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
# #
# This test ensures support for a 'count' argument. # This test ensures support for a 'count' argument.
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
insert(:internal_transaction, transaction: transaction, index: 2) insert(:internal_transaction,
insert(:internal_transaction, transaction: transaction, index: 0) transaction: transaction,
insert(:internal_transaction, transaction: transaction, index: 1) index: 2,
block_hash: transaction.block_hash,
block_index: 2
)
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:internal_transaction,
transaction: transaction,
index: 1,
block_hash: transaction.block_hash,
block_index: 1
)
query = """ query = """
query ($hash: FullHash!, $last: Int!, $count: Int!) { query ($hash: FullHash!, $last: Int!, $count: Int!) {
@ -407,10 +443,15 @@ defmodule BlockScoutWeb.Schema.Query.TransactionTest do
end end
test "pagination support with 'first' and 'after' arguments", %{conn: conn} do test "pagination support with 'first' and 'after' arguments", %{conn: conn} do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
for index <- 0..5 do for index <- 0..5 do
insert(:internal_transaction_create, transaction: transaction, index: index) insert(:internal_transaction_create,
transaction: transaction,
index: index,
block_hash: transaction.block_hash,
block_index: index
)
end end
query1 = """ query1 = """

@ -11,14 +11,16 @@ defmodule BlockScoutWeb.AddressViewTest do
end end
test "for a pending internal transaction contract creation to address" do test "for a pending internal transaction contract creation to address" do
transaction = insert(:transaction, to_address: nil) transaction = insert(:transaction, to_address: nil) |> with_block()
internal_transaction = internal_transaction =
insert(:internal_transaction, insert(:internal_transaction,
index: 1, index: 1,
transaction: transaction, transaction: transaction,
to_address: nil, to_address: nil,
created_contract_address_hash: nil created_contract_address_hash: nil,
block_hash: transaction.block_hash,
block_index: 1
) )
assert "Contract Address Pending" == AddressView.address_partial_selector(internal_transaction, :to, nil) assert "Contract Address Pending" == AddressView.address_partial_selector(internal_transaction, :to, nil)

@ -192,6 +192,8 @@ defmodule BlockScoutWeb.TransactionViewTest do
|> insert() |> insert()
|> with_block(block, status: :error) |> with_block(block, status: :error)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
status = TransactionView.transaction_status(transaction) status = TransactionView.transaction_status(transaction)
assert TransactionView.formatted_status(status) == "Error: (Awaiting internal transactions for reason)" assert TransactionView.formatted_status(status) == "Error: (Awaiting internal transactions for reason)"
end end
@ -200,7 +202,7 @@ defmodule BlockScoutWeb.TransactionViewTest do
transaction = transaction =
:transaction :transaction
|> insert() |> insert()
|> with_block(status: :error, internal_transactions_indexed_at: DateTime.utc_now(), error: "Out of Gas") |> with_block(status: :error, error: "Out of Gas")
status = TransactionView.transaction_status(transaction) status = TransactionView.transaction_status(transaction)
assert TransactionView.formatted_status(status) == "Error: Out of Gas" assert TransactionView.formatted_status(status) == "Error: Out of Gas"

@ -39,6 +39,7 @@ defmodule EthereumJSONRPC.Log do
...> ) ...> )
%{ %{
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
block_number: 37, block_number: 37,
data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
@ -47,6 +48,7 @@ defmodule EthereumJSONRPC.Log do
second_topic: nil, second_topic: nil,
third_topic: nil, third_topic: nil,
transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5",
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
type: "mined" type: "mined"
} }
@ -69,7 +71,9 @@ defmodule EthereumJSONRPC.Log do
...> ) ...> )
%{ %{
address_hash: "0xda8b3276cde6d768a44b9dac659faa339a41ac55", address_hash: "0xda8b3276cde6d768a44b9dac659faa339a41ac55",
block_hash: "0x0b89f7f894f5d8ba941e16b61490e999a0fcaaf92dfcc70aee2ac5ddb5f243e1",
block_number: 4448, block_number: 4448,
block_hash: "0x0b89f7f894f5d8ba941e16b61490e999a0fcaaf92dfcc70aee2ac5ddb5f243e1",
data: "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563", data: "0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563",
first_topic: "0xadc1e8a294f8415511303acc4a8c0c5906c7eb0bf2a71043d7f4b03b46a39130", first_topic: "0xadc1e8a294f8415511303acc4a8c0c5906c7eb0bf2a71043d7f4b03b46a39130",
fourth_topic: nil, fourth_topic: nil,
@ -84,6 +88,7 @@ defmodule EthereumJSONRPC.Log do
%{ %{
"address" => address_hash, "address" => address_hash,
"blockNumber" => block_number, "blockNumber" => block_number,
"blockHash" => block_hash,
"data" => data, "data" => data,
"logIndex" => index, "logIndex" => index,
"topics" => topics, "topics" => topics,
@ -93,6 +98,7 @@ defmodule EthereumJSONRPC.Log do
%{ %{
address_hash: address_hash, address_hash: address_hash,
block_number: block_number, block_number: block_number,
block_hash: block_hash,
data: data, data: data,
index: index, index: index,
transaction_hash: transaction_hash transaction_hash: transaction_hash

@ -34,6 +34,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do
cumulative_gas_used: 884_322, cumulative_gas_used: 884_322,
address_hash: "0x1e2fbe6be9eb39fc894d38be976111f332172d83", address_hash: "0x1e2fbe6be9eb39fc894d38be976111f332172d83",
block_number: 3_560_000, block_number: 3_560_000,
block_hash: nil,
data: data:
"0x00000000000000000000000033066f6a8adf2d4f5db193524b6fbae062ec0d110000000000000000000000000000000000000000000000000000000000001030", "0x00000000000000000000000033066f6a8adf2d4f5db193524b6fbae062ec0d110000000000000000000000000000000000000000000000000000000000001030",
index: 12, index: 12,
@ -48,6 +49,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do
EthereumJSONRPC.Parity -> EthereumJSONRPC.Parity ->
%{ %{
created_contract_address_hash: nil, created_contract_address_hash: nil,
block_hash: nil,
cumulative_gas_used: 50450, cumulative_gas_used: 50450,
gas_used: 50450, gas_used: 50450,
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
@ -82,7 +84,9 @@ defmodule EthereumJSONRPC.ReceiptsTest do
"logs" => [ "logs" => [
%{ %{
"address" => address_hash, "address" => address_hash,
"blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
"blockNumber" => integer_to_quantity(block_number), "blockNumber" => integer_to_quantity(block_number),
"blockHash" => nil,
"data" => data, "data" => data,
"logIndex" => integer_to_quantity(index), "logIndex" => integer_to_quantity(index),
"topics" => [first_topic], "topics" => [first_topic],

@ -38,6 +38,7 @@ defmodule Explorer.Chain do
Import, Import,
InternalTransaction, InternalTransaction,
Log, Log,
PendingBlockOperation,
SmartContract, SmartContract,
StakingPool, StakingPool,
Token, Token,
@ -194,6 +195,7 @@ defmodule Explorer.Chain do
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
InternalTransaction InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, direction) |> InternalTransaction.where_address_fields_match(hash, direction)
|> InternalTransaction.where_is_different_from_parent_transaction() |> InternalTransaction.where_is_different_from_parent_transaction()
|> InternalTransaction.where_block_number_is_not_null() |> InternalTransaction.where_block_number_is_not_null()
@ -355,9 +357,9 @@ defmodule Explorer.Chain do
base_query = base_query =
from(log in Log, from(log in Log,
inner_join: transaction in assoc(log, :transaction), inner_join: transaction in Transaction,
order_by: [desc: transaction.block_number, desc: transaction.index], on: transaction.hash == log.transaction_hash,
preload: [:transaction, transaction: [to_address: :smart_contract]], order_by: [desc: log.block_number, desc: log.index],
where: transaction.block_number < ^block_number, where: transaction.block_number < ^block_number,
or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index, or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index,
or_where: or_where:
@ -368,7 +370,19 @@ defmodule Explorer.Chain do
select: log select: log
) )
base_query wrapped_query =
from(
log in subquery(base_query),
inner_join: transaction in Transaction,
preload: [:transaction, transaction: [to_address: :smart_contract]],
where:
log.block_hash == transaction.block_hash and
log.block_number == transaction.block_number and
log.transaction_hash == transaction.hash,
select: log
)
wrapped_query
|> filter_topic(options) |> filter_topic(options)
|> Repo.all() |> Repo.all()
|> Enum.take(paging_options.page_size) |> Enum.take(paging_options.page_size)
@ -718,33 +732,25 @@ defmodule Explorer.Chain do
end end
@doc """ @doc """
Checks to see if the chain is down indexing based on the transaction from the oldest block having Checks to see if the chain is down indexing based on the transaction from the
an `internal_transactions_indexed_at` date. oldest block and the `fetch_internal_transactions` pending operation
""" """
@spec finished_indexing?() :: boolean() @spec finished_indexing?() :: boolean()
def finished_indexing? do def finished_indexing? do
transaction_exists = with {:transactions_exist, true} <- {:transactions_exist, Repo.exists?(Transaction)},
Transaction min_block_number when not is_nil(min_block_number) <- Repo.aggregate(Transaction, :min, :block_number) do
|> limit(1) query =
|> Repo.one() from(
b in Block,
min_block_number_transaction = Repo.aggregate(Transaction, :min, :block_number) join: pending_ops in assoc(b, :pending_operations),
where: pending_ops.fetch_internal_transactions,
where: b.consensus and b.number == ^min_block_number
)
if transaction_exists do !Repo.exists?(query)
if min_block_number_transaction do
Transaction
|> where([t], t.block_number == ^min_block_number_transaction and is_nil(t.internal_transactions_indexed_at))
|> limit(1)
|> Repo.one()
|> case do
nil -> true
_ -> false
end
else else
false {:transactions_exist, false} -> true
end nil -> false
else
true
end end
end end
@ -1350,11 +1356,8 @@ defmodule Explorer.Chain do
@doc """ @doc """
The number of `t:Explorer.Chain.InternalTransaction.t/0`. The number of `t:Explorer.Chain.InternalTransaction.t/0`.
iex> transaction = iex> transaction = :transaction |> insert() |> with_block()
...> :transaction |> iex> insert(:internal_transaction, index: 0, transaction: transaction, block_hash: transaction.block_hash, block_index: 0)
...> insert() |>
...> with_block()
iex> insert(:internal_transaction, index: 0, transaction: transaction)
iex> Explorer.Chain.internal_transaction_count() iex> Explorer.Chain.internal_transaction_count()
1 1
@ -1365,7 +1368,7 @@ defmodule Explorer.Chain do
""" """
def internal_transaction_count do def internal_transaction_count do
Repo.one!(from(it in "internal_transactions", select: fragment("COUNT(*)"))) Repo.aggregate(InternalTransaction.where_nonpending_block(), :count, :transaction_hash)
end end
@doc """ @doc """
@ -1641,18 +1644,20 @@ defmodule Explorer.Chain do
end end
@doc """ @doc """
Returns a stream of all blocks with unfetched internal transactions. Returns a stream of all blocks with unfetched internal transactions, using
the `pending_block_operation` table.
Only blocks with consensus are returned. Only blocks with consensus are returned.
iex> non_consensus = insert(:block, consensus: false) iex> non_consensus = insert(:block, consensus: false)
iex> insert(:pending_block_operation, block: non_consensus, fetch_internal_transactions: true)
iex> unfetched = insert(:block) iex> unfetched = insert(:block)
iex> fetched = insert(:block, internal_transactions_indexed_at: DateTime.utc_now()) iex> insert(:pending_block_operation, block: unfetched, fetch_internal_transactions: true)
iex> to_be_refetched = insert(:block, refetch_needed: true) iex> fetched = insert(:block)
iex> insert(:pending_block_operation, block: fetched, fetch_internal_transactions: false)
iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions( iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions(
...> [:number],
...> MapSet.new(), ...> MapSet.new(),
...> fn %Explorer.Chain.Block{number: number}, acc -> ...> fn number, acc ->
...> MapSet.put(acc, number) ...> MapSet.put(acc, number)
...> end ...> end
...> ) ...> )
@ -1662,112 +1667,55 @@ defmodule Explorer.Chain do
true true
iex> fetched.hash in number_set iex> fetched.hash in number_set
false false
iex> to_be_refetched.number in number_set
false
""" """
@spec stream_blocks_with_unfetched_internal_transactions( @spec stream_blocks_with_unfetched_internal_transactions(
fields :: [
:consensus
| :difficulty
| :gas_limit
| :gas_used
| :hash
| :miner
| :miner_hash
| :nonce
| :number
| :parent_hash
| :size
| :timestamp
| :total_difficulty
| :transactions
| :internal_transactions_indexed_at
],
initial :: accumulator, initial :: accumulator,
reducer :: (entry :: term(), accumulator -> accumulator) reducer :: (entry :: term(), accumulator -> accumulator)
) :: {:ok, accumulator} ) :: {:ok, accumulator}
when accumulator: term() when accumulator: term()
def stream_blocks_with_unfetched_internal_transactions(fields, initial, reducer) when is_function(reducer, 2) do def stream_blocks_with_unfetched_internal_transactions(initial, reducer) when is_function(reducer, 2) do
query = query =
from( from(
b in Block, b in Block,
join: pending_ops in assoc(b, :pending_operations),
where: pending_ops.fetch_internal_transactions,
where: b.consensus, where: b.consensus,
where: is_nil(b.internal_transactions_indexed_at), select: b.number
where: not b.refetch_needed,
select: ^fields
) )
Repo.stream_reduce(query, initial, reducer) Repo.stream_reduce(query, initial, reducer)
end end
@doc """ def remove_nonconsensus_blocks_from_pending_ops(block_hashes) do
Returns a stream of all collated transactions with unfetched internal transactions. query =
from(
po in PendingBlockOperation,
where: po.block_hash in ^block_hashes
)
Only transactions that have been collated into a block are returned; pending transactions not in a block are filtered {_, _} = Repo.delete_all(query)
out.
iex> pending = insert(:transaction) :ok
iex> unfetched_collated = end
...> :transaction |>
...> insert() |>
...> with_block()
iex> fetched_collated =
...> :transaction |>
...> insert() |>
...> with_block(internal_transactions_indexed_at: DateTime.utc_now())
iex> {:ok, hash_set} = Explorer.Chain.stream_transactions_with_unfetched_internal_transactions(
...> [:hash],
...> MapSet.new(),
...> fn %Explorer.Chain.Transaction{hash: hash}, acc ->
...> MapSet.put(acc, hash)
...> end
...> )
iex> pending.hash in hash_set
false
iex> unfetched_collated.hash in hash_set
true
iex> fetched_collated.hash in hash_set
false
""" def remove_nonconsensus_blocks_from_pending_ops do
@spec stream_transactions_with_unfetched_internal_transactions(
fields :: [
:block_hash
| :internal_transactions_indexed_at
| :from_address_hash
| :gas
| :gas_price
| :hash
| :index
| :input
| :nonce
| :r
| :s
| :to_address_hash
| :v
| :value
],
initial :: accumulator,
reducer :: (entry :: term(), accumulator -> accumulator)
) :: {:ok, accumulator}
when accumulator: term()
def stream_transactions_with_unfetched_internal_transactions(fields, initial, reducer) when is_function(reducer, 2) do
query = query =
from( from(
t in Transaction, po in PendingBlockOperation,
# exclude pending transactions and replaced transactions inner_join: block in Block,
where: not is_nil(t.block_hash) and is_nil(t.internal_transactions_indexed_at), on: block.hash == po.block_hash,
select: ^fields where: block.consensus == false
) )
Repo.stream_reduce(query, initial, reducer) {_, _} = Repo.delete_all(query)
:ok
end end
@spec stream_transactions_with_unfetched_created_contract_codes( @spec stream_transactions_with_unfetched_created_contract_codes(
fields :: [ fields :: [
:block_hash :block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at | :created_contract_code_indexed_at
| :from_address_hash | :from_address_hash
| :gas | :gas
@ -1802,7 +1750,6 @@ defmodule Explorer.Chain do
@spec stream_mined_transactions( @spec stream_mined_transactions(
fields :: [ fields :: [
:block_hash :block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at | :created_contract_code_indexed_at
| :from_address_hash | :from_address_hash
| :gas | :gas
@ -1834,7 +1781,6 @@ defmodule Explorer.Chain do
@spec stream_pending_transactions( @spec stream_pending_transactions(
fields :: [ fields :: [
:block_hash :block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at | :created_contract_code_indexed_at
| :from_address_hash | :from_address_hash
| :gas | :gas
@ -2216,8 +2162,8 @@ defmodule Explorer.Chain do
## Options ## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. then the `t:Explorer.Chain.Transaction.t/0` will not be included in the list.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and
`:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than
the `block_number` and `index` that are passed. the `block_number` and `index` that are passed.
@ -2271,8 +2217,8 @@ defmodule Explorer.Chain do
## Options ## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
`:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. then the `t:Explorer.Chain.Transaction.t/0` will not be included in the list.
* `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` (defaults to * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` (defaults to
`#{@default_paging_options.page_size}`) and `:key` (a tuple of the lowest/oldest `{inserted_at, hash}`) and. `#{@default_paging_options.page_size}`) and `:key` (a tuple of the lowest/oldest `{inserted_at, hash}`) and.
Results will be the transactions older than the `inserted_at` and `hash` that are passed. Results will be the transactions older than the `inserted_at` and `hash` that are passed.
@ -2452,6 +2398,7 @@ defmodule Explorer.Chain do
|> for_parent_transaction(hash) |> for_parent_transaction(hash)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> where_transaction_has_multiple_internal_transactions() |> where_transaction_has_multiple_internal_transactions()
|> InternalTransaction.where_nonpending_block()
|> page_internal_transaction(paging_options) |> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> order_by([internal_transaction], asc: internal_transaction.index) |> order_by([internal_transaction], asc: internal_transaction.index)
@ -2477,8 +2424,15 @@ defmodule Explorer.Chain do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Log log_with_transactions =
|> join(:inner, [log], transaction in assoc(log, :transaction)) from(log in Log,
inner_join: transaction in Transaction,
on:
transaction.block_hash == log.block_hash and transaction.block_number == log.block_number and
transaction.hash == log.transaction_hash
)
log_with_transactions
|> where([_, transaction], transaction.hash == ^transaction_hash) |> where([_, transaction], transaction.hash == ^transaction_hash)
|> page_logs(paging_options) |> page_logs(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
@ -2509,7 +2463,11 @@ defmodule Explorer.Chain do
TokenTransfer TokenTransfer
|> join(:inner, [token_transfer], transaction in assoc(token_transfer, :transaction)) |> join(:inner, [token_transfer], transaction in assoc(token_transfer, :transaction))
|> where([_, transaction], transaction.hash == ^transaction_hash) |> where(
[token_transfer, transaction],
transaction.hash == ^transaction_hash and token_transfer.block_hash == transaction.block_hash and
token_transfer.block_number == transaction.block_number
)
|> TokenTransfer.page_token_transfer(paging_options) |> TokenTransfer.page_token_transfer(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> order_by([token_transfer], asc: token_transfer.inserted_at) |> order_by([token_transfer], asc: token_transfer.inserted_at)
@ -2544,7 +2502,7 @@ defmodule Explorer.Chain do
def transaction_to_status(%Transaction{status: nil}), do: :awaiting_internal_transactions def transaction_to_status(%Transaction{status: nil}), do: :awaiting_internal_transactions
def transaction_to_status(%Transaction{status: :ok}), do: :success def transaction_to_status(%Transaction{status: :ok}), do: :success
def transaction_to_status(%Transaction{status: :error, internal_transactions_indexed_at: nil, error: nil}), def transaction_to_status(%Transaction{status: :error, error: nil}),
do: {:error, :awaiting_internal_transactions} do: {:error, :awaiting_internal_transactions}
def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error} def transaction_to_status(%Transaction{status: :error, error: error}) when is_binary(error), do: {:error, error}

@ -7,10 +7,10 @@ defmodule Explorer.Chain.Block do
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain.{Address, Gas, Hash, Transaction} alias Explorer.Chain.{Address, Gas, Hash, PendingBlockOperation, Transaction}
alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation}
@optional_attrs ~w(internal_transactions_indexed_at size refetch_needed total_difficulty difficulty)a @optional_attrs ~w(size refetch_needed total_difficulty difficulty)a
@required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a @required_attrs ~w(consensus gas_limit gas_used hash miner_hash nonce number parent_hash timestamp)a
@ -46,7 +46,6 @@ defmodule Explorer.Chain.Block do
* `timestamp` - When the block was collated * `timestamp` - When the block was collated
* `total_difficulty` - the total `difficulty` of the chain until this block. * `total_difficulty` - the total `difficulty` of the chain until this block.
* `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block.
* `internal_transactions_indexed_at` - when `internal_transactions` were fetched by `Indexer`.
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
consensus: boolean(), consensus: boolean(),
@ -63,7 +62,6 @@ defmodule Explorer.Chain.Block do
timestamp: DateTime.t(), timestamp: DateTime.t(),
total_difficulty: difficulty(), total_difficulty: difficulty(),
transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()],
internal_transactions_indexed_at: DateTime.t(),
refetch_needed: boolean() refetch_needed: boolean()
} }
@ -78,7 +76,6 @@ defmodule Explorer.Chain.Block do
field(:size, :integer) field(:size, :integer)
field(:timestamp, :utc_datetime_usec) field(:timestamp, :utc_datetime_usec)
field(:total_difficulty, :decimal) field(:total_difficulty, :decimal)
field(:internal_transactions_indexed_at, :utc_datetime_usec)
field(:refetch_needed, :boolean) field(:refetch_needed, :boolean)
timestamps() timestamps()
@ -97,6 +94,8 @@ defmodule Explorer.Chain.Block do
has_many(:transaction_forks, Transaction.Fork, foreign_key: :uncle_hash) has_many(:transaction_forks, Transaction.Fork, foreign_key: :uncle_hash)
has_many(:rewards, Reward, foreign_key: :block_hash) has_many(:rewards, Reward, foreign_key: :block_hash)
has_one(:pending_operations, PendingBlockOperation, foreign_key: :block_hash)
end end
def changeset(%__MODULE__{} = block, attrs) do def changeset(%__MODULE__{} = block, attrs) do

@ -12,7 +12,8 @@ defmodule Explorer.Chain.Import do
Import.Stage.Addresses, Import.Stage.Addresses,
Import.Stage.AddressReferencing, Import.Stage.AddressReferencing,
Import.Stage.BlockReferencing, Import.Stage.BlockReferencing,
Import.Stage.BlockFollowing Import.Stage.BlockFollowing,
Import.Stage.BlockPending
] ]
# in order so that foreign keys are inserted before being referenced # in order so that foreign keys are inserted before being referenced

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
import Ecto.Query, only: [from: 2, subquery: 1] import Ecto.Query, only: [from: 2, subquery: 1]
alias Ecto.{Changeset, Multi, Repo} alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Address, Block, Import, InternalTransaction, Log, TokenTransfer, Transaction} alias Explorer.Chain.{Address, Block, Import, PendingBlockOperation, Transaction}
alias Explorer.Chain.Block.Reward alias Explorer.Chain.Block.Reward
alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner
alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances
@ -56,6 +56,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
# Note, needs to be executed after `lose_consensus` for lock acquisition # Note, needs to be executed after `lose_consensus` for lock acquisition
insert(repo, changes_list, insert_options) insert(repo, changes_list, insert_options)
end) end)
|> Multi.run(:new_pending_operations, fn repo, %{lose_consensus: nonconsensus_hashes} ->
new_pending_operations(repo, nonconsensus_hashes, hashes, insert_options)
end)
|> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, _ -> |> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, _ ->
update_block_second_degree_relations(repo, hashes, %{ update_block_second_degree_relations(repo, hashes, %{
timeout: timeout:
@ -83,18 +86,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
transactions: transactions transactions: transactions
}) })
end) end)
|> Multi.run(:remove_nonconsensus_logs, fn repo, %{derive_transaction_forks: transactions} ->
remove_nonconsensus_logs(repo, transactions, insert_options)
end)
|> Multi.run(:remove_nonconsensus_internal_transactions, fn repo, %{derive_transaction_forks: transactions} ->
remove_nonconsensus_internal_transactions(repo, transactions, insert_options)
end)
|> Multi.run(:acquire_contract_address_tokens, fn repo, _ -> |> Multi.run(:acquire_contract_address_tokens, fn repo, _ ->
acquire_contract_address_tokens(repo, consensus_block_numbers) acquire_contract_address_tokens(repo, consensus_block_numbers)
end) end)
|> Multi.run(:remove_nonconsensus_token_transfers, fn repo, %{derive_transaction_forks: transactions} ->
remove_nonconsensus_token_transfers(repo, transactions, insert_options)
end)
|> Multi.run(:delete_address_token_balances, fn repo, _ -> |> Multi.run(:delete_address_token_balances, fn repo, _ ->
delete_address_token_balances(repo, consensus_block_numbers, insert_options) delete_address_token_balances(repo, consensus_block_numbers, insert_options)
end) end)
@ -160,7 +154,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
gas_used: nil, gas_used: nil,
cumulative_gas_used: nil, cumulative_gas_used: nil,
index: nil, index: nil,
internal_transactions_indexed_at: nil,
status: nil, status: nil,
error: nil, error: nil,
updated_at: ^updated_at updated_at: ^updated_at
@ -251,7 +244,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
difficulty: fragment("EXCLUDED.difficulty"), difficulty: fragment("EXCLUDED.difficulty"),
gas_limit: fragment("EXCLUDED.gas_limit"), gas_limit: fragment("EXCLUDED.gas_limit"),
gas_used: fragment("EXCLUDED.gas_used"), gas_used: fragment("EXCLUDED.gas_used"),
internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"),
miner_hash: fragment("EXCLUDED.miner_hash"), miner_hash: fragment("EXCLUDED.miner_hash"),
nonce: fragment("EXCLUDED.nonce"), nonce: fragment("EXCLUDED.nonce"),
number: fragment("EXCLUDED.number"), number: fragment("EXCLUDED.number"),
@ -270,8 +262,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
fragment("EXCLUDED.miner_hash <> ?", block.miner_hash) or fragment("EXCLUDED.nonce <> ?", block.nonce) or fragment("EXCLUDED.miner_hash <> ?", block.miner_hash) or fragment("EXCLUDED.nonce <> ?", block.nonce) or
fragment("EXCLUDED.number <> ?", block.number) or fragment("EXCLUDED.parent_hash <> ?", block.parent_hash) or fragment("EXCLUDED.number <> ?", block.number) or fragment("EXCLUDED.parent_hash <> ?", block.parent_hash) or
fragment("EXCLUDED.size <> ?", block.size) or fragment("EXCLUDED.timestamp <> ?", block.timestamp) or fragment("EXCLUDED.size <> ?", block.size) or fragment("EXCLUDED.timestamp <> ?", block.timestamp) or
fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty) or fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty)
fragment("EXCLUDED.internal_transactions_indexed_at <> ?", block.internal_transactions_indexed_at)
) )
end end
@ -317,92 +308,26 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:error, %{exception: postgrex_error, consensus_block_numbers: consensus_block_numbers}} {:error, %{exception: postgrex_error, consensus_block_numbers: consensus_block_numbers}}
end end
defp remove_nonconsensus_token_transfers(repo, forked_transaction_hashes, %{timeout: timeout}) do defp new_pending_operations(repo, nonconsensus_hashes, hashes, %{timeout: timeout, timestamps: timestamps}) do
ordered_token_transfers = sorted_pending_ops =
from( nonconsensus_hashes
token_transfer in TokenTransfer, |> MapSet.new()
where: token_transfer.transaction_hash in ^forked_transaction_hashes, |> MapSet.union(MapSet.new(hashes))
select: token_transfer.transaction_hash, |> Enum.sort()
# Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md) |> Enum.map(fn hash ->
order_by: [ %{block_hash: hash, fetch_internal_transactions: true}
token_transfer.transaction_hash, end)
token_transfer.log_index
],
lock: "FOR UPDATE"
)
query =
from(token_transfer in TokenTransfer,
select: map(token_transfer, [:transaction_hash, :log_index]),
inner_join: ordered_token_transfer in subquery(ordered_token_transfers),
on: ordered_token_transfer.transaction_hash == token_transfer.transaction_hash
)
{_count, deleted_token_transfers} = repo.delete_all(query, timeout: timeout)
{:ok, deleted_token_transfers}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, transactions: forked_transaction_hashes}}
end
defp remove_nonconsensus_internal_transactions(repo, forked_transaction_hashes, %{timeout: timeout}) do
query =
from(
internal_transaction in InternalTransaction,
where: internal_transaction.transaction_hash in ^forked_transaction_hashes,
select: %{transaction_hash: internal_transaction.transaction_hash},
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
order_by: [
internal_transaction.transaction_hash,
internal_transaction.index
],
lock: "FOR UPDATE"
)
delete_query =
from(
i in InternalTransaction,
join: s in subquery(query),
on: i.transaction_hash == s.transaction_hash,
select: map(i, [:transaction_hash, :index])
)
{_count, deleted_internal_transactions} = repo.delete_all(delete_query, timeout: timeout)
{:ok, deleted_internal_transactions}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, transactions: forked_transaction_hashes}}
end
defp remove_nonconsensus_logs(repo, forked_transaction_hashes, %{timeout: timeout}) do
ordered_logs =
from(
log in Log,
where: log.transaction_hash in ^forked_transaction_hashes,
select: log.transaction_hash,
# Enforce Log ShareLocks order (see docs: sharelocks.md)
order_by: [
log.transaction_hash,
log.index
],
lock: "FOR UPDATE"
)
query = Import.insert_changes_list(
from(log in Log, repo,
select: map(log, [:transaction_hash, :index]), sorted_pending_ops,
inner_join: ordered_log in subquery(ordered_logs), conflict_target: :block_hash,
on: ordered_log.transaction_hash == log.transaction_hash on_conflict: PendingBlockOperation.default_on_conflict(),
for: PendingBlockOperation,
returning: true,
timeout: timeout,
timestamps: timestamps
) )
{_count, deleted_logs} = repo.delete_all(query, timeout: timeout)
{:ok, deleted_logs}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, transactions: forked_transaction_hashes}}
end end
defp delete_address_token_balances(_, [], _), do: {:ok, []} defp delete_address_token_balances(_, [], _), do: {:ok, []}

@ -6,20 +6,19 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
require Ecto.Query require Ecto.Query
require Logger require Logger
alias Ecto.Adapters.SQL
alias Ecto.{Changeset, Multi, Repo} alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, Transaction} alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction}
alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2, or_where: 3]
@behaviour Runner @behaviour Runner
# milliseconds # milliseconds
@timeout 60_000 @timeout 60_000
@type imported :: [ @type imported :: [InternalTransaction.t()]
%{required(:index) => non_neg_integer(), required(:transaction_hash) => Hash.Full.t()}
]
@impl Runner @impl Runner
def ecto_schema_module, do: InternalTransaction def ecto_schema_module, do: InternalTransaction
@ -48,54 +47,73 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
update_transactions_options = %{timeout: transactions_timeout, timestamps: timestamps} update_transactions_options = %{timeout: transactions_timeout, timestamps: timestamps}
# filter out params with just `block_number` (indicating blocks without internal transactions)
internal_transactions_params = Enum.filter(changes_list, &Map.has_key?(&1, :type))
# Enforce ShareLocks tables order (see docs: sharelocks.md) # Enforce ShareLocks tables order (see docs: sharelocks.md)
multi multi
|> Multi.run(:acquire_transactions, fn repo, _ -> |> Multi.run(:acquire_blocks, fn repo, _ ->
acquire_transactions(repo, changes_list) acquire_blocks(repo, changes_list)
end) end)
|> Multi.run(:internal_transactions, fn repo, %{acquire_transactions: transactions} -> |> Multi.run(:acquire_pending_internal_txs, fn repo, %{acquire_blocks: block_hashes} ->
insert(repo, changes_list, transactions, insert_options) acquire_pending_internal_txs(repo, block_hashes)
end) end)
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, %{acquire_transactions: transactions} -> |> Multi.run(:acquire_transactions, fn repo, %{acquire_pending_internal_txs: pending_block_hashes} ->
update_transactions(repo, transactions, update_transactions_options) acquire_transactions(repo, pending_block_hashes)
end)
|> Multi.run(:invalid_block_numbers, fn _, %{acquire_transactions: transactions} ->
invalid_block_numbers(transactions, internal_transactions_params)
end)
|> Multi.run(:valid_internal_transactions, fn _,
%{
acquire_transactions: transactions,
invalid_block_numbers: invalid_block_numbers
} ->
valid_internal_transactions(transactions, internal_transactions_params, invalid_block_numbers)
end)
|> Multi.run(:remove_left_over_internal_transactions, fn repo,
%{valid_internal_transactions: valid_internal_transactions} ->
remove_left_over_internal_transactions(repo, valid_internal_transactions)
end)
|> Multi.run(:internal_transactions, fn repo, %{valid_internal_transactions: valid_internal_transactions} ->
insert(repo, valid_internal_transactions, insert_options)
end)
|> Multi.run(:update_transactions, fn repo, %{valid_internal_transactions: valid_internal_transactions} ->
update_transactions(repo, valid_internal_transactions, update_transactions_options)
end)
|> Multi.run(:remove_consensus_of_invalid_blocks, fn repo, %{invalid_block_numbers: invalid_block_numbers} ->
remove_consensus_of_invalid_blocks(repo, invalid_block_numbers)
end)
|> Multi.run(:update_pending_blocks_status, fn repo,
%{
acquire_pending_internal_txs: pending_block_hashes,
remove_consensus_of_invalid_blocks: invalid_block_hashes
} ->
update_pending_blocks_status(repo, pending_block_hashes, invalid_block_hashes)
end) end)
|> Multi.run(
:remove_consensus_of_missing_transactions_blocks,
fn repo, %{internal_transactions: inserted} = results_map ->
# NOTE: for this to work it has to follow the runner `internal_transactions_indexed_at_blocks`
block_hashes = Map.get(results_map, :internal_transactions_indexed_at_blocks, [])
remove_consensus_of_missing_transactions_blocks(repo, block_hashes, changes_list, inserted)
end
)
end end
@impl Runner @impl Runner
def timeout, do: @timeout def timeout, do: @timeout
@spec insert(Repo.t(), [map], [Transaction.t()], %{ @spec insert(Repo.t(), [map], %{
optional(:on_conflict) => Runner.on_conflict(), optional(:on_conflict) => Runner.on_conflict(),
required(:timeout) => timeout, required(:timeout) => timeout,
required(:timestamps) => Import.timestamps() required(:timestamps) => Import.timestamps()
}) :: }) ::
{:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]} {:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]}
| {:error, [Changeset.t()]} | {:error, [Changeset.t()]}
defp insert(repo, changes_list, transactions, %{timeout: timeout, timestamps: timestamps} = options) defp insert(repo, valid_internal_transactions, %{timeout: timeout, timestamps: timestamps} = options)
when is_list(changes_list) do when is_list(valid_internal_transactions) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
transactions_map = Map.new(transactions, &{&1.hash, &1}) ordered_changes_list = Enum.sort_by(valid_internal_transactions, &{&1.transaction_hash, &1.index})
final_changes_list =
changes_list
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
|> Enum.sort_by(&{&1.transaction_hash, &1.index})
|> reject_missing_transactions(transactions_map)
{:ok, internal_transactions} = {:ok, internal_transactions} =
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
final_changes_list, ordered_changes_list,
conflict_target: [:transaction_hash, :index], conflict_target: [:block_hash, :block_index],
for: InternalTransaction, for: InternalTransaction,
on_conflict: on_conflict, on_conflict: on_conflict,
returning: true, returning: true,
@ -119,24 +137,28 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
from_address_hash: fragment("EXCLUDED.from_address_hash"), from_address_hash: fragment("EXCLUDED.from_address_hash"),
gas: fragment("EXCLUDED.gas"), gas: fragment("EXCLUDED.gas"),
gas_used: fragment("EXCLUDED.gas_used"), 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 index: fragment("EXCLUDED.index"),
init: fragment("EXCLUDED.init"), init: fragment("EXCLUDED.init"),
input: fragment("EXCLUDED.input"), input: fragment("EXCLUDED.input"),
output: fragment("EXCLUDED.output"), output: fragment("EXCLUDED.output"),
to_address_hash: fragment("EXCLUDED.to_address_hash"), to_address_hash: fragment("EXCLUDED.to_address_hash"),
trace_address: fragment("EXCLUDED.trace_address"), 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_hash: fragment("EXCLUDED.transaction_hash"),
transaction_index: fragment("EXCLUDED.transaction_index"), transaction_index: fragment("EXCLUDED.transaction_index"),
type: fragment("EXCLUDED.type"), type: fragment("EXCLUDED.type"),
value: fragment("EXCLUDED.value"), value: fragment("EXCLUDED.value"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", internal_transaction.inserted_at), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", internal_transaction.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", internal_transaction.updated_at) updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", internal_transaction.updated_at)
# Don't update `block_hash` as it is used for the conflict target
# Don't update `block_index` as it is used for the conflict target
] ]
], ],
# `IS DISTINCT FROM` is used because it allows `NULL` to be equal to itself # `IS DISTINCT FROM` is used because it allows `NULL` to be equal to itself
where: where:
fragment( fragment(
"(EXCLUDED.call_type, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code, EXCLUDED.error, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_used, EXCLUDED.init, EXCLUDED.input, EXCLUDED.output, EXCLUDED.to_address_hash, EXCLUDED.trace_address, EXCLUDED.transaction_index, EXCLUDED.type, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "(EXCLUDED.transaction_hash, EXCLUDED.index, EXCLUDED.call_type, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code, EXCLUDED.error, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_used, EXCLUDED.init, EXCLUDED.input, EXCLUDED.output, EXCLUDED.to_address_hash, EXCLUDED.trace_address, EXCLUDED.transaction_index, EXCLUDED.type, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
internal_transaction.transaction_hash,
internal_transaction.index,
internal_transaction.call_type, internal_transaction.call_type,
internal_transaction.created_contract_address_hash, internal_transaction.created_contract_address_hash,
internal_transaction.created_contract_code, internal_transaction.created_contract_code,
@ -156,18 +178,42 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
) )
end end
defp acquire_transactions(repo, internal_transactions) do defp acquire_blocks(repo, changes_list) do
transaction_hashes = block_numbers = Enum.map(changes_list, & &1.block_number)
internal_transactions
|> MapSet.new(& &1.transaction_hash) query =
|> MapSet.to_list() from(
b in Block,
where: b.number in ^block_numbers and b.consensus,
select: b.hash,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: b.hash],
lock: "FOR UPDATE"
)
{:ok, repo.all(query)}
end
defp acquire_pending_internal_txs(repo, block_hashes) do
query =
from(
pending_ops in PendingBlockOperation,
where: pending_ops.block_hash in ^block_hashes,
where: pending_ops.fetch_internal_transactions,
select: pending_ops.block_hash,
# Enforce PendingBlockOperation ShareLocks order (see docs: sharelocks.md)
order_by: [asc: pending_ops.block_hash],
lock: "FOR UPDATE"
)
{:ok, repo.all(query)}
end
defp acquire_transactions(repo, pending_block_hashes) do
query = query =
from( from(
t in Transaction, t in Transaction,
where: t.hash in ^transaction_hashes, where: t.block_hash in ^pending_block_hashes,
# do not consider pending transactions
where: not is_nil(t.block_hash),
select: map(t, [:hash, :block_hash, :block_number]), select: map(t, [:hash, :block_hash, :block_number]),
# Enforce Transaction ShareLocks order (see docs: sharelocks.md) # Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: t.hash, order_by: t.hash,
@ -177,22 +223,115 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
{:ok, repo.all(query)} {:ok, repo.all(query)}
end end
defp update_transactions(repo, transactions, %{ defp invalid_block_numbers(transactions, internal_transactions_params) do
# Finds all mistmatches between transactions and internal transactions
# for a block number:
# - there are no internal txs for some transactions
# - there are no transactions for some internal transactions
# - there are internal txs with a different block number than their transactions
# Returns block numbers where any of these issues is found
required_tuples = MapSet.new(transactions, &{&1.hash, &1.block_number})
candidate_tuples = MapSet.new(internal_transactions_params, &{&1.transaction_hash, &1.block_number})
all_tuples = MapSet.union(required_tuples, candidate_tuples)
common_tuples = MapSet.intersection(required_tuples, candidate_tuples)
invalid_numbers =
all_tuples
|> MapSet.difference(common_tuples)
|> MapSet.new(fn {_hash, block_number} -> block_number end)
|> MapSet.to_list()
{:ok, invalid_numbers}
end
defp valid_internal_transactions(transactions, internal_transactions_params, invalid_block_numbers) do
blocks_map = Map.new(transactions, &{&1.block_number, &1.block_hash})
valid_internal_txs =
internal_transactions_params
|> Enum.group_by(& &1.block_number)
|> Map.drop(invalid_block_numbers)
|> Enum.flat_map(fn {block_number, entries} ->
block_hash = Map.fetch!(blocks_map, block_number)
entries
|> Enum.sort_by(&{&1.transaction_hash, &1.index})
|> Enum.with_index()
|> Enum.map(fn {entry, index} ->
entry
|> Map.put(:block_hash, block_hash)
|> Map.put(:block_index, index)
end)
end)
{:ok, valid_internal_txs}
end
def defer_internal_transactions_primary_key(repo) do
# Allows internal_transactions primary key to not be checked during the
# DB transactions and instead be checked only at the end of it.
# This allows us to use a more efficient upserting logic, while keeping the
# uniqueness valid.
SQL.query(repo, "SET CONSTRAINTS internal_transactions_pkey DEFERRED")
end
def remove_left_over_internal_transactions(repo, valid_internal_transactions) do
# Removes internal transactions that were part of a block before a refetch
# and have not been upserted with new ones (if any exist).
case valid_internal_transactions do
[] ->
{:ok, []}
_ ->
try do
delete_query_for_block_hash_block_index =
valid_internal_transactions
|> Enum.group_by(& &1.block_hash, & &1.block_index)
|> Enum.map(fn {block_hash, indexes} -> {block_hash, Enum.max(indexes)} end)
|> Enum.reduce(InternalTransaction, fn {block_hash, max_index}, acc ->
or_where(acc, [it], it.block_hash == ^block_hash and it.block_index > ^max_index)
end)
# removes old recoreds with the same primary key (transaction hash, transaction index)
delete_query =
valid_internal_transactions
|> Enum.map(fn params -> {params.transaction_hash, params.index} end)
|> Enum.reduce(delete_query_for_block_hash_block_index, fn {transaction_hash, index}, acc ->
or_where(acc, [it], it.transaction_hash == ^transaction_hash and it.index == ^index)
end)
# ShareLocks order already enforced by `acquire_pending_internal_txs` (see docs: sharelocks.md)
{count, result} = repo.delete_all(delete_query, [])
{:ok, {count, result}}
rescue
postgrex_error in Postgrex.Error -> {:error, %{exception: postgrex_error}}
end
end
end
defp update_transactions(repo, valid_internal_transactions, %{
timeout: timeout, timeout: timeout,
timestamps: timestamps timestamps: timestamps
}) })
when is_list(transactions) do when is_list(valid_internal_transactions) do
transaction_hashes = Enum.map(transactions, & &1.hash) transaction_hashes =
valid_internal_transactions
|> MapSet.new(& &1.transaction_hash)
|> MapSet.to_list()
update_query = update_query =
from( from(
t in Transaction, t in Transaction,
# pending transactions are already excluded by `acquire_transactions`
where: t.hash in ^transaction_hashes, where: t.hash in ^transaction_hashes,
# ShareLocks order already enforced by `acquire_transactions` (see docs: sharelocks.md) # ShareLocks order already enforced by `acquire_transactions` (see docs: sharelocks.md)
update: [ update: [
set: [ set: [
internal_transactions_indexed_at: ^timestamps.updated_at,
created_contract_address_hash: created_contract_address_hash:
fragment( fragment(
"(SELECT it.created_contract_address_hash FROM internal_transactions AS it WHERE it.transaction_hash = ? ORDER BY it.index ASC LIMIT 1)", "(SELECT it.created_contract_address_hash FROM internal_transactions AS it WHERE it.transaction_hash = ? ORDER BY it.index ASC LIMIT 1)",
@ -209,7 +348,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
t.hash, t.hash,
type(^:ok, t.status), type(^:ok, t.status),
type(^:error, t.status) type(^:error, t.status)
) ),
updated_at: ^timestamps.updated_at
] ]
] ]
) )
@ -224,26 +364,14 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end end
end end
# If not using Parity this is not relevant defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do
defp remove_consensus_of_missing_transactions_blocks(_, [], _, _), do: {:ok, []}
defp remove_consensus_of_missing_transactions_blocks(repo, block_hashes, changes_list, inserted) do
inserted_block_numbers = MapSet.new(inserted, & &1.block_number)
missing_transactions_block_numbers =
changes_list
|> MapSet.new(& &1.block_number)
|> MapSet.difference(inserted_block_numbers)
|> MapSet.to_list()
update_query = update_query =
from( from(
b in Block, b in Block,
where: b.number in ^missing_transactions_block_numbers, where: b.number in ^invalid_block_numbers and b.consensus,
where: b.hash in ^block_hashes, select: b.hash,
select: b.number, # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md)
# ShareLocks order already enforced by `internal_transactions_indexed_at_blocks` (see docs: sharelocks.md) update: [set: [consensus: false]]
update: [set: [consensus: false, internal_transactions_indexed_at: nil]]
) )
try do try do
@ -252,24 +380,39 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
Logger.debug(fn -> Logger.debug(fn ->
[ [
"consensus removed from blocks with numbers: ", "consensus removed from blocks with numbers: ",
inspect(missing_transactions_block_numbers), inspect(invalid_block_numbers),
" because of missing transactions" " because of mismatching transactions"
] ]
end) end)
{:ok, result} {:ok, result}
rescue rescue
postgrex_error in Postgrex.Error -> postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, missing_transactions_block_numbers: missing_transactions_block_numbers}} {:error, %{exception: postgrex_error, invalid_block_numbers: invalid_block_numbers}}
end end
end end
defp reject_missing_transactions(ordered_changes_list, transactions_map) do def update_pending_blocks_status(repo, pending_hashes, invalid_block_hashes) do
Enum.reject(ordered_changes_list, fn %{transaction_hash: hash} -> valid_block_hashes =
transactions_map pending_hashes
|> Map.get(hash, %{}) |> MapSet.new()
|> Map.get(:block_hash) |> MapSet.difference(MapSet.new(invalid_block_hashes))
|> is_nil() |> MapSet.to_list()
end)
delete_query =
from(
pending_ops in PendingBlockOperation,
where: pending_ops.block_hash in ^valid_block_hashes
)
try do
# ShreLocks order already enforced by `acquire_pending_internal_txs` (see docs: sharelocks.md)
{_count, deleted} = repo.delete_all(delete_query, [])
{:ok, deleted}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, pending_hashes: valid_block_hashes}}
end
end end
end end

@ -1,82 +0,0 @@
defmodule Explorer.Chain.Import.Runner.InternalTransactionsIndexedAtBlocks do
@moduledoc """
Bulk updates `internal_transactions_indexed_at` for provided blocks
"""
require Ecto.Query
alias Ecto.Multi
alias Explorer.Chain.Block
alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2]
@behaviour Runner
# milliseconds
@timeout 60_000
@type imported :: [%{number: Block.block_number()}]
@impl Runner
def ecto_schema_module, do: Block
@impl Runner
def option_key, do: :internal_transactions_indexed_at_blocks
@impl Runner
def imported_table_row do
%{
value_type: "[%{number: Explorer.Chain.Block.block_number()}]",
value_description: "List of block numbers to set `internal_transactions_indexed_at` field for"
}
end
@impl Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) when is_map(options) do
transactions_timeout = options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout()
update_transactions_options = %{timeout: transactions_timeout, timestamps: timestamps}
multi
|> Multi.run(:internal_transactions_indexed_at_blocks, fn repo, _ ->
update_blocks(repo, changes_list, update_transactions_options)
end)
end
@impl Runner
def timeout, do: @timeout
defp update_blocks(_repo, [], %{}), do: {:ok, []}
defp update_blocks(repo, changes_list, %{
timeout: timeout,
timestamps: timestamps
})
when is_list(changes_list) do
block_numbers = Enum.map(changes_list, fn %{number: number} -> number end)
query =
from(
b in Block,
where: b.number in ^block_numbers and b.consensus,
# Enforce Block ShareLocks order (see docs: sharelocks.md)
order_by: [asc: b.hash],
lock: "FOR UPDATE"
)
try do
{_, result} =
repo.update_all(
from(b in Block, join: s in subquery(query), on: b.hash == s.hash, select: b.hash),
[set: [internal_transactions_indexed_at: timestamps.updated_at]],
timeout: timeout
)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, block_numbers: block_numbers}}
end
end
end

@ -59,13 +59,13 @@ defmodule Explorer.Chain.Import.Runner.Logs do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
# Enforce Log ShareLocks order (see docs: sharelocks.md) # Enforce Log ShareLocks order (see docs: sharelocks.md)
ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.block_hash, &1.index})
{:ok, _} = {:ok, _} =
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
ordered_changes_list, ordered_changes_list,
conflict_target: [:transaction_hash, :index], conflict_target: [:transaction_hash, :index, :block_hash],
on_conflict: on_conflict, on_conflict: on_conflict,
for: Log, for: Log,
returning: true, returning: true,

@ -55,13 +55,13 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
# Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md) # Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md)
ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.log_index}) ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.block_hash, &1.log_index})
{:ok, _} = {:ok, _} =
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
ordered_changes_list, ordered_changes_list,
conflict_target: [:transaction_hash, :log_index], conflict_target: [:transaction_hash, :log_index, :block_hash],
on_conflict: on_conflict, on_conflict: on_conflict,
for: TokenTransfer, for: TokenTransfer,
returning: true, returning: true,

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2]
alias Ecto.{Multi, Repo} alias Ecto.{Multi, Repo}
alias Explorer.Chain.{Block, Data, Hash, Import, Transaction} alias Explorer.Chain.{Block, Hash, Import, Transaction}
alias Explorer.Chain.Import.Runner.TokenTransfers alias Explorer.Chain.Import.Runner.TokenTransfers
@behaviour Import.Runner @behaviour Import.Runner
@ -72,18 +72,14 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
changes_list, changes_list,
%{ %{
timeout: timeout, timeout: timeout,
timestamps: %{inserted_at: inserted_at} = timestamps, timestamps: timestamps
token_transfer_transaction_hash_set: token_transfer_transaction_hash_set
} = options } = options
) )
when is_list(changes_list) do when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
ordered_changes_list =
changes_list
|> put_internal_transactions_indexed_at(inserted_at, token_transfer_transaction_hash_set)
# Enforce Transaction ShareLocks order (see docs: sharelocks.md) # Enforce Transaction ShareLocks order (see docs: sharelocks.md)
|> Enum.sort_by(& &1.hash) ordered_changes_list = Enum.sort_by(changes_list, & &1.hash)
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
@ -114,7 +110,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
gas_price: fragment("EXCLUDED.gas_price"), gas_price: fragment("EXCLUDED.gas_price"),
gas_used: fragment("EXCLUDED.gas_used"), gas_used: fragment("EXCLUDED.gas_used"),
index: fragment("EXCLUDED.index"), index: fragment("EXCLUDED.index"),
internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"),
input: fragment("EXCLUDED.input"), input: fragment("EXCLUDED.input"),
nonce: fragment("EXCLUDED.nonce"), nonce: fragment("EXCLUDED.nonce"),
r: fragment("EXCLUDED.r"), r: fragment("EXCLUDED.r"),
@ -130,7 +125,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
], ],
where: where:
fragment( fragment(
"(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.internal_transactions_indexed_at, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
transaction.block_hash, transaction.block_hash,
transaction.block_number, transaction.block_number,
transaction.created_contract_address_hash, transaction.created_contract_address_hash,
@ -142,7 +137,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
transaction.gas_price, transaction.gas_price,
transaction.gas_used, transaction.gas_used,
transaction.index, transaction.index,
transaction.internal_transactions_indexed_at,
transaction.input, transaction.input,
transaction.nonce, transaction.nonce,
transaction.r, transaction.r,
@ -155,46 +149,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
) )
end end
defp put_internal_transactions_indexed_at(changes_list, timestamp, token_transfer_transaction_hash_set) do
if Application.get_env(:explorer, :index_internal_transactions_for_token_transfers) do
changes_list
else
do_put_internal_transactions_indexed_at(changes_list, timestamp, token_transfer_transaction_hash_set)
end
end
defp do_put_internal_transactions_indexed_at(changes_list, timestamp, token_transfer_transaction_hash_set)
when is_list(changes_list) do
Enum.map(changes_list, &put_internal_transactions_indexed_at(&1, timestamp, token_transfer_transaction_hash_set))
end
defp do_put_internal_transactions_indexed_at(%{hash: hash} = changes, timestamp, token_transfer_transaction_hash_set) do
token_transfer? = to_string(hash) in token_transfer_transaction_hash_set
if put_internal_transactions_indexed_at?(changes, token_transfer?) do
Map.put(changes, :internal_transactions_indexed_at, timestamp)
else
changes
end
end
# A post-Byzantium validated transaction will have a status and if it has no input, it is a value transfer only.
# Internal transactions are only needed when status is `:error` to set `error`.
defp put_internal_transactions_indexed_at?(%{status: :ok, input: %Data{bytes: <<>>}}, _), do: true
# A post-Byzantium validated transaction will have a status and if it transfers tokens, the token transfer is in the
# log and the internal transactions.
# `created_contract_address_hash` must be `nil` because if a contract is created the internal transactions are needed
# to get
defp put_internal_transactions_indexed_at?(%{status: :ok} = changes, true) do
case Map.fetch(changes, :created_contract_address_hash) do
{:ok, created_contract_address_hash} when not is_nil(created_contract_address_hash) -> false
:error -> true
end
end
defp put_internal_transactions_indexed_at?(_, _), do: false
defp discard_blocks_for_recollated_transactions(repo, changes_list, %{ defp discard_blocks_for_recollated_transactions(repo, changes_list, %{
timeout: timeout, timeout: timeout,
timestamps: %{updated_at: updated_at} timestamps: %{updated_at: updated_at}

@ -13,10 +13,8 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do
@impl Stage @impl Stage
def runners, def runners,
do: [ do: [
Runner.InternalTransactionsIndexedAtBlocks,
Runner.Block.SecondDegreeRelations, Runner.Block.SecondDegreeRelations,
Runner.Block.Rewards, Runner.Block.Rewards,
Runner.InternalTransactions,
Runner.Address.CurrentTokenBalances Runner.Address.CurrentTokenBalances
] ]

@ -0,0 +1,27 @@
defmodule Explorer.Chain.Import.Stage.BlockPending do
@moduledoc """
Imports any tables that uses `Explorer.Chain.PendingBlockOperation` to track
progress and cannot be imported at the same time as those imported by
`Explorer.Chain.Import.Stage.Addresses`,
`Explorer.Chain.Import.Stage.AddressReferencing` and
`Explorer.Chain.Import.Stage.BlockReferencing`
"""
alias Explorer.Chain.Import.{Runner, Stage}
@behaviour Stage
@impl Stage
def runners,
do: [
Runner.InternalTransactions
]
@impl Stage
def multis(runner_to_changes_list, options) do
{final_multi, final_remaining_runner_to_changes_list} =
Stage.single_multi(runners(), runner_to_changes_list, options)
{[final_multi], final_remaining_runner_to_changes_list}
end
end

@ -3,7 +3,7 @@ defmodule Explorer.Chain.InternalTransaction do
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain.{Address, Data, Gas, Hash, Transaction, Wei} alias Explorer.Chain.{Address, Block, Data, Gas, Hash, PendingBlockOperation, Transaction, Wei}
alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type} alias Explorer.Chain.InternalTransaction.{Action, CallType, Result, Type}
@typedoc """ @typedoc """
@ -22,11 +22,15 @@ defmodule Explorer.Chain.InternalTransaction do
* `to_address` - the sink of the `value` * `to_address` - the sink of the `value`
* `to_address_hash` - hash of the sink of the `value` * `to_address_hash` - hash of the sink of the `value`
* `trace_address` - list of traces * `trace_address` - list of traces
* `transaction` - transaction in which this transaction occurred * `transaction` - transaction in which this internal transaction occurred
* `transaction_hash` - foreign key for `transaction` * `transaction_hash` - foreign key for `transaction`
* `transaction_index` - the `t:Explorer.Chain.Transaction.t/0` `index` of `transaction` in `block_number`. * `transaction_index` - the `t:Explorer.Chain.Transaction.t/0` `index` of `transaction` in `block_number`.
* `type` - type of internal transaction * `type` - type of internal transaction
* `value` - value of transferred from `from_address` to `to_address` * `value` - value of transferred from `from_address` to `to_address`
* `block` - block in which this internal transaction occurred
* `block_hash` - foreign key for `block`
* `block_index` - the index of this internal transaction inside the `block`
* `pending_block` - `nil` if `block` has all its internal transactions fetched
""" """
@type t :: %__MODULE__{ @type t :: %__MODULE__{
block_number: Explorer.Chain.Block.block_number() | nil, block_number: Explorer.Chain.Block.block_number() | nil,
@ -50,7 +54,9 @@ defmodule Explorer.Chain.InternalTransaction do
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.t(), transaction_hash: Hash.t(),
transaction_index: Transaction.transaction_index() | nil, transaction_index: Transaction.transaction_index() | nil,
value: Wei.t() value: Wei.t(),
block_hash: Hash.Full.t(),
block_index: non_neg_integer()
} }
@primary_key false @primary_key false
@ -69,6 +75,7 @@ defmodule Explorer.Chain.InternalTransaction do
field(:value, Wei) field(:value, Wei)
field(:block_number, :integer) field(:block_number, :integer)
field(:transaction_index, :integer) field(:transaction_index, :integer)
field(:block_index, :integer)
timestamps() timestamps()
@ -102,6 +109,20 @@ defmodule Explorer.Chain.InternalTransaction do
references: :hash, references: :hash,
type: Hash.Full type: Hash.Full
) )
belongs_to(:block, Block,
foreign_key: :block_hash,
references: :hash,
type: Hash.Full
)
belongs_to(:pending_block, PendingBlockOperation,
foreign_key: :block_hash,
define_field: false,
references: :block_hash,
type: Hash.Full,
where: [fetch_internal_transactions: true]
)
end end
@doc """ @doc """
@ -125,7 +146,9 @@ defmodule Explorer.Chain.InternalTransaction do
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> type: "create", ...> type: "create",
...> value: 0, ...> value: 0,
...> block_number: 35 ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0
...> } ...> }
...> ) ...> )
iex> changeset.valid? iex> changeset.valid?
@ -165,6 +188,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create", ...> type: "create",
...> value: 0, ...> value: 0,
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0 ...> transaction_index: 0
...> } ...> }
iex> ) iex> )
@ -182,6 +207,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{}, ...> %Explorer.Chain.InternalTransaction{},
...> %{ ...> %{
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0, ...> transaction_index: 0,
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0, ...> index: 0,
@ -206,6 +233,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{}, ...> %Explorer.Chain.InternalTransaction{},
...> %{ ...> %{
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0, ...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0, ...> index: 0,
@ -230,6 +259,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{}, ...> %Explorer.Chain.InternalTransaction{},
...> %{ ...> %{
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0, ...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0, ...> index: 0,
@ -260,6 +291,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{}, ...> %Explorer.Chain.InternalTransaction{},
...> %{ ...> %{
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0, ...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0, ...> index: 0,
@ -300,6 +333,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create", ...> type: "create",
...> value: 0, ...> value: 0,
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0 ...> transaction_index: 0
...> } ...> }
iex> ) iex> )
@ -326,6 +361,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create", ...> type: "create",
...> value: 0, ...> value: 0,
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0 ...> transaction_index: 0
...> } ...> }
...> ) ...> )
@ -351,6 +388,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "selfdestruct", ...> type: "selfdestruct",
...> value: 0, ...> value: 0,
...> block_number: 35, ...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0 ...> transaction_index: 0
...> } ...> }
...> ) ...> )
@ -362,9 +401,34 @@ defmodule Explorer.Chain.InternalTransaction do
internal_transaction internal_transaction
|> cast(attrs, ~w(type)a) |> cast(attrs, ~w(type)a)
|> validate_required(~w(type)a) |> validate_required(~w(type)a)
|> validate_block_required(attrs)
|> type_changeset(attrs) |> type_changeset(attrs)
end end
@doc """
Accepts changes without `:type` but with `:block_number`, if `:type` is defined
works like `changeset`, except allowing `:block_hash` and `:block_index` to be undefined.
This is used because the `internal_transactions` runner can derive such values
on its own or use empty types to know that a block has no internal transactions.
"""
def blockless_changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do
changeset = cast(internal_transaction, attrs, ~w(type block_number)a)
if validate_required(changeset, ~w(type)a).valid? do
type_changeset(changeset, attrs)
else
validate_required(changeset, ~w(block_number)a)
end
end
defp validate_block_required(changeset, attrs) do
changeset
|> cast(attrs, ~w(block_hash block_index)a)
|> validate_required(~w(block_hash block_index)a)
|> foreign_key_constraint(:block_hash)
end
defp type_changeset(changeset, attrs) do defp type_changeset(changeset, attrs) do
type = get_field(changeset, :type) type = get_field(changeset, :type)
@ -503,6 +567,16 @@ defmodule Explorer.Chain.InternalTransaction do
where(query, [t], not is_nil(t.block_number)) where(query, [t], not is_nil(t.block_number))
end end
@doc """
Filters out internal_transactions of blocks that are flagged as needing fethching
of internal_transactions
"""
def where_nonpending_block(query \\ nil) do
(query || __MODULE__)
|> join(:left, [it], pending in assoc(it, :pending_block), as: :pending)
|> where([it, pending: pending], is_nil(pending.block_hash))
end
def internal_transactions_to_raw(internal_transactions) when is_list(internal_transactions) do def internal_transactions_to_raw(internal_transactions) when is_list(internal_transactions) do
internal_transactions internal_transactions
|> Enum.map(&internal_transaction_to_raw/1) |> Enum.map(&internal_transaction_to_raw/1)

@ -6,14 +6,16 @@ defmodule Explorer.Chain.Log do
require Logger require Logger
alias ABI.{Event, FunctionSelector} alias ABI.{Event, FunctionSelector}
alias Explorer.Chain.{Address, ContractMethod, Data, Hash, Transaction} alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Repo alias Explorer.Repo
@required_attrs ~w(address_hash data index transaction_hash)a @required_attrs ~w(address_hash data block_hash index transaction_hash)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic type)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a
@typedoc """ @typedoc """
* `address` - address of contract that generate the event * `address` - address of contract that generate the event
* `block_hash` - hash of the block
* `block_number` - The block number that the transfer took place.
* `address_hash` - foreign key for `address` * `address_hash` - foreign key for `address`
* `data` - non-indexed log parameters. * `data` - non-indexed log parameters.
* `first_topic` - `topics[0]` * `first_topic` - `topics[0]`
@ -28,6 +30,8 @@ defmodule Explorer.Chain.Log do
@type t :: %__MODULE__{ @type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(), address: %Ecto.Association.NotLoaded{} | Address.t(),
address_hash: Hash.Address.t(), address_hash: Hash.Address.t(),
block_hash: Hash.Full.t(),
block_number: non_neg_integer() | nil,
data: Data.t(), data: Data.t(),
first_topic: String.t(), first_topic: String.t(),
second_topic: String.t(), second_topic: String.t(),
@ -48,6 +52,7 @@ defmodule Explorer.Chain.Log do
field(:fourth_topic, :string) field(:fourth_topic, :string)
field(:index, :integer, primary_key: true) field(:index, :integer, primary_key: true)
field(:type, :string) field(:type, :string)
field(:block_number, :integer)
timestamps() timestamps()
@ -59,6 +64,13 @@ defmodule Explorer.Chain.Log do
references: :hash, references: :hash,
type: Hash.Full type: Hash.Full
) )
belongs_to(:block, Block,
foreign_key: :block_hash,
primary_key: true,
references: :hash,
type: Hash.Full
)
end end
@doc """ @doc """
@ -69,6 +81,7 @@ defmodule Explorer.Chain.Log do
...> %Explorer.Chain.Log{}, ...> %Explorer.Chain.Log{},
...> %{ ...> %{
...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", ...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", ...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
...> fourth_topic: nil, ...> fourth_topic: nil,

@ -0,0 +1,87 @@
defmodule Explorer.Chain.PendingBlockOperation do
@moduledoc """
Tracks a block that has pending operations.
"""
use Explorer.Schema
alias Explorer.Chain.{Block, Hash}
@required_attrs ~w(block_hash fetch_internal_transactions)a
@typedoc """
* `block_hash` - the hash of the block that has pending operations.
* `fetch_internal_transactions` - if the block needs its internal transactions fetched (or not)
"""
@type t :: %__MODULE__{
block_hash: Hash.Full.t(),
fetch_internal_transactions: boolean()
}
@primary_key false
schema "pending_block_operations" do
field(:fetch_internal_transactions, :boolean)
timestamps()
belongs_to(:block, Block, foreign_key: :block_hash, primary_key: true, references: :hash, type: Hash.Full)
end
def changeset(%__MODULE__{} = pending_ops, attrs) do
pending_ops
|> cast(attrs, @required_attrs)
|> validate_required(@required_attrs)
|> foreign_key_constraint(:block_hash)
|> unique_constraint(:block_hash, name: :pending_block_operations_pkey)
end
@doc """
Returns all pending block operations with the `block_hash` in the given list,
using "FOR UPDATE" to grab ShareLocks in order (see docs: sharelocks.md)
"""
def fetch_and_lock_by_hashes(hashes) when is_list(hashes) do
from(
pending_ops in __MODULE__,
where: pending_ops.block_hash in ^hashes,
order_by: [asc: pending_ops.block_hash],
lock: "FOR UPDATE"
)
end
def block_hashes(filter \\ nil)
def block_hashes(filter) when is_nil(filter) do
from(
pending_ops in __MODULE__,
select: pending_ops.block_hash
)
end
def block_hashes(filters) when is_list(filters) do
true_filters = Keyword.new(filters, &{&1, true})
from(
pending_ops in __MODULE__,
where: ^true_filters,
select: pending_ops.block_hash
)
end
def block_hashes(filter), do: block_hashes([filter])
def default_on_conflict do
from(
pending_ops in __MODULE__,
update: [
set: [
fetch_internal_transactions:
pending_ops.fetch_internal_transactions or fragment("EXCLUDED.fetch_internal_transactions"),
# Don't update `block_hash` as it is used for the conflict target
inserted_at: pending_ops.inserted_at,
updated_at: fragment("EXCLUDED.updated_at")
]
],
where: fragment("EXCLUDED.fetch_internal_transactions <> ?", pending_ops.fetch_internal_transactions)
)
end
end

@ -27,7 +27,7 @@ defmodule Explorer.Chain.TokenTransfer do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query, only: [from: 2, limit: 2, where: 3] import Ecto.Query, only: [from: 2, limit: 2, where: 3]
alias Explorer.Chain.{Address, Hash, TokenTransfer, Transaction} alias Explorer.Chain.{Address, Block, Hash, TokenTransfer, Transaction}
alias Explorer.Chain.Token.Instance alias Explorer.Chain.Token.Instance
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
@ -35,6 +35,7 @@ defmodule Explorer.Chain.TokenTransfer do
@typedoc """ @typedoc """
* `:amount` - The token transferred amount * `:amount` - The token transferred amount
* `:block_hash` - hash of the block
* `:block_number` - The block number that the transfer took place. * `:block_number` - The block number that the transfer took place.
* `:from_address` - The `t:Explorer.Chain.Address.t/0` that sent the tokens * `:from_address` - The `t:Explorer.Chain.Address.t/0` that sent the tokens
* `:from_address_hash` - Address hash foreign key * `:from_address_hash` - Address hash foreign key
@ -50,6 +51,7 @@ defmodule Explorer.Chain.TokenTransfer do
@type t :: %TokenTransfer{ @type t :: %TokenTransfer{
amount: Decimal.t(), amount: Decimal.t(),
block_number: non_neg_integer() | nil, block_number: non_neg_integer() | nil,
block_hash: Hash.Full.t(),
from_address: %Ecto.Association.NotLoaded{} | Address.t(), from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Address.t(), from_address_hash: Hash.Address.t(),
to_address: %Ecto.Association.NotLoaded{} | Address.t(), to_address: %Ecto.Association.NotLoaded{} | Address.t(),
@ -93,6 +95,13 @@ defmodule Explorer.Chain.TokenTransfer do
type: Hash.Full type: Hash.Full
) )
belongs_to(:block, Block,
foreign_key: :block_hash,
primary_key: true,
references: :hash,
type: Hash.Full
)
has_one( has_one(
:instance, :instance,
Instance, Instance,
@ -105,7 +114,7 @@ defmodule Explorer.Chain.TokenTransfer do
timestamps() timestamps()
end end
@required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash)a @required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash)a
@optional_attrs ~w(amount token_id)a @optional_attrs ~w(amount token_id)a
@doc false @doc false

@ -29,7 +29,7 @@ defmodule Explorer.Chain.Transaction do
alias Explorer.Repo alias Explorer.Repo
@optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start @optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start
error gas_used index internal_transactions_indexed_at created_contract_code_indexed_at status error gas_used index created_contract_code_indexed_at status
to_address_hash)a to_address_hash)a
@required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a @required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a
@ -105,8 +105,6 @@ defmodule Explorer.Chain.Transaction do
* `input`- data sent along with the transaction * `input`- data sent along with the transaction
* `internal_transactions` - transactions (value transfers) created while executing contract used for this * `internal_transactions` - transactions (value transfers) created while executing contract used for this
transaction transaction
* `internal_transactions_indexed_at` - when `internal_transactions` were fetched by `Indexer` or when they do not
need to be fetched at `inserted_at`.
* `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer` * `created_contract_code_indexed_at` - when created `address` code was fetched by `Indexer`
| `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description | | `status` | `contract_creation_address_hash` | `input` | Token Transfer? | `internal_transactions_indexed_at` | `internal_transactions` | Description |
@ -152,7 +150,6 @@ defmodule Explorer.Chain.Transaction do
index: transaction_index | nil, index: transaction_index | nil,
input: Data.t(), input: Data.t(),
internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()], internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
internal_transactions_indexed_at: DateTime.t(),
logs: %Ecto.Association.NotLoaded{} | [Log.t()], logs: %Ecto.Association.NotLoaded{} | [Log.t()],
nonce: non_neg_integer(), nonce: non_neg_integer(),
r: r(), r: r(),
@ -174,7 +171,6 @@ defmodule Explorer.Chain.Transaction do
:gas_price, :gas_price,
:gas_used, :gas_used,
:index, :index,
:internal_transactions_indexed_at,
:created_contract_code_indexed_at, :created_contract_code_indexed_at,
:input, :input,
:nonce, :nonce,
@ -195,7 +191,6 @@ defmodule Explorer.Chain.Transaction do
field(:gas_price, Wei) field(:gas_price, Wei)
field(:gas_used, :decimal) field(:gas_used, :decimal)
field(:index, :integer) field(:index, :integer)
field(:internal_transactions_indexed_at, :utc_datetime_usec)
field(:created_contract_code_indexed_at, :utc_datetime_usec) field(:created_contract_code_indexed_at, :utc_datetime_usec)
field(:input, Data) field(:input, Data)
field(:nonce, :integer) field(:nonce, :integer)

@ -8,7 +8,7 @@ defmodule Explorer.Etherscan do
alias Explorer.Etherscan.Logs alias Explorer.Etherscan.Logs
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.Address.TokenBalance
alias Explorer.Chain.{Block, Hash, InternalTransaction, Transaction} alias Explorer.Chain.{Block, Hash, InternalTransaction, TokenTransfer, Transaction}
@default_options %{ @default_options %{
order_by_direction: :desc, order_by_direction: :desc,
@ -97,6 +97,7 @@ defmodule Explorer.Etherscan do
query query
|> Chain.where_transaction_has_multiple_internal_transactions() |> Chain.where_transaction_has_multiple_internal_transactions()
|> InternalTransaction.where_nonpending_block()
|> Repo.all() |> Repo.all()
end end
@ -140,6 +141,7 @@ defmodule Explorer.Etherscan do
|> where_address_match(address_hash, options) |> where_address_match(address_hash, options)
|> where_start_block_match(options) |> where_start_block_match(options)
|> where_end_block_match(options) |> where_end_block_match(options)
|> InternalTransaction.where_nonpending_block()
|> Repo.all() |> Repo.all()
end end
@ -318,7 +320,8 @@ defmodule Explorer.Etherscan do
query = query =
from( from(
t in Transaction, t in Transaction,
inner_join: tt in assoc(t, :token_transfers), inner_join: tt in TokenTransfer,
on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash,
inner_join: tkn in assoc(tt, :token), inner_join: tkn in assoc(tt, :token),
inner_join: b in assoc(t, :block), inner_join: b in assoc(t, :block),
where: tt.from_address_hash == ^address_hash, where: tt.from_address_hash == ^address_hash,

@ -79,7 +79,7 @@ defmodule Explorer.Etherscan.Logs do
logs_query = where_topic_match(Log, prepared_filter) logs_query = where_topic_match(Log, prepared_filter)
internal_transaction_log_query = internal_transaction_log_query =
from(internal_transaction in InternalTransaction, from(internal_transaction in InternalTransaction.where_nonpending_block(),
join: transaction in assoc(internal_transaction, :transaction), join: transaction in assoc(internal_transaction, :transaction),
join: log in ^logs_query, join: log in ^logs_query,
on: log.transaction_hash == internal_transaction.transaction_hash, on: log.transaction_hash == internal_transaction.transaction_hash,

@ -40,7 +40,7 @@ defmodule Explorer.GraphQL do
""" """
@spec get_internal_transaction(map()) :: {:ok, InternalTransaction.t()} | {:error, String.t()} @spec get_internal_transaction(map()) :: {:ok, InternalTransaction.t()} | {:error, String.t()}
def get_internal_transaction(%{transaction_hash: _, index: _} = clauses) do def get_internal_transaction(%{transaction_hash: _, index: _} = clauses) do
if internal_transaction = Repo.get_by(InternalTransaction, clauses) do if internal_transaction = Repo.get_by(InternalTransaction.where_nonpending_block(), clauses) do
{:ok, internal_transaction} {:ok, internal_transaction}
else else
{:error, "Internal transaction not found."} {:error, "Internal transaction not found."}
@ -65,7 +65,9 @@ defmodule Explorer.GraphQL do
select: it select: it
) )
Chain.where_transaction_has_multiple_internal_transactions(query) query
|> InternalTransaction.where_nonpending_block()
|> Chain.where_transaction_has_multiple_internal_transactions()
end end
@doc """ @doc """

@ -0,0 +1,14 @@
defmodule Explorer.Repo.Migrations.CreatePendingBlockOperations do
use Ecto.Migration
def change do
create table(:pending_block_operations, primary_key: false) do
add(:block_hash, references(:blocks, column: :hash, type: :bytea, on_delete: :delete_all),
null: false,
primary_key: true
)
timestamps(null: false, type: :utc_datetime_usec)
end
end
end

@ -0,0 +1,122 @@
defmodule Explorer.Repo.Migrations.AddPendingInternalTxsOperation do
use Ecto.Migration
def change do
alter table(:pending_block_operations) do
add(:fetch_internal_transactions, :boolean, null: false)
end
execute("""
INSERT INTO pending_block_operations
(block_hash, inserted_at, updated_at, fetch_internal_transactions)
SELECT b.hash, now(), now(), TRUE FROM blocks b
WHERE b.internal_transactions_indexed_at IS NULL
AND EXISTS (
SELECT 1
FROM transactions t
WHERE b.hash = t.block_hash
AND t.internal_transactions_indexed_at IS NULL
);
""")
alter table(:blocks) do
remove(:internal_transactions_indexed_at)
end
alter table(:transactions) do
remove(:internal_transactions_indexed_at)
end
alter table(:internal_transactions) do
add(:block_hash, :bytea)
add(:block_index, :integer)
end
execute("""
UPDATE internal_transactions itx
SET block_hash = with_block.block_hash, block_index = with_block.block_index
FROM (
SELECT i.transaction_hash,
i.index,
t.block_hash,
row_number() OVER(
PARTITION BY t.block_hash
ORDER BY i.transaction_hash, i.index
) - 1 AS block_index
FROM internal_transactions i
JOIN transactions t
ON t.hash = i.transaction_hash
) AS with_block
WHERE itx.transaction_hash = with_block.transaction_hash
AND itx.index = with_block.index;
""")
execute("""
DELETE FROM internal_transactions WHERE block_hash IS NULL;
""")
execute("""
DO $$
DECLARE
duplicates_count INTEGER := 0;
blocks_scanned INTEGER := 0;
temprow RECORD;
BEGIN
FOR temprow IN
SELECT number, hash FROM blocks
LOOP
blocks_scanned := blocks_scanned + 1;
IF EXISTS (
SELECT 1 FROM transactions WHERE block_hash = temprow.hash
) THEN
IF EXISTS (
SELECT block_hash, block_index FROM internal_transactions
WHERE block_hash = temprow.hash
GROUP BY block_hash, block_index HAVING COUNT(*) > 1
) THEN
duplicates_count := duplicates_count + 1;
RAISE NOTICE '% duplicates, blocks scanned %, block #%, block hash is %', duplicates_count, blocks_scanned, temprow.number , temprow.hash;
IF NOT EXISTS (
SELECT 1 FROM pending_block_operations
WHERE block_hash = temprow.hash
) THEN
INSERT INTO pending_block_operations
(block_hash, inserted_at, updated_at, fetch_internal_transactions)
SELECT b.hash, now(), now(), TRUE FROM blocks b
WHERE b.hash = temprow.hash;
END IF;
DELETE FROM internal_transactions
WHERE block_hash = temprow.hash;
RAISE NOTICE 'DELETED';
END IF;
END IF;
END LOOP;
RAISE NOTICE 'SCRIPT FINISHED';
END $$;
""")
execute("""
ALTER table internal_transactions
DROP CONSTRAINT internal_transactions_pkey,
ADD PRIMARY KEY (block_hash, block_index);
""")
alter table(:internal_transactions) do
modify(:block_hash, references(:blocks, column: :hash, type: :bytea), null: false)
modify(:block_index, :integer, null: false)
end
drop(
index(
:internal_transactions,
[:transaction_hash, :index],
name: :internal_transactions_transaction_hash_index_index
)
)
create_if_not_exists(index(:internal_transactions, [:transaction_hash, :index]))
end
end

@ -0,0 +1,43 @@
defmodule Explorer.Repo.Migrations.AddBlockHashAndBlockIndexToLogs do
use Ecto.Migration
def change do
alter table(:logs) do
add(:block_hash, :bytea)
add(:block_number, :integer)
end
execute("""
UPDATE logs log
SET block_hash = with_block.block_hash,
block_number = with_block.block_number
FROM (
SELECT l.transaction_hash,
t.block_hash,
t.block_number
FROM logs l
JOIN transactions t
ON t.hash = l.transaction_hash
) AS with_block
WHERE log.transaction_hash = with_block.transaction_hash;
""")
execute("""
DELETE FROM logs WHERE block_hash IS NULL;
""")
alter table(:logs) do
modify(:block_hash, references(:blocks, column: :hash, type: :bytea), null: false)
end
execute("""
ALTER table logs
DROP CONSTRAINT logs_pkey,
ADD PRIMARY KEY (transaction_hash, block_hash, index);
""")
drop(unique_index(:logs, [:transaction_hash, :index]))
create_if_not_exists(index(:logs, [:transaction_hash, :index]))
end
end

@ -0,0 +1,40 @@
defmodule Explorer.Repo.Migrations.AddBlockHashToTokenTransfers do
use Ecto.Migration
def change do
alter table(:token_transfers) do
add(:block_hash, :bytea)
end
execute("""
UPDATE token_transfers token_transfer
SET block_hash = with_block.block_hash
FROM (
SELECT transfer.transaction_hash,
t.block_hash
FROM token_transfers transfer
JOIN transactions t
ON t.hash = transfer.transaction_hash
) AS with_block
WHERE token_transfer.transaction_hash = with_block.transaction_hash;
""")
execute("""
DELETE FROM token_transfers WHERE block_hash IS NULL;
""")
alter table(:token_transfers) do
modify(:block_hash, references(:blocks, column: :hash, type: :bytea), null: false)
end
execute("""
ALTER table token_transfers
DROP CONSTRAINT token_transfers_pkey,
ADD PRIMARY KEY (transaction_hash, block_hash, log_index);
""")
drop(unique_index(:token_transfers, [:transaction_hash, :log_index]))
create_if_not_exists(index(:token_transfers, [:transaction_hash, :log_index]))
end
end

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.LogsBlockNumberIndexIndex do
use Ecto.Migration
def change do
create_if_not_exists(index(:logs, ["block_number DESC, index DESC"]))
end
end

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.PendingBlockOperationsBlockHashPartialIndex do
use Ecto.Migration
def change do
execute(
"CREATE INDEX pending_block_operations_block_hash_index_partial ON pending_block_operations(block_hash) WHERE fetch_internal_transactions=true;"
)
end
end

@ -6,6 +6,7 @@
-- IMPORTANT NOTE: after making all the corrections needed the script will NOT -- IMPORTANT NOTE: after making all the corrections needed the script will NOT
-- run the constraint validations because this may be a very long and taxing -- run the constraint validations because this may be a very long and taxing
-- operation. To validate the constraint one can run, after the script fininshed: -- operation. To validate the constraint one can run, after the script fininshed:
-- UPDATE (2019-11-04): use pending_block_operations table instead of internal_transactions
-- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_call_type; -- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_call_type;
-- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_input; -- ALTER TABLE internal_transactions VALIDATE CONSTRAINT call_has_input;
@ -14,56 +15,62 @@
DO $$ DO $$
DECLARE DECLARE
batch_size integer := 10000; -- HOW MANY ITEMS WILL BE UPDATED AT A TIME batch_size integer := 10000; -- HOW MANY ITEMS WILL BE UPDATED AT A TIME
last_transaction_hash bytea; -- WILL CHECK ONLY TRANSACTIONS FOLLOWING THIS HASH (DESC) last_block_number integer; -- WILL CHECK ONLY TRANSACTIONS FOLLOWING THIS HASH (DESC)
last_fetched_batch_size integer; last_fetched_batch_size integer;
BEGIN BEGIN
RAISE NOTICE 'STARTING SCRIPT'; RAISE NOTICE 'STARTING SCRIPT';
CREATE TEMP TABLE transactions_with_deprecated_internal_transactions(hash bytea NOT NULL); CREATE TEMP TABLE blocks_with_deprecated_internal_transactions(block_number integer NOT NULL);
LOOP LOOP
RAISE NOTICE 'Fetching new batch of % transactions to correct', batch_size; RAISE NOTICE 'Fetching new batch of % transactions to correct', batch_size;
INSERT INTO transactions_with_deprecated_internal_transactions INSERT INTO blocks_with_deprecated_internal_transactions
SELECT DISTINCT transaction_hash SELECT DISTINCT a.block_number
FROM internal_transactions FROM (
SELECT DISTINCT i.block_number, i.transaction_index
FROM internal_transactions i
WHERE WHERE
(last_transaction_hash IS NULL OR transaction_hash < last_transaction_hash) AND i.block_number IS NOT NULL
AND
(last_block_number IS NULL OR i.block_number < last_block_number) AND
-- call_has_call_type CONSTRAINT -- call_has_call_type CONSTRAINT
((type = 'call' AND call_type IS NULL) OR ((i.type = 'call' AND i.call_type IS NULL) OR
-- call_has_input CONSTRAINT -- call_has_input CONSTRAINT
(type = 'call' AND input IS NULL) OR (i.type = 'call' AND i.input IS NULL) OR
-- create_has_init CONSTRAINT -- create_has_init CONSTRAINT
(type = 'create' AND init is NULL)) (i.type = 'create' AND i.init is NULL))
ORDER BY transaction_hash DESC LIMIT batch_size; ORDER BY i.block_number DESC, i.transaction_index LIMIT batch_size
) a;
SELECT INTO last_fetched_batch_size count(*) FROM transactions_with_deprecated_internal_transactions; SELECT INTO last_fetched_batch_size count(block_number) FROM blocks_with_deprecated_internal_transactions;
RAISE NOTICE 'Batch of % transactions was fetched, starting their deprecation', last_fetched_batch_size; RAISE NOTICE 'Batch of % transactions was fetched, starting their deprecation', last_fetched_batch_size;
-- UPDATE TRANSACTIONS INSERT INTO pending_block_operations (block_hash, inserted_at, updated_at, fetch_internal_transactions)
UPDATE transactions SELECT b.hash, NOW(), NOW(), true
SET internal_transactions_indexed_at = NULL, FROM blocks_with_deprecated_internal_transactions bd, blocks b
error = NULL WHERE bd.block_number = b.number
FROM transactions_with_deprecated_internal_transactions AND b.consensus = true
WHERE transactions.hash = transactions_with_deprecated_internal_transactions.hash; ON CONFLICT (block_hash)
DO NOTHING;
-- REMOVE THE DEPRECATED internal_transactions -- REMOVE THE DEPRECATED internal_transactions
DELETE FROM internal_transactions DELETE FROM internal_transactions
USING transactions_with_deprecated_internal_transactions USING blocks_with_deprecated_internal_transactions
WHERE internal_transactions.transaction_hash = transactions_with_deprecated_internal_transactions.hash; WHERE internal_transactions.block_number = blocks_with_deprecated_internal_transactions.block_number;
-- COMMIT THE BATCH UPDATES -- COMMIT THE BATCH UPDATES
CHECKPOINT; CHECKPOINT;
-- UPDATE last_transaction_hash TO KEEP TRACK OF ROWS ALREADY CHECKED -- UPDATE last_block_number TO KEEP TRACK OF ROWS ALREADY CHECKED
SELECT INTO last_transaction_hash hash SELECT INTO last_block_number block_number
FROM transactions_with_deprecated_internal_transactions FROM blocks_with_deprecated_internal_transactions
ORDER BY hash ASC LIMIT 1; ORDER BY block_number ASC LIMIT 1;
RAISE NOTICE 'Last batch completed, last transaction hash: %', last_transaction_hash; RAISE NOTICE 'Last batch completed, last block number: %', last_block_number;
-- CLEAR THE TEMP TABLE -- CLEAR THE TEMP TABLE
DELETE FROM transactions_with_deprecated_internal_transactions; DELETE FROM blocks_with_deprecated_internal_transactions;
-- EXIT IF ALL internal_transactions HAVE BEEN CHECKED ALREADY -- EXIT IF ALL internal_transactions HAVE BEEN CHECKED ALREADY
EXIT WHEN last_fetched_batch_size != batch_size; EXIT WHEN last_fetched_batch_size != batch_size;
@ -71,5 +78,5 @@ BEGIN
RAISE NOTICE 'SCRIPT FINISHED, all affected transactions have been deprecated'; RAISE NOTICE 'SCRIPT FINISHED, all affected transactions have been deprecated';
DROP TABLE transactions_with_deprecated_internal_transactions; DROP TABLE blocks_with_deprecated_internal_transactions;
END $$; END $$;

@ -9,9 +9,9 @@ defmodule Explorer.Chain.Cache.PendingTransactionsTest do
PendingTransactions.update([transaction]) PendingTransactions.update([transaction])
transaction_hash = transaction.hash assert [%{hash: pending_transaction_hash}] = PendingTransactions.all()
assert [%{hash: transaction_hash}] = PendingTransactions.all() assert transaction.hash == pending_transaction_hash
end end
end end
end end

@ -2,7 +2,6 @@ defmodule Explorer.Chain.Cache.UnclesTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.Chain.Cache.Uncles alias Explorer.Chain.Cache.Uncles
alias Explorer.Repo
setup do setup do
Supervisor.terminate_child(Explorer.Supervisor, Uncles.child_id()) Supervisor.terminate_child(Explorer.Supervisor, Uncles.child_id())

@ -7,7 +7,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.Import.Runner.{Blocks, Transactions} alias Explorer.Chain.Import.Runner.{Blocks, Transactions}
alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Transaction, TokenTransfer} alias Explorer.Chain.{Address, Block, Transaction, TokenTransfer}
alias Explorer.{Chain, Repo} alias Explorer.{Chain, Repo}
describe "run/1" do describe "run/1" do
@ -115,78 +115,6 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
assert count(Address.CurrentTokenBalance) == count assert count(Address.CurrentTokenBalance) == count
end end
test "remove_nonconsensus_token_transfers deletes token transfer rows with matching block number when new consensus block is inserted",
%{consensus_block: %{number: block_number} = block, options: options} do
consensus_block = insert(:block, number: block_number, consensus: true)
transaction = insert(:transaction) |> with_block(consensus_block)
%TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} =
insert(:token_transfer, block_number: block_number, transaction: transaction)
assert count(TokenTransfer) == 1
assert {:ok,
%{
remove_nonconsensus_token_transfers: [
%{transaction_hash: ^transaction_hash, log_index: ^log_index}
]
}} = run_block_consensus_change(block, true, options)
assert count(TokenTransfer) == 0
end
test "remove_nonconsensus_token_transfers does not delete token transfer rows with matching block number when new consensus block wasn't inserted",
%{consensus_block: %{number: block_number} = block, options: options} do
consensus_block = insert(:block, number: block_number, consensus: true)
transaction = insert(:transaction) |> with_block(consensus_block)
insert(:token_transfer, block_number: block_number, transaction: transaction)
count = 1
assert count(TokenTransfer) == count
assert {:ok, %{remove_nonconsensus_token_transfers: []}} = run_block_consensus_change(block, false, options)
assert count(TokenTransfer) == count
end
test "remove_nonconsensus_logs deletes nonconsensus logs", %{
consensus_block: %{number: block_number} = block,
options: options
} do
old_block = insert(:block, number: block_number, consensus: true)
forked_transaction = :transaction |> insert() |> with_block(old_block)
%Log{transaction_hash: hash, index: index} = insert(:log, transaction: forked_transaction)
assert count(Log) == 1
assert {:ok, %{remove_nonconsensus_logs: [%{transaction_hash: ^hash, index: ^index}]}} =
run_block_consensus_change(block, true, options)
assert count(Log) == 0
end
test "remove_nonconsensus_internal_transactions deletes nonconsensus internal transactions", %{
consensus_block: %{number: block_number} = block,
options: options
} do
old_block = insert(:block, number: block_number, consensus: true)
forked_transaction = :transaction |> insert() |> with_block(old_block)
%InternalTransaction{index: index, transaction_hash: hash} =
insert(:internal_transaction, index: 0, transaction: forked_transaction)
assert count(InternalTransaction) == 1
assert {:ok, %{remove_nonconsensus_internal_transactions: [%{transaction_hash: ^hash, index: ^index}]}} =
run_block_consensus_change(block, true, options)
assert count(InternalTransaction) == 0
end
test "derive_address_current_token_balances inserts rows if there is an address_token_balance left for the rows deleted by delete_address_current_token_balances", test "derive_address_current_token_balances inserts rows if there is an address_token_balance left for the rows deleted by delete_address_current_token_balances",
%{consensus_block: %{number: block_number} = block, options: options} do %{consensus_block: %{number: block_number} = block, options: options} do
token = insert(:token) token = insert(:token)

@ -2,19 +2,20 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
use Explorer.DataCase use Explorer.DataCase
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.{Block, Data, Wei, Transaction, InternalTransaction} alias Explorer.Chain.{Block, Data, Wei, PendingBlockOperation, Transaction, InternalTransaction}
alias Explorer.Chain.Import.Runner.InternalTransactions alias Explorer.Chain.Import.Runner.InternalTransactions
describe "run/1" do describe "run/1" do
test "transaction's status becomes :error when its internal_transaction has an error" do test "transaction's status becomes :error when its internal_transaction has an error" do
transaction = insert(:transaction) |> with_block(status: :ok) transaction = insert(:transaction) |> with_block(status: :ok)
insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true)
assert :ok == transaction.status assert :ok == transaction.status
index = 0 index = 0
error = "Reverted" error = "Reverted"
internal_transaction_changes = make_internal_transaction_changes(transaction.hash, index, error) internal_transaction_changes = make_internal_transaction_changes(transaction, index, error)
assert {:ok, _} = run_internal_transactions([internal_transaction_changes]) assert {:ok, _} = run_internal_transactions([internal_transaction_changes])
@ -25,18 +26,21 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
transaction = insert(:transaction) |> with_block(status: :ok) transaction = insert(:transaction) |> with_block(status: :ok)
pending = insert(:transaction) pending = insert(:transaction)
insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true)
assert :ok == transaction.status assert :ok == transaction.status
assert is_nil(pending.block_hash) assert is_nil(pending.block_hash)
index = 0 index = 0
transaction_changes = make_internal_transaction_changes(transaction.hash, index, nil) transaction_changes = make_internal_transaction_changes(transaction, index, nil)
pending_changes = make_internal_transaction_changes(pending.hash, index, nil) pending_changes = make_internal_transaction_changes(pending, index, nil)
assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes]) assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes])
assert %InternalTransaction{} = assert Repo.exists?(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash))
Repo.one(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash))
assert PendingBlockOperation |> Repo.get(transaction.block_hash) |> is_nil()
assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil()
@ -47,68 +51,112 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
empty_block = insert(:block) empty_block = insert(:block)
pending = insert(:transaction) pending = insert(:transaction)
insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true)
assert is_nil(pending.block_hash) assert is_nil(pending.block_hash)
full_block = insert(:block) full_block = insert(:block)
inserted = insert(:transaction) |> with_block(full_block) inserted = insert(:transaction) |> with_block(full_block)
insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true)
assert full_block.hash == inserted.block_hash assert full_block.hash == inserted.block_hash
index = 0 index = 0
pending_transaction_changes = pending_transaction_changes =
pending.hash pending
|> make_internal_transaction_changes(index, nil) |> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, empty_block.number) |> Map.put(:block_number, empty_block.number)
transaction_changes = transaction_changes = make_internal_transaction_changes(inserted, index, nil)
inserted.hash
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, full_block.number)
multi =
Multi.new()
|> Multi.run(:internal_transactions_indexed_at_blocks, fn _, _ -> {:ok, [empty_block.hash, full_block.hash]} end)
assert {:ok, _} = run_internal_transactions([pending_transaction_changes, transaction_changes], multi) assert {:ok, _} = run_internal_transactions([pending_transaction_changes, transaction_changes])
assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil()
assert %{consensus: false} = Repo.get(Block, empty_block.hash) assert %{consensus: false} = Repo.get(Block, empty_block.hash)
assert not is_nil(Repo.get(PendingBlockOperation, empty_block.hash))
assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() == assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() ==
false false
assert %{consensus: true} = Repo.get(Block, full_block.hash) assert %{consensus: true} = Repo.get(Block, full_block.hash)
assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil()
end
test "removes old records with the same primary key (transaction_hash, index)" do
full_block = insert(:block)
another_full_block = insert(:block)
transaction = insert(:transaction) |> with_block(full_block)
insert(:internal_transaction,
index: 0,
transaction: transaction,
block_hash: another_full_block.hash,
block_index: 0
)
insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true)
transaction_changes = make_internal_transaction_changes(transaction, 0, nil)
assert {:ok, %{remove_left_over_internal_transactions: {1, nil}}} =
run_internal_transactions([transaction_changes])
assert from(i in InternalTransaction,
where: i.transaction_hash == ^transaction.hash and i.block_hash == ^another_full_block.hash
)
|> Repo.one()
|> is_nil()
end
test "removes consensus to blocks where not all transactions are filled" do
full_block = insert(:block)
transaction_a = insert(:transaction) |> with_block(full_block)
transaction_b = insert(:transaction) |> with_block(full_block)
insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true)
transaction_a_changes = make_internal_transaction_changes(transaction_a, 0, nil)
assert {:ok, _} = run_internal_transactions([transaction_a_changes])
assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) |> Repo.one() |> is_nil()
assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) |> Repo.one() |> is_nil()
assert %{consensus: false} = Repo.get(Block, full_block.hash)
assert not is_nil(Repo.get(PendingBlockOperation, full_block.hash))
end end
test "does not remove consensus when block is empty and no transactions are missing" do test "does not remove consensus when block is empty and no transactions are missing" do
empty_block = insert(:block) empty_block = insert(:block)
insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true)
full_block = insert(:block) full_block = insert(:block)
inserted = insert(:transaction) |> with_block(full_block) inserted = insert(:transaction) |> with_block(full_block)
insert(:pending_block_operation, block_hash: full_block.hash, fetch_internal_transactions: true)
assert full_block.hash == inserted.block_hash assert full_block.hash == inserted.block_hash
index = 0 index = 0
transaction_changes = transaction_changes = make_internal_transaction_changes(inserted, index, nil)
inserted.hash empty_changes = make_empty_block_changes(empty_block.number)
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, full_block.number)
multi =
Multi.new()
|> Multi.run(:internal_transactions_indexed_at_blocks, fn _, _ -> {:ok, [empty_block.hash, full_block.hash]} end)
assert {:ok, _} = run_internal_transactions([transaction_changes], multi) assert {:ok, _} = run_internal_transactions([empty_changes, transaction_changes])
assert %{consensus: true} = Repo.get(Block, empty_block.hash) assert %{consensus: true} = Repo.get(Block, empty_block.hash)
assert PendingBlockOperation |> Repo.get(empty_block.hash) |> is_nil()
assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() == assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() ==
false false
assert %{consensus: true} = Repo.get(Block, full_block.hash) assert %{consensus: true} = Repo.get(Block, full_block.hash)
assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil()
end end
end end
@ -121,7 +169,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
|> Repo.transaction() |> Repo.transaction()
end end
defp make_internal_transaction_changes(transaction_hash, index, error) do defp make_empty_block_changes(block_number), do: %{block_number: block_number}
defp make_internal_transaction_changes(transaction, index, error) do
%{ %{
from_address_hash: insert(:address).hash, from_address_hash: insert(:address).hash,
to_address_hash: insert(:address).hash, to_address_hash: insert(:address).hash,
@ -142,10 +192,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
end, end,
index: index, index: index,
trace_address: [], trace_address: [],
transaction_hash: transaction_hash, transaction_hash: transaction.hash,
type: :call, type: :call,
value: Wei.from(Decimal.new(1), :wei), value: Wei.from(Decimal.new(1), :wei),
error: error error: error,
block_number: transaction.block_number
} }
end end
end end

@ -12,6 +12,7 @@ defmodule Explorer.Chain.ImportTest do
Log, Log,
Hash, Hash,
Import, Import,
PendingBlockOperation,
Token, Token,
TokenTransfer, TokenTransfer,
Transaction Transaction
@ -81,11 +82,13 @@ defmodule Explorer.Chain.ImportTest do
value: 0 value: 0
} }
], ],
timeout: 5 timeout: 5,
with: :blockless_changeset
}, },
logs: %{ logs: %{
params: [ params: [
%{ %{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
@ -149,6 +152,7 @@ defmodule Explorer.Chain.ImportTest do
%{ %{
amount: Decimal.new(1_000_000_000_000_000_000), amount: Decimal.new(1_000_000_000_000_000_000),
block_number: 37, block_number: 37,
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
log_index: 0, log_index: 0,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
@ -305,9 +309,7 @@ defmodule Explorer.Chain.ImportTest do
bytes: bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}, }
# because there are successful, non-contract-creation token transfer
internal_transactions_indexed_at: %DateTime{}
} }
], ],
tokens: [ tokens: [
@ -554,7 +556,8 @@ defmodule Explorer.Chain.ImportTest do
block_number: 37, block_number: 37,
transaction_index: 0 transaction_index: 0
} }
] ],
with: :blockless_changeset
} }
} }
@ -565,63 +568,7 @@ defmodule Explorer.Chain.ImportTest do
assert address.contract_code != smart_contract_bytecode assert address.contract_code != smart_contract_bytecode
end end
test "updates `error`, `status` and `internal_transaction_indexed_at` even if internal transactions were alreader inserted" do test "with internal_transactions updates PendingBlockOperation status" do
address_hash = "0x1c494fa496f1cfd918b5ff190835af3aaf609899"
from_address = insert(:address, hash: address_hash)
block = insert(:block, consensus: true, number: 37)
transaction =
:transaction
|> insert(error: nil, internal_transactions_indexed_at: nil, status: nil, from_address: from_address)
|> with_block(block, status: :error)
internal_transacton =
insert(:internal_transaction,
block_number: 37,
transaction_hash: transaction.hash,
error: "Bad Instruction",
index: 0,
gas_used: nil,
output: nil,
gas: 19,
type: "call"
)
options = %{
internal_transactions: %{
params: [
%{
block_number: internal_transacton.block_number,
call_type: internal_transacton.type,
gas: internal_transacton.gas,
gas_used: internal_transacton.gas_used,
index: internal_transacton.index,
output: internal_transacton.output,
transaction_hash: internal_transacton.transaction_hash,
type: internal_transacton.type,
from_address_hash: address_hash,
to_address_hash: address_hash,
trace_address: [],
value: 0,
transaction_index: 0,
error: internal_transacton.error,
input: internal_transacton.input
}
]
}
}
{:ok, _} = Import.all(options)
assert result =
%Transaction{error: "Bad Instruction", status: :error} =
Repo.one!(from(t in Transaction, where: t.hash == ^transaction.hash))
assert result.internal_transactions_indexed_at
end
test "with internal_transactions updates Transaction internal_transactions_indexed_at" do
block_hash = "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47" block_hash = "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47"
block_number = 34 block_number = 34
miner_hash = from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" miner_hash = from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
@ -674,7 +621,10 @@ defmodule Explorer.Chain.ImportTest do
value: 0 value: 0
} }
] ]
}, }
}
internal_txs_options = %{
internal_transactions: %{ internal_transactions: %{
params: [ params: [
%{ %{
@ -693,17 +643,18 @@ defmodule Explorer.Chain.ImportTest do
output: "0x", output: "0x",
value: 0 value: 0
} }
] ],
with: :blockless_changeset
} }
} }
refute Enum.any?(options[:transactions][:params], &Map.has_key?(&1, :internal_transactions_indexed_at))
assert {:ok, _} = Import.all(options) assert {:ok, _} = Import.all(options)
transaction = Explorer.Repo.get(Transaction, transaction_hash) assert [block_hash] = Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions))
assert {:ok, _} = Import.all(internal_txs_options)
refute transaction.internal_transactions_indexed_at == nil assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions))
end end
test "when the transaction has no to_address and an internal transaction with type create it populates the denormalized created_contract_address_hash" do test "when the transaction has no to_address and an internal transaction with type create it populates the denormalized created_contract_address_hash" do
@ -786,7 +737,8 @@ defmodule Explorer.Chain.ImportTest do
value: 0, value: 0,
transaction_index: 0 transaction_index: 0
} }
] ],
with: :blockless_changeset
} }
} }
@ -896,7 +848,8 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 1 transaction_index: 1
} }
], ],
timeout: 5 timeout: 5,
with: :blockless_changeset
}, },
addresses: %{ addresses: %{
params: [ params: [
@ -1080,7 +1033,8 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 0, transaction_index: 0,
transaction_block_number: 35 transaction_block_number: 35
} }
] ],
with: :blockless_changeset
} }
}) })
@ -1561,16 +1515,24 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 0 transaction_index: 0
) )
], ],
timeout: 1 timeout: 1,
with: :blockless_changeset
}, },
logs: %{ logs: %{
params: [params_for(:log, transaction_hash: transaction_hash, address_hash: miner_hash)], params: [
params_for(:log,
transaction_hash: transaction_hash,
address_hash: miner_hash,
block_hash: block_hash
)
],
timeout: 1 timeout: 1
}, },
token_transfers: %{ token_transfers: %{
params: [ params: [
params_for( params_for(
:token_transfer, :token_transfer,
block_hash: block_hash,
block_number: 35, block_number: 35,
from_address_hash: from_address_hash, from_address_hash: from_address_hash,
to_address_hash: to_address_hash, to_address_hash: to_address_hash,
@ -1792,7 +1754,6 @@ defmodule Explorer.Chain.ImportTest do
block_hash: block_hash_before, block_hash: block_hash_before,
block_number: block_number, block_number: block_number,
error: error, error: error,
internal_transactions_indexed_at: Timex.parse!("2019-01-01T01:00:00Z", "{ISO:Extended:Z}"),
from_address_hash: from_address_hash_before, from_address_hash: from_address_hash_before,
to_address_hash: to_address_hash_before, to_address_hash: to_address_hash_before,
gas: 21_000, gas: 21_000,
@ -1828,7 +1789,8 @@ defmodule Explorer.Chain.ImportTest do
block_number: block_number, block_number: block_number,
transaction_index: 0 transaction_index: 0
} }
] ],
with: :blockless_changeset
} }
}) })

@ -26,7 +26,9 @@ defmodule Explorer.Chain.InternalTransactionTest do
transaction_hash: transaction.hash, transaction_hash: transaction.hash,
type: "call", type: "call",
value: 100, value: 100,
block_number: 35 block_number: 35,
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
block_index: 0
}) })
assert changeset.valid? assert changeset.valid?

@ -9,7 +9,12 @@ defmodule Explorer.Chain.LogTest do
describe "changeset/2" do describe "changeset/2" do
test "accepts valid attributes" do test "accepts valid attributes" do
params = params_for(:log, address_hash: build(:address).hash, transaction_hash: build(:transaction).hash) params =
params_for(:log,
address_hash: build(:address).hash,
transaction_hash: build(:transaction).hash,
block_hash: build(:block).hash
)
assert %Changeset{valid?: true} = Log.changeset(%Log{}, params) assert %Changeset{valid?: true} = Log.changeset(%Log{}, params)
end end
@ -26,7 +31,8 @@ defmodule Explorer.Chain.LogTest do
:log, :log,
address_hash: build(:address).hash, address_hash: build(:address).hash,
first_topic: "ham", first_topic: "ham",
transaction_hash: build(:transaction).hash transaction_hash: build(:transaction).hash,
block_hash: build(:block).hash
) )
assert %Changeset{changes: %{first_topic: "ham"}, valid?: true} = Log.changeset(%Log{}, params) assert %Changeset{changes: %{first_topic: "ham"}, valid?: true} = Log.changeset(%Log{}, params)

@ -18,6 +18,7 @@ defmodule Explorer.ChainTest do
Hash, Hash,
InternalTransaction, InternalTransaction,
Log, Log,
PendingBlockOperation,
Token, Token,
TokenTransfer, TokenTransfer,
Transaction, Transaction,
@ -35,6 +36,38 @@ defmodule Explorer.ChainTest do
setup :verify_on_exit! setup :verify_on_exit!
describe "remove_nonconsensus_blocks_from_pending_ops/0" do
test "removes pending ops for nonconsensus blocks" do
block = insert(:block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
nonconsensus_block = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true)
:ok = Chain.remove_nonconsensus_blocks_from_pending_ops()
assert Repo.get(PendingBlockOperation, block.hash)
assert is_nil(Repo.get(PendingBlockOperation, nonconsensus_block.hash))
end
test "removes pending ops for nonconsensus blocks by block hashes" do
block = insert(:block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
nonconsensus_block = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true)
nonconsensus_block1 = insert(:block, consensus: false)
insert(:pending_block_operation, block: nonconsensus_block1, fetch_internal_transactions: true)
:ok = Chain.remove_nonconsensus_blocks_from_pending_ops([nonconsensus_block1.hash])
assert Repo.get(PendingBlockOperation, block.hash)
assert Repo.get(PendingBlockOperation, nonconsensus_block.hash)
assert is_nil(Repo.get(PendingBlockOperation, nonconsensus_block1.hash))
end
end
describe "count_addresses_with_balance_from_cache/0" do describe "count_addresses_with_balance_from_cache/0" do
test "returns the number of addresses with fetched_coin_balance > 0" do test "returns the number of addresses with fetched_coin_balance > 0" do
insert(:address, fetched_coin_balance: 0) insert(:address, fetched_coin_balance: 0)
@ -220,14 +253,26 @@ defmodule Explorer.ChainTest do
|> insert(to_address: address) |> insert(to_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction1, index: 1, address: address) insert(:log,
block: transaction1.block,
block_number: transaction1.block_number,
transaction: transaction1,
index: 1,
address: address
)
transaction2 = transaction2 =
:transaction :transaction
|> insert(from_address: address) |> insert(from_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction2, index: 2, address: address) insert(:log,
block: transaction2.block,
block_number: transaction2.block_number,
transaction: transaction2,
index: 2,
address: address
)
assert Enum.count(Chain.address_to_logs(address_hash)) == 2 assert Enum.count(Chain.address_to_logs(address_hash)) == 2
end end
@ -240,10 +285,18 @@ defmodule Explorer.ChainTest do
|> insert(to_address: address) |> insert(to_address: address)
|> with_block() |> with_block()
log1 = insert(:log, transaction: transaction, index: 1, address: address) log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number)
2..51 2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end) |> Enum.map(fn index ->
insert(:log,
block: transaction.block,
transaction: transaction,
index: index,
address: address,
block_number: transaction.block_number
)
end)
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
paging_options1 = %PagingOptions{page_size: 1} paging_options1 = %PagingOptions{page_size: 1}
@ -263,14 +316,27 @@ defmodule Explorer.ChainTest do
|> insert(to_address: address) |> insert(to_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction1, index: 1, address: address) insert(:log,
block: transaction1.block,
transaction: transaction1,
index: 1,
address: address,
block_number: transaction1.block_number
)
transaction2 = transaction2 =
:transaction :transaction
|> insert(from_address: address) |> insert(from_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction2, index: 2, address: address, first_topic: "test") insert(:log,
block: transaction2.block,
transaction: transaction2,
index: 2,
address: address,
first_topic: "test",
block_number: transaction2.block_number
)
[found_log] = Chain.address_to_logs(address_hash, topic: "test") [found_log] = Chain.address_to_logs(address_hash, topic: "test")
@ -285,14 +351,27 @@ defmodule Explorer.ChainTest do
|> insert(to_address: address) |> insert(to_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction1, index: 1, address: address, fourth_topic: "test") insert(:log,
block: transaction1.block,
block_number: transaction1.block_number,
transaction: transaction1,
index: 1,
address: address,
fourth_topic: "test"
)
transaction2 = transaction2 =
:transaction :transaction
|> insert(from_address: address) |> insert(from_address: address)
|> with_block() |> with_block()
insert(:log, transaction: transaction2, index: 2, address: address) insert(:log,
block: transaction2.block,
block_number: transaction2.block.number,
transaction: transaction2,
index: 2,
address: address
)
[found_log] = Chain.address_to_logs(address_hash, topic: "test") [found_log] = Chain.address_to_logs(address_hash, topic: "test")
@ -393,6 +472,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -806,7 +887,7 @@ defmodule Explorer.ChainTest do
:transaction :transaction
|> insert() |> insert()
|> with_block(block, internal_transactions_indexed_at: DateTime.utc_now()) |> with_block(block)
assert Chain.finished_indexing?() assert Chain.finished_indexing?()
end end
@ -822,6 +903,8 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block(block) |> with_block(block)
insert(:pending_block_operation, block: block, fetch_internal_transactions: true)
refute Chain.finished_indexing?() refute Chain.finished_indexing?()
end end
end end
@ -919,6 +1002,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -927,6 +1012,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index transaction_index: transaction.index
) )
end) end)
@ -1281,11 +1368,13 @@ defmodule Explorer.ChainTest do
output: "0x", output: "0x",
value: 0 value: 0
} }
] ],
with: :blockless_changeset
}, },
logs: %{ logs: %{
params: [ params: [
%{ %{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
@ -1343,6 +1432,7 @@ defmodule Explorer.ChainTest do
token_transfers: %{ token_transfers: %{
params: [ params: [
%{ %{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
amount: Decimal.new(1_000_000_000_000_000_000), amount: Decimal.new(1_000_000_000_000_000_000),
block_number: 37, block_number: 37,
log_index: 0, log_index: 0,
@ -1492,9 +1582,7 @@ defmodule Explorer.ChainTest do
bytes: bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57,
101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}, }
# because there are successful, non-contract-creation token transfer
internal_transactions_indexed_at: %DateTime{}
} }
], ],
tokens: [ tokens: [
@ -1768,6 +1856,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
to_address: address, to_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -1777,6 +1867,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
to_address: address, to_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -1803,6 +1895,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -1811,6 +1905,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -1858,6 +1954,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: pending_transaction.block_number, block_number: pending_transaction.block_number,
block_hash: pending_transaction.block_hash,
block_index: 1,
transaction_index: pending_transaction.index transaction_index: pending_transaction.index
) )
@ -1868,6 +1966,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: pending_transaction.block_number, block_number: pending_transaction.block_number,
block_hash: pending_transaction.block_hash,
block_index: 2,
transaction_index: pending_transaction.index transaction_index: pending_transaction.index
) )
@ -1885,6 +1985,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: first_a_transaction.block_number, block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 1,
transaction_index: first_a_transaction.index transaction_index: first_a_transaction.index
) )
@ -1895,6 +1997,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: first_a_transaction.block_number, block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 2,
transaction_index: first_a_transaction.index transaction_index: first_a_transaction.index
) )
@ -1910,6 +2014,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: second_a_transaction.block_number, block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 4,
transaction_index: second_a_transaction.index transaction_index: second_a_transaction.index
) )
@ -1920,6 +2026,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: second_a_transaction.block_number, block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 5,
transaction_index: second_a_transaction.index transaction_index: second_a_transaction.index
) )
@ -1937,6 +2045,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: first_b_transaction.block_number, block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 1,
transaction_index: first_b_transaction.index transaction_index: first_b_transaction.index
) )
@ -1947,6 +2057,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: first_b_transaction.block_number, block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 2,
transaction_index: first_b_transaction.index transaction_index: first_b_transaction.index
) )
@ -1972,10 +2084,14 @@ defmodule Explorer.ChainTest do
pending_transaction = insert(:transaction) pending_transaction = insert(:transaction)
old_block = insert(:block, consensus: false)
insert( insert(
:internal_transaction, :internal_transaction,
transaction: pending_transaction, transaction: pending_transaction,
to_address: address, to_address: address,
block_hash: old_block.hash,
block_index: 1,
index: 1 index: 1
) )
@ -1983,6 +2099,8 @@ defmodule Explorer.ChainTest do
:internal_transaction, :internal_transaction,
transaction: pending_transaction, transaction: pending_transaction,
to_address: address, to_address: address,
block_hash: old_block.hash,
block_index: 2,
index: 2 index: 2
) )
@ -2000,6 +2118,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: first_a_transaction.block_number, block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 1,
transaction_index: first_a_transaction.index transaction_index: first_a_transaction.index
) )
@ -2010,6 +2130,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: first_a_transaction.block_number, block_number: first_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 2,
transaction_index: first_a_transaction.index transaction_index: first_a_transaction.index
) )
@ -2025,6 +2147,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: second_a_transaction.block_number, block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 4,
transaction_index: second_a_transaction.index transaction_index: second_a_transaction.index
) )
@ -2035,6 +2159,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: second_a_transaction.block_number, block_number: second_a_transaction.block_number,
block_hash: a_block.hash,
block_index: 5,
transaction_index: second_a_transaction.index transaction_index: second_a_transaction.index
) )
@ -2052,6 +2178,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 1, index: 1,
block_number: first_b_transaction.block_number, block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 1,
transaction_index: first_b_transaction.index transaction_index: first_b_transaction.index
) )
@ -2062,6 +2190,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
index: 2, index: 2,
block_number: first_b_transaction.block_number, block_number: first_b_transaction.block_number,
block_hash: b_block.hash,
block_index: 2,
transaction_index: first_b_transaction.index transaction_index: first_b_transaction.index
) )
@ -2129,6 +2259,8 @@ defmodule Explorer.ChainTest do
to_address: address, to_address: address,
transaction: transaction, transaction: transaction,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2149,6 +2281,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
from_address: address, from_address: address,
transaction: transaction, transaction: transaction,
block_hash: transaction.block_hash,
block_index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2212,6 +2346,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2219,6 +2355,8 @@ defmodule Explorer.ChainTest do
insert(:internal_transaction, insert(:internal_transaction,
transaction: transaction, transaction: transaction,
index: 1, index: 1,
block_hash: transaction.block_hash,
block_index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2248,6 +2386,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2286,6 +2426,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2305,6 +2447,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: transaction, transaction: transaction,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2325,6 +2469,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
type: :reward, type: :reward,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2346,6 +2492,8 @@ defmodule Explorer.ChainTest do
gas: nil, gas: nil,
type: :selfdestruct, type: :selfdestruct,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2365,6 +2513,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2373,6 +2523,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2395,6 +2547,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2403,6 +2557,8 @@ defmodule Explorer.ChainTest do
transaction: transaction, transaction: transaction,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2436,7 +2592,8 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
%Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction) %Log{transaction_hash: transaction_hash, index: index} =
insert(:log, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash) assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash)
end end
@ -2447,11 +2604,24 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
log = insert(:log, transaction: transaction, index: 1) log =
insert(:log,
transaction: transaction,
index: 1,
block: transaction.block,
block_number: transaction.block_number
)
second_page_indexes = second_page_indexes =
2..51 2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index) end) |> Enum.map(fn index ->
insert(:log,
transaction: transaction,
index: index,
block: transaction.block,
block_number: transaction.block_number
)
end)
|> Enum.map(& &1.index) |> Enum.map(& &1.index)
assert second_page_indexes == assert second_page_indexes ==
@ -2466,7 +2636,7 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
insert(:log, transaction: transaction) insert(:log, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%Log{address: %Address{}, transaction: %Transaction{}}] = assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs( Chain.transaction_to_logs(
@ -2500,7 +2670,11 @@ defmodule Explorer.ChainTest do
|> with_block() |> with_block()
%TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} = %TokenTransfer{transaction_hash: transaction_hash, log_index: log_index} =
insert(:token_transfer, transaction: transaction) insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] = assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] =
Chain.transaction_to_token_transfers(transaction.hash) Chain.transaction_to_token_transfers(transaction.hash)
@ -2512,7 +2686,7 @@ defmodule Explorer.ChainTest do
|> insert() |> insert()
|> with_block() |> with_block()
insert(:token_transfer, transaction: transaction) insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number)
assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] = assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] =
Chain.transaction_to_token_transfers( Chain.transaction_to_token_transfers(
@ -2873,6 +3047,8 @@ defmodule Explorer.ChainTest do
created_contract_address: created_contract_address, created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode, created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -2973,6 +3149,8 @@ defmodule Explorer.ChainTest do
created_contract_address: created_contract_address, created_contract_address: created_contract_address,
created_contract_code: smart_contract_bytecode, created_contract_code: smart_contract_bytecode,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -3179,6 +3357,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: transaction, transaction: transaction,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -3225,6 +3405,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: transaction, transaction: transaction,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -3265,6 +3447,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: transaction, transaction: transaction,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
@ -3334,6 +3518,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: from_internal_transaction_transaction, transaction: from_internal_transaction_transaction,
block_number: from_internal_transaction_transaction.block_number, block_number: from_internal_transaction_transaction.block_number,
block_hash: from_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: from_internal_transaction_transaction.index transaction_index: from_internal_transaction_transaction.index
) )
@ -3352,6 +3538,8 @@ defmodule Explorer.ChainTest do
to_address: miner, to_address: miner,
transaction: to_internal_transaction_transaction, transaction: to_internal_transaction_transaction,
block_number: to_internal_transaction_transaction.block_number, block_number: to_internal_transaction_transaction.block_number,
block_hash: to_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: to_internal_transaction_transaction.index transaction_index: to_internal_transaction_transaction.index
) )
@ -3408,6 +3596,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: from_internal_transaction_transaction, transaction: from_internal_transaction_transaction,
block_number: from_internal_transaction_transaction.block_number, block_number: from_internal_transaction_transaction.block_number,
block_hash: from_internal_transaction_transaction.block_hash,
block_index: 0,
transaction_index: from_internal_transaction_transaction.index transaction_index: from_internal_transaction_transaction.index
) )
@ -3422,6 +3612,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
transaction: to_internal_transaction_transaction, transaction: to_internal_transaction_transaction,
block_number: to_internal_transaction_transaction.block_number, block_number: to_internal_transaction_transaction.block_number,
block_hash: to_internal_transaction_transaction.block_hash,
block_index: 1,
transaction_index: to_internal_transaction_transaction.index transaction_index: to_internal_transaction_transaction.index
) )
@ -4161,6 +4353,8 @@ defmodule Explorer.ChainTest do
index: 0, index: 0,
created_contract_address: created_contract_address, created_contract_address: created_contract_address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index, transaction_index: transaction.index,
input: input input: input
) )

@ -68,6 +68,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
@ -144,6 +146,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
@ -485,6 +489,8 @@ defmodule Explorer.EtherscanTest do
index: 0, index: 0,
from_address: address, from_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
@ -527,6 +533,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: index, index: index,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index transaction_index: transaction.index
) )
end end
@ -551,6 +559,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction1, transaction: transaction1,
index: 0, index: 0,
block_number: transaction1.block_number, block_number: transaction1.block_number,
block_hash: transaction1.block_hash,
block_index: 0,
transaction_index: transaction1.index transaction_index: transaction1.index
) )
@ -558,6 +568,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction1, transaction: transaction1,
index: 1, index: 1,
block_number: transaction1.block_number, block_number: transaction1.block_number,
block_hash: transaction1.block_hash,
block_index: 1,
transaction_index: transaction1.index transaction_index: transaction1.index
) )
@ -566,6 +578,8 @@ defmodule Explorer.EtherscanTest do
index: 0, index: 0,
type: :reward, type: :reward,
block_number: transaction2.block_number, block_number: transaction2.block_number,
block_hash: transaction2.block_hash,
block_index: 2,
transaction_index: transaction2.index transaction_index: transaction2.index
) )
@ -617,6 +631,8 @@ defmodule Explorer.EtherscanTest do
index: 0, index: 0,
from_address: address, from_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: block.hash,
block_index: 0,
transaction_index: transaction.index transaction_index: transaction.index
) )
|> with_contract_creation(contract_address) |> with_contract_creation(contract_address)
@ -665,6 +681,8 @@ defmodule Explorer.EtherscanTest do
index: index, index: index,
from_address: address, from_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index transaction_index: transaction.index
} }
@ -689,6 +707,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 0, index: 0,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: transaction.index, transaction_index: transaction.index,
created_contract_address: address1 created_contract_address: address1
) )
@ -697,6 +717,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 1, index: 1,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 1,
transaction_index: transaction.index, transaction_index: transaction.index,
from_address: address1 from_address: address1
) )
@ -705,6 +727,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 2, index: 2,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 2,
transaction_index: transaction.index, transaction_index: transaction.index,
to_address: address1 to_address: address1
) )
@ -713,6 +737,8 @@ defmodule Explorer.EtherscanTest do
transaction: transaction, transaction: transaction,
index: 3, index: 3,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: 3,
transaction_index: transaction.index, transaction_index: transaction.index,
from_address: address2 from_address: address2
) )
@ -740,6 +766,8 @@ defmodule Explorer.EtherscanTest do
index: index, index: index,
from_address: address, from_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index transaction_index: transaction.index
} }
@ -780,6 +808,8 @@ defmodule Explorer.EtherscanTest do
index: index, index: index,
from_address: address, from_address: address,
block_number: transaction.block_number, block_number: transaction.block_number,
block_hash: transaction.block_hash,
block_index: index,
transaction_index: transaction.index transaction_index: transaction.index
} }
@ -828,7 +858,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block() |> with_block()
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer =
insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
[found_token_transfer] = Etherscan.list_token_transfers(token_transfer.from_address_hash, nil) [found_token_transfer] = Etherscan.list_token_transfers(token_transfer.from_address_hash, nil)
@ -841,7 +876,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block() |> with_block()
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer =
insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
[found_token_transfer] = Etherscan.list_token_transfers(token_transfer.to_address_hash, nil) [found_token_transfer] = Etherscan.list_token_transfers(token_transfer.to_address_hash, nil)
@ -863,9 +903,26 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block() |> with_block()
insert(:token_transfer, from_address: address1, transaction: transaction) insert(:token_transfer,
insert(:token_transfer, from_address: address1, transaction: transaction) from_address: address1,
insert(:token_transfer, from_address: address2, transaction: transaction) transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
insert(:token_transfer,
from_address: address1,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
insert(:token_transfer,
from_address: address2,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
found_token_transfers = Etherscan.list_token_transfers(address1.hash, nil) found_token_transfers = Etherscan.list_token_transfers(address1.hash, nil)
@ -884,7 +941,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block() |> with_block()
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer =
insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
insert(:block) insert(:block)
@ -903,7 +965,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block() |> with_block()
token_transfer = insert(:token_transfer, transaction: transaction) token_transfer =
insert(:token_transfer,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
{:ok, token} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash) {:ok, token} = Chain.token_from_address_hash(token_transfer.token_contract_address_hash)
@ -1019,11 +1086,29 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block(third_block) |> with_block(third_block)
second_block_token_transfers = insert_list(2, :token_transfer, from_address: address, transaction: transaction2) second_block_token_transfers =
insert_list(2, :token_transfer,
from_address: address,
transaction: transaction2,
block: transaction2.block,
block_number: transaction2.block_number
)
first_block_token_transfers = insert_list(2, :token_transfer, from_address: address, transaction: transaction3) first_block_token_transfers =
insert_list(2, :token_transfer,
from_address: address,
transaction: transaction3,
block: transaction3.block,
block_number: transaction3.block_number
)
third_block_token_transfers = insert_list(2, :token_transfer, from_address: address, transaction: transaction1) third_block_token_transfers =
insert_list(2, :token_transfer,
from_address: address,
transaction: transaction1,
block: transaction1.block,
block_number: transaction1.block_number
)
options1 = %{page_number: 1, page_size: 2} options1 = %{page_number: 1, page_size: 2}
@ -1076,7 +1161,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block(block) |> with_block(block)
insert(:token_transfer, from_address: address, transaction: transaction) insert(:token_transfer,
from_address: address,
transaction: transaction,
block: block,
block_number: block.number
)
end end
options = %{ options = %{
@ -1105,7 +1195,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block(block) |> with_block(block)
insert(:token_transfer, from_address: address, transaction: transaction) insert(:token_transfer,
from_address: address,
transaction: transaction,
block: block,
block_number: block.number
)
end end
options = %{start_block: third_block.number} options = %{start_block: third_block.number}
@ -1131,7 +1226,12 @@ defmodule Explorer.EtherscanTest do
|> insert() |> insert()
|> with_block(block) |> with_block(block)
insert(:token_transfer, from_address: address, transaction: transaction) insert(:token_transfer,
from_address: address,
transaction: transaction,
block: block,
block_number: block.number
)
end end
options = %{end_block: second_block.number} options = %{end_block: second_block.number}
@ -1160,7 +1260,14 @@ defmodule Explorer.EtherscanTest do
|> with_block() |> with_block()
insert(:token_transfer, from_address: address, transaction: transaction) insert(:token_transfer, from_address: address, transaction: transaction)
insert(:token_transfer, from_address: address, token_contract_address: contract_address, transaction: transaction)
insert(:token_transfer,
from_address: address,
token_contract_address: contract_address,
transaction: transaction,
block: transaction.block,
block_number: transaction.block_number
)
[found_token_transfer] = Etherscan.list_token_transfers(address.hash, contract_address.hash) [found_token_transfer] = Etherscan.list_token_transfers(address.hash, contract_address.hash)

@ -92,9 +92,15 @@ defmodule Explorer.GraphQLTest do
describe "get_internal_transaction/1" do describe "get_internal_transaction/1" do
test "returns existing internal transaction" do test "returns existing internal transaction" do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
internal_transaction = insert(:internal_transaction, transaction: transaction, index: 0) internal_transaction =
insert(:internal_transaction,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
clauses = %{transaction_hash: transaction.hash, index: internal_transaction.index} clauses = %{transaction_hash: transaction.hash, index: internal_transaction.index}
@ -117,11 +123,23 @@ defmodule Explorer.GraphQLTest do
describe "transcation_to_internal_transactions_query/1" do describe "transcation_to_internal_transactions_query/1" do
test "with transaction with one internal transaction" do test "with transaction with one internal transaction" do
transaction1 = insert(:transaction) transaction1 = insert(:transaction) |> with_block()
transaction2 = insert(:transaction) transaction2 = insert(:transaction) |> with_block()
internal_transaction = insert(:internal_transaction_create, transaction: transaction1, index: 0) internal_transaction =
insert(:internal_transaction_create, transaction: transaction2, index: 0) insert(:internal_transaction_create,
transaction: transaction1,
index: 0,
block_hash: transaction1.block_hash,
block_index: 0
)
insert(:internal_transaction_create,
transaction: transaction2,
index: 0,
block_hash: transaction2.block_hash,
block_index: 0
)
[found_internal_transaction] = [found_internal_transaction] =
transaction1 transaction1
@ -133,14 +151,24 @@ defmodule Explorer.GraphQLTest do
end end
test "with transaction with multiple internal transactions" do test "with transaction with multiple internal transactions" do
transaction1 = insert(:transaction) transaction1 = insert(:transaction) |> with_block()
transaction2 = insert(:transaction) transaction2 = insert(:transaction) |> with_block()
for index <- 0..2 do for index <- 0..2 do
insert(:internal_transaction_create, transaction: transaction1, index: index) insert(:internal_transaction_create,
transaction: transaction1,
index: index,
block_hash: transaction1.block_hash,
block_index: index
)
end end
insert(:internal_transaction_create, transaction: transaction2, index: 0) insert(:internal_transaction_create,
transaction: transaction2,
index: 0,
block_hash: transaction2.block_hash,
block_index: 0
)
found_internal_transactions = found_internal_transactions =
transaction1 transaction1
@ -155,11 +183,28 @@ defmodule Explorer.GraphQLTest do
end end
test "orders internal transactions by ascending index" do test "orders internal transactions by ascending index" do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
insert(:internal_transaction_create, transaction: transaction, index: 2) insert(:internal_transaction_create,
insert(:internal_transaction_create, transaction: transaction, index: 0) transaction: transaction,
insert(:internal_transaction_create, transaction: transaction, index: 1) index: 2,
block_hash: transaction.block_hash,
block_index: 2
)
insert(:internal_transaction_create,
transaction: transaction,
index: 0,
block_hash: transaction.block_hash,
block_index: 0
)
insert(:internal_transaction_create,
transaction: transaction,
index: 1,
block_hash: transaction.block_hash,
block_index: 1
)
found_internal_transactions = found_internal_transactions =
transaction transaction

@ -10,7 +10,7 @@ defmodule Explorer.RepoTest do
describe "safe_insert_all/3" do describe "safe_insert_all/3" do
test "inserting duplicate rows in one chunk is logged before re-raising exception" do test "inserting duplicate rows in one chunk is logged before re-raising exception" do
transaction = insert(:transaction) transaction = insert(:transaction) |> with_block()
params = params =
params_for( params_for(
@ -20,6 +20,8 @@ defmodule Explorer.RepoTest do
transaction_hash: transaction.hash, transaction_hash: transaction.hash,
index: 0, index: 0,
block_number: 35, block_number: 35,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: 0 transaction_index: 0
) )
@ -33,7 +35,7 @@ defmodule Explorer.RepoTest do
Repo.safe_insert_all( Repo.safe_insert_all(
InternalTransaction, InternalTransaction,
[timestamped_changes, timestamped_changes], [timestamped_changes, timestamped_changes],
conflict_target: [:transaction_hash, :index], conflict_target: [:block_hash, :block_index],
on_conflict: :replace_all on_conflict: :replace_all
) )
end end
@ -42,7 +44,7 @@ defmodule Explorer.RepoTest do
assert log =~ "Chunk:\n" assert log =~ "Chunk:\n"
assert log =~ "index: 0" assert log =~ "index: 0"
assert log =~ "Options:\n\n[conflict_target: [:transaction_hash, :index], on_conflict: :replace_all]\n\n" assert log =~ "Options:\n\n[conflict_target: [:block_hash, :block_index], on_conflict: :replace_all]\n\n"
assert log =~ assert log =~
"Exception:\n\n** (Postgrex.Error) ERROR 21000 (cardinality_violation) ON CONFLICT DO UPDATE command cannot affect row a second time\n" "Exception:\n\n** (Postgrex.Error) ERROR 21000 (cardinality_violation) ON CONFLICT DO UPDATE command cannot affect row a second time\n"

@ -23,6 +23,7 @@ defmodule Explorer.Factory do
Hash, Hash,
InternalTransaction, InternalTransaction,
Log, Log,
PendingBlockOperation,
SmartContract, SmartContract,
Token, Token,
TokenTransfer, TokenTransfer,
@ -226,25 +227,20 @@ defmodule Explorer.Factory do
cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000) cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000)
gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000) gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000)
internal_transactions_indexed_at = collated_params[:internal_transactions_indexed_at]
status = Keyword.get(collated_params, :status, Enum.random([:ok, :error])) status = Keyword.get(collated_params, :status, Enum.random([:ok, :error]))
error = error = (status == :error && collated_params[:error]) || nil
if internal_transactions_indexed_at != nil && status == :error do
collated_params[:error] || "Something really bad happened"
else
nil
end
transaction transaction
|> Transaction.changeset(%{ |> Transaction.changeset(%{
block_hash: block_hash, block_hash: block_hash,
block_number: block_number, block_number: block_number,
cumulative_gas_used: cumulative_gas_used, cumulative_gas_used: cumulative_gas_used,
from_address_hash: transaction.from_address_hash,
to_address_hash: transaction.to_address_hash,
error: error, error: error,
gas_used: gas_used, gas_used: gas_used,
index: next_transaction_index, index: next_transaction_index,
internal_transactions_indexed_at: internal_transactions_indexed_at,
status: status status: status
}) })
|> Repo.update!() |> Repo.update!()
@ -290,6 +286,14 @@ defmodule Explorer.Factory do
data data
end end
def pending_block_operation_factory do
%PendingBlockOperation{
# caller MUST supply block
# all operations will default to false
fetch_internal_transactions: false
}
end
def internal_transaction_factory() do def internal_transaction_factory() do
gas = Enum.random(21_000..100_000) gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas) gas_used = Enum.random(0..gas)
@ -306,6 +310,8 @@ defmodule Explorer.Factory do
trace_address: [], trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra # caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction # transaction
# caller MUST supply `block_hash` (usually the same as the transaction's)
# caller MUST supply `block_index`
type: :call, type: :call,
value: sequence("internal_transaction_value", &Decimal.new(&1)) value: sequence("internal_transaction_value", &Decimal.new(&1))
} }
@ -328,6 +334,8 @@ defmodule Explorer.Factory do
trace_address: [], trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra # caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction # transaction
# caller MUST supply `block_hash` (usually the same as the transaction's)
# caller MUST supply `block_index`
type: :create, type: :create,
value: sequence("internal_transaction_value", &Decimal.new(&1)) value: sequence("internal_transaction_value", &Decimal.new(&1))
} }
@ -345,8 +353,12 @@ defmodule Explorer.Factory do
end end
def log_factory do def log_factory do
block = build(:block)
%Log{ %Log{
address: build(:address), address: build(:address),
block: block,
block_number: block.number,
data: data(:log_data), data: data(:log_data),
first_topic: nil, first_topic: nil,
fourth_topic: nil, fourth_topic: nil,
@ -417,6 +429,7 @@ defmodule Explorer.Factory do
insert(:token, contract_address: token_address) insert(:token, contract_address: token_address)
%TokenTransfer{ %TokenTransfer{
block: build(:block),
amount: Decimal.new(1), amount: Decimal.new(1),
block_number: block_number(), block_number: block_number(),
from_address: from_address, from_address: from_address,

@ -12,7 +12,7 @@ defmodule Indexer.Block.Catchup.Fetcher do
async_import_block_rewards: 1, async_import_block_rewards: 1,
async_import_coin_balances: 2, async_import_coin_balances: 2,
async_import_created_contract_codes: 1, async_import_created_contract_codes: 1,
async_import_internal_transactions: 2, async_import_internal_transactions: 1,
async_import_replaced_transactions: 1, async_import_replaced_transactions: 1,
async_import_tokens: 1, async_import_tokens: 1,
async_import_token_balances: 1, async_import_token_balances: 1,
@ -122,7 +122,7 @@ defmodule Indexer.Block.Catchup.Fetcher do
@async_import_remaining_block_data_options ~w(address_hash_to_fetched_balance_block_number)a @async_import_remaining_block_data_options ~w(address_hash_to_fetched_balance_block_number)a
@impl Block.Fetcher @impl Block.Fetcher
def import(%Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments}, options) when is_map(options) do def import(_block_fetcher, options) when is_map(options) do
{async_import_remaining_block_data_options, options_with_block_rewards_errors} = {async_import_remaining_block_data_options, options_with_block_rewards_errors} =
Map.split(options, @async_import_remaining_block_data_options) Map.split(options, @async_import_remaining_block_data_options)
@ -135,8 +135,7 @@ defmodule Indexer.Block.Catchup.Fetcher do
with {:import, {:ok, imported} = ok} <- {:import, Chain.import(full_chain_import_options)} do with {:import, {:ok, imported} = ok} <- {:import, Chain.import(full_chain_import_options)} do
async_import_remaining_block_data( async_import_remaining_block_data(
imported, imported,
Map.put(async_import_remaining_block_data_options, :block_rewards, %{errors: block_reward_errors}), Map.put(async_import_remaining_block_data_options, :block_rewards, %{errors: block_reward_errors})
json_rpc_named_arguments
) )
ok ok
@ -145,13 +144,12 @@ defmodule Indexer.Block.Catchup.Fetcher do
defp async_import_remaining_block_data( defp async_import_remaining_block_data(
imported, imported,
%{block_rewards: %{errors: block_reward_errors}} = options, %{block_rewards: %{errors: block_reward_errors}} = options
json_rpc_named_arguments
) do ) do
async_import_block_rewards(block_reward_errors) async_import_block_rewards(block_reward_errors)
async_import_coin_balances(imported, options) async_import_coin_balances(imported, options)
async_import_created_contract_codes(imported) async_import_created_contract_codes(imported)
async_import_internal_transactions(imported, Keyword.get(json_rpc_named_arguments, :variant)) async_import_internal_transactions(imported)
async_import_tokens(imported) async_import_tokens(imported)
async_import_token_balances(imported) async_import_token_balances(imported)
async_import_uncles(imported) async_import_uncles(imported)

@ -10,7 +10,6 @@ defmodule Indexer.Block.Fetcher do
import EthereumJSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} alias Explorer.Chain.{Address, Block, Hash, Import, Transaction}
alias Explorer.Chain.Cache.Blocks, as: BlocksCache alias Explorer.Chain.Cache.Blocks, as: BlocksCache
alias Explorer.Chain.Cache.{Accounts, BlockNumber, PendingTransactions, Transactions, Uncles} alias Explorer.Chain.Cache.{Accounts, BlockNumber, PendingTransactions, Transactions, Uncles}
@ -71,7 +70,6 @@ defmodule Indexer.Block.Fetcher do
@receipts_batch_size 250 @receipts_batch_size 250
@receipts_concurrency 10 @receipts_concurrency 10
@geth_block_limit 128
@doc false @doc false
def default_receipts_batch_size, do: @receipts_batch_size def default_receipts_batch_size, do: @receipts_batch_size
@ -264,14 +262,10 @@ defmodule Indexer.Block.Fetcher do
block_number: block_number, block_number: block_number,
hash: hash, hash: hash,
created_contract_address_hash: %Hash{} = created_contract_address_hash, created_contract_address_hash: %Hash{} = created_contract_address_hash,
created_contract_code_indexed_at: nil, created_contract_code_indexed_at: nil
internal_transactions_indexed_at: nil
} -> } ->
[%{block_number: block_number, hash: hash, created_contract_address_hash: created_contract_address_hash}] [%{block_number: block_number, hash: hash, created_contract_address_hash: created_contract_address_hash}]
%Transaction{internal_transactions_indexed_at: %DateTime{}} ->
[]
%Transaction{created_contract_address_hash: nil} -> %Transaction{created_contract_address_hash: nil} ->
[] []
end) end)
@ -280,30 +274,13 @@ defmodule Indexer.Block.Fetcher do
def async_import_created_contract_codes(_), do: :ok def async_import_created_contract_codes(_), do: :ok
def async_import_internal_transactions(%{blocks: blocks}, EthereumJSONRPC.Parity) do def async_import_internal_transactions(%{blocks: blocks}) do
blocks blocks
|> Enum.map(fn %Block{number: block_number} -> %{number: block_number} end) |> Enum.map(fn %Block{number: block_number} -> block_number end)
|> InternalTransaction.async_block_fetch(10_000)
end
def async_import_internal_transactions(%{transactions: transactions}, EthereumJSONRPC.Geth) do
max_block_number = Chain.fetch_max_block_number()
transactions
|> Enum.flat_map(fn
%Transaction{block_number: block_number, index: index, hash: hash, internal_transactions_indexed_at: nil} ->
[%{block_number: block_number, index: index, hash: hash}]
%Transaction{internal_transactions_indexed_at: %DateTime{}} ->
[]
end)
|> Enum.filter(fn %{block_number: block_number} ->
max_block_number - block_number < @geth_block_limit
end)
|> InternalTransaction.async_fetch(10_000) |> InternalTransaction.async_fetch(10_000)
end end
def async_import_internal_transactions(_, _), do: :ok def async_import_internal_transactions(_), do: :ok
def async_import_tokens(%{tokens: tokens}) do def async_import_tokens(%{tokens: tokens}) do
tokens tokens

@ -15,7 +15,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
only: [ only: [
async_import_block_rewards: 1, async_import_block_rewards: 1,
async_import_created_contract_codes: 1, async_import_created_contract_codes: 1,
async_import_internal_transactions: 2, async_import_internal_transactions: 1,
async_import_replaced_transactions: 1, async_import_replaced_transactions: 1,
async_import_tokens: 1, async_import_tokens: 1,
async_import_token_balances: 1, async_import_token_balances: 1,
@ -183,7 +183,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
@impl Block.Fetcher @impl Block.Fetcher
def import( def import(
%Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} = block_fetcher, block_fetcher,
%{ %{
address_coin_balances: %{params: address_coin_balances_params}, address_coin_balances: %{params: address_coin_balances_params},
address_hash_to_fetched_balance_block_number: address_hash_to_block_number, address_hash_to_fetched_balance_block_number: address_hash_to_block_number,
@ -209,8 +209,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
{:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do {:import, {:ok, imported} = ok} <- {:import, Chain.import(chain_import_options)} do
async_import_remaining_block_data( async_import_remaining_block_data(
imported, imported,
%{block_rewards: %{errors: block_reward_errors}}, %{block_rewards: %{errors: block_reward_errors}}
json_rpc_named_arguments
) )
Accounts.drop(imported[:addresses]) Accounts.drop(imported[:addresses])
@ -381,12 +380,11 @@ defmodule Indexer.Block.Realtime.Fetcher do
defp async_import_remaining_block_data( defp async_import_remaining_block_data(
imported, imported,
%{block_rewards: %{errors: block_reward_errors}}, %{block_rewards: %{errors: block_reward_errors}}
json_rpc_named_arguments
) do ) do
async_import_block_rewards(block_reward_errors) async_import_block_rewards(block_reward_errors)
async_import_created_contract_codes(imported) async_import_created_contract_codes(imported)
async_import_internal_transactions(imported, Keyword.get(json_rpc_named_arguments, :variant)) async_import_internal_transactions(imported)
async_import_tokens(imported) async_import_tokens(imported)
async_import_token_balances(imported) async_import_token_balances(imported)
async_import_token_instances(imported) async_import_token_instances(imported)

@ -13,7 +13,7 @@ defmodule Indexer.Fetcher.InternalTransaction do
import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2] import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2]
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Block
alias Explorer.Chain.Cache.{Accounts, Blocks} alias Explorer.Chain.Cache.{Accounts, Blocks}
alias Indexer.{BufferedTask, Tracer} alias Indexer.{BufferedTask, Tracer}
alias Indexer.Transform.Addresses alias Indexer.Transform.Addresses
@ -47,34 +47,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
*Note*: The internal transactions for individual transactions cannot be paginated, *Note*: The internal transactions for individual transactions cannot be paginated,
so the total number of internal transactions that could be produced is unknown. so the total number of internal transactions that could be produced is unknown.
""" """
@spec async_fetch([%{required(:block_number) => Block.block_number(), required(:hash) => Hash.Full.t()}]) :: :ok @spec async_fetch([Block.block_number()]) :: :ok
def async_fetch(transactions_fields, timeout \\ 5000) when is_list(transactions_fields) do def async_fetch(block_numbers, timeout \\ 5000) when is_list(block_numbers) do
entries = Enum.map(transactions_fields, &entry/1) BufferedTask.buffer(__MODULE__, block_numbers, timeout)
BufferedTask.buffer(__MODULE__, entries, timeout)
end
@doc """
Asynchronously fetches internal transactions.
## Limiting Upstream Load
Internal transactions are an expensive upstream operation. The number of
results to fetch is configured by `@max_batch_size` and represents the number
of transaction hashes to request internal transactions in a single JSONRPC
request. Defaults to `#{@max_batch_size}`.
The `@max_concurrency` attribute configures the number of concurrent requests
of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@max_concurrency}`.
*Note*: The internal transactions for individual transactions cannot be paginated,
so the total number of internal transactions that could be produced is unknown.
"""
@spec async_block_fetch([%{required(:block_number) => Block.block_number()}]) :: :ok
def async_block_fetch(transactions_fields, timeout \\ 5000) when is_list(transactions_fields) do
entries = Enum.map(transactions_fields, &block_entry/1)
BufferedTask.buffer(__MODULE__, entries, timeout)
end end
@doc false @doc false
@ -96,52 +71,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
end end
@impl BufferedTask @impl BufferedTask
def init(initial, reducer, json_rpc_named_arguments) do def init(initial, reducer, _json_rpc_named_arguments) do
{:ok, final} = {:ok, final} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do Chain.stream_blocks_with_unfetched_internal_transactions(initial, fn block_number, acc ->
EthereumJSONRPC.Parity -> reducer.(block_number, acc)
Chain.stream_blocks_with_unfetched_internal_transactions( end)
[:number],
initial,
fn block_fields, acc ->
block_fields
|> block_entry()
|> reducer.(acc)
end
)
_ ->
Chain.stream_transactions_with_unfetched_internal_transactions(
[:block_number, :hash, :index],
initial,
fn transaction_fields, acc ->
transaction_fields
|> entry()
|> reducer.(acc)
end
)
end
final final
end end
defp entry(%{block_number: block_number, hash: %Hash{bytes: bytes}, index: index}) when is_integer(block_number) do defp params(%{block_number: block_number, hash: hash, index: index}) when is_integer(block_number) do
{block_number, bytes, index}
end
defp params({block_number, hash_bytes, index}) when is_integer(block_number) do
{:ok, hash} = Hash.Full.cast(hash_bytes)
%{block_number: block_number, hash_data: to_string(hash), transaction_index: index} %{block_number: block_number, hash_data: to_string(hash), transaction_index: index}
end end
defp block_entry(%{number: block_number}) when is_integer(block_number) do
block_number
end
defp block_params(block_number) when is_integer(block_number) do
%{number: block_number}
end
@impl BufferedTask @impl BufferedTask
@decorate trace( @decorate trace(
name: "fetch", name: "fetch",
@ -149,52 +91,61 @@ defmodule Indexer.Fetcher.InternalTransaction do
service: :indexer, service: :indexer,
tracer: Tracer tracer: Tracer
) )
def run(entries, json_rpc_named_arguments) do def run(block_numbers, json_rpc_named_arguments) do
variant = Keyword.fetch!(json_rpc_named_arguments, :variant) unique_numbers = Enum.uniq(block_numbers)
unique_entries = unique_entries(entries, variant) unique_numbers_count = Enum.count(unique_numbers)
Logger.metadata(count: unique_numbers_count)
unique_entries_count = Enum.count(unique_entries) Logger.debug("fetching internal transactions for blocks")
Logger.metadata(count: unique_entries_count)
Logger.debug("fetching internal transactions for transactions") json_rpc_named_arguments
|> Keyword.fetch!(:variant)
variant
|> case do |> case do
EthereumJSONRPC.Parity -> EthereumJSONRPC.Parity ->
unique_entries EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
|> EthereumJSONRPC.fetch_block_internal_transactions(json_rpc_named_arguments)
_ -> _ ->
unique_entries fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments)
|> Enum.map(&params/1)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
end end
|> case do |> case do
{:ok, internal_transactions_params} -> {:ok, internal_transactions_params} ->
import_internal_transaction(internal_transactions_params, json_rpc_named_arguments, unique_entries) import_internal_transaction(internal_transactions_params, unique_numbers)
{:error, reason} -> {:error, reason} ->
Logger.error(fn -> ["failed to fetch internal transactions for transactions: ", inspect(reason)] end, Logger.error(fn -> ["failed to fetch internal transactions for blocks: ", inspect(reason)] end,
error_count: unique_entries_count error_count: unique_numbers_count
) )
# re-queue the de-duped entries # re-queue the de-duped entries
{:retry, unique_entries} {:retry, unique_numbers}
:ignore -> :ignore ->
:ok :ok
end end
end end
defp import_internal_transaction(internal_transactions_params, json_rpc_named_arguments, unique_entries) do defp fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments) do
internal_transactions_indexed_at_blocks = Enum.reduce(unique_numbers, {:ok, []}, fn
case Keyword.fetch!(json_rpc_named_arguments, :variant) do block_number, {:ok, acc_list} ->
EthereumJSONRPC.Parity -> Enum.map(unique_entries, &block_params/1) block_number
_ -> [] |> Chain.get_transactions_of_block_number()
|> Enum.map(&params(&1))
|> case do
[] -> {:ok, []}
transactions -> EthereumJSONRPC.fetch_internal_transactions(transactions, json_rpc_named_arguments)
end
|> case do
{:ok, internal_transactions} -> {:ok, internal_transactions ++ acc_list}
error_or_ignore -> error_or_ignore
end end
unique_entries_count = Enum.count(unique_entries) _, error_or_ignore ->
error_or_ignore
end)
end
defp import_internal_transaction(internal_transactions_params, unique_numbers) do
internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params)
addresses_params = addresses_params =
@ -207,14 +158,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
{hash, block_number} {hash, block_number}
end) end)
empty_block_numbers =
unique_numbers
|> MapSet.new()
|> MapSet.difference(MapSet.new(internal_transactions_params_without_failed_creations, & &1.block_number))
|> Enum.map(&%{block_number: &1})
internal_transactions_and_empty_block_numbers =
internal_transactions_params_without_failed_creations ++ empty_block_numbers
imports = imports =
Chain.import(%{ Chain.import(%{
addresses: %{params: addresses_params}, addresses: %{params: addresses_params},
internal_transactions: %{params: internal_transactions_params_without_failed_creations}, internal_transactions: %{params: internal_transactions_and_empty_block_numbers, with: :blockless_changeset},
internal_transactions_indexed_at_blocks: %{
params: internal_transactions_indexed_at_blocks,
with: :number_only_changeset
},
timeout: :infinity timeout: :infinity
}) })
@ -231,60 +187,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
Logger.error( Logger.error(
fn -> fn ->
[ [
"failed to import internal transactions for transactions: ", "failed to import internal transactions for blocks: ",
inspect(reason) inspect(reason)
] ]
end, end,
step: step, step: step,
error_count: unique_entries_count error_count: Enum.count(unique_numbers)
) )
# re-queue the de-duped entries # re-queue the de-duped entries
{:retry, unique_entries} {:retry, unique_numbers}
end
end
defp unique_entries(entries, EthereumJSONRPC.Parity), do: Enum.uniq(entries)
# Protection and improved reporting for https://github.com/poanetwork/blockscout/issues/289
defp unique_entries(entries, _) do
entries_by_hash_bytes = Enum.group_by(entries, &elem(&1, 1))
if map_size(entries_by_hash_bytes) < length(entries) do
{unique_entries, duplicate_entries} =
entries_by_hash_bytes
|> Map.values()
|> uniques_and_duplicates()
Logger.error(fn ->
duplicate_entries
|> Stream.with_index()
|> Enum.reduce(
["Duplicate entries being used to fetch internal transactions:\n"],
fn {entry, index}, acc ->
[acc, " ", to_string(index + 1), ". ", inspect(entry), "\n"]
end
)
end)
unique_entries
else
entries
end end
end end
defp uniques_and_duplicates(groups) do
Enum.reduce(groups, {[], []}, fn group, {acc_uniques, acc_duplicates} ->
case group do
[unique] ->
{[unique | acc_uniques], acc_duplicates}
[unique | _] = duplicates ->
{[unique | acc_uniques], duplicates ++ acc_duplicates}
end
end)
end
defp remove_failed_creations(internal_transactions_params) do defp remove_failed_creations(internal_transactions_params) do
internal_transactions_params internal_transactions_params
|> Enum.map(fn internal_transaction_params -> |> Enum.map(fn internal_transaction_params ->

@ -0,0 +1,45 @@
defmodule Indexer.PendingOpsCleaner do
@moduledoc """
Peiodically cleans non-consensus pending ops.
"""
use GenServer
require Logger
alias Explorer.Chain
@interval :timer.minutes(60)
def start_link([init_opts, gen_server_opts]) do
start_link(init_opts, gen_server_opts)
end
def start_link(init_opts, gen_server_opts) do
GenServer.start_link(__MODULE__, init_opts, gen_server_opts)
end
def init(opts) do
interval = opts[:interval] || @interval
Process.send_after(self(), :clean_nonconsensus_pending_ops, interval)
{:ok, %{interval: interval}}
end
def handle_info(:clean_nonconsensus_pending_ops, %{interval: interval} = state) do
Logger.debug(fn -> "Cleaning non-consensus pending ops" end)
clean_nonconsensus_pending_ops()
Process.send_after(self(), :clean_nonconsensus_pending_ops, interval)
{:noreply, state}
end
defp clean_nonconsensus_pending_ops do
:ok = Chain.remove_nonconsensus_blocks_from_pending_ops()
Logger.debug(fn -> "Non-consensus pending ops are cleaned" end)
end
end

@ -5,7 +5,7 @@ defmodule Indexer.Supervisor do
use Supervisor use Supervisor
alias Indexer.Block alias Indexer.{Block, PendingOpsCleaner}
alias Indexer.Block.{Catchup, Realtime} alias Indexer.Block.{Catchup, Realtime}
alias Indexer.Fetcher.{ alias Indexer.Fetcher.{
@ -129,7 +129,8 @@ defmodule Indexer.Supervisor do
{UnclesWithoutIndex.Supervisor, {UnclesWithoutIndex.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{BlocksTransactionsMismatch.Supervisor, {BlocksTransactionsMismatch.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]} [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{PendingOpsCleaner, [[], []]}
], ],
strategy: :one_for_one strategy: :one_for_one
) )

@ -40,6 +40,7 @@ defmodule Indexer.Transform.TokenTransfers do
token_transfer = %{ token_transfer = %{
amount: Decimal.new(amount || 0), amount: Decimal.new(amount || 0),
block_number: log.block_number, block_number: log.block_number,
block_hash: log.block_hash,
log_index: log.index, log_index: log.index,
from_address_hash: truncate_address_hash(log.second_topic), from_address_hash: truncate_address_hash(log.second_topic),
to_address_hash: truncate_address_hash(log.third_topic), to_address_hash: truncate_address_hash(log.third_topic),
@ -64,6 +65,7 @@ defmodule Indexer.Transform.TokenTransfers do
token_transfer = %{ token_transfer = %{
block_number: log.block_number, block_number: log.block_number,
log_index: log.index, log_index: log.index,
block_hash: log.block_hash,
from_address_hash: truncate_address_hash(log.second_topic), from_address_hash: truncate_address_hash(log.second_topic),
to_address_hash: truncate_address_hash(log.third_topic), to_address_hash: truncate_address_hash(log.third_topic),
token_contract_address_hash: log.address_hash, token_contract_address_hash: log.address_hash,
@ -87,6 +89,7 @@ defmodule Indexer.Transform.TokenTransfers do
token_transfer = %{ token_transfer = %{
block_number: log.block_number, block_number: log.block_number,
block_hash: log.block_hash,
log_index: log.index, log_index: log.index,
from_address_hash: encode_address_hash(from_address_hash), from_address_hash: encode_address_hash(from_address_hash),
to_address_hash: encode_address_hash(to_address_hash), to_address_hash: encode_address_hash(to_address_hash),

@ -485,8 +485,7 @@ defmodule Indexer.Block.FetcherTest do
bytes: bytes:
<<76, 188, 236, 37, 153, 153, 224, 115, 252, 79, 176, 224, 228, 166, 18, 66, 94, 61, 115, <<76, 188, 236, 37, 153, 153, 224, 115, 252, 79, 176, 224, 228, 166, 18, 66, 94, 61, 115,
57, 47, 162, 37, 255, 36, 96, 161, 238, 171, 66, 99, 10>> 57, 47, 162, 37, 255, 36, 96, 161, 238, 171, 66, 99, 10>>
}, }
internal_transactions_indexed_at: nil
}, },
%Transaction{ %Transaction{
block_number: block_number, block_number: block_number,
@ -496,8 +495,7 @@ defmodule Indexer.Block.FetcherTest do
bytes: bytes:
<<240, 237, 34, 44, 16, 174, 248, 135, 4, 196, 15, 198, 34, 220, 218, 174, 13, 208, 242, <<240, 237, 34, 44, 16, 174, 248, 135, 4, 196, 15, 198, 34, 220, 218, 174, 13, 208, 242,
122, 154, 143, 4, 28, 171, 95, 190, 255, 254, 174, 75, 182>> 122, 154, 143, 4, 28, 171, 95, 190, 255, 254, 174, 75, 182>>
}, }
internal_transactions_indexed_at: nil
} }
] ]
}} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) }} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number)
@ -589,8 +587,7 @@ defmodule Indexer.Block.FetcherTest do
bytes: bytes:
<<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35,
77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>>
}, }
internal_transactions_indexed_at: nil
} }
] ]
}, },

@ -2,10 +2,10 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
use EthereumJSONRPC.Case, async: false use EthereumJSONRPC.Case, async: false
use Explorer.DataCase use Explorer.DataCase
import ExUnit.CaptureLog
import Mox import Mox
alias Explorer.Chain.{Address, Hash, Transaction} alias Explorer.Chain
alias Explorer.Chain.PendingBlockOperation
alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction} alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction}
# MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow` # MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow`
@ -99,7 +99,8 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end end
block_number = 1_000_006 block_number = 1_000_006
insert(:block, number: block_number) block = insert(:block, number: block_number)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
assert :ok = InternalTransaction.run([block_number], json_rpc_named_arguments) assert :ok = InternalTransaction.run([block_number], json_rpc_named_arguments)
@ -111,54 +112,11 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end end
describe "init/2" do describe "init/2" do
test "does not buffer pending transactions", %{json_rpc_named_arguments: json_rpc_named_arguments} do
insert(:transaction)
assert InternalTransaction.init(
[],
fn hash_string, acc -> [hash_string | acc] end,
json_rpc_named_arguments
) == []
end
@tag :no_parity
test "buffers collated transactions with unfetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
block = insert(:block)
collated_unfetched_transaction =
:transaction
|> insert()
|> with_block(block)
assert InternalTransaction.init(
[],
fn hash_string, acc -> [hash_string | acc] end,
json_rpc_named_arguments
) == [{block.number, collated_unfetched_transaction.hash.bytes, collated_unfetched_transaction.index}]
end
@tag :no_parity
test "does not buffer collated transactions with fetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
:transaction
|> insert()
|> with_block(internal_transactions_indexed_at: DateTime.utc_now())
assert InternalTransaction.init(
[],
fn hash_string, acc -> [hash_string | acc] end,
json_rpc_named_arguments
) == []
end
@tag :no_geth
test "buffers blocks with unfetched internal transactions", %{ test "buffers blocks with unfetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } do
block = insert(:block) block = insert(:block)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
assert InternalTransaction.init( assert InternalTransaction.init(
[], [],
@ -171,7 +129,8 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
test "does not buffer blocks with fetched internal transactions", %{ test "does not buffer blocks with fetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } do
insert(:block, internal_transactions_indexed_at: DateTime.utc_now()) block = insert(:block)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: false)
assert InternalTransaction.init( assert InternalTransaction.init(
[], [],
@ -182,135 +141,152 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end end
describe "run/2" do describe "run/2" do
@tag :no_parity test "handles empty block numbers", %{
test "duplicate transaction hashes are logged", %{json_rpc_named_arguments: json_rpc_named_arguments} do json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> case Keyword.fetch!(json_rpc_named_arguments, :variant) do
{:ok, [%{id: 0, result: %{"trace" => []}}]} EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id}], _options ->
{:ok,
[
%{
id: id,
result: []
}
]}
end) end)
EthereumJSONRPC.Geth ->
# do nothing, this block has no transactions, so Geth shouldn't query
:ok
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
end end
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) block = insert(:block)
block_hash = block.hash
insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true)
%Transaction{hash: %Hash{bytes: bytes}} = assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
log = assert :ok == InternalTransaction.run([block.number], json_rpc_named_arguments)
capture_log(fn ->
InternalTransaction.run(
[
{1, bytes, 0},
{1, bytes, 0}
],
json_rpc_named_arguments
)
end)
assert log =~ assert nil == Repo.get(PendingBlockOperation, block_hash)
"""
Duplicate entries being used to fetch internal transactions:
1. {1, <<3, 205, 88, 153, 166, 59, 111, 98, 34, 175, 218, 135, 5, 208, 89, 253, 90, 125, 18, 107, 202, 190, 150, 47, 182, 84, 217, 115, 110, 107, 202, 250>>, 0}
2. {1, <<3, 205, 88, 153, 166, 59, 111, 98, 34, 175, 218, 135, 5, 208, 89, 253, 90, 125, 18, 107, 202, 190, 150, 47, 182, 84, 217, 115, 110, 107, 202, 250>>, 0}
"""
end end
@tag :no_parity test "handles blocks with transactions correctly", %{
test "internal transactions with failed parent does not create a new address", %{
json_rpc_named_arguments: json_rpc_named_arguments json_rpc_named_arguments: json_rpc_named_arguments
} do } do
block = insert(:block)
transaction = insert(:transaction) |> with_block(block)
block_hash = block.hash
insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true)
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "trace_replayBlockTransactions"}], _options ->
{:ok, {:ok,
[ [
%{ %{
id: 0, id: id,
jsonrpc: "2.0", result: [
result: %{ %{
"output" => "0x", "output" => "0x",
"stateDiff" => nil, "stateDiff" => nil,
"trace" => [ "trace" => [
%{ %{
"action" => %{ "action" => %{
"callType" => "call", "callType" => "call",
"from" => "0xc73add416e2119d20ce80e0904fc1877e33ef246", "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4",
"gas" => "0x13388", "gas" => "0x8600",
"input" => "0xc793bf97", "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008",
"to" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340", "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c",
"value" => "0x0" "value" => "0x174876e800"
}, },
"error" => "Reverted", "result" => %{"gasUsed" => "0x7d37", "output" => "0x"},
"subtraces" => 1, "subtraces" => 0,
"traceAddress" => [], "traceAddress" => [],
"type" => "call" "type" => "call"
},
%{
"action" => %{
"from" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340",
"gas" => "0xb2ab",
"init" =>
"0x608060405234801561001057600080fd5b5060d38061001f6000396000f3fe6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"value" => "0x0"
},
"result" => %{
"address" => "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75",
"code" =>
"0x6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"gasUsed" => "0xa535"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "create"
} }
], ],
"transactionHash" => transaction.hash,
"vmTrace" => nil "vmTrace" => nil
} }
]
} }
]} ]}
end) end)
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) EthereumJSONRPC.Geth ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "debug_traceTransaction"}], _options ->
{:ok,
[
%{
id: id,
result: [
%{
"blockNumber" => block.number,
"transactionIndex" => 0,
"transactionHash" => transaction.hash,
"index" => 0,
"traceAddress" => [],
"type" => "call",
"callType" => "call",
"from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4",
"to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c",
"gas" => "0x8600",
"gasUsed" => "0x7d37",
"input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008",
"output" => "0x",
"value" => "0x174876e800"
}
]
}
]}
end)
%Transaction{hash: %Hash{bytes: bytes}} = variant_name ->
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa") raise ArgumentError, "Unsupported variant name (#{variant_name})"
|> with_block() end
end
:ok = CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
InternalTransaction.run(
[
{7_202_692, bytes, 0}
],
json_rpc_named_arguments
)
address = "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75" assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
fetched_address = Repo.one(from(address in Address, where: address.hash == ^address)) assert :ok == InternalTransaction.run([block.number], json_rpc_named_arguments)
assert is_nil(fetched_address) assert nil == Repo.get(PendingBlockOperation, block_hash)
end
assert Repo.exists?(from(i in Chain.InternalTransaction, where: i.block_hash == ^block_hash))
end end
@tag :no_parity test "handles failure by retrying only unique numbers", %{
test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, [%{id: 0, error: %{code: -32602, message: "Invalid params"}}]} {:ok, [%{id: 0, error: %{code: -32602, message: "Invalid params"}}]}
end) end)
end end
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) block = insert(:block)
insert(:transaction) |> with_block(block)
block_hash = block.hash
insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true)
# not a real transaction hash, so that fetch fails assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
%Transaction{hash: %Hash{bytes: bytes}} =
insert(:transaction, hash: "0x0000000000000000000000000000000000000000000000000000000000000001")
assert InternalTransaction.run( assert {:retry, [block.number]} == InternalTransaction.run([block.number, block.number], json_rpc_named_arguments)
[
{1, bytes, 0}, assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
{1, bytes, 0}
],
json_rpc_named_arguments
) == {:retry, [{1, bytes, 0}]}
end end
end end
end end

@ -0,0 +1,34 @@
defmodule Indexer.PendingOpsCleanerTest do
use Explorer.DataCase
alias Explorer.Chain.PendingBlockOperation
alias Indexer.PendingOpsCleaner
describe "init/1" do
test "deletes non-consensus pending ops on init" do
block = insert(:block, consensus: false)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
assert Repo.one(from(block in PendingBlockOperation, where: block.block_hash == ^block.hash))
start_supervised!({PendingOpsCleaner, [[interval: :timer.seconds(1)], [name: :PendingOpsTest]]})
Process.sleep(2_000)
assert is_nil(Repo.one(from(block in PendingBlockOperation, where: block.block_hash == ^block.hash)))
end
test "re-schedules deletion" do
start_supervised!({PendingOpsCleaner, [[interval: :timer.seconds(1)], [name: :PendingOpsTest]]})
block = insert(:block, consensus: false)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
Process.sleep(2_000)
assert is_nil(Repo.one(from(block in PendingBlockOperation, where: block.block_hash == ^block.hash)))
end
end
end

@ -12,6 +12,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
%{ %{
address_hash: "0xf2eec76e45b328df99a34fa696320a262cb92154", address_hash: "0xf2eec76e45b328df99a34fa696320a262cb92154",
block_number: 3_530_917, block_number: 3_530_917,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000", data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: nil, fourth_topic: nil,
@ -23,6 +24,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
}, },
%{ %{
address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5", address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
block_number: 3_586_935, block_number: 3_586_935,
data: "0x", data: "0x",
first_topic: "0x55e10366a5f552746106978b694d7ef3bbddec06bd5f9b9d15ad46f475c653ef", first_topic: "0x55e10366a5f552746106978b694d7ef3bbddec06bd5f9b9d15ad46f475c653ef",
@ -36,6 +38,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
%{ %{
address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2", address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2",
block_number: 3_664_064, block_number: 3_664_064,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000", data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: "0x00000000000000000000000000000000000000000000000000000000000000b7", fourth_topic: "0x00000000000000000000000000000000000000000000000000000000000000b7",
@ -67,7 +70,8 @@ defmodule Indexer.Transform.TokenTransfersTest do
token_contract_address_hash: log_3.address_hash, token_contract_address_hash: log_3.address_hash,
token_id: 183, token_id: 183,
transaction_hash: log_3.transaction_hash, transaction_hash: log_3.transaction_hash,
token_type: "ERC-721" token_type: "ERC-721",
block_hash: log_3.block_hash
}, },
%{ %{
amount: Decimal.new(17_000_000_000_000_000_000), amount: Decimal.new(17_000_000_000_000_000_000),
@ -77,7 +81,8 @@ defmodule Indexer.Transform.TokenTransfersTest do
to_address_hash: truncated_hash(log_1.third_topic), to_address_hash: truncated_hash(log_1.third_topic),
token_contract_address_hash: log_1.address_hash, token_contract_address_hash: log_1.address_hash,
transaction_hash: log_1.transaction_hash, transaction_hash: log_1.transaction_hash,
token_type: "ERC-20" token_type: "ERC-20",
block_hash: log_1.block_hash
} }
] ]
} }
@ -97,6 +102,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
second_topic: nil, second_topic: nil,
third_topic: nil, third_topic: nil,
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
type: "mined" type: "mined"
} }
@ -113,6 +119,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
log_index: log.index, log_index: log.index,
from_address_hash: "0x58ab73cb79c8275628e0213742a85b163fe0a9fb", from_address_hash: "0x58ab73cb79c8275628e0213742a85b163fe0a9fb",
to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e", to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
token_contract_address_hash: log.address_hash, token_contract_address_hash: log.address_hash,
token_id: 14_939, token_id: 14_939,
transaction_hash: log.transaction_hash, transaction_hash: log.transaction_hash,
@ -128,6 +135,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
log = %{ log = %{
address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb", address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
block_number: 8_683_457, block_number: 8_683_457,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x", data: "0x",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: nil, fourth_topic: nil,

Loading…
Cancel
Save