From 6a7381b2963184c8c73e640b582c6ecfa0cdec24 Mon Sep 17 00:00:00 2001 From: Tim Mecklem Date: Tue, 31 Jul 2018 19:54:35 -0400 Subject: [PATCH 1/7] Add `created_contract_address_hash` to transactions and update queries --- apps/explorer/lib/explorer/chain.ex | 16 ------ .../lib/explorer/chain/transaction.ex | 57 +++++++++---------- .../20180117221923_create_transactions.exs | 2 + apps/explorer/test/explorer/chain_test.exs | 29 ---------- apps/explorer/test/support/factory.ex | 20 +++++++ ...n_internal_transaction_controller_test.exs | 10 +++- .../features/viewing_addresses_test.exs | 10 +++- .../features/viewing_blocks_test.exs | 10 +++- .../features/viewing_transactions_test.exs | 8 ++- 9 files changed, 80 insertions(+), 82 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index fc8a228664..bbee4d5c96 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -11,7 +11,6 @@ defmodule Explorer.Chain do order_by: 2, order_by: 3, preload: 2, - select_merge: 3, where: 2, where: 3 ] @@ -1535,21 +1534,6 @@ defmodule Explorer.Chain do defp fetch_transactions(paging_options \\ nil) do Transaction - |> select_merge([transaction], %{ - created_contract_address_hash: - type( - fragment( - ~s[ - (SELECT i."created_contract_address_hash" - FROM "internal_transactions" AS i - WHERE (i."transaction_hash" = ?) AND (i."type" = 'create') - LIMIT 1) - ], - transaction.hash - ), - Explorer.Chain.Hash.Address - ) - }) |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) |> handle_paging_options(paging_options) end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 10665ad6ff..7f30b9bf6a 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -19,7 +19,7 @@ defmodule Explorer.Chain.Transaction do alias Explorer.Chain.Transaction.Status - @optional_attrs ~w(block_hash block_number cumulative_gas_used gas_used index internal_transactions_indexed_at status + @optional_attrs ~w(block_hash block_number created_contract_address_hash cumulative_gas_used gas_used index internal_transactions_indexed_at status to_address_hash)a @required_attrs ~w(from_address_hash gas gas_price hash input nonce r s v value)a @@ -66,6 +66,9 @@ defmodule Explorer.Chain.Transaction do * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending. * `block_hash` - `block` foreign key. `nil` when transaction is pending. * `block_number` - Denormalized `block` `number`. `nil` when transaction is pending. + * `created_contract_address` - belongs_to association to `address` corresponding to `created_contract_address_hash`. + * `created_contract_address_hash` - Denormalized `internal_transaction` `created_contract_address_hash` + populated only when `to_address_hash` is nil. * `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before `transaction`'s `index`. `nil` when transaction is pending. * `from_address` - the source of `value` @@ -95,6 +98,8 @@ defmodule Explorer.Chain.Transaction do block: %Ecto.Association.NotLoaded{} | Block.t() | nil, block_hash: Hash.t() | nil, block_number: Block.block_number() | nil, + created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, + created_contract_address_hash: Hash.Address.t() | nil, cumulative_gas_used: Gas.t() | nil, from_address: %Ecto.Association.NotLoaded{} | Address.t(), from_address_hash: Hash.Address.t(), @@ -133,7 +138,6 @@ defmodule Explorer.Chain.Transaction do field(:status, Status) field(:v, :integer) field(:value, Wei) - field(:created_contract_address_hash, Hash.Address, virtual: true) timestamps() @@ -158,6 +162,14 @@ defmodule Explorer.Chain.Transaction do references: :hash, type: Hash.Address ) + + belongs_to( + :created_contract_address, + Address, + foreign_key: :created_contract_address_hash, + references: :hash, + type: Hash.Address + ) end @doc """ @@ -343,22 +355,6 @@ defmodule Explorer.Chain.Transaction do end end - defmacrop exists_contract_creation_with_matching_address_hash_fragment(transaction_hash, bytes) do - quote do - fragment( - ~s[ - EXISTS ( - SELECT 1 - FROM "internal_transactions" AS i - WHERE i."transaction_hash" = ? AND i."type" = 'create' AND i."created_contract_address_hash" = ? - ) - ], - unquote(transaction_hash), - unquote(bytes) - ) - end - end - @doc """ Adds to the given transaction's query a `where` with one of the conditions that the matched function returns. @@ -369,11 +365,11 @@ defmodule Explorer.Chain.Transaction do `where_address_fields_match(query, address, :from)` - returns a query considering that the given address_hash is equal to from_address_hash from - transactions' table or is equal to from_address_hash from token transfers't table. + transactions' table or is equal to from_address_hash from token transfers' table. `where_address_fields_match(query, address, nil)` - returns a query considering that the given address_hash can be: to_address_hash, - from_address_hash, created_contract_address_hash from internal_transactions' table, + from_address_hash, created_contract_address_hash, to_address_hash or from_address_hash from token_transfers' table. ### Token transfers' preload @@ -383,32 +379,35 @@ defmodule Explorer.Chain.Transaction do """ def where_address_fields_match(query, address_hash, :to) do query - |> join_token_tranfers() + |> join_token_transfers() |> preload_token_transfers(address_hash) - |> where([t, tt], t.to_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) + |> where( + [t, tt], + t.to_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or + t.created_contract_address_hash == ^address_hash + ) end def where_address_fields_match(query, address_hash, :from) do query - |> join_token_tranfers() + |> join_token_transfers() |> preload_token_transfers(address_hash) |> where([t, tt], t.from_address_hash == ^address_hash or tt.from_address_hash == ^address_hash) end def where_address_fields_match(query, address_hash, nil) do query - |> join_token_tranfers() + |> join_token_transfers() |> preload_token_transfers(address_hash) |> where( [t, tt], - t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or - tt.from_address_hash == ^address_hash or - (is_nil(t.to_address_hash) and - exists_contract_creation_with_matching_address_hash_fragment(t.hash, ^address_hash.bytes)) + t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or + t.created_contract_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or + tt.from_address_hash == ^address_hash ) end - defp join_token_tranfers(query) do + defp join_token_transfers(query) do join(query, :left, [t], tt in assoc(t, :token_transfers)) end diff --git a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs index 1f07aa357c..01f831e07e 100644 --- a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs @@ -44,6 +44,7 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do add(:from_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false) # `null` when it is a contract creation transaction add(:to_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) + add(:created_contract_address_hash, references(:addresses, column: :hash, type: :bytea), null: true) end create( @@ -129,6 +130,7 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do create(index(:transactions, :block_hash)) create(index(:transactions, :from_address_hash)) create(index(:transactions, :to_address_hash)) + create(index(:transactions, :created_contract_address_hash)) create(index(:transactions, :inserted_at)) create(index(:transactions, :updated_at)) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 6dad44b353..d0c115545c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -474,23 +474,6 @@ defmodule Explorer.ChainTest do ) end - test "created_contract_address_hash populated when existing" do - transaction = - %Transaction{hash: hash_with_block} = - :transaction - |> insert() - |> with_block() - - %InternalTransaction{created_contract_address_hash: contract_hash} = - insert(:internal_transaction_create, transaction: transaction, index: 0) - - assert {:ok, %Transaction{hash: ^hash_with_block, created_contract_address_hash: ^contract_hash}} = - Chain.hash_to_transaction( - hash_with_block, - necessity_by_association: %{block: :required} - ) - end - test "transaction with multiple create internal transactions is returned" do transaction = %Transaction{hash: hash_with_block} = @@ -1043,18 +1026,6 @@ defmodule Explorer.ChainTest do insert(:transaction) assert [] == Explorer.Chain.recent_collated_transactions() end - - test "it has contract_creation_address_hash added" do - transaction = - :transaction - |> insert() - |> with_block() - - %InternalTransaction{created_contract_address_hash: hash} = - insert(:internal_transaction_create, transaction: transaction, index: 0) - - assert [%Transaction{created_contract_address_hash: ^hash}] = Explorer.Chain.recent_collated_transactions() - end end describe "smart_contract_bytecode/1" do diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 029d695c53..0cc851b750 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -160,6 +160,26 @@ defmodule Explorer.Factory do |> Repo.preload(:block) end + def with_contract_creation(%Transaction{} = transaction, %Address{hash: contract_address_hash}) do + transaction + |> Transaction.changeset(%{ + created_contract_address_hash: contract_address_hash + }) + |> Repo.update!() + end + + def with_contract_creation(%InternalTransaction{} = internal_transaction, %Address{ + contract_code: contract_code, + hash: contract_address_hash + }) do + internal_transaction + |> InternalTransaction.changeset(%{ + contract_code: contract_code, + created_contract_address_hash: contract_address_hash + }) + |> Repo.update!() + end + def data(sequence_name) do unpadded = sequence_name diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs index d3f44fb39b..ce9f7c2c78 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_internal_transaction_controller_test.exs @@ -61,12 +61,18 @@ defmodule ExplorerWeb.TransactionInternalTransactionControllerTest do end test "with no to_address_hash overview contains contract create address", %{conn: conn} do + contract_address = insert(:contract_address) + transaction = :transaction - |> insert() + |> insert(to_address: nil) + |> with_contract_creation(contract_address) |> with_block() - internal_transaction = insert(:internal_transaction_create, transaction: transaction, index: 0) + internal_transaction = + :internal_transaction_create + |> insert(transaction: transaction, index: 0) + |> with_contract_creation(contract_address) conn = get( diff --git a/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs index 834649b23f..effc655333 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_addresses_test.exs @@ -50,8 +50,8 @@ defmodule ExplorerWeb.ViewingAddressesTest do describe "viewing contract creator" do test "see the contract creator and transaction links", %{session: session} do address = insert(:address) - transaction = insert(:transaction, from_address: address) contract = insert(:address, contract_code: Explorer.Factory.data("contract_code")) + transaction = insert(:transaction, from_address: address, created_contract_address: contract) internal_transaction = insert( @@ -148,18 +148,22 @@ defmodule ExplorerWeb.ViewingAddressesTest do } do lincoln = addresses.lincoln + contract_address = insert(:contract_address) + from_lincoln = :transaction |> insert(from_address: lincoln, to_address: nil) + |> with_contract_creation(contract_address) |> with_block(block) internal_transaction = - insert( - :internal_transaction_create, + :internal_transaction_create + |> insert( transaction: from_lincoln, from_address: lincoln, index: 0 ) + |> with_contract_creation(contract_address) session |> AddressPage.visit_page(addresses.lincoln) diff --git a/apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs index 9b4b9a2a3e..481e1e92df 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_blocks_test.exs @@ -76,12 +76,18 @@ defmodule ExplorerWeb.ViewingBlocksTest do test "contract creation is shown for to_address in transaction list", %{session: session} do block = insert(:block, number: 42) + contract_address = insert(:contract_address) + transaction = :transaction - |> insert(to_address: nil, to_address: nil) + |> insert(to_address: nil) + |> with_contract_creation(contract_address) |> with_block(block) - internal_transaction = insert(:internal_transaction_create, transaction: transaction, index: 0) + internal_transaction = + :internal_transaction_create + |> insert(transaction: transaction, index: 0) + |> with_contract_creation(contract_address) session |> BlockPage.visit_page(block) diff --git a/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs b/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs index 5e51ac69d4..e66035d5e3 100644 --- a/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs +++ b/apps/explorer_web/test/explorer_web/features/viewing_transactions_test.exs @@ -78,12 +78,18 @@ defmodule ExplorerWeb.ViewingTransactionsTest do end test "contract creation is shown for to_address on home page", %{session: session} do + contract_address = insert(:contract_address) + transaction = :transaction |> insert(to_address: nil) + |> with_contract_creation(contract_address) |> with_block() - internal_transaction = insert(:internal_transaction_create, transaction: transaction, index: 0) + internal_transaction = + :internal_transaction_create + |> insert(transaction: transaction, index: 0) + |> with_contract_creation(contract_address) session |> HomePage.visit_page() From ab0ca21dac8c2011ee9d003556775272c8565670 Mon Sep 17 00:00:00 2001 From: Tim Mecklem Date: Thu, 2 Aug 2018 14:31:27 -0400 Subject: [PATCH 2/7] Update indexer to set created_contract_address_hash on transaction Co-authored-by: stamates --- apps/explorer/lib/explorer/chain/import.ex | 12 +++- apps/explorer/test/explorer/import_test.exs | 66 +++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 0d6b28954e..b069a727d5 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -618,7 +618,17 @@ defmodule Explorer.Chain.Import do from( t in Transaction, where: t.hash in ^ordered_transaction_hashes, - update: [set: [internal_transactions_indexed_at: ^timestamps.updated_at]] + 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 = ? and it.type = 'create' and ? IS NULL)", + t.hash, + t.to_address_hash + ) + ] + ] ) transaction_count = Enum.count(ordered_transaction_hashes) diff --git a/apps/explorer/test/explorer/import_test.exs b/apps/explorer/test/explorer/import_test.exs index 31a4f5f3d0..a5d7a2f451 100644 --- a/apps/explorer/test/explorer/import_test.exs +++ b/apps/explorer/test/explorer/import_test.exs @@ -113,5 +113,71 @@ defmodule Explorer.Chain.ImportTest do refute transaction.internal_transactions_indexed_at == nil 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 + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + created_contract_address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + transaction_hash = "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6" + + options = %{ + addresses: %{ + params: [ + %{hash: from_address_hash}, + %{ + contract_code: smart_contract_bytecode, + hash: created_contract_address_hash + } + ] + }, + transactions: %{ + params: [ + %{ + from_address_hash: from_address_hash, + gas: 4_677_320, + gas_price: 1, + hash: transaction_hash, + input: "0x", + nonce: 0, + r: 0, + s: 0, + v: 0, + value: 0 + } + ], + on_conflict: :replace_all + }, + internal_transactions: %{ + params: [ + %{ + block_number: 35, + call_type: "call", + created_contract_code: smart_contract_bytecode, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash, + gas: 4_677_320, + gas_used: 27770, + index: 0, + init: + "0x6060604052341561000c57fe5b5b6101a68061001c6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631d3b9edf811461005b57806366098d4f1461007b578063a12f69e01461009b578063f4f3bdc1146100bb575bfe5b6100696004356024356100db565b60408051918252519081900360200190f35b61006960043560243561010a565b60408051918252519081900360200190f35b610069600435602435610124565b60408051918252519081900360200190f35b610069600435602435610163565b60408051918252519081900360200190f35b60008282028315806100f757508284828115156100f457fe5b04145b15156100ff57fe5b8091505b5092915050565b6000828201838110156100ff57fe5b8091505b5092915050565b60008080831161013057fe5b828481151561013b57fe5b049050828481151561014957fe5b0681840201841415156100ff57fe5b8091505b5092915050565b60008282111561016f57fe5b508082035b929150505600a165627a7a7230582020c944d8375ca14e2c92b14df53c2d044cb99dc30c3ba9f55e2bcde87bd4709b0029", + output: "0x", + trace_address: [], + transaction_hash: transaction_hash, + type: "create", + value: 0 + } + ] + } + } + + assert {:ok, _} = Import.all(options) + + transaction = + Explorer.Repo.one(from(transaction in Explorer.Chain.Transaction, where: transaction.hash == ^transaction_hash)) + + refute transaction.created_contract_address_hash == nil + end end end From 7b82a3442d25aa667bf76251ce28f7516f9dd8c8 Mon Sep 17 00:00:00 2001 From: Stamates Date: Fri, 3 Aug 2018 10:03:53 -0400 Subject: [PATCH 3/7] Added test that contract_creadted_address_hash is nil if to_address is not nil --- apps/explorer/test/explorer/import_test.exs | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/apps/explorer/test/explorer/import_test.exs b/apps/explorer/test/explorer/import_test.exs index a5d7a2f451..3569daf882 100644 --- a/apps/explorer/test/explorer/import_test.exs +++ b/apps/explorer/test/explorer/import_test.exs @@ -179,5 +179,74 @@ defmodule Explorer.Chain.ImportTest do refute transaction.created_contract_address_hash == nil end + + test "when the transaction has a to_address and an internal transaction with type create it does not populates the denormalized created_contract_address_hash" do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + to_address_hash = "0xf7ddc5c7a2d2f0d7a9798459c0104fdf5e9a7bbb" + created_contract_address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + transaction_hash = "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6" + + options = %{ + addresses: %{ + params: [ + %{hash: from_address_hash}, + %{ + contract_code: smart_contract_bytecode, + hash: created_contract_address_hash + } + ] + }, + transactions: %{ + params: [ + %{ + from_address_hash: from_address_hash, + to_address_hash: from_address_hash, + gas: 4_677_320, + gas_price: 1, + hash: transaction_hash, + input: "0x", + nonce: 0, + r: 0, + s: 0, + v: 0, + value: 0 + } + ], + on_conflict: :replace_all + }, + internal_transactions: %{ + params: [ + %{ + block_number: 35, + call_type: "call", + created_contract_code: smart_contract_bytecode, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash, + gas: 4_677_320, + gas_used: 27770, + index: 0, + init: + "0x6060604052341561000c57fe5b5b6101a68061001c6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631d3b9edf811461005b57806366098d4f1461007b578063a12f69e01461009b578063f4f3bdc1146100bb575bfe5b6100696004356024356100db565b60408051918252519081900360200190f35b61006960043560243561010a565b60408051918252519081900360200190f35b610069600435602435610124565b60408051918252519081900360200190f35b610069600435602435610163565b60408051918252519081900360200190f35b60008282028315806100f757508284828115156100f457fe5b04145b15156100ff57fe5b8091505b5092915050565b6000828201838110156100ff57fe5b8091505b5092915050565b60008080831161013057fe5b828481151561013b57fe5b049050828481151561014957fe5b0681840201841415156100ff57fe5b8091505b5092915050565b60008282111561016f57fe5b508082035b929150505600a165627a7a7230582020c944d8375ca14e2c92b14df53c2d044cb99dc30c3ba9f55e2bcde87bd4709b0029", + output: "0x", + trace_address: [], + transaction_hash: transaction_hash, + type: "create", + value: 0 + } + ] + } + } + + assert {:ok, _} = Import.all(options) + + transaction = + Explorer.Repo.one(from(transaction in Explorer.Chain.Transaction, where: transaction.hash == ^transaction_hash)) + + assert transaction.created_contract_address_hash == nil + end end + end From 5a54caecb79983de0cd44caf55f4f8cc16b54790 Mon Sep 17 00:00:00 2001 From: Tim Mecklem Date: Fri, 3 Aug 2018 10:54:39 -0400 Subject: [PATCH 4/7] Use union for address fields match to improve slow token_transfers join Co-authored-by: stamates --- apps/explorer/lib/explorer/chain.ex | 1 + .../lib/explorer/chain/transaction.ex | 180 +++++++++++------- apps/explorer/test/explorer/import_test.exs | 7 +- 3 files changed, 116 insertions(+), 72 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index bbee4d5c96..1158c2dacf 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -183,6 +183,7 @@ defmodule Explorer.Chain do |> fetch_transactions() |> Transaction.where_address_fields_match(address_hash, direction) |> join_associations(necessity_by_association) + |> Transaction.preload_token_transfers(address_hash) |> Repo.all() end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 7f30b9bf6a..61b4d312f2 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -298,6 +298,117 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + def preload_token_transfers(query, address_hash) do + token_transfers_query = + from( + tt in TokenTransfer, + where: + tt.token_contract_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or + tt.from_address_hash == ^address_hash, + preload: [:token, :from_address, :to_address] + ) + + preload(query, [tt], token_transfers: ^token_transfers_query) + end + + @doc """ + Adds to the given transaction's query a `where` with one of the conditions that the matched + function returns. + + `where_address_fields_match(query, address, :to)` + - returns a query considering that the given address_hash is equal to to_address_hash from + transactions' table or is equal to to_address_hash from token transfers' table. + + `where_address_fields_match(query, address, :from)` + - returns a query considering that the given address_hash is equal to from_address_hash from + transactions' table or is equal to from_address_hash from token transfers' table. + + `where_address_fields_match(query, address, nil)` + - returns a query considering that the given address_hash can be: to_address_hash, + from_address_hash, created_contract_address_hash, + to_address_hash or from_address_hash from token_transfers' table. + + ### Token transfers' preload + + Token transfers will be preloaded according to the given address_hash considering if it's equal + to token_contract_address_hash, to_address_hash or from_address_hash from Token Transfers's table. + """ + def where_address_fields_match(query, address_hash, :to) do + query + |> where( + [t], + t.hash in fragment( + """ + ( + SELECT t0.hash AS hash + FROM transactions AS t0 + WHERE t0.to_address_hash = ? OR t0.created_contract_address_hash = ? + ) + UNION + ( + SELECT tt.transaction_hash AS hash + FROM token_transfers AS tt + WHERE tt.to_address_hash = ? + ) + """, + ^address_hash.bytes, + ^address_hash.bytes, + ^address_hash.bytes + ) + ) + end + + def where_address_fields_match(query, address_hash, :from) do + query + |> where( + [t], + t.hash in fragment( + """ + ( + SELECT t0.hash AS hash + FROM transactions AS t0 + WHERE t0.from_address_hash = ? + ) + UNION + ( + SELECT tt.transaction_hash AS hash + FROM token_transfers AS tt + WHERE tt.from_address_hash = ? + ) + """, + ^address_hash.bytes, + ^address_hash.bytes + ) + ) + end + + def where_address_fields_match(query, address_hash, nil) do + query + |> where( + [t], + t.hash in fragment( + """ + ( + SELECT t0.hash AS hash + FROM transactions AS t0 + WHERE t0.to_address_hash = ? OR t0.from_address_hash = ? OR t0.created_contract_address_hash = ? + ) + UNION + ( + SELECT tt.transaction_hash AS hash + FROM token_transfers AS tt + WHERE tt.to_address_hash = ? OR tt.from_address_hash = ? + ) + """, + ^address_hash.bytes, + ^address_hash.bytes, + ^address_hash.bytes, + ^address_hash.bytes, + ^address_hash.bytes + ) + ) + end + @collated_fields ~w(block_number cumulative_gas_used gas_used index status)a @collated_message "can't be blank when the transaction is collated into a block" @@ -354,73 +465,4 @@ defmodule Explorer.Chain.Transaction do _ -> changeset end end - - @doc """ - Adds to the given transaction's query a `where` with one of the conditions that the matched - function returns. - - `where_address_fields_match(query, address, :to)` - - returns a query considering that the given address_hash is equal to to_address_hash from - transactions' table or is equal to to_address_hash from token transfers' table. - - `where_address_fields_match(query, address, :from)` - - returns a query considering that the given address_hash is equal to from_address_hash from - transactions' table or is equal to from_address_hash from token transfers' table. - - `where_address_fields_match(query, address, nil)` - - returns a query considering that the given address_hash can be: to_address_hash, - from_address_hash, created_contract_address_hash, - to_address_hash or from_address_hash from token_transfers' table. - - ### Token transfers' preload - - Token transfers will be preloaded according to the given address_hash considering if it's equal - to token_contract_address_hash, to_address_hash or from_address_hash from Token Transfers's table. - """ - def where_address_fields_match(query, address_hash, :to) do - query - |> join_token_transfers() - |> preload_token_transfers(address_hash) - |> where( - [t, tt], - t.to_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or - t.created_contract_address_hash == ^address_hash - ) - end - - def where_address_fields_match(query, address_hash, :from) do - query - |> join_token_transfers() - |> preload_token_transfers(address_hash) - |> where([t, tt], t.from_address_hash == ^address_hash or tt.from_address_hash == ^address_hash) - end - - def where_address_fields_match(query, address_hash, nil) do - query - |> join_token_transfers() - |> preload_token_transfers(address_hash) - |> where( - [t, tt], - t.to_address_hash == ^address_hash or t.from_address_hash == ^address_hash or - t.created_contract_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or - tt.from_address_hash == ^address_hash - ) - end - - defp join_token_transfers(query) do - join(query, :left, [t], tt in assoc(t, :token_transfers)) - end - - defp preload_token_transfers(query, address_hash) do - token_transfers_query = - from( - tt in TokenTransfer, - where: - tt.token_contract_address_hash == ^address_hash or tt.to_address_hash == ^address_hash or - tt.from_address_hash == ^address_hash, - preload: [:token, :from_address, :to_address] - ) - - preload(query, [tt], token_transfers: ^token_transfers_query) - end end diff --git a/apps/explorer/test/explorer/import_test.exs b/apps/explorer/test/explorer/import_test.exs index 3569daf882..b179ae9d0d 100644 --- a/apps/explorer/test/explorer/import_test.exs +++ b/apps/explorer/test/explorer/import_test.exs @@ -196,14 +196,14 @@ defmodule Explorer.Chain.ImportTest do %{ contract_code: smart_contract_bytecode, hash: created_contract_address_hash - } + }, + %{hash: to_address_hash} ] }, transactions: %{ params: [ %{ from_address_hash: from_address_hash, - to_address_hash: from_address_hash, gas: 4_677_320, gas_price: 1, hash: transaction_hash, @@ -211,6 +211,7 @@ defmodule Explorer.Chain.ImportTest do nonce: 0, r: 0, s: 0, + to_address_hash: to_address_hash, v: 0, value: 0 } @@ -231,6 +232,7 @@ defmodule Explorer.Chain.ImportTest do init: "0x6060604052341561000c57fe5b5b6101a68061001c6000396000f300606060405263ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416631d3b9edf811461005b57806366098d4f1461007b578063a12f69e01461009b578063f4f3bdc1146100bb575bfe5b6100696004356024356100db565b60408051918252519081900360200190f35b61006960043560243561010a565b60408051918252519081900360200190f35b610069600435602435610124565b60408051918252519081900360200190f35b610069600435602435610163565b60408051918252519081900360200190f35b60008282028315806100f757508284828115156100f457fe5b04145b15156100ff57fe5b8091505b5092915050565b6000828201838110156100ff57fe5b8091505b5092915050565b60008080831161013057fe5b828481151561013b57fe5b049050828481151561014957fe5b0681840201841415156100ff57fe5b8091505b5092915050565b60008282111561016f57fe5b508082035b929150505600a165627a7a7230582020c944d8375ca14e2c92b14df53c2d044cb99dc30c3ba9f55e2bcde87bd4709b0029", output: "0x", + to_address_hash: to_address_hash, trace_address: [], transaction_hash: transaction_hash, type: "create", @@ -248,5 +250,4 @@ defmodule Explorer.Chain.ImportTest do assert transaction.created_contract_address_hash == nil end end - end From d7fd7d27fb9a4c5e02e69b37e426aa120fd5ecb4 Mon Sep 17 00:00:00 2001 From: Stamates Date: Fri, 3 Aug 2018 15:28:06 -0400 Subject: [PATCH 5/7] Update etherscan context transactions list to use created_contract_address_hash from transaction --- apps/explorer/lib/explorer/etherscan.ex | 23 +++++++++---------- .../explorer/test/explorer/etherscan_test.exs | 16 +++++++++---- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index a480a8ee23..e8cdd3ea18 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -45,20 +45,21 @@ defmodule Explorer.Etherscan do end @transaction_fields [ - :block_number, - :hash, - :nonce, :block_hash, - :index, + :block_number, + :created_contract_address_hash, + :cumulative_gas_used, :from_address_hash, - :to_address_hash, - :value, :gas, :gas_price, - :status, + :gas_used, + :hash, + :index, :input, - :cumulative_gas_used, - :gas_used + :nonce, + :status, + :to_address_hash, + :value ] defp list_transactions(address_hash, max_block_number, options) do @@ -66,17 +67,15 @@ defmodule Explorer.Etherscan do from( t in Transaction, inner_join: b in assoc(t, :block), - left_join: it in assoc(t, :internal_transactions), where: t.to_address_hash == ^address_hash, or_where: t.from_address_hash == ^address_hash, - or_where: it.transaction_hash == t.hash and it.type == ^"create", + or_where: t.created_contract_address_hash == ^address_hash, order_by: [{^options.order_by_direction, t.block_number}], limit: ^options.page_size, offset: ^offset(options), select: merge(map(t, ^@transaction_fields), %{ block_timestamp: b.timestamp, - created_contract_address_hash: it.created_contract_address_hash, confirmations: fragment("? - ?", ^max_block_number, t.block_number) }) ) diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 99828632ec..aaa32d742d 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -54,14 +54,18 @@ defmodule Explorer.EtherscanTest do test "with created contract address" do address = insert(:address) + contract_address = insert(:contract_address) transaction = :transaction - |> insert(from_address: address) + |> insert(from_address: address, to_address: nil) + |> with_contract_creation(contract_address) |> with_block() %{created_contract_address_hash: contract_address_hash} = - insert(:internal_transaction_create, transaction: transaction, index: 0) + :internal_transaction_create + |> insert(transaction: transaction, index: 0) + |> with_contract_creation(contract_address) [found_transaction] = Etherscan.list_transactions(contract_address_hash) @@ -121,14 +125,18 @@ defmodule Explorer.EtherscanTest do test "loads created_contract_address_hash if available" do address = insert(:address) + contract_address = insert(:contract_address) transaction = :transaction - |> insert(from_address: address) + |> insert(from_address: address, to_address: nil) + |> with_contract_creation(contract_address) |> with_block() %{created_contract_address_hash: contract_hash} = - insert(:internal_transaction_create, transaction: transaction, index: 0) + :internal_transaction_create + |> insert(transaction: transaction, index: 0) + |> with_contract_creation(contract_address) [found_transaction] = Etherscan.list_transactions(address.hash) From f75eee5e6134b2e55d7e393bd8996ac9237ff28d Mon Sep 17 00:00:00 2001 From: Stamates Date: Fri, 3 Aug 2018 15:39:27 -0400 Subject: [PATCH 6/7] Refactor import test --- apps/explorer/test/explorer/import_test.exs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/explorer/test/explorer/import_test.exs b/apps/explorer/test/explorer/import_test.exs index b179ae9d0d..73651c15b4 100644 --- a/apps/explorer/test/explorer/import_test.exs +++ b/apps/explorer/test/explorer/import_test.exs @@ -1,7 +1,8 @@ defmodule Explorer.Chain.ImportTest do use Explorer.DataCase - alias Explorer.Chain.Import + alias Explorer.Chain + alias Explorer.Chain.{Address, Import, Transaction} doctest Import @@ -50,9 +51,9 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) - address = Explorer.Repo.one(from(address in Explorer.Chain.Address, where: address.hash == ^address_hash)) + address = Explorer.Repo.get(Address, address_hash) - assert address.contract_code != nil + assert address.contract_code != smart_contract_bytecode end test "with internal_transactions updates Transaction internal_transactions_indexed_at" do @@ -108,8 +109,7 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) - transaction = - Explorer.Repo.one(from(transaction in Explorer.Chain.Transaction, where: transaction.hash == ^transaction_hash)) + transaction = Explorer.Repo.get(Transaction, transaction_hash) refute transaction.internal_transactions_indexed_at == nil end @@ -174,10 +174,10 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) - transaction = - Explorer.Repo.one(from(transaction in Explorer.Chain.Transaction, where: transaction.hash == ^transaction_hash)) + transaction = Explorer.Repo.get(Transaction, transaction_hash) - refute transaction.created_contract_address_hash == nil + assert {:ok, transaction.created_contract_address_hash} == + Chain.string_to_address_hash(created_contract_address_hash) end test "when the transaction has a to_address and an internal transaction with type create it does not populates the denormalized created_contract_address_hash" do @@ -244,8 +244,7 @@ defmodule Explorer.Chain.ImportTest do assert {:ok, _} = Import.all(options) - transaction = - Explorer.Repo.one(from(transaction in Explorer.Chain.Transaction, where: transaction.hash == ^transaction_hash)) + transaction = Explorer.Repo.get(Transaction, transaction_hash) assert transaction.created_contract_address_hash == nil end From b941b6b927c337b4916f130566e0e38fb4b0c8aa Mon Sep 17 00:00:00 2001 From: Stamates Date: Mon, 6 Aug 2018 09:56:56 -0400 Subject: [PATCH 7/7] Rename function --- apps/explorer/lib/explorer/chain/import.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index b069a727d5..f1d997d04d 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -351,7 +351,7 @@ defmodule Explorer.Chain.Import do end) |> Multi.run(:internal_transactions_indexed_at_transactions, fn %{internal_transactions: internal_transactions} when is_list(internal_transactions) -> - update_transactions_internal_transactions_indexed_at( + update_transactions( internal_transactions, %{ timeout: options[:transactions][:timeout] || @insert_transactions_timeout, @@ -604,7 +604,7 @@ defmodule Explorer.Chain.Import do {:ok, inserted} end - defp update_transactions_internal_transactions_indexed_at(internal_transactions, %{ + defp update_transactions(internal_transactions, %{ timeout: timeout, timestamps: timestamps })