Merge branch 'master' into ab-add-contract-code

pull/1577/head
Victor Baranov 6 years ago committed by GitHub
commit c2cf387d2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      CHANGELOG.md
  2. 2
      README.md
  3. 21
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  4. 66
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  5. 12
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  6. 43
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  7. 42
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  8. 3
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
  9. 13
      apps/explorer/lib/explorer/chain.ex
  10. 7
      apps/explorer/priv/repo/migrations/20190318151809_add_inserted_at_index_to_accounts.exs
  11. 7
      apps/indexer/lib/indexer/block/fetcher.ex
  12. 9
      apps/indexer/lib/indexer/block/fetcher/receipts.ex
  13. 2
      apps/indexer/lib/indexer/block/reward/fetcher.ex
  14. 96
      apps/indexer/test/indexer/block/fetcher_test.exs

@ -4,6 +4,32 @@
### Fixes ### Fixes
### Chore
## 1.3.6-beta
### Features
- [#1589](https://github.com/poanetwork/blockscout/pull/1589) - RPC endpoint to list addresses
- [#1567](https://github.com/poanetwork/blockscout/pull/1567) - Allow setting different configuration just for realtime fetcher
- [#1562](https://github.com/poanetwork/blockscout/pull/1562) - Add incoming transactions count to contract view
### Fixes
- [#1595](https://github.com/poanetwork/blockscout/pull/1595) - Reduce block_rewards in the catchup fetcher
- [#1590](https://github.com/poanetwork/blockscout/pull/1590) - Added guard for fetching blocks with invalid number
- [#1588](https://github.com/poanetwork/blockscout/pull/1588) - Fix usd value on address page
- [#1586](https://github.com/poanetwork/blockscout/pull/1586) - Exact timestamp display
- [#1581](https://github.com/poanetwork/blockscout/pull/1581) - Consider `creates` param when fetching transactions
- [#1559](https://github.com/poanetwork/blockscout/pull/1559) - Change v column type for Transactions table
### Chore
- [#1579](https://github.com/poanetwork/blockscout/pull/1579) - Add SpringChain to the list of Additional Chains Utilizing BlockScout
- [#1578](https://github.com/poanetwork/blockscout/pull/1578) - Refine contributing procedure
- [#1572](https://github.com/poanetwork/blockscout/pull/1572) - Add option to disable block rewards in indexer config
## 1.3.5-beta ## 1.3.5-beta

@ -60,6 +60,8 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
* [ARTIS](https://explorer.sigma1.artis.network) * [ARTIS](https://explorer.sigma1.artis.network)
* [SafeChain](https://explorer.safechain.io) * [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/) * [SpringChain](https://explorer.springrole.com/)
* [PIRL](http://pirl.es/)
### Visual Interface ### Visual Interface

@ -4,6 +4,20 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
alias Explorer.{Chain, Etherscan} alias Explorer.{Chain, Etherscan}
alias Explorer.Chain.{Address, Wei} alias Explorer.Chain.{Address, Wei}
def listaccounts(conn, params) do
options =
params
|> optional_params()
|> Map.put_new(:page_number, 0)
|> Map.put_new(:page_size, 10)
accounts = list_accounts(options)
conn
|> put_status(200)
|> render(:listaccounts, %{accounts: accounts})
end
def balance(conn, params, template \\ :balance) do def balance(conn, params, template \\ :balance) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do
@ -260,6 +274,13 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
Enum.any?(address_hashes, &(&1 == :error)) Enum.any?(address_hashes, &(&1 == :error))
end end
defp list_accounts(%{page_number: page_number, page_size: page_size}) do
offset = (max(page_number, 1) - 1) * page_size
# limit is just page_size
Chain.list_ordered_addresses(offset, page_size)
end
defp hashes_to_addresses(address_hashes) do defp hashes_to_addresses(address_hashes) do
address_hashes address_hashes
|> Chain.hashes_to_addresses() |> Chain.hashes_to_addresses()

@ -168,6 +168,17 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_listaccounts_example_value %{
"status" => "1",
"message" => "OK",
"result" => [
%{
"address" => "0x0000000000000000000000000000000000000000",
"balance" => "135499"
}
]
}
@account_getminedblocks_example_value_error %{ @account_getminedblocks_example_value_error %{
"status" => "0", "status" => "0",
"message" => "No blocks found", "message" => "No blocks found",
@ -720,6 +731,14 @@ defmodule BlockScoutWeb.Etherscan do
} }
} }
@account_model %{
name: "Account",
fields: %{
"address" => @address_hash_type,
"balance" => @wei_type
}
}
@contract_model %{ @contract_model %{
name: "Contract", name: "Contract",
fields: %{ fields: %{
@ -1289,6 +1308,50 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@account_listaccounts_action %{
name: "listaccounts",
description:
"Get a list of accounts and their balances, sorted ascending by the time they were first seen by the explorer.",
required_params: [],
optional_params: [
%{
key: "page",
type: "integer",
description:
"A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction."
},
%{
key: "offset",
type: "integer",
description:
"A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction."
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@account_listaccounts_example_value),
model: %{
name: "Result",
fields: %{
status: @status_type,
message: @message_type,
result: %{
type: "array",
array_type: @account_model
}
}
}
},
%{
code: "200",
description: "error",
example_value: Jason.encode!(@account_getminedblocks_example_value_error)
}
]
}
@logs_getlogs_action %{ @logs_getlogs_action %{
name: "getLogs", name: "getLogs",
description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.", description: "Get event logs for an address and/or topics. Up to a maximum of 1,000 event logs.",
@ -1767,7 +1830,8 @@ defmodule BlockScoutWeb.Etherscan do
@account_tokentx_action, @account_tokentx_action,
@account_tokenbalance_action, @account_tokenbalance_action,
@account_tokenlist_action, @account_tokenlist_action,
@account_getminedblocks_action @account_getminedblocks_action,
@account_listaccounts_action
] ]
} }

@ -3,6 +3,11 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.RPCView
def render("listaccounts.json", %{accounts: accounts}) do
accounts = Enum.map(accounts, &prepare_account/1)
RPCView.render("show.json", data: accounts)
end
def render("balance.json", %{addresses: [address]}) do def render("balance.json", %{addresses: [address]}) do
RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}") RPCView.render("show.json", data: "#{address.fetched_coin_balance.value}")
end end
@ -56,6 +61,13 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end
defp prepare_account(address) do
%{
"balance" => to_string(address.fetched_coin_balance.value),
"address" => to_string(address.hash)
}
end
defp prepare_transaction(transaction) do defp prepare_transaction(transaction) do
%{ %{
"blockNumber" => "#{transaction.block_number}", "blockNumber" => "#{transaction.block_number}",

@ -6,6 +6,49 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
alias Explorer.Chain.{Transaction, Wei} alias Explorer.Chain.{Transaction, Wei}
alias BlockScoutWeb.API.RPC.AddressController alias BlockScoutWeb.API.RPC.AddressController
describe "listaccounts" do
setup do
%{params: %{"module" => "account", "action" => "listaccounts"}}
end
test "with no addresses", %{params: params, conn: conn} do
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == []
end
test "with existing addresses", %{params: params, conn: conn} do
first_address = insert(:address, fetched_coin_balance: 10, inserted_at: Timex.shift(Timex.now(), minutes: -10))
second_address = insert(:address, fetched_coin_balance: 100, inserted_at: Timex.shift(Timex.now(), minutes: -5))
first_address_hash = to_string(first_address.hash)
second_address_hash = to_string(second_address.hash)
response =
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert [
%{
"address" => ^first_address_hash,
"balance" => "10"
},
%{
"address" => ^second_address_hash,
"balance" => "100"
}
] = response["result"]
end
end
describe "balance" do describe "balance" do
test "with missing address hash", %{conn: conn} do test "with missing address hash", %{conn: conn} do
params = %{ params = %{

@ -149,23 +149,25 @@ defmodule EthereumJSONRPC.Transaction do
elixir_to_params(%{transaction | "input" => "0x"}) elixir_to_params(%{transaction | "input" => "0x"})
end end
def elixir_to_params(%{ def elixir_to_params(
"blockHash" => block_hash, %{
"blockNumber" => block_number, "blockHash" => block_hash,
"from" => from_address_hash, "blockNumber" => block_number,
"gas" => gas, "from" => from_address_hash,
"gasPrice" => gas_price, "gas" => gas,
"hash" => hash, "gasPrice" => gas_price,
"input" => input, "hash" => hash,
"nonce" => nonce, "input" => input,
"r" => r, "nonce" => nonce,
"s" => s, "r" => r,
"to" => to_address_hash, "s" => s,
"transactionIndex" => index, "to" => to_address_hash,
"v" => v, "transactionIndex" => index,
"value" => value "v" => v,
}) do "value" => value
%{ } = transaction
) do
result = %{
block_hash: block_hash, block_hash: block_hash,
block_number: block_number, block_number: block_number,
from_address_hash: from_address_hash, from_address_hash: from_address_hash,
@ -182,6 +184,12 @@ defmodule EthereumJSONRPC.Transaction do
value: value, value: value,
transaction_index: index transaction_index: index
} }
if transaction["creates"] do
Map.put(result, :created_contract_address_hash, transaction["creates"])
else
result
end
end end
# Ganache bug. it return `to: "0x0"` except of `to: null` # Ganache bug. it return `to: "0x0"` except of `to: null`

@ -56,7 +56,8 @@ defmodule EthereumJSONRPC.Transactions do
to_address_hash: nil, to_address_hash: nil,
v: "0xbd", v: "0xbd",
value: 0, value: 0,
transaction_index: 0 transaction_index: 0,
created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4"
} }
] ]

@ -724,6 +724,19 @@ defmodule Explorer.Chain do
Repo.all(query) Repo.all(query)
end end
@spec list_ordered_addresses(non_neg_integer(), non_neg_integer()) :: [Address.t()]
def list_ordered_addresses(offset, limit) do
query =
from(
address in Address,
order_by: [asc: address.inserted_at],
offset: ^offset,
limit: ^limit
)
Repo.all(query)
end
def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query = query =
from( from(

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AddInsertedAtIndexToAccounts do
use Ecto.Migration
def change do
create(index(:addresses, :inserted_at))
end
end

@ -14,7 +14,7 @@ defmodule Indexer.Block.Fetcher do
alias Indexer.{AddressExtraction, CoinBalance, MintTransfer, ReplacedTransaction, Token, TokenTransfers, Tracer} alias Indexer.{AddressExtraction, CoinBalance, MintTransfer, ReplacedTransaction, Token, TokenTransfers, Tracer}
alias Indexer.Address.{CoinBalances, TokenBalances} alias Indexer.Address.{CoinBalances, TokenBalances}
alias Indexer.Block.Fetcher.Receipts alias Indexer.Block.Fetcher.Receipts
alias Indexer.Block.Transform alias Indexer.Block.{Reward, Transform}
@type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()} @type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()}
@ -127,7 +127,10 @@ defmodule Indexer.Block.Fetcher do
transactions_params: transactions_with_receipts transactions_params: transactions_with_receipts
} }
|> CoinBalances.params_set(), |> CoinBalances.params_set(),
beneficiaries_with_gas_payment <- add_gas_payments(beneficiary_params_set, transactions_with_receipts), beneficiaries_with_gas_payment <-
beneficiary_params_set
|> add_gas_payments(transactions_with_receipts)
|> Reward.Fetcher.reduce_uncle_rewards(),
address_token_balances = TokenBalances.params_set(%{token_transfers_params: token_transfers}), address_token_balances = TokenBalances.params_set(%{token_transfers_params: token_transfers}),
{:ok, inserted} <- {:ok, inserted} <-
__MODULE__.import( __MODULE__.import(

@ -42,7 +42,14 @@ defmodule Indexer.Block.Fetcher.Receipts do
end) end)
Enum.map(transactions_params, fn %{hash: transaction_hash} = transaction_params -> Enum.map(transactions_params, fn %{hash: transaction_hash} = transaction_params ->
Map.merge(transaction_params, Map.fetch!(transaction_hash_to_receipt_params, transaction_hash)) receipts_params = Map.fetch!(transaction_hash_to_receipt_params, transaction_hash)
merged_params = Map.merge(transaction_params, receipts_params)
if transaction_params[:created_contract_address_hash] && is_nil(receipts_params[:created_contract_address_hash]) do
Map.put(merged_params, :created_contract_address_hash, transaction_params[:created_contract_address_hash])
else
merged_params
end
end) end)
end end

@ -204,7 +204,7 @@ defmodule Indexer.Block.Reward.Fetcher do
end) end)
end end
defp reduce_uncle_rewards(beneficiaries_params) do def reduce_uncle_rewards(beneficiaries_params) do
beneficiaries_params beneficiaries_params
|> Enum.reduce([], fn %{address_type: address_type} = beneficiary, acc -> |> Enum.reduce([], fn %{address_type: address_type} = beneficiary, acc ->
current = current =

@ -609,6 +609,102 @@ defmodule Indexer.Block.FetcherTest do
raise ArgumentError, "Unsupported variant (#{variant})" raise ArgumentError, "Unsupported variant (#{variant})"
end end
end end
@tag :no_geth
test "correctly imports blocks with multiple uncle rewards for the same address", %{
block_fetcher: %Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} = block_fetcher
} do
block_number = 7_374_455
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
EthereumJSONRPC.Mox
|> expect(:json_rpc, 2, fn requests, _options ->
{:ok,
Enum.map(requests, fn
%{id: id, method: "eth_getBlockByNumber", params: ["0x708677", true]} ->
%{
id: id,
result: %{
"author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
"difficulty" => "0x6bc767dd80781",
"extraData" => "0x5050594520737061726b706f6f6c2d6574682d7477",
"gasLimit" => "0x7a121d",
"gasUsed" => "0x79cbe9",
"hash" => "0x1b6fb99af0b51af6685a191b2f7bcba684f8565629bf084c70b2530479407455",
"logsBloom" =>
"0x044d42d008801488400e1809190200a80d06105bc0c4100b047895c0d518327048496108388040140010b8208006288102e206160e21052322440924002090c1c808a0817405ab238086d028211014058e949401012403210314896702d06880c815c3060a0f0809987c81044488292cc11d57882c912a808ca10471c84460460040000c0001012804022000a42106591881d34407420ba401e1c08a8d00a000a34c11821a80222818a4102152c8a0c044032080c6462644223104d618e0e544072008120104408205c60510542264808488220403000106281a0290404220112c10b080145028c8000300b18a2c8280701c882e702210b00410834840108084",
"miner" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
"mixHash" => "0xda53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
"nonce" => "0x0946e5f01fce12bc",
"number" => "0x708677",
"parentHash" => "0x62543e836e0ef7edfa9e38f26526092c4be97efdf5ba9e0f53a4b0b7d5bc930a",
"receiptsRoot" => "0xa7d2b82bd8526de11736c18bd5cc8cfe2692106c4364526f3310ad56d78669c4",
"sealFields" => [
"0xa0da53ae7c2b3c529783d6cdacdb90587fd70eb651c0f04253e8ff17de97844010",
"0x880946e5f01fce12bc"
],
"sha3Uncles" => "0x483a8a21a5825ad270f358b3ea56e060bbb8b3082d9a92ec8fa17a5c7e6fc1b6",
"size" => "0x544c",
"stateRoot" => "0x85daa9cd528004c1609d4cb3520fd958e85983bb4183124a4a9f7137fd39c691",
"timestamp" => "0x5c8bc76e",
"totalDifficulty" => "0x201a42c35142ae94458",
"transactions" => [],
"transactionsRoot" => "0xcd6c12fa43cd4e92ad5c0bf232b30488bbcbfe273c5b4af0366fced0767d54db",
"uncles" => []
}
}
%{id: id, method: "trace_block"} ->
%{
id: id,
result: [
%{
"action" => %{
"author" => "0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c",
"rewardType" => "block",
"value" => "0x1d7d843dc3b48000"
},
"blockHash" => "0x1b6fb99af0b51af6685a191b2f7bcba684f8565629bf084c70b2530479407455",
"blockNumber" => block_number,
"subtraces" => 0,
"traceAddress" => [],
"type" => "reward"
},
%{
"action" => %{
"author" => "0xea674fdde714fd979de3edf0f56aa9716b898ec8",
"rewardType" => "uncle",
"value" => "0x14d1120d7b160000"
},
"blockHash" => "0x1b6fb99af0b51af6685a191b2f7bcba684f8565629bf084c70b2530479407455",
"blockNumber" => block_number,
"subtraces" => 0,
"traceAddress" => [],
"type" => "reward"
},
%{
"action" => %{
"author" => "0xea674fdde714fd979de3edf0f56aa9716b898ec8",
"rewardType" => "uncle",
"value" => "0x18493fba64ef0000"
},
"blockHash" => "0x1b6fb99af0b51af6685a191b2f7bcba684f8565629bf084c70b2530479407455",
"blockNumber" => block_number,
"subtraces" => 0,
"traceAddress" => [],
"type" => "reward"
}
]
}
end)}
end)
end
assert {:ok, %{errors: [], inserted: %{block_rewards: block_rewards}}} =
Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number)
assert Repo.one!(select(Chain.Block.Reward, fragment("COUNT(*)"))) == 2
end
end end
defp wait_until(timeout, producer) do defp wait_until(timeout, producer) do

Loading…
Cancel
Save