diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a88575126..c85ee009b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ ## Current - ### Features +- [#2182](https://github.com/poanetwork/blockscout/pull/2182) - add market history cache - [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty +- [#2191](https://github.com/poanetwork/blockscout/pull/2191) - allow to configure token metadata update interval +- [#2146](https://github.com/poanetwork/blockscout/pull/2146) - feat: add eth_getLogs rpc endpoint ### Fixes +- [#2201](https://github.com/poanetwork/blockscout/pull/2201) - footer columns fix - [#2179](https://github.com/poanetwork/blockscout/pull/2179) - fix docker build error - [#2165](https://github.com/poanetwork/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time - [#2175](https://github.com/poanetwork/blockscout/pull/2175) - fix coinmarketcap response errors @@ -32,13 +35,16 @@ - [#2123](https://github.com/poanetwork/blockscout/pull/2123) - fix coins percentage view - [#2119](https://github.com/poanetwork/blockscout/pull/2119) - fix map logging - [#2130](https://github.com/poanetwork/blockscout/pull/2130) - fix navigation +- [#2148](https://github.com/poanetwork/blockscout/pull/2148) - filter pending logs - [#2147](https://github.com/poanetwork/blockscout/pull/2147) - add rsk format of checksum - [#2149](https://github.com/poanetwork/blockscout/pull/2149) - remove pending transaction count +- [#2177](https://github.com/poanetwork/blockscout/pull/2177) - remove duplicate entries from UncleBlock's Fetcher - [#2169](https://github.com/poanetwork/blockscout/pull/2169) - add more validator reward types for xDai - [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions - [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining - [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test - [#2196](https://github.com/poanetwork/blockscout/pull/2196) - Nethermind client fixes +- [#2167](https://github.com/poanetwork/blockscout/pull/2168) - feat: document eth rpc api mimicking endpoints ### Chore - [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version diff --git a/README.md b/README.md index e0e9ece96b..cbf1af6acf 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on **all EVM** (Ethereum Virtual Machine) blockchains. This includes the Ethereum main and test networks as well as **Ethereum forks and sidechains**. -Following is an overview of the project and instructions for [getting started](#getting-started). +See our [project documentation](https://poanetwork.github.io/blockscout) for detailed information and setup instructions. -Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for additional deployment instructions, FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here. +Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here. You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout). @@ -24,290 +24,16 @@ You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/p BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the entire Ethereum network including all forks and sidechains. -Currently available block explorers (i.e. Etherscan and Etherchain) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent tools are needed to analyze and validate transactions. - - -### Features - -- [x] **Open source development**: The code is community driven and available for anyone to use, explore and improve. - -- [x] **Real time transaction tracking**: Transactions are updated in real time - no page refresh required. Infinite scrolling is also enabled. - -- [x] **Smart contract interaction**: Users can read and verify Solidity smart contracts and access pre-existing contracts to fast-track development. Support for Vyper, LLL, and Web Assembly contracts is in progress. - -- [x] **Token support**: ERC20 and ERC721 tokens are supported. Future releases will support additional token types including ERC223 and ERC1155. - -- [x] **User customization**: Users can easily deploy on a network and customize the Bootstrap interface. - -- [x] **Ethereum sidechain networks**: BlockScout supports the Ethereum mainnet, Ethereum testnets, POA network, and forks like Ethereum Classic, xDAI, additional sidechains, and private EVM networks. - -### Supported Projects - -| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | -|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------| -| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | -| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | -| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | -| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | -| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | -| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) | -| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | -| | | [SpringChain](https://explorer.springrole.com/) | -| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | -| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | -| | | [Tenda](https://tenda.network) | - - -### Visual Interface - -Interface for the POA network _updated 02/2019_ - -![BlockScout Example](explorer_example_2_2019.gif) - - -### Umbrella Project Organization - -This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`. - -Each OTP application has a restricted domain. - -| Directory | OTP Application | Namespace | Purpose | -|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` | -| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | -| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | -| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. | - +Currently available full-featured block explorers (Etherscan, Etherchain, Blockchair) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent, open-source tools are needed to analyze and validate transactions. ## Getting Started -### Requirements - -| Dependency | Mac | Linux | -|-------------|-----|-------| -| [Erlang/OTP 21.0.4](https://github.com/erlang/otp) | `brew install erlang` | [Erlang Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L134) | -| [Elixir 1.8.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) | -| [Postgres 10.3](https://www.postgresql.org/) | `brew install postgresql` | [Postgres Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L187) | -| [Node.js 10.x.x](https://nodejs.org/en/) | `brew install node` | [Node.js Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L66) | -| [Automake](https://www.gnu.org/software/automake/) | `brew install automake` | [Automake Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L72) | -| [Libtool](https://www.gnu.org/software/libtool/) | `brew install libtool` | [Libtool Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L62) | -| [Inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki) | Not Required | Ubuntu - `apt-get install inotify-tools` | -| [GCC Compiler](https://gcc.gnu.org/) | `brew install gcc` | [GCC Compiler Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L70) | -| [GMP](https://gmplib.org/) | `brew install gmp` | [Install GMP Devel](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L74) | - -### Build and Run - -#### Playbook Deployment - -We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details and instructions. - -#### Manual Deployment - -See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions. - -#### Environment Variables - -Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814). - -#### Configuring EVM Chains - -* **CSS:** Update the import instruction in `apps/block_scout_web/assets/css/theme/_variables.scss` to select a preset css file. This is reflected in the `production-${chain}` branch for each instance. For example, in the `production-xdai` branch, it is set to `@import "dai-variables"`. - -* **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs. - -#### Automating Restarts - -By default `blockscout` does not restart if it crashes. To enable automated -restarts, set the environment variable `HEART_COMMAND` to whatever command you run to start `blockscout`. Configure the heart beat timeout to change how long it waits before considering the application unresponsive. At that point, it will kill the current blockscout instance and execute the `HEART_COMMAND`. By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` to a positive or negative integer. See the [heart](http://erlang.org/doc/man/heart.html) documentation for more information. - - -#### CircleCI Updates - -To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with the following url: [`https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604) - - -## Testing - -### Requirements - - * PhantomJS (for wallaby) - -### Running the tests - - 1. Build the assets. - `cd apps/block_scout_web/assets && npm run build; cd -` - - 2. Format the Elixir code. - `mix format` - - 3. Run the test suite with coverage for whole umbrella project. This step can be run with different configuration outlined below. - `mix coveralls.html --umbrella` - - 4. Lint the Elixir code. - `mix credo --strict` - - 5. Run the dialyzer. - `mix dialyzer --halt-exit-status` - - 6. Check the Elixir code for vulnerabilities. - `cd apps/explorer && mix sobelow --config; cd -` - `cd apps/block_scout_web && mix sobelow --config; cd -` - - 7. Lint the JavaScript code. - `cd apps/block_scout_web/assets && npm run eslint; cd -` - - 8. Test the JavaScript code. - `cd apps/block_scout_web/assets && npm run test; cd -` - -#### Parity - -##### Mox - -**This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**: - -```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox -export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox -mix coveralls.html --umbrella --exclude no_parity -``` - -##### HTTP / WebSocket - -```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket -export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Parity -mix coveralls.html --umbrella --exclude no_parity -``` - -| Protocol | URL | -|:----------|:-----------------------------------| -| HTTP | `http://localhost:8545` | -| WebSocket | `ws://localhost:8546` | - -#### Geth - -##### Mox - -```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox -export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox -mix coveralls.html --umbrella --exclude no_geth -``` - -##### HTTP / WebSocket - -```shell -export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket -export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Geth -mix coveralls.html --umbrella --exclude no_geth -``` - -| Protocol | URL | -|:----------|:--------------------------------------------------| -| HTTP | `https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY` | -| WebSocket | `wss://mainnet.infura.io/ws/8lTvJTKmHPCHazkneJsY` | - -### API Documentation - -To view Modules and API Reference documentation: - -1. Generate documentation. -`mix docs` -2. View the generated docs. -`open doc/index.html` - -## Front-end - -### Javascript - -All Javascript files are under [apps/block_scout_web/assets/js](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js) and the main file is [app.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/app.js). This file imports all javascript used in the application. If you want to create a new JS file consider creating into [/js/pages](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/pages) or [/js/lib](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/lib), as follows: - -#### js/lib -This folder contains all scripts that can be reused in any page or can be used as a helper to some component. - -#### js/pages -This folder contains the scripts that are specific for some page. - -#### Redux -This project uses Redux to control the state in some pages. There are pages that have things happening in real-time thanks to the Phoenix channels, e.g. Address page, so the page state changes a lot depending on which events it is listening. The redux is also used to load some contents asynchronous, see [async_listing_load.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/async_listing_load.js). - -To understand how to build new pages that need redux in this project, see the [redux_helpers.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/redux_helpers.js) - -## Internationalization - -The app is currently internationalized. It is only localized to U.S. English. To translate new strings. - -1. To setup translation file. -`cd apps/block_scout_web; mix gettext.extract --merge; cd -` -2. To edit the new strings, go to `apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po`. - -## Metrics - -BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`. - -### Prometheus - -1. Install prometheus: `brew install prometheus` -2. Start the web server `iex -S mix phx.server` -3. Start prometheus: `prometheus --config.file=prometheus.yml` - -### Grafana - -1. Install grafana: `brew install grafana` -2. Install Pie Chart panel plugin: `grafana-cli plugins install grafana-piechart-panel` -3. Start grafana: `brew services start grafana` -4. Add Prometheus as a Data Source - 1. `open http://localhost:3000/datasources` - 2. Click "+ Add data source" - 3. Put "Prometheus" for "Name" - 4. Change "Type" to "Prometheus" - 5. Set "URL" to "http://localhost:9090" - 6. Set "Scrape Interval" to "10s" -5. Add the dashboards from https://github.com/deadtrickster/beam-dashboards: - For each `*.json` file in the repo. - 1. `open http://localhost:3000/dashboard/import` - 2. Copy the contents of the JSON file in the "Or paste JSON" entry - 3. Click "Load" -6. View the dashboards. (You will need to click-around and use BlockScout for the web-related metrics to show up.) - -## Tracing - -Blockscout supports tracing via -[Spandex](http://git@github.com:spandex-project/spandex.git). Each application -has its own tracer, that is configured internally to that application. In order -to enable it, visit each application's `config/.ex` and update its tracer -configuration to change `disabled?: true` to `disabled?: false`. Do this for -each application you'd like included in your trace data. - -Currently, only [Datadog](https://www.datadoghq.com/) is supported as a -tracing backend, but more will be added soon. - -### DataDog - -If you would like to use DataDog, after enabling `Spandex`, set -`"DATADOG_HOST"` and `"DATADOG_PORT"` environment variables to the -host/port that your Datadog agent is running on. For more information on -Datadog and the Datadog agent, see their -[documentation](https://docs.datadoghq.com/). - -### Other - -If you want to use a different backend, remove the -`SpandexDatadog.ApiServer` `Supervisor.child_spec` from -`Explorer.Application` and follow any instructions provided in `Spandex` -for setting up that backend. - -## Memory Usage - -The work queues for building the index of all blocks, balances (coin and token), and internal transactions can grow quite large. By default, the soft-limit is 1 GiB, which can be changed in `apps/indexer/config/config.exs`: - -``` -config :indexer, memory_limit: 1 <<< 30 -``` - -Memory usage is checked once per minute. If the soft-limit is reached, the shrinkable work queues will shed half their load. The shed load will be restored from the database, the same as when a restart of the server occurs, so rebuilding the work queue will be slower, but use less memory. - -If all queues are at their minimum size, then no more memory can be reclaimed and an error will be logged. +See the [project documentation](https://poanetwork.github.io/blockscout) for instructions: +- [Requirements](https://poanetwork.github.io/blockscout/#/requirements) +- [Ansible deployment](https://poanetwork.github.io/blockscout/#/ansible-deployment) +- [Manual deployment](https://poanetwork.github.io/blockscout/#/manual-deployment) +- [ENV variables](https://poanetwork.github.io/blockscout/#/env-variables) +- [Configuration options](https://poanetwork.github.io/blockscout/#/dev-env) ## Acknowledgements @@ -317,7 +43,6 @@ We would like to thank the [EthPrize foundation](http://ethprize.io/) for their See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments. - ## License [![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) diff --git a/UPGRADING.md b/UPGRADING.md deleted file mode 100644 index fcf2121ffb..0000000000 --- a/UPGRADING.md +++ /dev/null @@ -1,20 +0,0 @@ -# Upgrading Guide - -### Migration scripts - -There is in the project a `scripts` folder that contains `SQL` files responsible to migrate data from the database. - -This script should be used if you already have an indexed database with a large amount of data. - -#### `address_current_token_balances_in_batches.sql` - -Is responsible to populate a new table using the `token_balances` table information. - -#### `internal_transaction_update_in_batches.sql` - -Is responsible to migrate data from the `transactions` table to the `internal_transactions` one in order to improve the application listing performance; - -#### `transaction_update_in_baches.sql` - -Parity call traces contain the input, but it was not put in the internal_transactions_params. -Enforce input and call_type being non-NULL for calls in new constraints on internal_transactions. diff --git a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss index 71822bdc5a..444cdff178 100644 --- a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss @@ -40,6 +40,10 @@ $dropdown-menu-item-hover-background: rgba($sub-accent-color, .1) !default; $header-icon-color-hover: $sub-accent-color; $header-icon-border-color-hover: $sub-accent-color; +// Logo Size +$footer-logo-height: 20px; +$navbar-logo-height: 20px; + // buttons $btn-line-bg: #fff; // button bg $btn-line-color: $sub-accent-color; // button border and font color && hover bg color diff --git a/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg b/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg index 51ef19cc31..83bf9ed4ee 100644 --- a/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg +++ b/apps/block_scout_web/assets/static/images/classic_ethereum_logo.svg @@ -1 +1,4 @@ - + + + + diff --git a/apps/block_scout_web/assets/static/images/sokol_logo.svg b/apps/block_scout_web/assets/static/images/sokol_logo.svg index d57d1f08f0..adf2baac7d 100644 --- a/apps/block_scout_web/assets/static/images/sokol_logo.svg +++ b/apps/block_scout_web/assets/static/images/sokol_logo.svg @@ -1 +1,3 @@ - + + + diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index f38ed779b5..e601e299f8 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -35,6 +35,11 @@ config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: t # Configures the endpoint config :block_scout_web, BlockScoutWeb.Endpoint, instrumenters: [BlockScoutWeb.Prometheus.Instrumenter, SpandexPhoenix.Instrumenter], + http: [ + protocol_options: [ + idle_timeout: 90_000 + ] + ], url: [ host: "localhost", path: System.get_env("NETWORK_PATH") || "/" diff --git a/apps/block_scout_web/config/dev.exs b/apps/block_scout_web/config/dev.exs index 27ce5aff40..ce955a764c 100644 --- a/apps/block_scout_web/config/dev.exs +++ b/apps/block_scout_web/config/dev.exs @@ -15,8 +15,16 @@ port = end config :block_scout_web, BlockScoutWeb.Endpoint, - http: [port: port || 4000], + http: [ + protocol_options: [ + idle_timeout: 90_000 + ], + port: port || 4000 + ], https: [ + protocol_options: [ + idle_timeout: 90_000 + ], port: (port && port + 1) || 4001, cipher_suite: :strong, certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex index 693772ed8c..bb386940b1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex @@ -1,8 +1,35 @@ defmodule BlockScoutWeb.API.RPC.EthController do use BlockScoutWeb, :controller - alias Explorer.Chain - alias Explorer.Chain.Wei + alias Ecto.Type, as: EctoType + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei} + alias Explorer.Etherscan.Logs + + @methods %{ + "eth_getBalance" => %{ + action: :eth_get_balance, + notes: """ + the `earliest` parameter will not work as expected currently, because genesis block balances + are not currently imported + """ + }, + "eth_getLogs" => %{ + action: :eth_get_logs, + notes: """ + Will never return more than 1000 log entries. + """ + } + } + + @index_to_word %{ + 0 => "first", + 1 => "second", + 2 => "third", + 3 => "fourth" + } + + def methods, do: @methods def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do responses = responses(requests) @@ -39,6 +66,142 @@ defmodule BlockScoutWeb.API.RPC.EthController do |> render("response.json", %{response: response}) end + def eth_get_balance(address_param, block_param \\ nil) do + with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)}, + {:block, {:ok, block}} <- {:block, block_param(block_param)}, + {:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do + {:ok, Wei.hex_format(balance)} + else + {:address, :error} -> + {:error, "Query parameter 'address' is invalid"} + + {:block, :error} -> + {:error, "Query parameter 'block' is invalid"} + + {:balance, {:error, :not_found}} -> + {:error, "Balance not found"} + end + end + + def eth_get_logs(filter_options) do + with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options), + {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options), + {:ok, from_block} <- cast_block(from_block_param), + {:ok, to_block} <- cast_block(to_block_param) do + filter = + address_or_topic_params + |> Map.put(:from_block, from_block) + |> Map.put(:to_block, to_block) + |> Map.put(:allow_non_consensus, true) + + {:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)} + else + {:error, message} when is_bitstring(message) -> + {:error, message} + + {:error, :empty} -> + {:ok, []} + + _ -> + {:error, "Something went wrong."} + end + end + + defp render_log(log) do + topics = + Enum.reject( + [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic], + &is_nil/1 + ) + + %{ + "address" => to_string(log.address_hash), + "blockHash" => to_string(log.block_hash), + "blockNumber" => Integer.to_string(log.block_number, 16), + "data" => to_string(log.data), + "logIndex" => Integer.to_string(log.index, 16), + "removed" => log.block_consensus == false, + "topics" => topics, + "transactionHash" => to_string(log.transaction_hash), + "transactionIndex" => log.transaction_index, + "transactionLogIndex" => log.index, + "type" => "mined" + } + end + + defp cast_block("0x" <> hexadecimal_digits = input) do + case Integer.parse(hexadecimal_digits, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, input <> " is not a valid block number"} + end + end + + defp cast_block(integer) when is_integer(integer), do: {:ok, integer} + defp cast_block(_), do: {:error, "invalid block number"} + + defp address_or_topic_params(filter_options) do + address_param = Map.get(filter_options, "address") + topics_param = Map.get(filter_options, "topics") + + with {:ok, address} <- validate_address(address_param), + {:ok, topics} <- validate_topics(topics_param) do + address_and_topics(address, topics) + end + end + + defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"} + defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}} + defp address_and_topics(nil, topics), do: {:ok, topics} + defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)} + + defp validate_address(nil), do: {:ok, nil} + + defp validate_address(address) do + case Address.cast(address) do + {:ok, address} -> {:ok, address} + :error -> {:error, "invalid address"} + end + end + + defp validate_topics(nil), do: {:ok, nil} + defp validate_topics([]), do: [] + + defp validate_topics(topics) when is_list(topics) do + topics + |> Stream.with_index() + |> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} -> + case cast_topics(topic) do + {:ok, data} -> + with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data) + + {:ok, add_operator(with_filter, index)} + + :error -> + {:error, "invalid topics"} + end + end) + end + + defp add_operator(filters, 0), do: filters + + defp add_operator(filters, index) do + Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and") + end + + defp cast_topics(topics) when is_list(topics) do + case EctoType.cast({:array, Data}, topics) do + {:ok, data} -> {:ok, Enum.map(data, &to_string/1)} + :error -> :error + end + end + + defp cast_topics(topic) do + case Data.cast(topic) do + {:ok, data} -> {:ok, to_string(data)} + :error -> :error + end + end + defp responses(requests) do Enum.map(requests, fn request -> with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")}, @@ -51,6 +214,86 @@ defmodule BlockScoutWeb.API.RPC.EthController do end) end + defp logs_blocks_filter(filter_options) do + with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options}, + {:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)}, + {:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do + {:ok, number, number} + else + {:filter, filters} -> + from_block = Map.get(filters, "fromBlock", "latest") + to_block = Map.get(filters, "toBlock", "latest") + + max_block_number = + if from_block == "latest" || to_block == "latest" do + max_consensus_block_number() + end + + pending_block_number = + if from_block == "pending" || to_block == "pending" do + max_non_consensus_block_number(max_block_number) + end + + if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do + {:error, :empty} + else + to_block_numbers(from_block, to_block, max_block_number, pending_block_number) + end + + {:block, _} -> + {:error, "Invalid Block Hash"} + + {:block_hash, _} -> + {:error, "Invalid Block Hash"} + end + end + + defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do + actual_pending_block_number = pending_block_number || max_block_number + + with {:ok, from} <- + to_block_number(from_block, max_block_number, actual_pending_block_number), + {:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do + {:ok, from, to} + end + end + + defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer} + defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0} + defp to_block_number("earliest", _, _), do: {:ok, 0} + defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0} + defp to_block_number("pending", _, pending), do: {:ok, pending} + + defp to_block_number("0x" <> number, _, _) do + case Integer.parse(number, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, "invalid block number"} + end + end + + defp to_block_number(number, _, _) when is_bitstring(number) do + case Integer.parse(number, 16) do + {integer, ""} -> {:ok, integer} + _ -> {:error, "invalid block number"} + end + end + + defp to_block_number(_, _, _), do: {:error, "invalid block number"} + + defp max_non_consensus_block_number(max) do + case Chain.max_non_consensus_block_number(max) do + {:ok, number} -> number + _ -> nil + end + end + + defp max_consensus_block_number do + case Chain.max_consensus_block_number() do + {:ok, number} -> number + _ -> nil + end + end + defp format_success(result, id) do %{result: result, id: id} end @@ -66,9 +309,13 @@ defmodule BlockScoutWeb.API.RPC.EthController do defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params}) when is_list(params) do with {:ok, action} <- get_action(method), - true <- :erlang.function_exported(__MODULE__, action, Enum.count(params)) do + {:correct_arity, true} <- + {:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do apply(__MODULE__, action, params) else + {:correct_arity, _} -> + {:error, "Incorrect number of params."} + _ -> {:error, "Action not found."} end @@ -82,26 +329,16 @@ defmodule BlockScoutWeb.API.RPC.EthController do {:error, "Method, params, and jsonrpc, are all required parameters."} end - def eth_get_balance(address_param, block_param \\ nil) do - with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)}, - {:block, {:ok, block}} <- {:block, block_param(block_param)}, - {:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do - {:ok, Wei.hex_format(balance)} - else - {:address, :error} -> - {:error, "Query parameter 'address' is invalid"} - - {:block, :error} -> - {:error, "Query parameter 'block' is invalid"} + defp get_action(action) do + case Map.get(@methods, action) do + %{action: action} -> + {:ok, action} - {:balance, {:error, :not_found}} -> - {:error, "Balance not found"} + _ -> + :error end end - defp get_action("eth_getBalance"), do: {:ok, :eth_get_balance} - defp get_action(_), do: :error - defp block_param("latest"), do: {:ok, :latest} defp block_param("earliest"), do: {:ok, :earliest} defp block_param("pending"), do: {:ok, :pending} diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex index 1c5ba68229..9309884d5c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex @@ -1,6 +1,7 @@ defmodule BlockScoutWeb.APIDocsController do use BlockScoutWeb, :controller + alias BlockScoutWeb.API.RPC.EthController alias BlockScoutWeb.Etherscan def index(conn, _params) do @@ -8,4 +9,10 @@ defmodule BlockScoutWeb.APIDocsController do |> assign(:documentation, Etherscan.get_documentation()) |> render("index.html") end + + def eth_rpc(conn, _params) do + conn + |> assign(:documentation, EthController.methods()) + |> render("eth_rpc.html") + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex index 4a498b8430..a89728949c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain/market_history_chart_controller.ex @@ -8,18 +8,20 @@ defmodule BlockScoutWeb.Chain.MarketHistoryChartController do with true <- ajax?(conn) do exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() + recent_market_history = Market.fetch_recent_history() + market_history_data = - 30 - |> Market.fetch_recent_history() - |> case do - [today | the_rest] -> [%{today | closing_price: exchange_rate.usd_value} | the_rest] - data -> data + case recent_market_history do + [today | the_rest] -> + encode_market_history_data([%{today | closing_price: exchange_rate.usd_value} | the_rest]) + + data -> + encode_market_history_data(data) end - |> encode_market_history_data() json(conn, %{ history_data: market_history_data, - supply_data: available_supply(Chain.supply_for_days(30), exchange_rate) + supply_data: available_supply(Chain.supply_for_days(), exchange_rate) }) else _ -> unprocessable_entity(conn) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index 48d84dd5d1..40a0fd3bbb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -4,6 +4,7 @@ defmodule BlockScoutWeb.ChainController do alias BlockScoutWeb.ChainView alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.{Address, Block, Transaction} + alias Explorer.Chain.Supply.RSK alias Explorer.Counters.AverageBlockTime alias Explorer.ExchangeRates.Token alias Explorer.Market @@ -13,6 +14,15 @@ defmodule BlockScoutWeb.ChainController do transaction_estimated_count = Chain.transaction_estimated_count() block_count = Chain.block_estimated_count() + market_cap_calculation = + case Application.get_env(:explorer, :supply) do + RSK -> + RSK + + _ -> + :standard + end + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() render( @@ -22,6 +32,7 @@ defmodule BlockScoutWeb.ChainController do average_block_time: AverageBlockTime.average_block_time(), exchange_rate: exchange_rate, chart_data_path: market_history_chart_path(conn, :show), + market_cap_calculation: market_cap_calculation, transaction_estimated_count: transaction_estimated_count, transactions_path: recent_transactions_path(conn, :index), block_count: block_count diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index 21a104848e..a5bc99e242 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -37,7 +37,7 @@ defmodule BlockScoutWeb.Notifier do exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() market_history_data = - case Market.fetch_recent_history(30) do + case Market.fetch_recent_history() do [today | the_rest] -> [%{today | closing_price: exchange_rate.usd_value} | the_rest] data -> data end diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index 786c77cdff..308114a402 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -247,6 +247,7 @@ defmodule BlockScoutWeb.Router do get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/api_docs", APIDocsController, :index) + get("/eth_rpc_api_docs", APIDocsController, :eth_rpc) get("/:page", PageNotFoundController, :index) end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex new file mode 100644 index 0000000000..f85620681a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex @@ -0,0 +1,34 @@ +
+
+
+

<%= gettext("ETH RPC API Documentation") %>

+

[ <%= gettext "Base URL:" %> <%= @conn.host %>/api/eth_rpc ]

+

+ <%= gettext "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " %> + + <%= gettext "here." %> + <%= gettext "This is useful to allow sending requests to blockscout without having to change anything about the request." %> + <%= gettext "However, in general, the" %> <%= link( + gettext("custom RPC"), + to: api_docs_path(@conn, :index) + ) %> <%= gettext " is recommended." %> + <%= gettext "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." %> +

+
+
+
+
+ + + + + + <%= for {method, info} <- Map.to_list(@documentation) do %> + + + + + <% end %> +
Supported MethodNotes
<%= method %> <%= Map.get(info, :notes, "N/A") %>
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index b5d7283a03..17bd9c4afe 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -30,7 +30,7 @@ <%= gettext "Market Cap" %> - + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex index 1058654d49..7188f283a1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex @@ -10,10 +10,10 @@ <% other_explorers = other_explorers() %> - <% col_size = if Enum.empty?(other_explorers), do: 3, else: 4 %> + <% col_size = if Enum.empty?(other_explorers), do: 3, else: 2 %>
-