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. 14
      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. 123
      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. 56
      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. 67
      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. 77
      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. 195
      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. 278
      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
### 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
- [#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
- [#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
- [#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
@ -12,7 +16,6 @@
- [#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
- [#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
- [#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
internal_transactions
|> Stream.map(
&(InternalTransaction
&(InternalTransaction.where_nonpending_block()
|> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index)
|> Repo.preload([:from_address, :to_address, transaction: :block]))
)

@ -134,7 +134,15 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> insert(from_address: address)
|> 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]})
@ -158,7 +166,14 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> insert(to_address: address)
|> 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]})
@ -186,7 +201,14 @@ defmodule BlockScoutWeb.AddressChannelTest do
|> with_block()
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]})

@ -35,13 +35,15 @@ defmodule BlockScoutWeb.AddressContractControllerTest 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)
transaction = insert(:transaction, from_address: address)
transaction = insert(:transaction, from_address: address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
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))

@ -44,7 +44,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
)
to_internal_transaction =
@ -53,7 +55,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address,
index: 2,
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"})
@ -83,7 +87,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
)
to_internal_transaction =
@ -92,7 +98,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address,
index: 2,
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"})
@ -125,7 +133,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
)
to_internal_transaction =
@ -134,7 +144,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
to_address: address,
index: 2,
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"})
@ -167,7 +179,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: 1,
block_number: transaction.block_number,
transaction_index: transaction.index
transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: 1
)
to_internal_transaction =
@ -177,7 +191,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
created_contract_address: address,
index: 2,
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"})
@ -226,7 +242,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: index,
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)
@ -239,7 +257,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: index,
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)
@ -252,7 +272,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: index,
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)
@ -265,7 +287,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: 11,
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 =
@ -304,7 +328,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
from_address: address,
index: index,
block_number: transaction.block_number,
transaction_index: transaction.index
transaction_index: transaction.index,
block_hash: transaction.block_hash,
block_index: index
)
end)
@ -335,7 +361,9 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do
:internal_transaction,
transaction: transaction,
from_address: address,
index: index
index: index,
block_hash: transaction.block_hash,
block_index: index
)
end)

@ -21,13 +21,15 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
test "successfully renders the page when the address is a contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
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)
@ -42,13 +44,15 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do
test "returns not found for an unverified contract", %{conn: conn} do
contract_address = insert(:contract_address)
transaction = insert(:transaction, from_address: contract_address)
transaction = insert(:transaction, from_address: contract_address) |> with_block()
insert(
:internal_transaction_create,
index: 0,
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))

@ -126,7 +126,9 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do
index: 0,
created_contract_address: address,
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"})

@ -1456,7 +1456,13 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction =
: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)
params = %{
@ -1505,7 +1511,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
transaction: transaction,
index: 0,
type: :reward,
error: "some error"
error: "some error",
block_hash: transaction.block_hash,
block_index: 0
]
insert(:internal_transaction_create, internal_transaction_details)
@ -1535,7 +1543,12 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> with_block()
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
params = %{
@ -1609,7 +1622,13 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction =
: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)
params = %{
@ -1661,7 +1680,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
transaction: transaction,
index: 0,
type: :reward,
error: "some error"
error: "some error",
block_hash: transaction.block_hash,
block_index: 0
]
insert(:internal_transaction_create, internal_transaction_details)
@ -1696,7 +1717,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
internal_transaction_details = %{
from_address: address,
transaction: transaction,
index: index
index: index,
block_hash: transaction.block_hash,
block_index: index
}
insert(:internal_transaction_create, internal_transaction_details)
@ -1790,7 +1813,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
insert(:token_transfer, %{
token_contract_address: token_address,
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)
@ -1820,7 +1845,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> insert()
|> 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)
params = %{
@ -1897,8 +1924,20 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
|> insert()
|> with_block()
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,
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 = %{
"module" => "account",

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

@ -76,7 +76,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
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)}])
@ -94,7 +94,7 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
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"]}])
@ -112,8 +112,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
block = insert(:block, number: 0)
transaction = insert(:transaction, from_address: address) |> with_block(block)
insert(:log, address: address, 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: "0x010101", first_topic: "0x01")
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"]]}])
@ -127,14 +127,15 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
test "paginates logs", %{conn: conn, api_params: api_params} do
contract_address = insert(:contract_address)
block = insert(:block)
transaction =
:transaction
|> insert(to_address: contract_address)
|> with_block()
|> with_block(block)
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"]]}])
@ -191,10 +192,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction,
data: "0x010101",
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"]}])
@ -219,7 +221,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction,
data: "0x010101",
first_topic: "0x01",
second_topic: "0x02"
second_topic: "0x02",
block: block
)
insert(:log,
@ -227,7 +230,8 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
transaction: transaction,
data: "0x020202",
first_topic: "0x01",
second_topic: "0x03"
second_topic: "0x03",
block: block
)
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)
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})

@ -243,8 +243,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
transaction_details = [
status: :error,
error: error,
internal_transactions_indexed_at: DateTime.utc_now()
error: error
]
transaction =
@ -256,7 +255,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
transaction: transaction,
index: 0,
type: :reward,
error: error
error: error,
block_hash: transaction.block_hash,
block_index: 0
]
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
transaction_details = [
status: :error,
error: nil,
internal_transactions_indexed_at: nil
error: nil
]
transaction =
@ -411,7 +411,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
address: address,
transaction: transaction,
first_topic: "first topic",
second_topic: "second topic"
second_topic: "second topic",
block: block,
block_number: block.number
)
end)
@ -486,7 +488,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
address: address,
transaction: transaction,
first_topic: "first topic",
second_topic: "second topic"
second_topic: "second topic",
block: block,
block_number: block.number
)
params = %{

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

@ -29,7 +29,13 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block()
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"})
@ -46,7 +52,13 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block()
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"})
@ -73,11 +85,24 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> insert()
|> 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 =
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)
conn =
@ -98,7 +123,14 @@ defmodule BlockScoutWeb.TransactionLogControllerTest do
|> with_block()
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"})

@ -46,11 +46,11 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
end
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)
@ -106,7 +106,9 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
insert(
:token_transfer,
transaction: transaction,
log_index: log_index
log_index: log_index,
block: transaction.block,
block_number: transaction.block_number
)
end)
@ -129,7 +131,9 @@ defmodule BlockScoutWeb.TransactionTokenTransferControllerTest do
insert(
:token_transfer,
transaction: transaction,
log_index: log_index
log_index: log_index,
block_number: transaction.block_number,
block: transaction.block
)
end)

@ -80,7 +80,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
test "see the contract creator and transaction links", %{session: session} do
address = insert(: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 =
insert(
@ -88,7 +88,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 1,
transaction: transaction,
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)
@ -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
lincoln = insert(:address)
contract = insert(:contract_address)
transaction = insert(:transaction)
transaction = insert(:transaction) |> with_block()
another_contract = insert(:contract_address)
insert(
@ -112,7 +114,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: lincoln,
to_address: contract,
created_contract_address: contract,
type: :call
type: :call,
block_hash: transaction.block_hash,
block_index: 1
)
internal_transaction =
@ -121,7 +125,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 2,
transaction: transaction,
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)
@ -208,7 +214,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
to_address: address,
index: 1,
block_number: 7000,
transaction_index: 1
transaction_index: 1,
block_hash: transaction.block_hash,
block_index: 1
)
insert(:internal_transaction,
@ -216,7 +224,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
from_address: address,
index: 2,
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}}
@ -251,7 +261,9 @@ defmodule BlockScoutWeb.ViewingAddressesTest do
index: 2,
from_address: addresses.lincoln,
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]})

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.ViewingAppTest do
alias BlockScoutWeb.Counters.BlocksIndexedCounter
alias Explorer.Counters.AddressesCounter
alias Explorer.{Repo}
alias Explorer.Chain.{Transaction}
alias Explorer.Chain.PendingBlockOperation
setup do
start_supervised!(AddressesCounter)
@ -29,6 +29,8 @@ defmodule BlockScoutWeb.ViewingAppTest do
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
|> AppPage.visit_page()
|> 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
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session
|> AppPage.visit_page()
|> 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
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session
|> AppPage.visit_page()
|> 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
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
session
|> AppPage.visit_page()
|> assert_has(AppPage.indexed_status("90% Blocks Indexed"))
@ -111,6 +119,10 @@ defmodule BlockScoutWeb.ViewingAppTest do
|> insert()
|> with_block(block)
block_hash = block.hash
insert(:pending_block_operation, block_hash: block_hash, fetch_internal_transactions: true)
BlocksIndexedCounter.calculate_blocks_indexed()
assert Decimal.cmp(Explorer.Chain.indexed_ratio(), 1) == :eq
@ -119,7 +131,10 @@ defmodule BlockScoutWeb.ViewingAppTest do
|> AppPage.visit_page()
|> 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()

@ -59,7 +59,12 @@ defmodule BlockScoutWeb.ViewingBlocksTest do
internal_transaction =
: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)
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)
insert(:log, address: lincoln, index: 0, transaction: transaction)
internal = insert(:internal_transaction, 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,
block_hash: transaction.block_hash,
block_index: 0
)
{:ok,
%{
@ -95,7 +101,7 @@ defmodule BlockScoutWeb.ViewingTransactionsTest do
|> with_contract_creation(contract_address)
: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)
session

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

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

@ -11,14 +11,16 @@ defmodule BlockScoutWeb.AddressViewTest do
end
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 =
insert(:internal_transaction,
index: 1,
transaction: transaction,
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)

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

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

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

@ -38,6 +38,7 @@ defmodule Explorer.Chain do
Import,
InternalTransaction,
Log,
PendingBlockOperation,
SmartContract,
StakingPool,
Token,
@ -194,6 +195,7 @@ defmodule Explorer.Chain do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
InternalTransaction
|> InternalTransaction.where_nonpending_block()
|> InternalTransaction.where_address_fields_match(hash, direction)
|> InternalTransaction.where_is_different_from_parent_transaction()
|> InternalTransaction.where_block_number_is_not_null()
@ -355,9 +357,9 @@ defmodule Explorer.Chain do
base_query =
from(log in Log,
inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index],
preload: [:transaction, transaction: [to_address: :smart_contract]],
inner_join: transaction in Transaction,
on: transaction.hash == log.transaction_hash,
order_by: [desc: log.block_number, desc: log.index],
where: transaction.block_number < ^block_number,
or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index,
or_where:
@ -368,7 +370,19 @@ defmodule Explorer.Chain do
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)
|> Repo.all()
|> Enum.take(paging_options.page_size)
@ -718,33 +732,25 @@ defmodule Explorer.Chain do
end
@doc """
Checks to see if the chain is down indexing based on the transaction from the oldest block having
an `internal_transactions_indexed_at` date.
Checks to see if the chain is down indexing based on the transaction from the
oldest block and the `fetch_internal_transactions` pending operation
"""
@spec finished_indexing?() :: boolean()
def finished_indexing? do
transaction_exists =
Transaction
|> limit(1)
|> Repo.one()
min_block_number_transaction = Repo.aggregate(Transaction, :min, :block_number)
with {:transactions_exist, true} <- {:transactions_exist, Repo.exists?(Transaction)},
min_block_number when not is_nil(min_block_number) <- Repo.aggregate(Transaction, :min, :block_number) do
query =
from(
b in Block,
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
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
false
end
!Repo.exists?(query)
else
true
{:transactions_exist, false} -> true
nil -> false
end
end
@ -1350,11 +1356,8 @@ defmodule Explorer.Chain do
@doc """
The number of `t:Explorer.Chain.InternalTransaction.t/0`.
iex> transaction =
...> :transaction |>
...> insert() |>
...> with_block()
iex> insert(:internal_transaction, index: 0, transaction: transaction)
iex> transaction = :transaction |> insert() |> with_block()
iex> insert(:internal_transaction, index: 0, transaction: transaction, block_hash: transaction.block_hash, block_index: 0)
iex> Explorer.Chain.internal_transaction_count()
1
@ -1365,7 +1368,7 @@ defmodule Explorer.Chain 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
@doc """
@ -1641,18 +1644,20 @@ defmodule Explorer.Chain do
end
@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.
iex> non_consensus = insert(:block, consensus: false)
iex> insert(:pending_block_operation, block: non_consensus, fetch_internal_transactions: true)
iex> unfetched = insert(:block)
iex> fetched = insert(:block, internal_transactions_indexed_at: DateTime.utc_now())
iex> to_be_refetched = insert(:block, refetch_needed: true)
iex> insert(:pending_block_operation, block: unfetched, fetch_internal_transactions: 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(
...> [:number],
...> MapSet.new(),
...> fn %Explorer.Chain.Block{number: number}, acc ->
...> fn number, acc ->
...> MapSet.put(acc, number)
...> end
...> )
@ -1662,112 +1667,55 @@ defmodule Explorer.Chain do
true
iex> fetched.hash in number_set
false
iex> to_be_refetched.number in number_set
false
"""
@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,
reducer :: (entry :: term(), accumulator -> accumulator)
) :: {:ok, accumulator}
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 =
from(
b in Block,
join: pending_ops in assoc(b, :pending_operations),
where: pending_ops.fetch_internal_transactions,
where: b.consensus,
where: is_nil(b.internal_transactions_indexed_at),
where: not b.refetch_needed,
select: ^fields
select: b.number
)
Repo.stream_reduce(query, initial, reducer)
end
@doc """
Returns a stream of all collated transactions with unfetched internal transactions.
def remove_nonconsensus_blocks_from_pending_ops(block_hashes) do
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
out.
{_, _} = Repo.delete_all(query)
iex> pending = insert(:transaction)
iex> unfetched_collated =
...> :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
:ok
end
"""
@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
def remove_nonconsensus_blocks_from_pending_ops do
query =
from(
t in Transaction,
# exclude pending transactions and replaced transactions
where: not is_nil(t.block_hash) and is_nil(t.internal_transactions_indexed_at),
select: ^fields
po in PendingBlockOperation,
inner_join: block in Block,
on: block.hash == po.block_hash,
where: block.consensus == false
)
Repo.stream_reduce(query, initial, reducer)
{_, _} = Repo.delete_all(query)
:ok
end
@spec stream_transactions_with_unfetched_created_contract_codes(
fields :: [
:block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at
| :from_address_hash
| :gas
@ -1802,7 +1750,6 @@ defmodule Explorer.Chain do
@spec stream_mined_transactions(
fields :: [
:block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at
| :from_address_hash
| :gas
@ -1834,7 +1781,6 @@ defmodule Explorer.Chain do
@spec stream_pending_transactions(
fields :: [
:block_hash
| :internal_transactions_indexed_at
| :created_contract_code_indexed_at
| :from_address_hash
| :gas
@ -2216,8 +2162,8 @@ defmodule Explorer.Chain do
## Options
* `: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,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list.
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association,
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
`: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.
@ -2271,8 +2217,8 @@ defmodule Explorer.Chain do
## Options
* `: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,
then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list.
`:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association,
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
`#{@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.
@ -2452,6 +2398,7 @@ defmodule Explorer.Chain do
|> for_parent_transaction(hash)
|> join_associations(necessity_by_association)
|> where_transaction_has_multiple_internal_transactions()
|> InternalTransaction.where_nonpending_block()
|> page_internal_transaction(paging_options)
|> limit(^paging_options.page_size)
|> 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, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Log
|> join(:inner, [log], transaction in assoc(log, :transaction))
log_with_transactions =
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)
|> page_logs(paging_options)
|> limit(^paging_options.page_size)
@ -2509,7 +2463,11 @@ defmodule Explorer.Chain do
TokenTransfer
|> 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)
|> limit(^paging_options.page_size)
|> 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: :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}
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
alias Explorer.Chain.{Address, Gas, Hash, Transaction}
alias Explorer.Chain.{Address, Gas, Hash, PendingBlockOperation, Transaction}
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
@ -46,7 +46,6 @@ defmodule Explorer.Chain.Block do
* `timestamp` - When the block was collated
* `total_difficulty` - the total `difficulty` of the chain until 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__{
consensus: boolean(),
@ -63,7 +62,6 @@ defmodule Explorer.Chain.Block do
timestamp: DateTime.t(),
total_difficulty: difficulty(),
transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()],
internal_transactions_indexed_at: DateTime.t(),
refetch_needed: boolean()
}
@ -78,7 +76,6 @@ defmodule Explorer.Chain.Block do
field(:size, :integer)
field(:timestamp, :utc_datetime_usec)
field(:total_difficulty, :decimal)
field(:internal_transactions_indexed_at, :utc_datetime_usec)
field(:refetch_needed, :boolean)
timestamps()
@ -97,6 +94,8 @@ defmodule Explorer.Chain.Block do
has_many(:transaction_forks, Transaction.Fork, foreign_key: :uncle_hash)
has_many(:rewards, Reward, foreign_key: :block_hash)
has_one(:pending_operations, PendingBlockOperation, foreign_key: :block_hash)
end
def changeset(%__MODULE__{} = block, attrs) do

@ -12,7 +12,8 @@ defmodule Explorer.Chain.Import do
Import.Stage.Addresses,
Import.Stage.AddressReferencing,
Import.Stage.BlockReferencing,
Import.Stage.BlockFollowing
Import.Stage.BlockFollowing,
Import.Stage.BlockPending
]
# 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]
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.Import.Runner
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
insert(repo, changes_list, insert_options)
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, _ ->
update_block_second_degree_relations(repo, hashes, %{
timeout:
@ -83,18 +86,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
transactions: transactions
})
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, _ ->
acquire_contract_address_tokens(repo, consensus_block_numbers)
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, _ ->
delete_address_token_balances(repo, consensus_block_numbers, insert_options)
end)
@ -160,7 +154,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
gas_used: nil,
cumulative_gas_used: nil,
index: nil,
internal_transactions_indexed_at: nil,
status: nil,
error: nil,
updated_at: ^updated_at
@ -251,7 +244,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
difficulty: fragment("EXCLUDED.difficulty"),
gas_limit: fragment("EXCLUDED.gas_limit"),
gas_used: fragment("EXCLUDED.gas_used"),
internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"),
miner_hash: fragment("EXCLUDED.miner_hash"),
nonce: fragment("EXCLUDED.nonce"),
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.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.total_difficulty <> ?", block.total_difficulty) or
fragment("EXCLUDED.internal_transactions_indexed_at <> ?", block.internal_transactions_indexed_at)
fragment("EXCLUDED.total_difficulty <> ?", block.total_difficulty)
)
end
@ -317,92 +308,26 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
{:error, %{exception: postgrex_error, consensus_block_numbers: consensus_block_numbers}}
end
defp remove_nonconsensus_token_transfers(repo, forked_transaction_hashes, %{timeout: timeout}) do
ordered_token_transfers =
from(
token_transfer in TokenTransfer,
where: token_transfer.transaction_hash in ^forked_transaction_hashes,
select: token_transfer.transaction_hash,
# Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md)
order_by: [
token_transfer.transaction_hash,
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 =
from(log in Log,
select: map(log, [:transaction_hash, :index]),
inner_join: ordered_log in subquery(ordered_logs),
on: ordered_log.transaction_hash == log.transaction_hash
)
{_count, deleted_logs} = repo.delete_all(query, timeout: timeout)
defp new_pending_operations(repo, nonconsensus_hashes, hashes, %{timeout: timeout, timestamps: timestamps}) do
sorted_pending_ops =
nonconsensus_hashes
|> MapSet.new()
|> MapSet.union(MapSet.new(hashes))
|> Enum.sort()
|> Enum.map(fn hash ->
%{block_hash: hash, fetch_internal_transactions: true}
end)
{:ok, deleted_logs}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, transactions: forked_transaction_hashes}}
Import.insert_changes_list(
repo,
sorted_pending_ops,
conflict_target: :block_hash,
on_conflict: PendingBlockOperation.default_on_conflict(),
for: PendingBlockOperation,
returning: true,
timeout: timeout,
timestamps: timestamps
)
end
defp delete_address_token_balances(_, [], _), do: {:ok, []}

@ -6,20 +6,19 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
require Ecto.Query
require Logger
alias Ecto.Adapters.SQL
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
import Ecto.Query, only: [from: 2]
import Ecto.Query, only: [from: 2, or_where: 3]
@behaviour Runner
# milliseconds
@timeout 60_000
@type imported :: [
%{required(:index) => non_neg_integer(), required(:transaction_hash) => Hash.Full.t()}
]
@type imported :: [InternalTransaction.t()]
@impl Runner
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}
# 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)
multi
|> Multi.run(:acquire_transactions, fn repo, _ ->
acquire_transactions(repo, changes_list)
|> Multi.run(:acquire_blocks, fn repo, _ ->
acquire_blocks(repo, changes_list)
end)
|> Multi.run(:internal_transactions, fn repo, %{acquire_transactions: transactions} ->
insert(repo, changes_list, transactions, insert_options)
|> Multi.run(:acquire_pending_internal_txs, fn repo, %{acquire_blocks: block_hashes} ->
acquire_pending_internal_txs(repo, block_hashes)
end)
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, %{acquire_transactions: transactions} ->
update_transactions(repo, transactions, update_transactions_options)
|> Multi.run(:acquire_transactions, fn repo, %{acquire_pending_internal_txs: pending_block_hashes} ->
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)
|> 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
@impl Runner
def timeout, do: @timeout
@spec insert(Repo.t(), [map], [Transaction.t()], %{
@spec insert(Repo.t(), [map], %{
optional(:on_conflict) => Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, transactions, %{timeout: timeout, timestamps: timestamps} = options)
when is_list(changes_list) do
defp insert(repo, valid_internal_transactions, %{timeout: timeout, timestamps: timestamps} = options)
when is_list(valid_internal_transactions) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
transactions_map = Map.new(transactions, &{&1.hash, &1})
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)
ordered_changes_list = Enum.sort_by(valid_internal_transactions, &{&1.transaction_hash, &1.index})
{:ok, internal_transactions} =
Import.insert_changes_list(
repo,
final_changes_list,
conflict_target: [:transaction_hash, :index],
ordered_changes_list,
conflict_target: [:block_hash, :block_index],
for: InternalTransaction,
on_conflict: on_conflict,
returning: true,
@ -119,24 +137,28 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
from_address_hash: fragment("EXCLUDED.from_address_hash"),
gas: fragment("EXCLUDED.gas"),
gas_used: fragment("EXCLUDED.gas_used"),
# Don't update `index` as it is part of the composite primary key and used for the conflict target
index: fragment("EXCLUDED.index"),
init: fragment("EXCLUDED.init"),
input: fragment("EXCLUDED.input"),
output: fragment("EXCLUDED.output"),
to_address_hash: fragment("EXCLUDED.to_address_hash"),
trace_address: fragment("EXCLUDED.trace_address"),
# Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target
transaction_hash: fragment("EXCLUDED.transaction_hash"),
transaction_index: fragment("EXCLUDED.transaction_index"),
type: fragment("EXCLUDED.type"),
value: fragment("EXCLUDED.value"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", internal_transaction.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", internal_transaction.updated_at)
# 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
where:
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.created_contract_address_hash,
internal_transaction.created_contract_code,
@ -156,18 +178,42 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
)
end
defp acquire_transactions(repo, internal_transactions) do
transaction_hashes =
internal_transactions
|> MapSet.new(& &1.transaction_hash)
|> MapSet.to_list()
defp acquire_blocks(repo, changes_list) do
block_numbers = Enum.map(changes_list, & &1.block_number)
query =
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 =
from(
t in Transaction,
where: t.hash in ^transaction_hashes,
# do not consider pending transactions
where: not is_nil(t.block_hash),
where: t.block_hash in ^pending_block_hashes,
select: map(t, [:hash, :block_hash, :block_number]),
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: t.hash,
@ -177,22 +223,115 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
{:ok, repo.all(query)}
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,
timestamps: timestamps
})
when is_list(transactions) do
transaction_hashes = Enum.map(transactions, & &1.hash)
when is_list(valid_internal_transactions) do
transaction_hashes =
valid_internal_transactions
|> MapSet.new(& &1.transaction_hash)
|> MapSet.to_list()
update_query =
from(
t in Transaction,
# pending transactions are already excluded by `acquire_transactions`
where: t.hash in ^transaction_hashes,
# ShareLocks order already enforced by `acquire_transactions` (see docs: sharelocks.md)
update: [
set: [
internal_transactions_indexed_at: ^timestamps.updated_at,
created_contract_address_hash:
fragment(
"(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,
type(^:ok, t.status),
type(^:error, t.status)
)
),
updated_at: ^timestamps.updated_at
]
]
)
@ -224,26 +364,14 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end
end
# If not using Parity this is not relevant
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()
defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do
update_query =
from(
b in Block,
where: b.number in ^missing_transactions_block_numbers,
where: b.hash in ^block_hashes,
select: b.number,
# ShareLocks order already enforced by `internal_transactions_indexed_at_blocks` (see docs: sharelocks.md)
update: [set: [consensus: false, internal_transactions_indexed_at: nil]]
where: b.number in ^invalid_block_numbers and b.consensus,
select: b.hash,
# ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md)
update: [set: [consensus: false]]
)
try do
@ -252,24 +380,39 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
Logger.debug(fn ->
[
"consensus removed from blocks with numbers: ",
inspect(missing_transactions_block_numbers),
" because of missing transactions"
inspect(invalid_block_numbers),
" because of mismatching transactions"
]
end)
{:ok, result}
rescue
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
defp reject_missing_transactions(ordered_changes_list, transactions_map) do
Enum.reject(ordered_changes_list, fn %{transaction_hash: hash} ->
transactions_map
|> Map.get(hash, %{})
|> Map.get(:block_hash)
|> is_nil()
end)
def update_pending_blocks_status(repo, pending_hashes, invalid_block_hashes) do
valid_block_hashes =
pending_hashes
|> MapSet.new()
|> MapSet.difference(MapSet.new(invalid_block_hashes))
|> MapSet.to_list()
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

@ -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)
# 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, _} =
Import.insert_changes_list(
repo,
ordered_changes_list,
conflict_target: [:transaction_hash, :index],
conflict_target: [:transaction_hash, :index, :block_hash],
on_conflict: on_conflict,
for: Log,
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)
# 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, _} =
Import.insert_changes_list(
repo,
ordered_changes_list,
conflict_target: [:transaction_hash, :log_index],
conflict_target: [:transaction_hash, :log_index, :block_hash],
on_conflict: on_conflict,
for: TokenTransfer,
returning: true,

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
import Ecto.Query, only: [from: 2]
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
@behaviour Import.Runner
@ -72,18 +72,14 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
changes_list,
%{
timeout: timeout,
timestamps: %{inserted_at: inserted_at} = timestamps,
token_transfer_transaction_hash_set: token_transfer_transaction_hash_set
timestamps: timestamps
} = options
)
when is_list(changes_list) do
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)
|> Enum.sort_by(& &1.hash)
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
ordered_changes_list = Enum.sort_by(changes_list, & &1.hash)
Import.insert_changes_list(
repo,
@ -114,7 +110,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
gas_price: fragment("EXCLUDED.gas_price"),
gas_used: fragment("EXCLUDED.gas_used"),
index: fragment("EXCLUDED.index"),
internal_transactions_indexed_at: fragment("EXCLUDED.internal_transactions_indexed_at"),
input: fragment("EXCLUDED.input"),
nonce: fragment("EXCLUDED.nonce"),
r: fragment("EXCLUDED.r"),
@ -130,7 +125,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
],
where:
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_number,
transaction.created_contract_address_hash,
@ -142,7 +137,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
transaction.gas_price,
transaction.gas_used,
transaction.index,
transaction.internal_transactions_indexed_at,
transaction.input,
transaction.nonce,
transaction.r,
@ -155,46 +149,6 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
)
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, %{
timeout: timeout,
timestamps: %{updated_at: updated_at}

@ -13,10 +13,8 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do
@impl Stage
def runners,
do: [
Runner.InternalTransactionsIndexedAtBlocks,
Runner.Block.SecondDegreeRelations,
Runner.Block.Rewards,
Runner.InternalTransactions,
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
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}
@typedoc """
@ -22,11 +22,15 @@ defmodule Explorer.Chain.InternalTransaction do
* `to_address` - the sink of the `value`
* `to_address_hash` - hash of the sink of the `value`
* `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_index` - the `t:Explorer.Chain.Transaction.t/0` `index` of `transaction` in `block_number`.
* `type` - type of internal transaction
* `value` - value of transferred from `from_address` to `to_address`
* `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__{
block_number: Explorer.Chain.Block.block_number() | nil,
@ -50,7 +54,9 @@ defmodule Explorer.Chain.InternalTransaction do
transaction: %Ecto.Association.NotLoaded{} | Transaction.t(),
transaction_hash: Hash.t(),
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
@ -69,6 +75,7 @@ defmodule Explorer.Chain.InternalTransaction do
field(:value, Wei)
field(:block_number, :integer)
field(:transaction_index, :integer)
field(:block_index, :integer)
timestamps()
@ -102,6 +109,20 @@ defmodule Explorer.Chain.InternalTransaction do
references: :hash,
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
@doc """
@ -125,7 +146,9 @@ defmodule Explorer.Chain.InternalTransaction do
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> type: "create",
...> value: 0,
...> block_number: 35
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0
...> }
...> )
iex> changeset.valid?
@ -165,6 +188,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create",
...> value: 0,
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0
...> }
iex> )
@ -182,6 +207,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0,
...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6",
...> index: 0,
@ -206,6 +233,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
@ -230,6 +259,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
@ -260,6 +291,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> %Explorer.Chain.InternalTransaction{},
...> %{
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0,
...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b",
...> index: 0,
@ -300,6 +333,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create",
...> value: 0,
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0
...> }
iex> )
@ -326,6 +361,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "create",
...> value: 0,
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0
...> }
...> )
@ -351,6 +388,8 @@ defmodule Explorer.Chain.InternalTransaction do
...> type: "selfdestruct",
...> value: 0,
...> block_number: 35,
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> block_index: 0,
...> transaction_index: 0
...> }
...> )
@ -362,9 +401,34 @@ defmodule Explorer.Chain.InternalTransaction do
internal_transaction
|> cast(attrs, ~w(type)a)
|> validate_required(~w(type)a)
|> validate_block_required(attrs)
|> type_changeset(attrs)
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
type = get_field(changeset, :type)
@ -503,6 +567,16 @@ defmodule Explorer.Chain.InternalTransaction do
where(query, [t], not is_nil(t.block_number))
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
internal_transactions
|> Enum.map(&internal_transaction_to_raw/1)

@ -6,14 +6,16 @@ defmodule Explorer.Chain.Log do
require Logger
alias ABI.{Event, FunctionSelector}
alias Explorer.Chain.{Address, ContractMethod, Data, Hash, Transaction}
alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction}
alias Explorer.Repo
@required_attrs ~w(address_hash data index transaction_hash)a
@optional_attrs ~w(first_topic second_topic third_topic fourth_topic type)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 block_number)a
@typedoc """
* `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`
* `data` - non-indexed log parameters.
* `first_topic` - `topics[0]`
@ -28,6 +30,8 @@ defmodule Explorer.Chain.Log do
@type t :: %__MODULE__{
address: %Ecto.Association.NotLoaded{} | Address.t(),
address_hash: Hash.Address.t(),
block_hash: Hash.Full.t(),
block_number: non_neg_integer() | nil,
data: Data.t(),
first_topic: String.t(),
second_topic: String.t(),
@ -48,6 +52,7 @@ defmodule Explorer.Chain.Log do
field(:fourth_topic, :string)
field(:index, :integer, primary_key: true)
field(:type, :string)
field(:block_number, :integer)
timestamps()
@ -59,6 +64,13 @@ defmodule Explorer.Chain.Log do
references: :hash,
type: Hash.Full
)
belongs_to(:block, Block,
foreign_key: :block_hash,
primary_key: true,
references: :hash,
type: Hash.Full
)
end
@doc """
@ -69,6 +81,7 @@ defmodule Explorer.Chain.Log do
...> %Explorer.Chain.Log{},
...> %{
...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef",
...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22",
...> 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.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.{PagingOptions, Repo}
@ -35,6 +35,7 @@ defmodule Explorer.Chain.TokenTransfer do
@typedoc """
* `:amount` - The token transferred amount
* `:block_hash` - hash of the block
* `: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_hash` - Address hash foreign key
@ -50,6 +51,7 @@ defmodule Explorer.Chain.TokenTransfer do
@type t :: %TokenTransfer{
amount: Decimal.t(),
block_number: non_neg_integer() | nil,
block_hash: Hash.Full.t(),
from_address: %Ecto.Association.NotLoaded{} | Address.t(),
from_address_hash: Hash.Address.t(),
to_address: %Ecto.Association.NotLoaded{} | Address.t(),
@ -93,6 +95,13 @@ defmodule Explorer.Chain.TokenTransfer do
type: Hash.Full
)
belongs_to(:block, Block,
foreign_key: :block_hash,
primary_key: true,
references: :hash,
type: Hash.Full
)
has_one(
:instance,
Instance,
@ -105,7 +114,7 @@ defmodule Explorer.Chain.TokenTransfer do
timestamps()
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
@doc false

@ -29,7 +29,7 @@ defmodule Explorer.Chain.Transaction do
alias Explorer.Repo
@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
@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
* `internal_transactions` - transactions (value transfers) created while executing contract used for this
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`
| `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,
input: Data.t(),
internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()],
internal_transactions_indexed_at: DateTime.t(),
logs: %Ecto.Association.NotLoaded{} | [Log.t()],
nonce: non_neg_integer(),
r: r(),
@ -174,7 +171,6 @@ defmodule Explorer.Chain.Transaction do
:gas_price,
:gas_used,
:index,
:internal_transactions_indexed_at,
:created_contract_code_indexed_at,
:input,
:nonce,
@ -195,7 +191,6 @@ defmodule Explorer.Chain.Transaction do
field(:gas_price, Wei)
field(:gas_used, :decimal)
field(:index, :integer)
field(:internal_transactions_indexed_at, :utc_datetime_usec)
field(:created_contract_code_indexed_at, :utc_datetime_usec)
field(:input, Data)
field(:nonce, :integer)

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

@ -79,7 +79,7 @@ defmodule Explorer.Etherscan.Logs do
logs_query = where_topic_match(Log, prepared_filter)
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: log in ^logs_query,
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()}
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}
else
{:error, "Internal transaction not found."}
@ -65,7 +65,9 @@ defmodule Explorer.GraphQL do
select: it
)
Chain.where_transaction_has_multiple_internal_transactions(query)
query
|> InternalTransaction.where_nonpending_block()
|> Chain.where_transaction_has_multiple_internal_transactions()
end
@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
-- 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:
-- 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_input;
@ -14,56 +15,62 @@
DO $$
DECLARE
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;
BEGIN
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
RAISE NOTICE 'Fetching new batch of % transactions to correct', batch_size;
INSERT INTO transactions_with_deprecated_internal_transactions
SELECT DISTINCT transaction_hash
FROM internal_transactions
WHERE
(last_transaction_hash IS NULL OR transaction_hash < last_transaction_hash) AND
-- call_has_call_type CONSTRAINT
((type = 'call' AND call_type IS NULL) OR
-- call_has_input CONSTRAINT
(type = 'call' AND input IS NULL) OR
-- create_has_init CONSTRAINT
(type = 'create' AND init is NULL))
ORDER BY transaction_hash DESC LIMIT batch_size;
INSERT INTO blocks_with_deprecated_internal_transactions
SELECT DISTINCT a.block_number
FROM (
SELECT DISTINCT i.block_number, i.transaction_index
FROM internal_transactions i
WHERE
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
((i.type = 'call' AND i.call_type IS NULL) OR
-- call_has_input CONSTRAINT
(i.type = 'call' AND i.input IS NULL) OR
-- create_has_init CONSTRAINT
(i.type = 'create' AND i.init is NULL))
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;
-- UPDATE TRANSACTIONS
UPDATE transactions
SET internal_transactions_indexed_at = NULL,
error = NULL
FROM transactions_with_deprecated_internal_transactions
WHERE transactions.hash = transactions_with_deprecated_internal_transactions.hash;
INSERT INTO pending_block_operations (block_hash, inserted_at, updated_at, fetch_internal_transactions)
SELECT b.hash, NOW(), NOW(), true
FROM blocks_with_deprecated_internal_transactions bd, blocks b
WHERE bd.block_number = b.number
AND b.consensus = true
ON CONFLICT (block_hash)
DO NOTHING;
-- REMOVE THE DEPRECATED internal_transactions
DELETE FROM internal_transactions
USING transactions_with_deprecated_internal_transactions
WHERE internal_transactions.transaction_hash = transactions_with_deprecated_internal_transactions.hash;
USING blocks_with_deprecated_internal_transactions
WHERE internal_transactions.block_number = blocks_with_deprecated_internal_transactions.block_number;
-- COMMIT THE BATCH UPDATES
CHECKPOINT;
-- UPDATE last_transaction_hash TO KEEP TRACK OF ROWS ALREADY CHECKED
SELECT INTO last_transaction_hash hash
FROM transactions_with_deprecated_internal_transactions
ORDER BY hash ASC LIMIT 1;
-- UPDATE last_block_number TO KEEP TRACK OF ROWS ALREADY CHECKED
SELECT INTO last_block_number block_number
FROM blocks_with_deprecated_internal_transactions
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
DELETE FROM transactions_with_deprecated_internal_transactions;
DELETE FROM blocks_with_deprecated_internal_transactions;
-- EXIT IF ALL internal_transactions HAVE BEEN CHECKED ALREADY
EXIT WHEN last_fetched_batch_size != batch_size;
@ -71,5 +78,5 @@ BEGIN
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 $$;

@ -9,9 +9,9 @@ defmodule Explorer.Chain.Cache.PendingTransactionsTest do
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

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

@ -7,7 +7,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
alias Ecto.Multi
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}
describe "run/1" do
@ -115,78 +115,6 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
assert count(Address.CurrentTokenBalance) == count
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",
%{consensus_block: %{number: block_number} = block, options: options} do
token = insert(:token)

@ -2,19 +2,20 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
use Explorer.DataCase
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
describe "run/1" do
test "transaction's status becomes :error when its internal_transaction has an error" do
transaction = insert(:transaction) |> with_block(status: :ok)
insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true)
assert :ok == transaction.status
index = 0
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])
@ -25,18 +26,21 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
transaction = insert(:transaction) |> with_block(status: :ok)
pending = insert(:transaction)
insert(:pending_block_operation, block_hash: transaction.block_hash, fetch_internal_transactions: true)
assert :ok == transaction.status
assert is_nil(pending.block_hash)
index = 0
transaction_changes = make_internal_transaction_changes(transaction.hash, index, nil)
pending_changes = make_internal_transaction_changes(pending.hash, index, nil)
transaction_changes = make_internal_transaction_changes(transaction, index, nil)
pending_changes = make_internal_transaction_changes(pending, index, nil)
assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes])
assert %InternalTransaction{} =
Repo.one(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash))
assert Repo.exists?(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()
@ -47,68 +51,112 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
empty_block = insert(:block)
pending = insert(:transaction)
insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true)
assert is_nil(pending.block_hash)
full_block = insert(: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
index = 0
pending_transaction_changes =
pending.hash
pending
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, empty_block.number)
transaction_changes =
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)
transaction_changes = make_internal_transaction_changes(inserted, index, nil)
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 %{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() ==
false
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
test "does not remove consensus when block is empty and no transactions are missing" do
empty_block = insert(:block)
insert(:pending_block_operation, block_hash: empty_block.hash, fetch_internal_transactions: true)
full_block = insert(: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
index = 0
transaction_changes =
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)
transaction_changes = make_internal_transaction_changes(inserted, index, nil)
empty_changes = make_empty_block_changes(empty_block.number)
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 PendingBlockOperation |> Repo.get(empty_block.hash) |> is_nil()
assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() ==
false
assert %{consensus: true} = Repo.get(Block, full_block.hash)
assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil()
end
end
@ -121,7 +169,9 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
|> Repo.transaction()
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,
to_address_hash: insert(:address).hash,
@ -142,10 +192,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
end,
index: index,
trace_address: [],
transaction_hash: transaction_hash,
transaction_hash: transaction.hash,
type: :call,
value: Wei.from(Decimal.new(1), :wei),
error: error
error: error,
block_number: transaction.block_number
}
end
end

@ -12,6 +12,7 @@ defmodule Explorer.Chain.ImportTest do
Log,
Hash,
Import,
PendingBlockOperation,
Token,
TokenTransfer,
Transaction
@ -81,11 +82,13 @@ defmodule Explorer.Chain.ImportTest do
value: 0
}
],
timeout: 5
timeout: 5,
with: :blockless_changeset
},
logs: %{
params: [
%{
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b",
data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
@ -149,6 +152,7 @@ defmodule Explorer.Chain.ImportTest do
%{
amount: Decimal.new(1_000_000_000_000_000_000),
block_number: 37,
block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd",
log_index: 0,
from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca",
to_address_hash: "0x515c09c5bba1ed566b02a5b0599ec5d5d0aee73d",
@ -305,9 +309,7 @@ defmodule Explorer.Chain.ImportTest do
bytes:
<<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>>
},
# because there are successful, non-contract-creation token transfer
internal_transactions_indexed_at: %DateTime{}
}
}
],
tokens: [
@ -554,7 +556,8 @@ defmodule Explorer.Chain.ImportTest do
block_number: 37,
transaction_index: 0
}
]
],
with: :blockless_changeset
}
}
@ -565,63 +568,7 @@ defmodule Explorer.Chain.ImportTest do
assert address.contract_code != smart_contract_bytecode
end
test "updates `error`, `status` and `internal_transaction_indexed_at` even if internal transactions were alreader inserted" 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
test "with internal_transactions updates PendingBlockOperation status" do
block_hash = "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47"
block_number = 34
miner_hash = from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca"
@ -674,7 +621,10 @@ defmodule Explorer.Chain.ImportTest do
value: 0
}
]
},
}
}
internal_txs_options = %{
internal_transactions: %{
params: [
%{
@ -693,17 +643,18 @@ defmodule Explorer.Chain.ImportTest do
output: "0x",
value: 0
}
]
],
with: :blockless_changeset
}
}
refute Enum.any?(options[:transactions][:params], &Map.has_key?(&1, :internal_transactions_indexed_at))
assert {:ok, _} = Import.all(options)
transaction = Explorer.Repo.get(Transaction, transaction_hash)
assert [block_hash] = Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions))
refute transaction.internal_transactions_indexed_at == nil
assert {:ok, _} = Import.all(internal_txs_options)
assert [] == Explorer.Repo.all(PendingBlockOperation.block_hashes(:fetch_internal_transactions))
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
@ -786,7 +737,8 @@ defmodule Explorer.Chain.ImportTest do
value: 0,
transaction_index: 0
}
]
],
with: :blockless_changeset
}
}
@ -896,7 +848,8 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 1
}
],
timeout: 5
timeout: 5,
with: :blockless_changeset
},
addresses: %{
params: [
@ -1080,7 +1033,8 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 0,
transaction_block_number: 35
}
]
],
with: :blockless_changeset
}
})
@ -1561,16 +1515,24 @@ defmodule Explorer.Chain.ImportTest do
transaction_index: 0
)
],
timeout: 1
timeout: 1,
with: :blockless_changeset
},
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
},
token_transfers: %{
params: [
params_for(
:token_transfer,
block_hash: block_hash,
block_number: 35,
from_address_hash: from_address_hash,
to_address_hash: to_address_hash,
@ -1792,7 +1754,6 @@ defmodule Explorer.Chain.ImportTest do
block_hash: block_hash_before,
block_number: block_number,
error: error,
internal_transactions_indexed_at: Timex.parse!("2019-01-01T01:00:00Z", "{ISO:Extended:Z}"),
from_address_hash: from_address_hash_before,
to_address_hash: to_address_hash_before,
gas: 21_000,
@ -1828,7 +1789,8 @@ defmodule Explorer.Chain.ImportTest do
block_number: block_number,
transaction_index: 0
}
]
],
with: :blockless_changeset
}
})

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

@ -9,7 +9,12 @@ defmodule Explorer.Chain.LogTest do
describe "changeset/2" 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)
end
@ -26,7 +31,8 @@ defmodule Explorer.Chain.LogTest do
:log,
address_hash: build(:address).hash,
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)

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

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

@ -92,9 +92,15 @@ defmodule Explorer.GraphQLTest do
describe "get_internal_transaction/1" 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}
@ -117,11 +123,23 @@ defmodule Explorer.GraphQLTest do
describe "transcation_to_internal_transactions_query/1" do
test "with transaction with one internal transaction" do
transaction1 = insert(:transaction)
transaction2 = insert(:transaction)
internal_transaction = insert(:internal_transaction_create, transaction: transaction1, index: 0)
insert(:internal_transaction_create, transaction: transaction2, index: 0)
transaction1 = insert(:transaction) |> with_block()
transaction2 = insert(:transaction) |> with_block()
internal_transaction =
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] =
transaction1
@ -133,14 +151,24 @@ defmodule Explorer.GraphQLTest do
end
test "with transaction with multiple internal transactions" do
transaction1 = insert(:transaction)
transaction2 = insert(:transaction)
transaction1 = insert(:transaction) |> with_block()
transaction2 = insert(:transaction) |> with_block()
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
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 =
transaction1
@ -155,11 +183,28 @@ defmodule Explorer.GraphQLTest do
end
test "orders internal transactions by ascending index" do
transaction = insert(:transaction)
insert(:internal_transaction_create, transaction: transaction, index: 2)
insert(:internal_transaction_create, transaction: transaction, index: 0)
insert(:internal_transaction_create, transaction: transaction, index: 1)
transaction = insert(:transaction) |> with_block()
insert(:internal_transaction_create,
transaction: transaction,
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 =
transaction

@ -10,7 +10,7 @@ defmodule Explorer.RepoTest do
describe "safe_insert_all/3" 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_for(
@ -20,6 +20,8 @@ defmodule Explorer.RepoTest do
transaction_hash: transaction.hash,
index: 0,
block_number: 35,
block_hash: transaction.block_hash,
block_index: 0,
transaction_index: 0
)
@ -33,7 +35,7 @@ defmodule Explorer.RepoTest do
Repo.safe_insert_all(
InternalTransaction,
[timestamped_changes, timestamped_changes],
conflict_target: [:transaction_hash, :index],
conflict_target: [:block_hash, :block_index],
on_conflict: :replace_all
)
end
@ -42,7 +44,7 @@ defmodule Explorer.RepoTest do
assert log =~ "Chunk:\n"
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 =~
"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,
InternalTransaction,
Log,
PendingBlockOperation,
SmartContract,
Token,
TokenTransfer,
@ -226,25 +227,20 @@ defmodule Explorer.Factory do
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)
internal_transactions_indexed_at = collated_params[:internal_transactions_indexed_at]
status = Keyword.get(collated_params, :status, Enum.random([:ok, :error]))
error =
if internal_transactions_indexed_at != nil && status == :error do
collated_params[:error] || "Something really bad happened"
else
nil
end
error = (status == :error && collated_params[:error]) || nil
transaction
|> Transaction.changeset(%{
block_hash: block_hash,
block_number: block_number,
cumulative_gas_used: cumulative_gas_used,
from_address_hash: transaction.from_address_hash,
to_address_hash: transaction.to_address_hash,
error: error,
gas_used: gas_used,
index: next_transaction_index,
internal_transactions_indexed_at: internal_transactions_indexed_at,
status: status
})
|> Repo.update!()
@ -290,6 +286,14 @@ defmodule Explorer.Factory do
data
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
gas = Enum.random(21_000..100_000)
gas_used = Enum.random(0..gas)
@ -306,6 +310,8 @@ defmodule Explorer.Factory do
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction
# caller MUST supply `block_hash` (usually the same as the transaction's)
# caller MUST supply `block_index`
type: :call,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
@ -328,6 +334,8 @@ defmodule Explorer.Factory do
trace_address: [],
# caller MUST supply `transaction` because it can't be built lazily to allow overrides without creating an extra
# transaction
# caller MUST supply `block_hash` (usually the same as the transaction's)
# caller MUST supply `block_index`
type: :create,
value: sequence("internal_transaction_value", &Decimal.new(&1))
}
@ -345,8 +353,12 @@ defmodule Explorer.Factory do
end
def log_factory do
block = build(:block)
%Log{
address: build(:address),
block: block,
block_number: block.number,
data: data(:log_data),
first_topic: nil,
fourth_topic: nil,
@ -417,6 +429,7 @@ defmodule Explorer.Factory do
insert(:token, contract_address: token_address)
%TokenTransfer{
block: build(:block),
amount: Decimal.new(1),
block_number: block_number(),
from_address: from_address,

@ -12,7 +12,7 @@ defmodule Indexer.Block.Catchup.Fetcher do
async_import_block_rewards: 1,
async_import_coin_balances: 2,
async_import_created_contract_codes: 1,
async_import_internal_transactions: 2,
async_import_internal_transactions: 1,
async_import_replaced_transactions: 1,
async_import_tokens: 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
@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} =
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
async_import_remaining_block_data(
imported,
Map.put(async_import_remaining_block_data_options, :block_rewards, %{errors: block_reward_errors}),
json_rpc_named_arguments
Map.put(async_import_remaining_block_data_options, :block_rewards, %{errors: block_reward_errors})
)
ok
@ -145,13 +144,12 @@ defmodule Indexer.Block.Catchup.Fetcher do
defp async_import_remaining_block_data(
imported,
%{block_rewards: %{errors: block_reward_errors}} = options,
json_rpc_named_arguments
%{block_rewards: %{errors: block_reward_errors}} = options
) do
async_import_block_rewards(block_reward_errors)
async_import_coin_balances(imported, options)
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_token_balances(imported)
async_import_uncles(imported)

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

@ -15,7 +15,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
only: [
async_import_block_rewards: 1,
async_import_created_contract_codes: 1,
async_import_internal_transactions: 2,
async_import_internal_transactions: 1,
async_import_replaced_transactions: 1,
async_import_tokens: 1,
async_import_token_balances: 1,
@ -183,7 +183,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
@impl Block.Fetcher
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_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
async_import_remaining_block_data(
imported,
%{block_rewards: %{errors: block_reward_errors}},
json_rpc_named_arguments
%{block_rewards: %{errors: block_reward_errors}}
)
Accounts.drop(imported[:addresses])
@ -381,12 +380,11 @@ defmodule Indexer.Block.Realtime.Fetcher do
defp async_import_remaining_block_data(
imported,
%{block_rewards: %{errors: block_reward_errors}},
json_rpc_named_arguments
%{block_rewards: %{errors: block_reward_errors}}
) do
async_import_block_rewards(block_reward_errors)
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_token_balances(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]
alias Explorer.Chain
alias Explorer.Chain.{Block, Hash}
alias Explorer.Chain.Block
alias Explorer.Chain.Cache.{Accounts, Blocks}
alias Indexer.{BufferedTask, Tracer}
alias Indexer.Transform.Addresses
@ -47,34 +47,9 @@ defmodule Indexer.Fetcher.InternalTransaction do
*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_fetch([%{required(:block_number) => Block.block_number(), required(:hash) => Hash.Full.t()}]) :: :ok
def async_fetch(transactions_fields, timeout \\ 5000) when is_list(transactions_fields) do
entries = Enum.map(transactions_fields, &entry/1)
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)
@spec async_fetch([Block.block_number()]) :: :ok
def async_fetch(block_numbers, timeout \\ 5000) when is_list(block_numbers) do
BufferedTask.buffer(__MODULE__, block_numbers, timeout)
end
@doc false
@ -96,52 +71,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
end
@impl BufferedTask
def init(initial, reducer, json_rpc_named_arguments) do
def init(initial, reducer, _json_rpc_named_arguments) do
{:ok, final} =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
Chain.stream_blocks_with_unfetched_internal_transactions(
[: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
Chain.stream_blocks_with_unfetched_internal_transactions(initial, fn block_number, acc ->
reducer.(block_number, acc)
end)
final
end
defp entry(%{block_number: block_number, hash: %Hash{bytes: bytes}, 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)
defp params(%{block_number: block_number, hash: hash, index: index}) when is_integer(block_number) do
%{block_number: block_number, hash_data: to_string(hash), transaction_index: index}
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
@decorate trace(
name: "fetch",
@ -149,52 +91,61 @@ defmodule Indexer.Fetcher.InternalTransaction do
service: :indexer,
tracer: Tracer
)
def run(entries, json_rpc_named_arguments) do
variant = Keyword.fetch!(json_rpc_named_arguments, :variant)
unique_entries = unique_entries(entries, variant)
def run(block_numbers, json_rpc_named_arguments) do
unique_numbers = Enum.uniq(block_numbers)
unique_entries_count = Enum.count(unique_entries)
Logger.metadata(count: unique_entries_count)
unique_numbers_count = Enum.count(unique_numbers)
Logger.metadata(count: unique_numbers_count)
Logger.debug("fetching internal transactions for transactions")
Logger.debug("fetching internal transactions for blocks")
variant
json_rpc_named_arguments
|> Keyword.fetch!(:variant)
|> case do
EthereumJSONRPC.Parity ->
unique_entries
|> EthereumJSONRPC.fetch_block_internal_transactions(json_rpc_named_arguments)
EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments)
_ ->
unique_entries
|> Enum.map(&params/1)
|> EthereumJSONRPC.fetch_internal_transactions(json_rpc_named_arguments)
fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments)
end
|> case do
{: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} ->
Logger.error(fn -> ["failed to fetch internal transactions for transactions: ", inspect(reason)] end,
error_count: unique_entries_count
Logger.error(fn -> ["failed to fetch internal transactions for blocks: ", inspect(reason)] end,
error_count: unique_numbers_count
)
# re-queue the de-duped entries
{:retry, unique_entries}
{:retry, unique_numbers}
:ignore ->
:ok
end
end
defp import_internal_transaction(internal_transactions_params, json_rpc_named_arguments, unique_entries) do
internal_transactions_indexed_at_blocks =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity -> Enum.map(unique_entries, &block_params/1)
_ -> []
end
defp fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments) do
Enum.reduce(unique_numbers, {:ok, []}, fn
block_number, {:ok, acc_list} ->
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
_, error_or_ignore ->
error_or_ignore
end)
end
unique_entries_count = Enum.count(unique_entries)
defp import_internal_transaction(internal_transactions_params, unique_numbers) do
internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params)
addresses_params =
@ -207,14 +158,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
{hash, block_number}
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 =
Chain.import(%{
addresses: %{params: addresses_params},
internal_transactions: %{params: internal_transactions_params_without_failed_creations},
internal_transactions_indexed_at_blocks: %{
params: internal_transactions_indexed_at_blocks,
with: :number_only_changeset
},
internal_transactions: %{params: internal_transactions_and_empty_block_numbers, with: :blockless_changeset},
timeout: :infinity
})
@ -231,60 +187,19 @@ defmodule Indexer.Fetcher.InternalTransaction do
Logger.error(
fn ->
[
"failed to import internal transactions for transactions: ",
"failed to import internal transactions for blocks: ",
inspect(reason)
]
end,
step: step,
error_count: unique_entries_count
error_count: Enum.count(unique_numbers)
)
# 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
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
internal_transactions_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
alias Indexer.Block
alias Indexer.{Block, PendingOpsCleaner}
alias Indexer.Block.{Catchup, Realtime}
alias Indexer.Fetcher.{
@ -129,7 +129,8 @@ defmodule Indexer.Supervisor do
{UnclesWithoutIndex.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{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
)

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

@ -485,8 +485,7 @@ defmodule Indexer.Block.FetcherTest do
bytes:
<<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>>
},
internal_transactions_indexed_at: nil
}
},
%Transaction{
block_number: block_number,
@ -496,8 +495,7 @@ defmodule Indexer.Block.FetcherTest do
bytes:
<<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>>
},
internal_transactions_indexed_at: nil
}
}
]
}} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number)
@ -589,8 +587,7 @@ defmodule Indexer.Block.FetcherTest do
bytes:
<<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>>
},
internal_transactions_indexed_at: nil
}
}
]
},

@ -2,10 +2,10 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
use EthereumJSONRPC.Case, async: false
use Explorer.DataCase
import ExUnit.CaptureLog
import Mox
alias Explorer.Chain.{Address, Hash, Transaction}
alias Explorer.Chain
alias Explorer.Chain.PendingBlockOperation
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`
@ -99,7 +99,8 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end
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)
@ -111,54 +112,11 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end
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", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
block = insert(:block)
insert(:pending_block_operation, block_hash: block.hash, fetch_internal_transactions: true)
assert InternalTransaction.init(
[],
@ -171,7 +129,8 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
test "does not buffer blocks with fetched internal transactions", %{
json_rpc_named_arguments: json_rpc_named_arguments
} 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(
[],
@ -182,135 +141,152 @@ defmodule Indexer.Fetcher.InternalTransactionTest do
end
describe "run/2" do
@tag :no_parity
test "duplicate transaction hashes are logged", %{json_rpc_named_arguments: json_rpc_named_arguments} do
test "handles empty block numbers", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, [%{id: 0, result: %{"trace" => []}}]}
end)
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id}], _options ->
{:ok,
[
%{
id: id,
result: []
}
]}
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
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}} =
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
log =
capture_log(fn ->
InternalTransaction.run(
[
{1, bytes, 0},
{1, bytes, 0}
],
json_rpc_named_arguments
)
end)
assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
assert :ok == InternalTransaction.run([block.number], json_rpc_named_arguments)
assert log =~
"""
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}
"""
assert nil == Repo.get(PendingBlockOperation, block_hash)
end
@tag :no_parity
test "internal transactions with failed parent does not create a new address", %{
test "handles blocks with transactions correctly", %{
json_rpc_named_arguments: json_rpc_named_arguments
} 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
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: %{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity ->
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [%{id: id, method: "trace_replayBlockTransactions"}], _options ->
{:ok,
[
%{
id: id,
result: [
%{
"output" => "0x",
"stateDiff" => nil,
"trace" => [
%{
"action" => %{
"callType" => "call",
"from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4",
"gas" => "0x8600",
"input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008",
"to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c",
"value" => "0x174876e800"
},
"result" => %{"gasUsed" => "0x7d37", "output" => "0x"},
"subtraces" => 0,
"traceAddress" => [],
"type" => "call"
}
],
"transactionHash" => transaction.hash,
"vmTrace" => nil
}
]
}
]}
end)
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" => "0xc73add416e2119d20ce80e0904fc1877e33ef246",
"gas" => "0x13388",
"input" => "0xc793bf97",
"to" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340",
"value" => "0x0"
},
"error" => "Reverted",
"subtraces" => 1,
"traceAddress" => [],
"type" => "call"
},
%{
"action" => %{
"from" => "0x2d07e106b5d280e4ccc2d10deee62441c91d4340",
"gas" => "0xb2ab",
"init" =>
"0x608060405234801561001057600080fd5b5060d38061001f6000396000f3fe6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"value" => "0x0"
},
"result" => %{
"address" => "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75",
"code" =>
"0x6080604052600436106038577c010000000000000000000000000000000000000000000000000000000060003504633ccfd60b8114604f575b336000908152602081905260409020805434019055005b348015605a57600080fd5b5060616063565b005b33600081815260208190526040808220805490839055905190929183156108fc02918491818181858888f1935050505015801560a3573d6000803e3d6000fd5b505056fea165627a7a72305820e9a226f249def650de957dd8b4127b85a3049d6bfa818cadc4e2d3c44b6a53530029",
"gasUsed" => "0xa535"
},
"subtraces" => 0,
"traceAddress" => [0],
"type" => "create"
}
],
"vmTrace" => nil
}
}
]}
end)
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
"from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4",
"to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c",
"gas" => "0x8600",
"gasUsed" => "0x7d37",
"input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008",
"output" => "0x",
"value" => "0x174876e800"
}
]
}
]}
end)
variant_name ->
raise ArgumentError, "Unsupported variant name (#{variant_name})"
end
end
%Transaction{hash: %Hash{bytes: bytes}} =
insert(:transaction, hash: "0x03cd5899a63b6f6222afda8705d059fd5a7d126bcabe962fb654d9736e6bcafa")
|> with_block()
CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments)
:ok =
InternalTransaction.run(
[
{7_202_692, bytes, 0}
],
json_rpc_named_arguments
)
assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
address = "0xf4a5afe28b91cf928c2568805cfbb36d477f0b75"
assert :ok == InternalTransaction.run([block.number], json_rpc_named_arguments)
fetched_address = Repo.one(from(address in Address, where: address.hash == ^address))
assert nil == Repo.get(PendingBlockOperation, block_hash)
assert is_nil(fetched_address)
end
assert Repo.exists?(from(i in Chain.InternalTransaction, where: i.block_hash == ^block_hash))
end
@tag :no_parity
test "duplicate transaction hashes only retry uniques", %{json_rpc_named_arguments: json_rpc_named_arguments} do
test "handles failure by retrying only unique numbers", %{
json_rpc_named_arguments: json_rpc_named_arguments
} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, [%{id: 0, error: %{code: -32602, message: "Invalid params"}}]}
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
%Transaction{hash: %Hash{bytes: bytes}} =
insert(:transaction, hash: "0x0000000000000000000000000000000000000000000000000000000000000001")
assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
assert InternalTransaction.run(
[
{1, bytes, 0},
{1, bytes, 0}
],
json_rpc_named_arguments
) == {:retry, [{1, bytes, 0}]}
assert {:retry, [block.number]} == InternalTransaction.run([block.number, block.number], json_rpc_named_arguments)
assert %{block_hash: block_hash} = Repo.get(PendingBlockOperation, block_hash)
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",
block_number: 3_530_917,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: nil,
@ -23,6 +24,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
},
%{
address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
block_number: 3_586_935,
data: "0x",
first_topic: "0x55e10366a5f552746106978b694d7ef3bbddec06bd5f9b9d15ad46f475c653ef",
@ -36,6 +38,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
%{
address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2",
block_number: 3_664_064,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x000000000000000000000000000000000000000000000000ebec21ee1da40000",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: "0x00000000000000000000000000000000000000000000000000000000000000b7",
@ -67,7 +70,8 @@ defmodule Indexer.Transform.TokenTransfersTest do
token_contract_address_hash: log_3.address_hash,
token_id: 183,
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),
@ -77,7 +81,8 @@ defmodule Indexer.Transform.TokenTransfersTest do
to_address_hash: truncated_hash(log_1.third_topic),
token_contract_address_hash: log_1.address_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,
third_topic: nil,
transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
type: "mined"
}
@ -113,6 +119,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
log_index: log.index,
from_address_hash: "0x58ab73cb79c8275628e0213742a85b163fe0a9fb",
to_address_hash: "0xbe8cdfc13ffda20c844ac3da2b53a23ac5787f1e",
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
token_contract_address_hash: log.address_hash,
token_id: 14_939,
transaction_hash: log.transaction_hash,
@ -128,6 +135,7 @@ defmodule Indexer.Transform.TokenTransfersTest do
log = %{
address_hash: "0x58Ab73CB79c8275628E0213742a85B163fE0A9Fb",
block_number: 8_683_457,
block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca",
data: "0x",
first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
fourth_topic: nil,

Loading…
Cancel
Save