content issues, test runned

pull/1982/head
maxgrapps 6 years ago
parent 5b0a70bcfc
commit 5892fa6de6
  1. 2
      .dialyzer-ignore
  2. 10
      CHANGELOG.md
  3. 179
      README.md
  4. 4
      apps/block_scout_web/assets/css/components/_card.scss
  5. 4
      apps/block_scout_web/assets/css/components/_dropdown.scss
  6. 8
      apps/block_scout_web/lib/block_scout_web/chain.ex
  7. 46
      apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
  8. 8
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
  9. 5
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex
  10. 59
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  11. 7
      apps/block_scout_web/lib/block_scout_web/router.ex
  12. 7
      apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex
  13. 58
      apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
  14. 82
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
  15. 4
      apps/block_scout_web/lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex
  16. 13
      apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
  17. 5
      apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
  18. 3
      apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
  19. 1
      apps/block_scout_web/lib/block_scout_web/views/address_view.ex
  20. 28
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/block_view.ex
  21. 33
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex
  22. 3
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  23. 18
      apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
  24. 72
      apps/block_scout_web/priv/gettext/default.pot
  25. 76
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  26. 1
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  27. 57
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs
  28. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  29. 24
      apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs
  30. 28
      apps/block_scout_web/test/block_scout_web/views/layout_view_test.exs
  31. 10
      apps/explorer/config/config.exs
  32. 2
      apps/explorer/config/test.exs
  33. 3
      apps/explorer/lib/explorer/application.ex
  34. 119
      apps/explorer/lib/explorer/chain.ex
  35. 6
      apps/explorer/lib/explorer/chain/block.ex
  36. 57
      apps/explorer/lib/explorer/chain/block_number_cache.ex
  37. 47
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  38. 50
      apps/explorer/lib/explorer/chain/import/runner/transactions.ex
  39. 2
      apps/explorer/lib/explorer/chain/internal_transaction/result.ex
  40. 5
      apps/explorer/lib/explorer/chain/transaction.ex
  41. 5
      apps/explorer/lib/explorer/paging_options.ex
  42. 124
      apps/explorer/lib/explorer/staking/epoch_counter.ex
  43. 23
      apps/explorer/lib/explorer/staking/pools_reader.ex
  44. 802
      apps/explorer/priv/contracts_abi/pos/staking.json
  45. 12
      apps/explorer/priv/repo/migrations/20190508152922_add_old_block_hash_for_transactions.exs
  46. 11
      apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs
  47. 64
      apps/explorer/test/explorer/chain/block_number_cache_test.exs
  48. 47
      apps/explorer/test/explorer/chain/import/runner/blocks_test.exs
  49. 86
      apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs
  50. 4
      apps/explorer/test/explorer/chain/internal_transaction_test.exs
  51. 100
      apps/explorer/test/explorer/chain_test.exs
  52. 97
      apps/explorer/test/explorer/staking/epoch_counter_test.exs
  53. 21
      apps/explorer/test/explorer/staking/pools_reader_test.exs
  54. 2
      apps/explorer/test/support/data_case.ex
  55. 20
      apps/explorer/test/support/factory.ex
  56. 1
      apps/indexer/README.md
  57. 14
      apps/indexer/lib/indexer/block/fetcher.ex
  58. 1
      apps/indexer/lib/indexer/fetcher/staking_pools.ex
  59. 3
      apps/indexer/lib/indexer/supervisor.ex
  60. 115
      apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex
  61. 19
      apps/indexer/test/indexer/fetcher/staking_pools_test.exs

@ -4,4 +4,4 @@
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer() apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer()
apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:162: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t() apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()

@ -23,9 +23,15 @@
- [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint
- [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks
- [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc
- [#1957](https://github.com/poanetwork/blockscout/pull/1957) - Calculate stakes ratio before insert pools
- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address
- [#1933](https://github.com/poanetwork/blockscout/pull/1933) - add eth_BlockNumber json rpc method
- [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default
- [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct
### Fixes ### Fixes
- [#1944](https://github.com/poanetwork/blockscout/pull/1944) - fixed styles for token's dropdown.
- [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment - [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment
- [#1829](https://github.com/poanetwork/blockscout/pull/1829) - Handle nil quantities in block decoding routine - [#1829](https://github.com/poanetwork/blockscout/pull/1829) - Handle nil quantities in block decoding routine
- [#1830](https://github.com/poanetwork/blockscout/pull/1830) - Make block size field nullable - [#1830](https://github.com/poanetwork/blockscout/pull/1830) - Make block size field nullable
@ -44,6 +50,8 @@
- [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments - [#1898](https://github.com/poanetwork/blockscout/pull/1898) - check if the constructor has arguments before verifying constructor arguments
- [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions
- [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it
- [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts
- [#1917](https://github.com/poanetwork/blockscout/pull/1917) - Force block refetch if transaction is re-collated in a different block
### Chore ### Chore
@ -51,6 +59,8 @@
- [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder - [#1837](https://github.com/poanetwork/blockscout/pull/1837) - Add -f flag to clear_build.sh script delete static folder
- [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var - [#1900](https://github.com/poanetwork/blockscout/pull/1900) - SUPPORTED_CHAINS ENV var
- [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules - [#1892](https://github.com/poanetwork/blockscout/pull/1892) - Remove temporary worker modules
- [#1958](https://github.com/poanetwork/blockscout/pull/1958) - Default value for release link env var
- [#1975](https://github.com/poanetwork/blockscout/pull/1975) - add log index to transaction view
## 1.3.10-beta ## 1.3.10-beta

@ -16,7 +16,9 @@ BlockScout provides a comprehensive, easy-to-use interface for users to view, co
Following is an overview of the project and instructions for [getting started](#getting-started). Following is an overview of the project and instructions for [getting started](#getting-started).
Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) or the [Gitter Channel](https://gitter.im/poanetwork/blockscout) to access additional information or post questions. 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.
You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout).
## About BlockScout ## About BlockScout
@ -41,31 +43,16 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
### Supported Projects ### Supported Projects
#### Hosted Chains | **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** |
|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------|
* [POA Core Network](https://blockscout.com/poa/core) | [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) |
* [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) |
* [xDai Chain](https://blockscout.com/poa/dai) | [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) | [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) |
* [Kovan Testnet](https://blockscout.com/eth/kovan) | [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) |
* [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) |
* [Goerli Testnet](https://blockscout.com/eth/goerli) | [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) |
* [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | | | [SpringChain](https://explorer.springrole.com/) |
* [Ethereum Classic](https://blockscout.com/etc/mainnet)
* [Aerum](https://blockscout.com/aerum/mainnet)
* [Callisto](https://blockscout.com/callisto/mainnet)
* [RSK](https://blockscout.com/rsk/mainnet)
#### Additional Chains Utilizing BlockScout
* [Oasis Labs](https://blockexplorer.oasiscloud.io/)
* [Fuse Network](https://explorer.fuse.io/)
* [ARTIS](https://explorer.sigma1.artis.network)
* [SafeChain](https://explorer.safechain.io)
* [SpringChain](https://explorer.springrole.com/)
* [PIRL](http://pirl.es/)
* [Petrichor](https://explorer.petrichor-dev.com/)
* [Ether-1](https://blocks.ether1.wattpool.net/)
### Visual Interface ### Visual Interface
@ -74,13 +61,24 @@ Interface for the POA network _updated 02/2019_
![BlockScout Example](explorer_example_2_2019.gif) ![BlockScout Example](explorer_example_2_2019.gif)
## Getting Started
We use [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. ### Umbrella Project Organization
### Requirements 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`.
The [development stack page](https://github.com/poanetwork/blockscout/wiki/Development-Stack) contains more information about these frameworks. 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`. |
## Getting Started
### Requirements
| Dependency | Mac | Linux | | Dependency | Mac | Linux |
|-------------|-----|-------| |-------------|-----|-------|
@ -96,121 +94,42 @@ The [development stack page](https://github.com/poanetwork/blockscout/wiki/Devel
### Build and Run ### Build and Run
1. Clone the repository. #### Playbook Deployment
`git clone https://github.com/poanetwork/blockscout`
2. Go to the explorer subdirectory.
`cd blockscout`
3. Set up default configurations.
`cp apps/explorer/config/dev.secret.exs.example apps/explorer/config/dev.secret.exs`
`cp apps/block_scout_web/config/dev.secret.exs.example apps/block_scout_web/config/dev.secret.exs`
<br />Linux: Update the database username and password configuration in `apps/explorer/config/dev.secret.exs`
<br />Mac: Remove the `username` and `password` fields from `apps/explorer/config/dev.secret.exs`
<br />Optional: Set up default configuration for testing.
`cp apps/explorer/config/test.secret.exs.example apps/explorer/config/test.secret.exs`
Example usage: Changing the default Postgres port from localhost:15432 if [Boxen](https://github.com/boxen/boxen) is installed.
4. Install dependencies.
`mix do deps.get, local.rebar --force, deps.compile, compile`
5. Create and migrate database.
`mix ecto.create && mix ecto.migrate`
<br />_Note:_ If you have run previously, drop the previous database
`mix do ecto.drop, ecto.create, ecto.migrate`
6. Install Node.js dependencies.
`cd apps/block_scout_web/assets && npm install; cd -`
`cd apps/explorer && npm install; cd -`
7. Update your JSON RPC Variant in `apps/explorer/config/dev.exs` and `apps/indexer/config/dev.exs`. 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.
For `variant`, enter `ganache`, `geth`, `parity`, or `rsk`
8. Update your JSON RPC Endpoint in `apps/explorer/config/dev/` and `apps/indexer/config/dev/` #### Manual Deployment
For the `variant` chosen in step 7, enter the correct information for the corresponding JSON RPC Endpoint in `parity.exs`, `geth.exs`, or `ganache.exs`
9. Enable HTTPS in development. The Phoenix server only runs with HTTPS. See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions.
* `cd apps/block_scout_web`
* `mix phx.gen.cert blockscout blockscout.local; cd -`
* Add blockscout and blockscout.local to your `/etc/hosts`
```
127.0.0.1 localhost blockscout blockscout.local
255.255.255.255 broadcasthost
::1 localhost blockscout blockscout.local
```
* If using Chrome, Enable `chrome://flags/#allow-insecure-localhost`.
9. Run the Phoenix Server from the root directory of your application. #### Environment Variables
`mix phx.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814).
_Additional runtime options:_ #### Configuring EVM Chains
* Run Phoenix Server with IEx (Interactive Elixer) * **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"`.
`iex -S mix phx.server`
* Run Phoenix Server with real time indexer * **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs.
`iex -S mix phx.server`
### Automating Restarts #### Automating Restarts
By default `blockscout` does not restart if it crashes. To enable automated By default `blockscout` does not restart if it crashes. To enable automated
restarts, set the environment variable `HEART_COMMAND` to whatever you run to 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.
start `blockscout`. You can configure the heart beat timeout, which will change
how long it will wait before considering the application to be unresponsive. At
that point, it will kill the current blockscout and execute `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 documentation for
[heart](http://erlang.org/doc/man/heart.html) for more information.
### Configuring Ethereum Classic and other EVM Chains #### CircleCI Updates
**Note: Most of these modifications will be consolidated into a single file in the future.**
1. Update the import file in `apps/block_scout_web/assets/css/theme/_variables.scss`. There are several preset css files for our supported chains which include Ethereum Classic, Ethereum Mainnet, Ropsten Testnet, Kovan Testnet, POA Core, and POA Sokol. To deploy Ethereum Classic, change the import to `ethereum_classic_variables`.
2. Update the logo file in `apps/block_scout_web/config/config.exs`. To deploy Ethereum Classic, change this file to `classic_ethereum_logo.svg`.
3. Update the `check_origin` configuration in `apps/block_scout_web/config/prod.exs`. This allows realtime events to occur on your endpoint.
4. Update the node configuration. You will need a full tracing node with WebSockets enabled. Make the changes in the following files (dev/prod):
* `apps/explorer/config/dev/parity.exs`
* `apps/explorer/config/prod/parity.exs`
* `apps/indexer/config/dev/parity.exs`
* `apps/indexer/config/prod/parity.exs`
5. Update the dropdown menu in the main navigation `apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex`
6. Update the coin in `apps/explorer/config/config.exs`. This will pull relevant information from Coinmarketcap.com.
### 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`. |
### 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) 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 ## Testing
#### Requirements ### Requirements
* PhantomJS (for wallaby) * PhantomJS (for wallaby)
#### Running the tests ### Running the tests
1. Build the assets. 1. Build the assets.
`cd apps/block_scout_web/assets && npm run build; cd -` `cd apps/block_scout_web/assets && npm run build; cd -`
@ -237,9 +156,9 @@ To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with
8. Test the JavaScript code. 8. Test the JavaScript code.
`cd apps/block_scout_web/assets && npm run test; cd -` `cd apps/block_scout_web/assets && npm run test; cd -`
##### Parity #### Parity
###### Mox ##### Mox
**This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**: **This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**:
@ -249,7 +168,7 @@ export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_parity mix coveralls.html --umbrella --exclude no_parity
``` ```
###### HTTP / WebSocket ##### HTTP / WebSocket
```shell ```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket
@ -262,9 +181,9 @@ mix coveralls.html --umbrella --exclude no_parity
| HTTP | `http://localhost:8545` | | HTTP | `http://localhost:8545` |
| WebSocket | `ws://localhost:8546` | | WebSocket | `ws://localhost:8546` |
##### Geth #### Geth
###### Mox ##### Mox
```shell ```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox
@ -272,7 +191,7 @@ export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox
mix coveralls.html --umbrella --exclude no_geth mix coveralls.html --umbrella --exclude no_geth
``` ```
###### HTTP / WebSocket ##### HTTP / WebSocket
```shell ```shell
export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket

@ -18,8 +18,8 @@ $card-background-1-text-color: #fff !default;
background-color: $card-background-1; background-color: $card-background-1;
color: $card-background-1-text-color; color: $card-background-1-text-color;
a, a:not(.dropdown-item),
a:hover { a:not(.dropdown-item):hover {
color: $card-background-1-text-color; color: $card-background-1-text-color;
} }
} }

@ -60,9 +60,9 @@
.dropdown-search-icon { .dropdown-search-icon {
color: $gray-300; color: $gray-300;
left: 0.625rem; left: 8px;
pointer-events: none; pointer-events: none;
top: 0.5rem; top: 5px;
} }
.dropdown-search-field { .dropdown-search-field {

@ -205,8 +205,12 @@ defmodule BlockScoutWeb.Chain do
%{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index} %{"block_number" => block_number, "transaction_index" => transaction_index, "index" => index}
end end
defp paging_params(%Log{index: index}) do defp paging_params(%Log{index: index} = log) do
%{"index" => index} if Ecto.assoc_loaded?(log.transaction) do
%{"block_number" => log.transaction.block_number, "transaction_index" => log.transaction.index, "index" => index}
else
%{"index" => index}
end
end end
defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do defp paging_params(%Transaction{block_number: nil, inserted_at: inserted_at, hash: hash}) do

@ -0,0 +1,46 @@
defmodule BlockScoutWeb.AddressLogsController do
@moduledoc """
Manages events logs tab.
"""
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Indexer.Fetcher.CoinBalanceOnDemand
use BlockScoutWeb, :controller
def index(conn, %{"address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
logs_plus_one = Chain.address_to_logs(address, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one)
next_page_url =
case next_page_params(next_page, results, params) do
nil ->
nil
next_page_params ->
address_logs_path(conn, :index, address, next_page_params)
end
render(
conn,
"index.html",
address: address,
logs: results,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
transaction_count: transaction_count(address),
validation_count: validation_count(address),
next_page_url: next_page_url
)
else
_ ->
not_found(conn)
end
end
end

@ -3,6 +3,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
alias BlockScoutWeb.Chain, as: ChainWeb alias BlockScoutWeb.Chain, as: ChainWeb
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.BlockNumberCache
def getblockreward(conn, params) do def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")}, with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
@ -23,4 +24,11 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
render(conn, :error, error: "Block does not exist") render(conn, :error, error: "Block does not exist")
end end
end end
def eth_block_number(conn, params) do
id = Map.get(params, "id", 1)
max_block_number = BlockNumberCache.max_number()
render(conn, :eth_block_number, number: max_block_number, id: id)
end
end end

@ -103,6 +103,9 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
:not_decompiled -> :not_decompiled ->
Chain.list_not_decompiled_contracts(page_size, offset) Chain.list_not_decompiled_contracts(page_size, offset)
:empty ->
Chain.list_empty_contracts(page_size, offset)
_ -> _ ->
Chain.list_contracts(page_size, offset) Chain.list_contracts(page_size, offset)
end end
@ -140,10 +143,12 @@ defmodule BlockScoutWeb.API.RPC.ContractController do
defp contracts_filter(2), do: {:ok, :decompiled} defp contracts_filter(2), do: {:ok, :decompiled}
defp contracts_filter(3), do: {:ok, :unverified} defp contracts_filter(3), do: {:ok, :unverified}
defp contracts_filter(4), do: {:ok, :not_decompiled} defp contracts_filter(4), do: {:ok, :not_decompiled}
defp contracts_filter(5), do: {:ok, :empty}
defp contracts_filter("verified"), do: {:ok, :verified} defp contracts_filter("verified"), do: {:ok, :verified}
defp contracts_filter("decompiled"), do: {:ok, :decompiled} defp contracts_filter("decompiled"), do: {:ok, :decompiled}
defp contracts_filter("unverified"), do: {:ok, :unverified} defp contracts_filter("unverified"), do: {:ok, :unverified}
defp contracts_filter("not_decompiled"), do: {:ok, :not_decompiled} defp contracts_filter("not_decompiled"), do: {:ok, :not_decompiled}
defp contracts_filter("empty"), do: {:ok, :empty}
defp contracts_filter(filter) when is_bitstring(filter) do defp contracts_filter(filter) when is_bitstring(filter) do
case Integer.parse(filter) do case Integer.parse(filter) do

@ -279,6 +279,12 @@ defmodule BlockScoutWeb.Etherscan do
"result" => nil "result" => nil
} }
@block_eth_block_number_example_value %{
"jsonrpc" => "2.0",
"result" => "767969",
"id" => 1
}
@contract_listcontracts_example_value %{ @contract_listcontracts_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -476,11 +482,26 @@ defmodule BlockScoutWeb.Etherscan do
enum_interpretation: %{"0" => "error", "1" => "ok"} enum_interpretation: %{"0" => "error", "1" => "ok"}
} }
@jsonrpc_version_type %{
type: "string",
example: ~s("2.0")
}
@message_type %{ @message_type %{
type: "string", type: "string",
example: ~s("OK") example: ~s("OK")
} }
@hex_number_type %{
type: "string",
example: ~s("767969")
}
@id_type %{
type: "string",
example: ~s("1")
}
@wei_type %{ @wei_type %{
type: "wei", type: "wei",
definition: &__MODULE__.wei_type_definition/1, definition: &__MODULE__.wei_type_definition/1,
@ -573,6 +594,11 @@ defmodule BlockScoutWeb.Etherscan do
type: "block number", type: "block number",
definition: "A nonnegative number used to identify blocks.", definition: "A nonnegative number used to identify blocks.",
example: ~s("0x5c958") example: ~s("0x5c958")
},
index: %{
type: "log index",
definition: "A nonnegative number used to identify logs.",
example: ~s("1")
} }
} }
} }
@ -1737,6 +1763,35 @@ defmodule BlockScoutWeb.Etherscan do
] ]
} }
@block_eth_block_number_action %{
name: "eth_block_number",
description: "Mimics Ethereum JSON RPC's eth_blockNumber. Returns the lastest block number",
required_params: [],
optional_params: [
%{
key: "id",
placeholder: "request id",
type: "integer",
description: "A nonnegative integer that represents the json rpc request id."
}
],
responses: [
%{
code: "200",
description: "successful request",
example_value: Jason.encode!(@block_eth_block_number_example_value),
model: %{
name: "Result",
fields: %{
jsonrpc: @jsonrpc_version_type,
id: @id_type,
result: @hex_number_type
}
}
}
]
}
@block_getblockreward_action %{ @block_getblockreward_action %{
name: "getblockreward", name: "getblockreward",
description: "Get block reward by block number.", description: "Get block reward by block number.",
@ -1795,7 +1850,7 @@ defmodule BlockScoutWeb.Etherscan do
key: "filter", key: "filter",
type: "string", type: "string",
description: description:
"verified|decompiled|unverified|not_decompiled, or 1|2|3|4 respectively. This requests only contracts with that status." "verified|decompiled|unverified|not_decompiled|empty, or 1|2|3|4|5 respectively. This requests only contracts with that status."
}, },
%{ %{
key: "not_decompiled_with_version", key: "not_decompiled_with_version",
@ -2171,7 +2226,7 @@ defmodule BlockScoutWeb.Etherscan do
@block_module %{ @block_module %{
name: "block", name: "block",
actions: [@block_getblockreward_action] actions: [@block_getblockreward_action, @block_eth_block_number_action]
} }
@contract_module %{ @contract_module %{

@ -147,6 +147,13 @@ defmodule BlockScoutWeb.Router do
as: :decompiled_contract as: :decompiled_contract
) )
resources(
"/logs",
AddressLogsController,
only: [:index],
as: :logs
)
resources( resources(
"/contract_verifications", "/contract_verifications",
AddressContractVerificationController, AddressContractVerificationController,

@ -22,6 +22,11 @@
"data-test": "coin_balance_tab_link", "data-test": "coin_balance_tab_link",
to: address_coin_balance_path(@conn, :index, @address.hash) to: address_coin_balance_path(@conn, :index, @address.hash)
) %> ) %>
<%= link(
gettext("Logs"),
class: "card-tab #{tab_status("logs", @conn.request_path)}",
to: address_logs_path(@conn, :index, @address.hash)
) %>
<%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %> <%= if BlockScoutWeb.AddressView.validator?(@validation_count) do %>
<%= link( <%= link(
gettext("Blocks Validated"), gettext("Blocks Validated"),
@ -55,4 +60,4 @@
class: "card-tab #{tab_status("read_contract", @conn.request_path)}") class: "card-tab #{tab_status("read_contract", @conn.request_path)}")
%> %>
<% end %> <% end %>
</div> </div>

@ -1,18 +1,25 @@
<% contract_creation_code = contract_creation_code(@address) %>
<section class="container"> <section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> <%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card"> <div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<div class="card-body"> <div class="card-body">
<%= if !BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %> <%= if match?({:selfdestructed, _}, contract_creation_code) do %>
<%= link( <div class="button button-disabled button-sm float-right ml-3">
gettext("Verify & Publish"), <%= gettext("Verify & Publish") %>
to: address_verify_contract_path(@conn, :new, @address.hash), </div>
class: "button button-primary button-sm float-right ml-3", <% else %>
"data-test": "verify_and_publish" <%= if !BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
) %> <%= link(
gettext("Verify & Publish"),
to: address_verify_contract_path(@conn, :new, @address.hash),
class: "button button-primary button-sm float-right ml-3",
"data-test": "verify_and_publish"
) %>
<% end %>
<% end %> <% end %>
<%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %> <%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
<div class="mb-4"> <div class="mb-4">
<dl class="row"> <dl class="row">
@ -56,15 +63,32 @@
<% end %> <% end %>
<section> <section>
<div class="d-flex justify-content-between align-items-baseline"> <%= case contract_creation_code do %>
<h3><%= gettext "Contract creation code" %></h3> <% {:selfdestructed, transaction_init} -> %>
<button type="button" class="button button-secondary button-sm" id="button" data-clipboard-text="<%= @address.contract_code %>" aria-label="copy contract creation code"> <div class="d-flex justify-content-between align-items-baseline">
<%= gettext "Copy Contract Creation Code" %> <h3><%= gettext "Contract Creation Code" %></h3>
</button> <button type="button" class="button button-secondary button-sm" id="button" data-clipboard-text="<%= transaction_init %>" aria-label="copy contract creation code">
</div> <%= gettext "Copy Contract Creation Code" %>
<div class="tile tile-muted"> </button>
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= @address.contract_code %></code></pre> </div>
</div> <div class="alert alert-info">
<p><%= gettext "Contracts that self destruct in their constructors have no contract code published and cannot be verified." %></p>
<p><%= gettext "Displaying the init data provided of the creating transaction." %></p>
</div>
<div class="tile tile-muted">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= transaction_init %></code></pre>
</div>
<% {:ok, contract_code} -> %>
<div class="d-flex justify-content-between align-items-baseline">
<h3><%= gettext "Contract Byte Code" %></h3>
<button type="button" class="button button-secondary button-sm" id="button" data-clipboard-text="<%= contract_code %>" aria-label="copy contract creation code">
<%= gettext "Copy Contract Byte Code" %>
</button>
</div>
<div class="tile tile-muted">
<pre class="pre-wrap pre-scrollable"><code class="nohighlight"><%= contract_code %></code></pre>
</div>
<% end %>
</section> </section>
</div> </div>

@ -0,0 +1,82 @@
<section class="container">
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<div class="card-body">
<h2 class="card-title"><%= gettext "Logs" %></h2>
<%= if @next_page_url do %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, next_page_path: @next_page_url %>
<% end %>
<%= if !@next_page_url do %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true %>
<% end %>
<%= if Enum.count(@logs) > 0 do %>
<%= for log <- @logs do %>
<div data-test="transaction_log" class="tile tile-muted">
<dl class="row">
<dt class="col-md-2"> <%= gettext "Transaction" %> </dt>
<dd class="col-md-10">
<h3 class="">
<%= link(
log.transaction,
to: transaction_path(@conn, :show, log.transaction),
"data-test": "log_address_link",
"data-address-hash": log.transaction
) %>
</h3>
</dd>
<dt class="col-md-2"><%= gettext "Topics" %></dt>
<dd class="col-md-10">
<div class="raw-transaction-log-topics">
<%= unless is_nil(log.first_topic) do %>
<div class="text-dark">
<span class="text-dark">[0]</span>
<%= log.first_topic %>
</div>
<% end %>
<%= unless is_nil(log.second_topic) do %>
<div class="text-dark">
<span class="">[1] </span>
<%= log.second_topic %>
</div>
<% end %>
<%= unless is_nil(log.third_topic) do %>
<div class="text-dark">
<span>[2]</span>
<%= log.third_topic %>
</div>
<% end %>
<%= unless is_nil(log.fourth_topic) do %>
<div class="text-dark">
<span>[3]</span>
<%= log.fourth_topic %>
</div>
<% end %>
</div>
</dd>
<dt class="col-md-2">
<%= gettext "Data" %>
</dt>
<dd class="col-md-10">
<%= unless is_nil(log.data) do %>
<div class="text-dark raw-transaction-log-data">
<%= log.data %>
</div>
<% end %>
</dd>
</dl>
</div>
<% end %>
<% else %>
<div class="tile tile-muted text-center">
<span><%= gettext "There are no logs for this address." %></span>
</div>
<% end %>
</div>
</div>
</section>

@ -1,6 +1,6 @@
<%= if Enum.any?(@token_balances) do %> <%= if Enum.any?(@token_balances) do %>
<a href="#" <a
class="text-white text-faded" href="#"
data-dropdown-toggle data-dropdown-toggle
data-toggle="dropdown" data-toggle="dropdown"
role="button" role="button"

@ -1,6 +1,8 @@
defmodule BlockScoutWeb.AddressContractView do defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias Explorer.Chain.{Address, Data, InternalTransaction}
def render("scripts.html", %{conn: conn}) do def render("scripts.html", %{conn: conn}) do
render_scripts(conn, "address_contract/code_highlighting.js") render_scripts(conn, "address_contract/code_highlighting.js")
end end
@ -34,4 +36,15 @@ defmodule BlockScoutWeb.AddressContractView do
{value, String.pad_leading(to_string(line), max_digits, " ")} {value, String.pad_leading(to_string(line), max_digits, " ")}
end) end)
end end
def contract_creation_code(%Address{
contract_code: %Data{bytes: <<>>},
contracts_creation_internal_transaction: %InternalTransaction{init: init}
}) do
{:selfdestructed, init}
end
def contract_creation_code(%Address{contract_code: contract_code}) do
{:ok, contract_code}
end
end end

@ -165,7 +165,10 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do
|> String.replace("\e[1m", "<span style=\"font-weight:bold\">") |> String.replace("\e[1m", "<span style=\"font-weight:bold\">")
|> String.replace("»", "&raquo;") |> String.replace("»", "&raquo;")
|> String.replace("\e[0m", "</span>") |> String.replace("\e[0m", "</span>")
|> String.split(~r/\<span style=.*?\)"\>|\<\/span\>/, include_captures: true, trim: true) |> String.split(~r/\<span style=.*?\)"\>|\<span style=\"font-weight:bold\"\>|\<\/span\>/,
include_captures: true,
trim: true
)
|> add_styles_to_every_line() |> add_styles_to_every_line()
result result

@ -0,0 +1,3 @@
defmodule BlockScoutWeb.AddressLogsView do
use BlockScoutWeb, :view
end

@ -300,6 +300,7 @@ defmodule BlockScoutWeb.AddressView do
defp tab_name(["read_contract"]), do: gettext("Read Contract") defp tab_name(["read_contract"]), do: gettext("Read Contract")
defp tab_name(["coin_balances"]), do: gettext("Coin Balance History") defp tab_name(["coin_balances"]), do: gettext("Coin Balance History")
defp tab_name(["validations"]), do: gettext("Blocks Validated") defp tab_name(["validations"]), do: gettext("Blocks Validated")
defp tab_name(["logs"]), do: gettext("Logs")
def short_hash(%Address{hash: hash}) do def short_hash(%Address{hash: hash}) do
<< <<

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.API.RPC.BlockView do defmodule BlockScoutWeb.API.RPC.BlockView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView}
alias Explorer.Chain.{Hash, Wei} alias Explorer.Chain.{Hash, Wei}
def render("block_reward.json", %{block: block, reward: reward}) do def render("block_reward.json", %{block: block, reward: reward}) do
@ -22,7 +22,33 @@ defmodule BlockScoutWeb.API.RPC.BlockView do
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
end end
def render("eth_block_number.json", %{number: number, id: id}) do
result = encode_quantity(number)
EthRPCView.render("show.json", %{result: result, id: id})
end
def render("error.json", %{error: error}) do def render("error.json", %{error: error}) do
RPCView.render("error.json", error: error) RPCView.render("error.json", error: error)
end end
defp encode_quantity(binary) when is_binary(binary) do
hex_binary = Base.encode16(binary, case: :lower)
result = String.replace_leading(hex_binary, "0", "")
final_result = if result == "", do: "0", else: result
"0x#{final_result}"
end
defp encode_quantity(value) when is_integer(value) do
value
|> :binary.encode_unsigned()
|> encode_quantity()
end
defp encode_quantity(value) when is_nil(value) do
nil
end
end end

@ -0,0 +1,33 @@
defmodule BlockScoutWeb.API.RPC.EthRPCView do
use BlockScoutWeb, :view
defstruct [:result, :id, :error]
def render("show.json", %{result: result, id: id}) do
%__MODULE__{
result: result,
id: id
}
end
def render("error.json", %{error: message, id: id}) do
%__MODULE__{
error: message,
id: id
}
end
defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do
def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do
"""
{"jsonrpc":"2.0","result":"#{result}","id":#{id}}
"""
end
def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do
"""
{"jsonrpc":"2.0","error": #{error},"id": #{id}}
"""
end
end
end

@ -77,7 +77,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do
%{ %{
"address" => "#{log.address_hash}", "address" => "#{log.address_hash}",
"topics" => get_topics(log), "topics" => get_topics(log),
"data" => "#{log.data}" "data" => "#{log.data}",
"index" => "#{log.index}"
} }
end end

@ -142,10 +142,22 @@ defmodule BlockScoutWeb.LayoutView do
end end
def release_link(version) do def release_link(version) do
release_link = Application.get_env(:block_scout_web, :release_link) release_link_env_var = Application.get_env(:block_scout_web, :release_link)
if release_link == "" || release_link == nil do release_link =
version cond do
version == "" || version == nil ->
nil
release_link_env_var == "" || release_link_env_var == nil ->
"https://github.com/poanetwork/blockscout/releases/tag/" <> version
true ->
release_link_env_var
end
if release_link == nil do
""
else else
html_escape({:safe, "<a href=\"#{release_link}\" class=\"footer-link\" target=\"_blank\">#{version}</a>"}) html_escape({:safe, "<a href=\"#{release_link}\" class=\"footer-link\" target=\"_blank\">#{version}</a>"})
end end

@ -187,7 +187,7 @@ msgid "Blocks Indexed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:27 #: lib/block_scout_web/templates/address/_tabs.html.eex:32
#: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address/overview.html.eex:95
#: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:302 #: lib/block_scout_web/views/address_view.ex:302
@ -215,7 +215,7 @@ msgid "Close"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: lib/block_scout_web/templates/address/_tabs.html.eex:42
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/views/address_view.ex:298 #: lib/block_scout_web/views/address_view.ex:298
@ -228,7 +228,7 @@ msgid "Compiler"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:23 #: lib/block_scout_web/templates/address_contract/index.html.eex:30
msgid "Compiler version" msgid "Compiler version"
msgstr "" msgstr ""
@ -256,7 +256,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:46 #: lib/block_scout_web/templates/address_contract/index.html.eex:53
msgid "Contract ABI" msgid "Contract ABI"
msgstr "" msgstr ""
@ -289,17 +289,12 @@ msgid "Contract Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:60 #: lib/block_scout_web/templates/address_contract/index.html.eex:26
msgid "Contract creation code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:19
msgid "Contract name:" msgid "Contract name:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:34 #: lib/block_scout_web/templates/address_contract/index.html.eex:41
msgid "Contract source code" msgid "Contract source code"
msgstr "" msgstr ""
@ -332,6 +327,7 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:63
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67
#: lib/block_scout_web/templates/transaction_log/index.html.eex:133 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133
@ -503,8 +499,11 @@ msgid "Limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:303
#: lib/block_scout_web/views/transaction_view.ex:340 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
@ -619,7 +618,7 @@ msgid "Older"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:34
msgid "Optimization enabled" msgid "Optimization enabled"
msgstr "" msgstr ""
@ -680,7 +679,7 @@ msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53 #: lib/block_scout_web/templates/address/_tabs.html.eex:58
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/address_view.ex:300
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
@ -872,6 +871,7 @@ msgid "Top Accounts - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:33
#: lib/block_scout_web/templates/transaction_log/index.html.eex:103 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103
msgid "Topics" msgid "Topics"
msgstr "" msgstr ""
@ -892,6 +892,7 @@ msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:22
#: lib/block_scout_web/views/transaction_view.ex:287 #: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -979,7 +980,8 @@ msgid "Value"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:9 #: lib/block_scout_web/templates/address_contract/index.html.eex:11
#: lib/block_scout_web/templates/address_contract/index.html.eex:16
msgid "Verify & Publish" msgid "Verify & Publish"
msgstr "" msgstr ""
@ -1044,7 +1046,7 @@ msgid "at"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:20 #: lib/block_scout_web/views/address_contract_view.ex:22
msgid "false" msgid "false"
msgstr "" msgstr ""
@ -1062,7 +1064,7 @@ msgid "string"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:19 #: lib/block_scout_web/views/address_contract_view.ex:21
msgid "true" msgid "true"
msgstr "" msgstr ""
@ -1466,17 +1468,17 @@ msgid "Support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:48 #: lib/block_scout_web/templates/address_contract/index.html.eex:55
msgid "Copy ABI" msgid "Copy ABI"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:62 #: lib/block_scout_web/templates/address_contract/index.html.eex:71
msgid "Copy Contract Creation Code" msgid "Copy Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:36 #: lib/block_scout_web/templates/address_contract/index.html.eex:43
msgid "Copy Source Code" msgid "Copy Source Code"
msgstr "" msgstr ""
@ -1618,7 +1620,7 @@ msgid "Decompiled Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:47 #: lib/block_scout_web/templates/address/_tabs.html.eex:52
msgid "Decompiled code" msgid "Decompiled code"
msgstr "" msgstr ""
@ -1704,6 +1706,36 @@ msgid "of"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
<<<<<<< HEAD
#: lib/block_scout_web/templates/block_transaction/404.html.eex:7 #: lib/block_scout_web/templates/block_transaction/404.html.eex:7
msgid "Block Details" msgid "Block Details"
=======
#: lib/block_scout_web/templates/address_contract/index.html.eex:83
msgid "Contract Byte Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:69
msgid "Contract Creation Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:85
msgid "Copy Contract Byte Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:76
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:77
msgid "There are no logs for this address."
>>>>>>> 59d33a42ddf629ec042ae03fa17818f4c6eb8211
msgstr "" msgstr ""

@ -187,7 +187,7 @@ msgid "Blocks Indexed"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:27 #: lib/block_scout_web/templates/address/_tabs.html.eex:32
#: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address/overview.html.eex:95
#: lib/block_scout_web/templates/address_validation/index.html.eex:13 #: lib/block_scout_web/templates/address_validation/index.html.eex:13
#: lib/block_scout_web/views/address_view.ex:302 #: lib/block_scout_web/views/address_view.ex:302
@ -215,7 +215,7 @@ msgid "Close"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:37 #: lib/block_scout_web/templates/address/_tabs.html.eex:42
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187
#: lib/block_scout_web/views/address_view.ex:298 #: lib/block_scout_web/views/address_view.ex:298
@ -228,7 +228,7 @@ msgid "Compiler"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:23 #: lib/block_scout_web/templates/address_contract/index.html.eex:30
msgid "Compiler version" msgid "Compiler version"
msgstr "" msgstr ""
@ -256,7 +256,7 @@ msgid "Connection Lost, click to load newer validations"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:46 #: lib/block_scout_web/templates/address_contract/index.html.eex:53
msgid "Contract ABI" msgid "Contract ABI"
msgstr "" msgstr ""
@ -289,17 +289,12 @@ msgid "Contract Name"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:60 #: lib/block_scout_web/templates/address_contract/index.html.eex:26
msgid "Contract creation code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:19
msgid "Contract name:" msgid "Contract name:"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:34 #: lib/block_scout_web/templates/address_contract/index.html.eex:41
msgid "Contract source code" msgid "Contract source code"
msgstr "" msgstr ""
@ -334,6 +329,7 @@ msgid "Curl"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:63
#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
#: lib/block_scout_web/templates/transaction_log/index.html.eex:67 #: lib/block_scout_web/templates/transaction_log/index.html.eex:67
#: lib/block_scout_web/templates/transaction_log/index.html.eex:133 #: lib/block_scout_web/templates/transaction_log/index.html.eex:133
@ -505,8 +501,11 @@ msgid "Limit"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:26
#: lib/block_scout_web/templates/address_logs/index.html.eex:8
#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17
#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8
#: lib/block_scout_web/views/address_view.ex:303
#: lib/block_scout_web/views/transaction_view.ex:340 #: lib/block_scout_web/views/transaction_view.ex:340
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
@ -621,7 +620,7 @@ msgid "Older"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:34
msgid "Optimization enabled" msgid "Optimization enabled"
msgstr "" msgstr ""
@ -682,7 +681,7 @@ msgid "Query"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:53 #: lib/block_scout_web/templates/address/_tabs.html.eex:58
#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25
#: lib/block_scout_web/views/address_view.ex:300 #: lib/block_scout_web/views/address_view.ex:300
#: lib/block_scout_web/views/tokens/overview_view.ex:37 #: lib/block_scout_web/views/tokens/overview_view.ex:37
@ -874,6 +873,7 @@ msgid "Top Accounts - %{subnetwork} Explorer"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:33
#: lib/block_scout_web/templates/transaction_log/index.html.eex:103 #: lib/block_scout_web/templates/transaction_log/index.html.eex:103
msgid "Topics" msgid "Topics"
msgstr "" msgstr ""
@ -894,6 +894,7 @@ msgid "Total transactions"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:22
#: lib/block_scout_web/views/transaction_view.ex:287 #: lib/block_scout_web/views/transaction_view.ex:287
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
@ -982,7 +983,8 @@ msgid "Value"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:9 #: lib/block_scout_web/templates/address_contract/index.html.eex:11
#: lib/block_scout_web/templates/address_contract/index.html.eex:16
msgid "Verify & Publish" msgid "Verify & Publish"
msgstr "" msgstr ""
@ -1047,7 +1049,7 @@ msgid "at"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:20 #: lib/block_scout_web/views/address_contract_view.ex:22
msgid "false" msgid "false"
msgstr "" msgstr ""
@ -1065,7 +1067,7 @@ msgid "string"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/address_contract_view.ex:19 #: lib/block_scout_web/views/address_contract_view.ex:21
msgid "true" msgid "true"
msgstr "" msgstr ""
@ -1357,7 +1359,7 @@ msgid "Loading balances"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:23 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:22
#: lib/block_scout_web/templates/chain/show.html.eex:13 #: lib/block_scout_web/templates/chain/show.html.eex:13
msgid "Loading chart" msgid "Loading chart"
msgstr "" msgstr ""
@ -1368,7 +1370,7 @@ msgid "There is no coin history for this address."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:26 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:25
#: lib/block_scout_web/templates/chain/show.html.eex:16 #: lib/block_scout_web/templates/chain/show.html.eex:16
msgid "There was a problem loading the chart." msgid "There was a problem loading the chart."
msgstr "" msgstr ""
@ -1509,17 +1511,17 @@ msgid "Support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:48 #: lib/block_scout_web/templates/address_contract/index.html.eex:55
msgid "Copy ABI" msgid "Copy ABI"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:62 #: lib/block_scout_web/templates/address_contract/index.html.eex:71
msgid "Copy Contract Creation Code" msgid "Copy Contract Creation Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:36 #: lib/block_scout_web/templates/address_contract/index.html.eex:43
msgid "Copy Source Code" msgid "Copy Source Code"
msgstr "" msgstr ""
@ -1661,7 +1663,7 @@ msgid "Decompiled Code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address/_tabs.html.eex:47 #: lib/block_scout_web/templates/address/_tabs.html.eex:52
msgid "Decompiled code" msgid "Decompiled code"
msgstr "" msgstr ""
@ -1745,3 +1747,33 @@ msgstr ""
#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:37
msgid "of" msgid "of"
msgstr "" msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract/index.html.eex:83
msgid "Contract Byte Code"
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract/index.html.eex:69
msgid "Contract Creation Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_contract/index.html.eex:85
msgid "Copy Contract Byte Code"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_contract/index.html.eex:76
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_logs/index.html.eex:77
msgid "There are no logs for this address."
msgstr ""

@ -25,7 +25,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
start_supervised!(AddressesWithBalanceCounter) start_supervised!(AddressesWithBalanceCounter)
Application.put_env(:explorer, AverageBlockTime, enabled: true) Application.put_env(:explorer, AverageBlockTime, enabled: true)
BlockNumberCache.setup(cache_period: 0)
on_exit(fn -> on_exit(fn ->
Application.put_env(:explorer, AverageBlockTime, enabled: false) Application.put_env(:explorer, AverageBlockTime, enabled: false)

@ -100,6 +100,34 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
] ]
end end
test "filtering for only unverified contracts does not show self destructed contracts", %{
params: params,
conn: conn
} do
address = insert(:contract_address)
insert(:smart_contract)
insert(:contract_address, contract_code: "0x")
response =
conn
|> get("/api", Map.put(params, "filter", "unverified"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompilerVersion" => "",
"OptimizationUsed" => ""
}
]
end
test "filtering for only verified contracts shows only verified contracts", %{params: params, conn: conn} do test "filtering for only verified contracts shows only verified contracts", %{params: params, conn: conn} do
insert(:contract_address) insert(:contract_address)
contract = insert(:smart_contract) contract = insert(:smart_contract)
@ -222,6 +250,35 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do
} }
] ]
end end
test "filtering for only not_decompiled (and by extension not verified contracts) does not show empty contracts", %{
params: params,
conn: conn
} do
insert(:decompiled_smart_contract)
insert(:smart_contract)
insert(:contract_address, contract_code: "0x")
contract_address = insert(:contract_address)
response =
conn
|> get("/api", Map.put(params, "filter", "not_decompiled"))
|> json_response(200)
assert response["message"] == "OK"
assert response["status"] == "1"
assert response["result"] == [
%{
"ABI" => "Contract source code not verified",
"Address" => to_string(contract_address.hash),
"CompilerVersion" => "",
"ContractName" => "",
"DecompilerVersion" => "",
"OptimizationUsed" => ""
}
]
end
end end
describe "getabi" do describe "getabi" do

@ -460,7 +460,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
%{ %{
"address" => "#{address.hash}", "address" => "#{address.hash}",
"data" => "#{log.data}", "data" => "#{log.data}",
"topics" => ["first topic", "second topic", nil, nil] "topics" => ["first topic", "second topic", nil, nil],
"index" => "#{log.index}"
} }
], ],
"next_page_params" => nil "next_page_params" => nil

@ -56,7 +56,7 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do
result = AddressDecompiledContractView.highlight_decompiled_code(code) result = AddressDecompiledContractView.highlight_decompiled_code(code)
assert result == assert result ==
"<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # eveem.org 6 Feb 2019</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Decompiled source of </span>0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Let's make the world open source</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # </span></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # I failed with these:</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> </span><span style=\"color:rgb(111, 110, 111)\"># - </span>unknowne77c646d(?)<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> </span><span style=\"color:rgb(111, 110, 111)\"># - </span>transferFromWithData(address _from, address _to, uint256 _value, bytes _data)<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # All the rest is below.</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># Storage definitions and getters</span></code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> storage:</code>\n<code> allowance is <span class=\"hljs-keyword\">uint</span>256 => <span class=\"hljs-keyword\">uint</span>256 <span style=\"color:rgb(111, 110, 111)\"># mask(256, 0) at storage #2</span></code>\n<code> stor4 is <span class=\"hljs-keyword\">uint</span>256 => <span class=\"hljs-keyword\">uint</span>8 <span style=\"color:rgb(111, 110, 111)\"># mask(8, 0) at storage #4</span></code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> allowance(<span class=\"hljs-keyword\">address</span> _owner, <span class=\"hljs-keyword\">address</span> _spender) <span class=\"hljs-title\">payable</span>: 64</code>\n<code> <span class=\"hljs-keyword\">return</span> allowance[_owner_spender(320 - 1))]</code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Regular functions - see Tutorial for understanding quirks of the code</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># folder failed in this function - may be terribly long, sorry</span></code>\n<code> <span class=\"hljs-keyword\">def</span> unknownc47d033b(?) <span class=\"hljs-title\">payable</span>: not cd[4]:</code>\n<code> <span class=\"hljs-keyword\">revert</span></code>\n<code> else:</code>\n<code> <span class=\"hljs-keyword\">mem</span>[0]cd[4]</code>\n<code> <span class=\"hljs-keyword\">mem</span>[32] = 4</code>\n<code> <span class=\"hljs-keyword\">mem</span>[96] = <span class=\"hljs-keyword\">bool</span>(stor4[cd[4])])</code>\n<code> <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">bool</span>(stor4[cd[4])])</code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> _fallback() <span class=\"hljs-title\">payable</span>: <span style=\"color:rgb(111, 110, 111)\"># default function</span></code>\n<code> <span class=\"hljs-keyword\">revert</span></code>\n<code></code>\n<code></code>\n" "<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # eveem.org 6 Feb 2019</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Decompiled source of </span>0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Let's make the world open source</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # </span></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # I failed with these:</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> </span><span style=\"color:rgb(111, 110, 111)\"># - </span>unknowne77c646d(?)<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> </span><span style=\"color:rgb(111, 110, 111)\"># - </span>transferFromWithData(address _from, address _to, uint256 _value, bytes _data)<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # All the rest is below.</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># Storage definitions and getters</span></code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> storage:</code>\n<code> allowance is <span class=\"hljs-keyword\">uint</span>256 => <span class=\"hljs-keyword\">uint</span>256 <span style=\"color:rgb(111, 110, 111)\"># mask(256, 0) at storage #2</span></code>\n<code> stor4 is <span class=\"hljs-keyword\">uint</span>256 => <span class=\"hljs-keyword\">uint</span>8 <span style=\"color:rgb(111, 110, 111)\"># mask(8, 0) at storage #4</span></code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> allowance(<span class=\"hljs-keyword\">address</span> _owner, <span class=\"hljs-keyword\">address</span> _spender) <span class=\"hljs-title\">payable</span>: </code>\n<code> <span class=\"hljs-keyword\">require</span> (calldata.size - 4)<span style=\"font-weight:bold\"> >= </span>64</code>\n<code> <span class=\"hljs-keyword\">return</span> allowance[sha3(((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>_owner), 1), ((320 - 1)<span style=\"font-weight:bold\"> and </span>_spender<span style=\"font-weight:bold\"> and </span>(320 - 1))]</code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Regular functions - see Tutorial for understanding quirks of the code</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code></code>\n<code></code>\n<code> <span style=\"color:rgb(111, 110, 111)\"># folder failed in this function - may be terribly long, sorry</span></code>\n<code> <span class=\"hljs-keyword\">def</span> unknownc47d033b(?) <span class=\"hljs-title\">payable</span>: </code>\n<code> if (calldata.size - 4)<span style=\"font-weight:bold\"> < </span>32:</code>\n<code> <span class=\"hljs-keyword\">revert</span></code>\n<code> else:</code>\n<code> if not (320 - 1)<span style=\"font-weight:bold\"> or </span>not cd[4]:</code>\n<code> <span class=\"hljs-keyword\">revert</span></code>\n<code> else:</code>\n<code> <span class=\"hljs-keyword\">mem</span>[0] = (320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4]</code>\n<code> <span class=\"hljs-keyword\">mem</span>[32] = 4</code>\n<code> <span class=\"hljs-keyword\">mem</span>[96] = <span class=\"hljs-keyword\">bool</span>(stor4[((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4])])</code>\n<code> <span class=\"hljs-keyword\">return</span> <span class=\"hljs-keyword\">bool</span>(stor4[((320 - 1)<span style=\"font-weight:bold\"> and </span>(320 - 1)<span style=\"font-weight:bold\"> and </span>cd[4])])</code>\n<code></code>\n<code> <span class=\"hljs-keyword\">def</span> _fallback() <span class=\"hljs-title\">payable</span>: <span style=\"color:rgb(111, 110, 111)\"># default function</span></code>\n<code> <span class=\"hljs-keyword\">revert</span></code>\n<code></code>\n<code></code>\n"
end end
test "adds style span to every line" do test "adds style span to every line" do
@ -72,6 +72,28 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do
assert AddressDecompiledContractView.highlight_decompiled_code(code) == assert AddressDecompiledContractView.highlight_decompiled_code(code) ==
"<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # eveem.org 6 Feb 2019</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Decompiled source of </span>0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Let's make the world open source</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # </span></code>\n<code></code>\n<code></code>\n" "<code> <span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # eveem.org 6 Feb 2019</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Decompiled source of </span>0x00Bd9e214FAb74d6fC21bf1aF34261765f57e875<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> #</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # Let's make the world open source</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"> # </span></code>\n<code></code>\n<code></code>\n"
end end
test "does not remove bold text" do
code = """
#
# Eveem.org 26 Apr 2019
# Decompiled source of 0x06012c8cf97bead5deae237070f9587f8e7a266d
#
# Let's make the world open source
# 
const name = 'CryptoKitties'
const symbol = 'CK'
const GEN0_STARTING_PRICE = 10^16
const GEN0_AUCTION_DURATION = (24 * 3600)
const GEN0_CREATION_LIMIT = 45000
const PROMO_CREATION_LIMIT = 5000
"""
assert AddressDecompiledContractView.highlight_decompiled_code(code) ==
"<code><span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"># Eveem.org 26 Apr 2019</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"># Decompiled source of </span>0x06012c8cf97bead5deae237070f9587f8e7a266d<span style=\"color:rgb(111, 110, 111)\"></span></code>\n<code><span style=\"color:rgb(111, 110, 111)\">#</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"># Let's make the world open source</span></code>\n<code><span style=\"color:rgb(111, 110, 111)\"># </span></code>\n<code></code>\n<code>const name = <span style=\"font-weight:bold\">'CryptoKitties'</span></code>\n<code>const symbol = <span style=\"font-weight:bold\">'CK'</span></code>\n<code>const GEN0_STARTING_PRICE = <span style=\"font-weight:bold\">10^16</span></code>\n<code>const GEN0_AUCTION_DURATION = <span style=\"font-weight:bold\">(24 * 3600)</span></code>\n<code>const GEN0_CREATION_LIMIT = <span style=\"font-weight:bold\">45000</span></code>\n<code>const PROMO_CREATION_LIMIT = <span style=\"font-weight:bold\">5000</span></code>\n<code></code>\n<code></code>\n<code></code>\n"
end
end end
describe "sort_contracts_by_version/1" do describe "sort_contracts_by_version/1" do

@ -62,16 +62,32 @@ defmodule BlockScoutWeb.LayoutViewTest do
end end
describe "release_link/1" do describe "release_link/1" do
test "use the version when there is no release_link env configured for it" do test "set empty string if no blockscout version configured" do
Application.put_env(:block_scout_web, :blockscout_version, nil)
assert LayoutView.release_link(nil) == ""
end
test "set empty string if blockscout version is empty string" do
Application.put_env(:block_scout_web, :blockscout_version, "")
assert LayoutView.release_link("") == ""
end
test "use the default value when there is no release_link env configured for it" do
Application.put_env(:block_scout_web, :release_link, nil) Application.put_env(:block_scout_web, :release_link, nil)
assert LayoutView.release_link("1.3.4") == "1.3.4" assert LayoutView.release_link("v1.3.4-beta") ==
{:safe,
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">v1.3.4-beta</a>)}
end end
test "use the version when empty release_link env configured for it" do test "use the default value when empty release_link env configured for it" do
Application.put_env(:block_scout_web, :release_link, "") Application.put_env(:block_scout_web, :release_link, "")
assert LayoutView.release_link("1.3.4") == "1.3.4" assert LayoutView.release_link("v1.3.4-beta") ==
{:safe,
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">v1.3.4-beta</a>)}
end end
test "use the enviroment release link when it's configured" do test "use the enviroment release link when it's configured" do
@ -81,9 +97,9 @@ defmodule BlockScoutWeb.LayoutViewTest do
"https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" "https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta"
) )
assert LayoutView.release_link("1.3.4") == assert LayoutView.release_link("v1.3.4-beta") ==
{:safe, {:safe,
~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">1.3.4</a>)} ~s(<a href="https://github.com/poanetwork/blockscout/releases/tag/v1.3.4-beta" class="footer-link" target="_blank">v1.3.4-beta</a>)}
end end
end end

@ -13,6 +13,8 @@ config :explorer,
config :explorer, Explorer.Counters.AverageBlockTime, enabled: true config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
config :explorer, Explorer.Chain.BlockNumberCache, enabled: true
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap, config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,
pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10") pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10")
@ -58,6 +60,14 @@ config :explorer, Explorer.Staking.PoolsReader,
validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"), validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"),
staking_contract_address: System.get_env("POS_STAKING_CONTRACT") staking_contract_address: System.get_env("POS_STAKING_CONTRACT")
if System.get_env("POS_STAKING_CONTRACT") do
config :explorer, Explorer.Staking.EpochCounter,
enabled: true,
staking_contract_address: System.get_env("POS_STAKING_CONTRACT")
else
config :explorer, Explorer.Staking.EpochCounter, enabled: false
end
if System.get_env("SUPPLY_MODULE") == "TokenBridge" do if System.get_env("SUPPLY_MODULE") == "TokenBridge" do
config :explorer, supply: Explorer.Chain.Supply.TokenBridge config :explorer, supply: Explorer.Chain.Supply.TokenBridge
end end

@ -13,6 +13,8 @@ config :explorer, Explorer.Repo,
config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets
config :explorer, Explorer.Chain.BlockNumberCache, enabled: false
config :explorer, Explorer.KnownTokens, enabled: false, store: :ets config :explorer, Explorer.KnownTokens, enabled: false, store: :ets
config :explorer, Explorer.Counters.AverageBlockTime, enabled: false config :explorer, Explorer.Counters.AverageBlockTime, enabled: false

@ -51,7 +51,8 @@ defmodule Explorer.Application do
configure(Explorer.Market.History.Cataloger), configure(Explorer.Market.History.Cataloger),
configure(Explorer.Counters.AddressesWithBalanceCounter), configure(Explorer.Counters.AddressesWithBalanceCounter),
configure(Explorer.Counters.AverageBlockTime), configure(Explorer.Counters.AverageBlockTime),
configure(Explorer.Validator.MetadataProcessor) configure(Explorer.Validator.MetadataProcessor),
configure(Explorer.Staking.EpochCounter)
] ]
|> List.flatten() |> List.flatten()
end end

@ -10,6 +10,7 @@ defmodule Explorer.Chain do
limit: 2, limit: 2,
order_by: 2, order_by: 2,
order_by: 3, order_by: 3,
offset: 2,
preload: 2, preload: 2,
select: 2, select: 2,
subquery: 1, subquery: 1,
@ -279,6 +280,38 @@ defmodule Explorer.Chain do
|> Enum.take(paging_options.page_size) |> Enum.take(paging_options.page_size)
end end
@spec address_to_logs(Address.t(), [paging_options]) :: [
Log.t()
]
def address_to_logs(
%Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash},
options \\ []
)
when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50}
{block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0}
query =
from(log in Log,
inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index],
preload: [:transaction],
where:
log.address_hash == ^address_hash and
(transaction.block_number < ^block_number or
(transaction.block_number == ^block_number and transaction.index > ^transaction_index) or
(transaction.block_number == ^block_number and transaction.index == ^transaction_index and
log.index > ^log_index)),
limit: ^paging_options.page_size,
select: log
)
query
|> Repo.all()
|> Enum.take(paging_options.page_size)
end
@doc """ @doc """
Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract
address hash. address hash.
@ -2565,7 +2598,7 @@ defmodule Explorer.Chain do
join: duplicate in subquery(query), join: duplicate in subquery(query),
on: duplicate.nonce == pending.nonce, on: duplicate.nonce == pending.nonce,
on: duplicate.from_address_hash == pending.from_address_hash, on: duplicate.from_address_hash == pending.from_address_hash,
where: pending.hash in ^hashes where: pending.hash in ^hashes and is_nil(pending.block_hash)
) )
Repo.update_all(transactions_to_update, [set: [error: "dropped/replaced", status: :error]], timeout: timeout) Repo.update_all(transactions_to_update, [set: [error: "dropped/replaced", status: :error]], timeout: timeout)
@ -2802,6 +2835,7 @@ defmodule Explorer.Chain do
on: smart_contract.address_hash == address.hash, on: smart_contract.address_hash == address.hash,
where: not is_nil(address.contract_code), where: not is_nil(address.contract_code),
where: is_nil(smart_contract.address_hash), where: is_nil(smart_contract.address_hash),
where: address.contract_code != <<>>,
preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts], preload: [{:smart_contract, smart_contract}, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at], order_by: [asc: address.inserted_at],
limit: ^limit, limit: ^limit,
@ -2811,6 +2845,19 @@ defmodule Explorer.Chain do
Repo.all(query) Repo.all(query)
end end
def list_empty_contracts(limit, offset) do
query =
from(address in Address,
where: address.contract_code == <<>>,
preload: [:smart_contract, :decompiled_smart_contracts],
order_by: [asc: address.inserted_at],
limit: ^limit,
offset: ^offset
)
Repo.all(query)
end
def list_not_decompiled_contracts(limit, offset) do def list_not_decompiled_contracts(limit, offset) do
query = query =
from( from(
@ -2820,6 +2867,7 @@ defmodule Explorer.Chain do
"NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)", "NOT EXISTS (SELECT 1 FROM decompiled_smart_contracts WHERE decompiled_smart_contracts.address_hash = ?)",
address.hash address.hash
), ),
where: address.contract_code != <<>>,
left_join: smart_contract in SmartContract, left_join: smart_contract in SmartContract,
on: smart_contract.address_hash == address.hash, on: smart_contract.address_hash == address.hash,
left_join: decompiled_smart_contract in DecompiledSmartContract, left_join: decompiled_smart_contract in DecompiledSmartContract,
@ -2856,6 +2904,75 @@ defmodule Explorer.Chain do
value value
end end
@doc "Get staking pools from the DB"
@spec staking_pools(filter :: :validator | :active | :inactive, options :: PagingOptions.t()) :: [map()]
def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do
off = page_size * (page_number - 1)
Address.Name
|> staking_pool_filter(filter)
|> limit(^page_size)
|> offset(^off)
|> Repo.all()
end
@doc "Get count of staking pools from the DB"
@spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer
def staking_pools_count(filter) do
Address.Name
|> staking_pool_filter(filter)
|> Repo.aggregate(:count, :address_hash)
end
defp staking_pool_filter(query, :validator) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true and
(?->>'is_validator')::boolean = true
""",
address.metadata,
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, :active) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, :inactive) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = false and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
)
end
defp staking_pool_filter(query, _), do: query
defp with_decompiled_code_flag(query, hash) do defp with_decompiled_code_flag(query, hash) do
has_decompiled_code_query = has_decompiled_code_query =
from(decompiled_contract in DecompiledSmartContract, from(decompiled_contract in DecompiledSmartContract,

@ -10,7 +10,7 @@ defmodule Explorer.Chain.Block do
alias Explorer.Chain.{Address, Gas, Hash, Transaction} alias Explorer.Chain.{Address, Gas, Hash, Transaction}
alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} alias Explorer.Chain.Block.{Reward, SecondDegreeRelation}
@optional_attrs ~w(internal_transactions_indexed_at size)a @optional_attrs ~w(internal_transactions_indexed_at size refetch_needed)a
@required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash timestamp total_difficulty)a @required_attrs ~w(consensus difficulty gas_limit gas_used hash miner_hash nonce number parent_hash timestamp total_difficulty)a
@ -63,7 +63,8 @@ defmodule Explorer.Chain.Block do
timestamp: DateTime.t(), timestamp: DateTime.t(),
total_difficulty: difficulty(), total_difficulty: difficulty(),
transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()], transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()],
internal_transactions_indexed_at: DateTime.t() internal_transactions_indexed_at: DateTime.t(),
refetch_needed: boolean()
} }
@primary_key {:hash, Hash.Full, autogenerate: false} @primary_key {:hash, Hash.Full, autogenerate: false}
@ -78,6 +79,7 @@ defmodule Explorer.Chain.Block do
field(:timestamp, :utc_datetime_usec) field(:timestamp, :utc_datetime_usec)
field(:total_difficulty, :decimal) field(:total_difficulty, :decimal)
field(:internal_transactions_indexed_at, :utc_datetime_usec) field(:internal_transactions_indexed_at, :utc_datetime_usec)
field(:refetch_needed, :boolean)
timestamps() timestamps()

@ -6,13 +6,10 @@ defmodule Explorer.Chain.BlockNumberCache do
alias Explorer.Chain alias Explorer.Chain
@tab :block_number_cache @tab :block_number_cache
# 30 minutes
@cache_period 1_000 * 60 * 30
@key "min_max" @key "min_max"
@opts_key "opts"
@spec setup(Keyword.t()) :: :ok @spec setup() :: :ok
def setup(opts \\ []) do def setup do
if :ets.whereis(@tab) == :undefined do if :ets.whereis(@tab) == :undefined do
:ets.new(@tab, [ :ets.new(@tab, [
:set, :set,
@ -22,7 +19,6 @@ defmodule Explorer.Chain.BlockNumberCache do
]) ])
end end
setup_opts(opts)
update_cache() update_cache()
:ok :ok
@ -41,15 +37,11 @@ defmodule Explorer.Chain.BlockNumberCache do
end end
defp value(type) do defp value(type) do
initial_cache = {_min, _max, old_current_time} = cached_values() {min, max} =
if Application.get_env(:explorer, __MODULE__)[:enabled] do
{min, max, _current_time} =
if current_time() - old_current_time > cache_period() do
update_cache()
cached_values() cached_values()
else else
initial_cache min_and_max_from_db()
end end
case type do case type do
@ -59,18 +51,29 @@ defmodule Explorer.Chain.BlockNumberCache do
end end
end end
defp update_cache do @spec update(non_neg_integer()) :: boolean()
current_time = current_time() def update(number) do
{min, max} = min_and_max_from_db() {old_min, old_max} = cached_values()
tuple = {min, max, current_time}
:ets.insert(@tab, {@key, tuple}) cond do
number > old_max ->
tuple = {old_min, number}
:ets.insert(@tab, {@key, tuple})
number < old_min ->
tuple = {number, old_max}
:ets.insert(@tab, {@key, tuple})
true ->
false
end
end end
defp setup_opts(opts) do defp update_cache do
cache_period = opts[:cache_period] || @cache_period {min, max} = min_and_max_from_db()
tuple = {min, max}
:ets.insert(@tab, {@opts_key, cache_period}) :ets.insert(@tab, {@key, tuple})
end end
defp cached_values do defp cached_values do
@ -79,22 +82,10 @@ defmodule Explorer.Chain.BlockNumberCache do
cached_values cached_values
end end
defp cache_period do
[{_, cache_period}] = :ets.lookup(@tab, @opts_key)
cache_period
end
defp min_and_max_from_db do defp min_and_max_from_db do
Chain.fetch_min_and_max_block_numbers() Chain.fetch_min_and_max_block_numbers()
rescue rescue
_e -> _e ->
{0, 0} {0, 0}
end end
defp current_time do
utc_now = DateTime.utc_now()
DateTime.to_unix(utc_now, :millisecond)
end
end end

@ -41,6 +41,9 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
|> Map.put(:timestamps, timestamps) |> Map.put(:timestamps, timestamps)
multi multi
|> Multi.run(:mark_as_deleted, fn repo, _ ->
mark_as_deleted(repo, changes_list, insert_options)
end)
|> Multi.run(:insert_staking_pools, fn repo, _ -> |> Multi.run(:insert_staking_pools, fn repo, _ ->
insert(repo, changes_list, insert_options) insert(repo, changes_list, insert_options)
end) end)
@ -49,6 +52,32 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
@impl Import.Runner @impl Import.Runner
def timeout, do: @timeout def timeout, do: @timeout
defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do
addresses = Enum.map(changes_list, & &1.address_hash)
query =
from(
address_name in Address.Name,
where:
address_name.address_hash not in ^addresses and
fragment("(?->>'is_pool')::boolean = true", address_name.metadata),
update: [
set: [
metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata)
]
]
)
try do
{_, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error}}
end
end
@spec insert(Repo.t(), [map()], %{ @spec insert(Repo.t(), [map()], %{
optional(:on_conflict) => Import.Runner.on_conflict(), optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout, required(:timeout) => timeout,
@ -62,7 +91,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
{:ok, _} = {:ok, _} =
Import.insert_changes_list( Import.insert_changes_list(
repo, repo,
changes_list, stakes_ratio(changes_list),
conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"},
on_conflict: on_conflict, on_conflict: on_conflict,
for: Address.Name, for: Address.Name,
@ -85,4 +114,20 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
] ]
) )
end end
# Calculates staked ratio for each pool
defp stakes_ratio(pools) do
active_pools = Enum.filter(pools, & &1.metadata[:is_active])
stakes_total =
Enum.reduce(pools, 0, fn pool, acc ->
acc + pool.metadata[:staked_amount]
end)
Enum.map(active_pools, fn pool ->
staked_ratio = if stakes_total > 0, do: pool.metadata[:staked_amount] / stakes_total, else: 0
put_in(pool, [:metadata, :staked_ratio], staked_ratio)
end)
end
end end

@ -8,7 +8,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2]
alias Ecto.{Multi, Repo} alias Ecto.{Multi, Repo}
alias Explorer.Chain.{Data, Hash, Import, Transaction} alias Explorer.Chain.{Block, Data, Hash, Import, Transaction}
alias Explorer.Chain.Import.Runner.TokenTransfers alias Explorer.Chain.Import.Runner.TokenTransfers
@behaviour Import.Runner @behaviour Import.Runner
@ -42,9 +42,13 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
|> Map.put(:timestamps, timestamps) |> Map.put(:timestamps, timestamps)
|> Map.put(:token_transfer_transaction_hash_set, token_transfer_transaction_hash_set(options)) |> Map.put(:token_transfer_transaction_hash_set, token_transfer_transaction_hash_set(options))
Multi.run(multi, :transactions, fn repo, _ -> multi
|> Multi.run(:transactions, fn repo, _ ->
insert(repo, changes_list, insert_options) insert(repo, changes_list, insert_options)
end) end)
|> Multi.run(:recollated_transactions, fn repo, %{transactions: transactions} ->
discard_blocks_for_recollated_transactions(repo, transactions, insert_options)
end)
end end
@impl Import.Runner @impl Import.Runner
@ -87,7 +91,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
on_conflict: on_conflict, on_conflict: on_conflict,
for: Transaction, for: Transaction,
returning: returning:
~w(block_number index hash internal_transactions_indexed_at block_hash nonce from_address_hash created_contract_address_hash)a, ~w(block_number index hash internal_transactions_indexed_at block_hash old_block_hash nonce from_address_hash created_contract_address_hash)a,
timeout: timeout, timeout: timeout,
timestamps: timestamps timestamps: timestamps
) )
@ -99,6 +103,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
update: [ update: [
set: [ set: [
block_hash: fragment("EXCLUDED.block_hash"), block_hash: fragment("EXCLUDED.block_hash"),
old_block_hash: transaction.block_hash,
block_number: fragment("EXCLUDED.block_number"), block_number: fragment("EXCLUDED.block_number"),
created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"),
cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"),
@ -179,4 +184,43 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
end end
defp put_internal_transactions_indexed_at?(_, _), do: false defp put_internal_transactions_indexed_at?(_, _), do: false
defp discard_blocks_for_recollated_transactions(repo, transactions, %{
timeout: timeout,
timestamps: %{updated_at: updated_at}
})
when is_list(transactions) do
ordered_block_hashes =
transactions
|> Enum.filter(fn %{block_hash: block_hash, old_block_hash: old_block_hash} ->
not is_nil(old_block_hash) and block_hash != old_block_hash
end)
|> MapSet.new(& &1.old_block_hash)
|> Enum.sort()
if Enum.empty?(ordered_block_hashes) do
{:ok, []}
else
query =
from(
block in Block,
where: block.hash in ^ordered_block_hashes,
update: [
set: [
consensus: false,
updated_at: ^updated_at
]
]
)
try do
{_, result} = repo.update_all(query, [], timeout: timeout)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, block_hashes: ordered_block_hashes}}
end
end
end
end end

@ -19,7 +19,7 @@ defmodule Explorer.Chain.InternalTransaction.Result do
{key, to_string(hash)} {key, to_string(hash)}
end end
defp entry_to_raw({"code", _} = entry), do: entry defp entry_to_raw({"code", code}), do: {"code", Data.to_string(code)}
defp entry_to_raw({key, decimal}) when key in ~w(gasUsed) do defp entry_to_raw({key, decimal}) when key in ~w(gasUsed) do
integer = integer =

@ -205,6 +205,11 @@ defmodule Explorer.Chain.Transaction do
field(:v, :decimal) field(:v, :decimal)
field(:value, Wei) field(:value, Wei)
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated
# in a different block. See: https://github.com/poanetwork/blockscout/issues/1911
field(:old_block_hash, Hash.Full)
timestamps() timestamps()
belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full) belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full)

@ -4,10 +4,11 @@ defmodule Explorer.PagingOptions do
number and index. number and index.
""" """
@type t :: %__MODULE__{key: key, page_size: page_size} @type t :: %__MODULE__{key: key, page_size: page_size, page_number: page_number}
@typep key :: any() @typep key :: any()
@typep page_size :: non_neg_integer() @typep page_size :: non_neg_integer()
@typep page_number :: pos_integer()
defstruct [:key, :page_size] defstruct [:key, :page_size, page_number: 1]
end end

@ -0,0 +1,124 @@
defmodule Explorer.Staking.EpochCounter do
@moduledoc """
Fetches current staking epoch number and the epoch end block number.
It subscribes to handle new blocks and conclude whether the epoch is over.
"""
use GenServer
alias Explorer.Chain.Events.Subscriber
alias Explorer.SmartContract.Reader
@table_name __MODULE__
@epoch_key "epoch_num"
@epoch_end_key "epoch_end_block"
@doc "Current staking epoch number"
def epoch_number do
if :ets.info(@table_name) != :undefined do
case :ets.lookup(@table_name, @epoch_key) do
[{_, epoch_num}] ->
epoch_num
_ ->
0
end
end
end
@doc "Block number on which will start new epoch"
def epoch_end_block do
if :ets.info(@table_name) != :undefined do
case :ets.lookup(@table_name, @epoch_end_key) do
[{_, epoch_end}] ->
epoch_end
_ ->
0
end
end
end
def start_link([]) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def init([]) do
:ets.new(@table_name, [
:set,
:named_table,
:public,
write_concurrency: true
])
Subscriber.to(:blocks, :realtime)
{:ok, [], {:continue, :epoch_info}}
end
def handle_continue(:epoch_info, state) do
fetch_epoch_info()
{:noreply, state}
end
@doc "Handles new blocks and decides to fetch new epoch info"
def handle_info({:chain_event, :blocks, :realtime, blocks}, state) do
new_block_number =
blocks
|> Enum.map(&Map.get(&1, :number, 0))
|> Enum.max(fn -> 0 end)
case :ets.lookup(@table_name, @epoch_end_key) do
[] ->
fetch_epoch_info()
[{_, epoch_end_block}] when epoch_end_block < new_block_number ->
fetch_epoch_info()
_ ->
:ok
end
{:noreply, state}
end
defp fetch_epoch_info do
with data <- get_epoch_info(),
{:ok, [epoch_num]} <- data["stakingEpoch"],
{:ok, [epoch_end_block]} <- data["stakingEpochEndBlock"] do
:ets.insert(@table_name, {@epoch_key, epoch_num})
:ets.insert(@table_name, {@epoch_end_key, epoch_end_block})
end
end
defp get_epoch_info do
contract_abi = abi("staking.json")
functions = ["stakingEpoch", "stakingEpochEndBlock"]
functions
|> Enum.map(fn function ->
%{
contract_address: staking_address(),
function_name: function,
args: []
}
end)
|> Reader.query_contracts(contract_abi)
|> Enum.zip(functions)
|> Enum.into(%{}, fn {response, function} ->
{function, response}
end)
end
defp staking_address do
Application.get_env(:explorer, __MODULE__, [])[:staking_contract_address]
end
# sobelow_skip ["Traversal"]
defp abi(file_name) do
:explorer
|> Application.app_dir("priv/contracts_abi/pos/#{file_name}")
|> File.read!()
|> Jason.decode!()
end
end

@ -29,6 +29,7 @@ defmodule Explorer.Staking.PoolsReader do
{:ok, [delegator_addresses]} <- data["poolDelegators"], {:ok, [delegator_addresses]} <- data["poolDelegators"],
delegators_count = Enum.count(delegator_addresses), delegators_count = Enum.count(delegator_addresses),
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], {:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"],
{:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"],
{:ok, [is_validator]} <- data["isValidator"], {:ok, [is_validator]} <- data["isValidator"],
{:ok, [was_validator_count]} <- data["validatorCounter"], {:ok, [was_validator_count]} <- data["validatorCounter"],
{:ok, [is_banned]} <- data["isValidatorBanned"], {:ok, [is_banned]} <- data["isValidatorBanned"],
@ -42,6 +43,7 @@ defmodule Explorer.Staking.PoolsReader do
is_active: is_active, is_active: is_active,
delegators_count: delegators_count, delegators_count: delegators_count,
staked_amount: staked_amount, staked_amount: staked_amount,
self_staked_amount: self_staked_amount,
is_validator: is_validator, is_validator: is_validator,
was_validator_count: was_validator_count, was_validator_count: was_validator_count,
is_banned: is_banned, is_banned: is_banned,
@ -77,14 +79,15 @@ defmodule Explorer.Staking.PoolsReader do
contract_abi = abi("staking.json") ++ abi("validators.json") contract_abi = abi("staking.json") ++ abi("validators.json")
methods = [ methods = [
{:staking, "isPoolActive", staking_address}, {:staking, "isPoolActive", [staking_address]},
{:staking, "poolDelegators", staking_address}, {:staking, "poolDelegators", [staking_address]},
{:staking, "stakeAmountTotalMinusOrderedWithdraw", staking_address}, {:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]},
{:validators, "isValidator", mining_address}, {:staking, "stakeAmountMinusOrderedWithdraw", [staking_address, staking_address]},
{:validators, "validatorCounter", mining_address}, {:validators, "isValidator", [mining_address]},
{:validators, "isValidatorBanned", mining_address}, {:validators, "validatorCounter", [mining_address]},
{:validators, "bannedUntil", mining_address}, {:validators, "isValidatorBanned", [mining_address]},
{:validators, "banCounter", mining_address} {:validators, "bannedUntil", [mining_address]},
{:validators, "banCounter", [mining_address]}
] ]
methods methods
@ -96,11 +99,11 @@ defmodule Explorer.Staking.PoolsReader do
end) end)
end end
defp format_request({contract_name, function_name, param}) do defp format_request({contract_name, function_name, params}) do
%{ %{
contract_address: contract(contract_name), contract_address: contract(contract_name),
function_name: function_name, function_name: function_name,
args: [param] args: params
} }
end end

File diff suppressed because it is too large Load Diff

@ -0,0 +1,12 @@
defmodule Explorer.Repo.Migrations.AddOldBlockHashForTransactions do
use Ecto.Migration
def change do
alter table(:transactions) do
# A transient field for deriving old block hash during transaction upserts.
# Used to force refetch of a block in case a transaction is re-collated
# in a different block. See: https://github.com/poanetwork/blockscout/issues/1911
add(:old_block_hash, :bytea, null: true)
end
end
end

@ -0,0 +1,11 @@
defmodule Explorer.Repo.Migrations.AddRefetchNeededToBlock do
use Ecto.Migration
def change do
alter table(:blocks) do
add(:refetch_needed, :boolean, default: false)
end
execute("UPDATE blocks SET refetch_needed = TRUE;", "")
end
end

@ -3,6 +3,14 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
alias Explorer.Chain.BlockNumberCache alias Explorer.Chain.BlockNumberCache
setup do
Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true)
on_exit(fn ->
Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: false)
end)
end
describe "max_number/1" do describe "max_number/1" do
test "returns max number" do test "returns max number" do
insert(:block, number: 5) insert(:block, number: 5)
@ -11,33 +19,6 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.max_number() == 5
end end
test "invalidates cache if period did pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
Process.sleep(2_000)
assert BlockNumberCache.max_number() == 10
assert BlockNumberCache.min_number() == 5
end
test "does not invalidate cache if period time did not pass" do
insert(:block, number: 5)
BlockNumberCache.setup(cache_period: 10_000)
assert BlockNumberCache.max_number() == 5
insert(:block, number: 10)
assert BlockNumberCache.max_number() == 5
end
end end
describe "min_number/1" do describe "min_number/1" do
@ -48,32 +29,31 @@ defmodule Explorer.Chain.BlockNumberCacheTest do
assert BlockNumberCache.max_number() == 2 assert BlockNumberCache.max_number() == 2
end end
end
test "invalidates cache" do describe "update/1" do
insert(:block, number: 5) test "updates max number" do
insert(:block, number: 2)
BlockNumberCache.setup(cache_period: 2_000)
assert BlockNumberCache.min_number() == 5 BlockNumberCache.setup()
insert(:block, number: 2) assert BlockNumberCache.max_number() == 2
Process.sleep(2_000) assert BlockNumberCache.update(3)
assert BlockNumberCache.min_number() == 2 assert BlockNumberCache.max_number() == 3
assert BlockNumberCache.max_number() == 5
end end
test "does not invalidate cache if period time did not pass" do test "updates min number" do
insert(:block, number: 5) insert(:block, number: 2)
BlockNumberCache.setup(cache_period: 10_000) BlockNumberCache.setup()
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.min_number() == 2
insert(:block, number: 2) assert BlockNumberCache.update(1)
assert BlockNumberCache.max_number() == 5 assert BlockNumberCache.min_number() == 1
end end
end end
end end

@ -6,7 +6,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
import Explorer.Chain.Import.RunnerCase, only: [insert_address_with_token_balances: 1, update_holder_count!: 2] import Explorer.Chain.Import.RunnerCase, only: [insert_address_with_token_balances: 1, update_holder_count!: 2]
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.Import.Runner.{Blocks, Transaction} alias Explorer.Chain.Import.Runner.{Blocks, Transactions}
alias Explorer.Chain.{Address, Block, Transaction} alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Repo alias Explorer.Repo
@ -283,6 +283,29 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
insert_block(new_block1, options) insert_block(new_block1, options)
assert Chain.missing_block_number_ranges(range) == [] assert Chain.missing_block_number_ranges(range) == []
end end
# Regression test for https://github.com/poanetwork/blockscout/issues/1911
test "forces block refetch if transaction is re-collated in a different block",
%{consensus_block: %Block{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do
new_block1 = params_for(:block, miner_hash: miner_hash, parent_hash: block_hash, number: block_number + 1)
new_block2 = params_for(:block, miner_hash: miner_hash, parent_hash: new_block1.hash, number: block_number + 2)
range = block_number..(block_number + 2)
insert_block(new_block1, options)
insert_block(new_block2, options)
assert Chain.missing_block_number_ranges(range) == []
trans_hash = transaction_hash()
transaction1 = transaction_params_with_block([hash: trans_hash], new_block1)
insert_transaction(transaction1, options)
assert Chain.missing_block_number_ranges(range) == []
transaction2 = transaction_params_with_block([hash: trans_hash], new_block2)
insert_transaction(transaction2, options)
assert Chain.missing_block_number_ranges(range) == [(block_number + 1)..(block_number + 1)]
end
end end
defp insert_block(block_params, options) do defp insert_block(block_params, options) do
@ -293,6 +316,28 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do
|> Repo.transaction() |> Repo.transaction()
end end
defp transaction_params_with_block(transaction_params, block_params) do
params_for(:transaction, transaction_params)
|> Map.merge(%{
block_hash: block_params.hash,
block_number: block_params.number,
cumulative_gas_used: 50_000,
error: nil,
gas_used: 50_000,
index: 0,
from_address_hash: insert(:address).hash
})
end
defp insert_transaction(transaction_params, options) do
%Ecto.Changeset{valid?: true, changes: transaction_changes} =
Transaction.changeset(%Transaction{}, transaction_params)
Multi.new()
|> Transactions.run([transaction_changes], options)
|> Repo.transaction()
end
defp count(schema) do defp count(schema) do
Repo.one!(select(schema, fragment("COUNT(*)"))) Repo.one!(select(schema, fragment("COUNT(*)")))
end end

@ -1,85 +1,29 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do
use Explorer.DataCase use Explorer.DataCase
import Explorer.Factory
alias Ecto.Multi alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPools alias Explorer.Chain.Import.Runner.StakingPools
describe "run/1" do describe "run/1" do
test "insert new pools list" do test "insert new pools list" do
pools = [ pools = [pool1, pool2, pool3, pool4] = build_list(4, :staking_pool)
%{
address_hash: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>
},
metadata: %{
banned_unitil: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>
},
retries_count: 1,
staked_amount: 0,
was_banned_count: 0,
was_validator_count: 1
},
name: "anonymous",
primary: true
},
%{
address_hash: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<170, 148, 182, 135, 211, 249, 85, 42, 69, 59, 129, 178, 131, 76, 165, 55, 120, 152, 13, 192>>
},
metadata: %{
banned_unitil: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<117, 223, 66, 56, 58, 254, 107, 245, 25, 74, 168, 250, 14, 155, 61, 95, 158, 134, 148, 65>>
},
retries_count: 1,
staked_amount: 0,
was_banned_count: 0,
was_validator_count: 1
},
name: "anonymous",
primary: true
},
%{
address_hash: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<49, 44, 35, 14, 125, 109, 176, 82, 36, 246, 2, 8, 166, 86, 227, 84, 28, 92, 66, 186>>
},
metadata: %{
banned_unitil: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address: %Explorer.Chain.Hash{
byte_count: 20,
bytes: <<82, 45, 243, 150, 174, 112, 160, 88, 189, 105, 119, 132, 8, 99, 15, 219, 2, 51, 137, 178>>
},
retries_count: 1,
staked_amount: 0,
was_banned_count: 0,
was_validator_count: 1
},
name: "anonymous",
primary: true
}
]
assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) assert {:ok, %{insert_staking_pools: list}} = run_changes(pools)
assert Enum.count(list) == Enum.count(pools) assert Enum.count(list) == Enum.count(pools)
saved_list =
Explorer.Chain.Address.Name
|> Repo.all()
|> Enum.reduce(%{}, fn pool, acc ->
Map.put(acc, pool.address_hash, pool)
end)
assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool4.address_hash].metadata["staked_ratio"] == 0.25
end end
end end

@ -1,7 +1,7 @@
defmodule Explorer.Chain.InternalTransactionTest do defmodule Explorer.Chain.InternalTransactionTest do
use Explorer.DataCase use Explorer.DataCase
alias Explorer.Chain.{InternalTransaction, Wei} alias Explorer.Chain.{Data, InternalTransaction, Wei}
alias Explorer.Factory alias Explorer.Factory
import EthereumJSONRPC, only: [integer_to_quantity: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1]
@ -173,7 +173,7 @@ defmodule Explorer.Chain.InternalTransactionTest do
end end
test "it correctly formats a create" do test "it correctly formats a create" do
contract_code = Factory.contract_code_info().bytecode {:ok, contract_code} = Data.cast(Factory.contract_code_info().bytecode)
contract_address = Factory.address_hash() contract_address = Factory.address_hash()
from = Factory.address_hash() from = Factory.address_hash()
gas = 50_000 gas = 50_000

@ -50,6 +50,51 @@ defmodule Explorer.ChainTest do
end end
end end
describe "address_to_logs/2" do
test "fetches logs" do
address = insert(:address)
transaction1 =
:transaction
|> insert(to_address: address)
|> with_block()
insert(:log, transaction: transaction1, index: 1, address: address)
transaction2 =
:transaction
|> insert(from_address: address)
|> with_block()
insert(:log, transaction: transaction2, index: 2, address: address)
assert Enum.count(Chain.address_to_logs(address)) == 2
end
test "paginates logs" do
address = insert(:address)
transaction =
:transaction
|> insert(to_address: address)
|> with_block()
log1 = insert(:log, transaction: transaction, index: 1, address: address)
2..51
|> Enum.map(fn index -> insert(:log, transaction: transaction, index: index, address: address) end)
|> Enum.map(& &1.index)
paging_options1 = %PagingOptions{page_size: 1}
[_log] = Chain.address_to_logs(address, paging_options: paging_options1)
paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}}
assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50
end
end
describe "address_to_transactions_with_rewards/2" do describe "address_to_transactions_with_rewards/2" do
test "without transactions" do test "without transactions" do
address = insert(:address) address = insert(:address)
@ -3903,4 +3948,59 @@ defmodule Explorer.ChainTest do
refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments) refute Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments)
end end
end end
describe "staking_pools/3" do
test "validators staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:validator, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
test "active staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:active, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false})
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:inactive, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
end
end
describe "staking_pools_count/1" do
test "validators staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
assert Chain.staking_pools_count(:validator) == 1
end
test "active staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
assert Chain.staking_pools_count(:active) == 1
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
assert Chain.staking_pools_count(:inactive) == 1
end
end
end end

@ -0,0 +1,97 @@
defmodule Explorer.Staking.EpochCounterTest do
use ExUnit.Case, async: false
import Mox
alias Explorer.Staking.EpochCounter
alias Explorer.Chain.Events.Publisher
setup :verify_on_exit!
setup :set_mox_global
test "when disabled, it returns nil" do
assert EpochCounter.epoch_number() == nil
assert EpochCounter.epoch_end_block() == nil
end
test "fetch epoch data" do
set_mox(10, 880)
Application.put_env(:explorer, EpochCounter, enabled: true)
start_supervised!(EpochCounter)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 10
assert EpochCounter.epoch_end_block() == 880
end
test "fetch new epoch data" do
set_mox(10, 880)
Application.put_env(:explorer, EpochCounter, enabled: true)
start_supervised!(EpochCounter)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 10
assert EpochCounter.epoch_end_block() == 880
event_type = :blocks
broadcast_type = :realtime
event_data = [%Explorer.Chain.Block{number: 881}]
set_mox(11, 960)
Publisher.broadcast([{event_type, event_data}], broadcast_type)
Process.sleep(1_000)
assert EpochCounter.epoch_number() == 11
assert EpochCounter.epoch_end_block() == 960
end
defp set_mox(epoch_num, end_block_num) do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [
%{
id: 0,
jsonrpc: "2.0",
method: "eth_call",
params: _
},
%{
id: 1,
jsonrpc: "2.0",
method: "eth_call",
params: _
}
],
_options ->
{:ok,
[
%{
id: 0,
jsonrpc: "2.0",
result: encode_num(epoch_num)
},
%{
id: 1,
jsonrpc: "2.0",
result: encode_num(end_block_num)
}
]}
end
)
end
defp encode_num(num) do
selector = %ABI.FunctionSelector{function: nil, types: [uint: 32]}
encoded_num =
[num]
|> ABI.TypeEncoder.encode(selector)
|> Base.encode16(case: :lower)
"0x" <> encoded_num
end
end

@ -1,6 +1,5 @@
defmodule Explorer.Token.PoolsReaderTest do defmodule Explorer.Token.PoolsReaderTest do
use EthereumJSONRPC.Case use EthereumJSONRPC.Case
use Explorer.DataCase
alias Explorer.Staking.PoolsReader alias Explorer.Staking.PoolsReader
@ -44,6 +43,7 @@ defmodule Explorer.Token.PoolsReaderTest do
mining_address: mining_address:
<<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>,
staked_amount: 0, staked_amount: 0,
self_staked_amount: 0,
staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>,
was_banned_count: 0, was_banned_count: 0,
was_validator_count: 2 was_validator_count: 2
@ -162,6 +162,25 @@ defmodule Explorer.Token.PoolsReaderTest do
result: "0x0000000000000000000000000000000000000000000000000000000000000000" result: "0x0000000000000000000000000000000000000000000000000000000000000000"
} }
# stakeAmountMinusOrderedWithdraw
%{
id: id,
jsonrpc: "2.0",
method: "eth_call",
params: [
%{
data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator # isValidator
%{ %{
id: id, id: id,

@ -39,7 +39,7 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end end
Explorer.Chain.BlockNumberCache.setup(cache_period: 0) Explorer.Chain.BlockNumberCache.setup()
:ok :ok
end end

@ -609,4 +609,24 @@ defmodule Explorer.Factory do
user: build(:user) user: build(:user)
} }
end end
def staking_pool_factory do
%{
address_hash: address_hash(),
metadata: %{
banned_unitil: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address: address_hash(),
retries_count: 1,
staked_amount: 25,
was_banned_count: 0,
was_validator_count: 1
},
name: "anonymous",
primary: true
}
end
end end

@ -92,6 +92,7 @@ After all deployed instances get all needed data, these fetchers should be depre
- `uncataloged_token_transfers`: extracts token transfers from logs, which previously weren't parsed due to unknown format - `uncataloged_token_transfers`: extracts token transfers from logs, which previously weren't parsed due to unknown format
- `uncles_without_index`: adds previously unfetched `index` field for unfetched blocks in `block_second_degree_relations` - `uncles_without_index`: adds previously unfetched `index` field for unfetched blocks in `block_second_degree_relations`
- `blocks_transactions_mismatch`: refetches each block once and revokes consensus to those whose transaction number mismatches with the number currently stored. This is meant to force the correction of a race condition that caused successfully fetched transactions to be overwritten by a following non-consensus block: [#1911](https://github.com/poanetwork/blockscout/issues/1911).
## Memory Usage ## Memory Usage

@ -11,7 +11,7 @@ defmodule Indexer.Block.Fetcher do
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} alias Explorer.Chain.{Address, Block, BlockNumberCache, Hash, Import, Transaction}
alias Indexer.Block.Fetcher.Receipts alias Indexer.Block.Fetcher.Receipts
alias Indexer.Fetcher.{ alias Indexer.Fetcher.{
@ -171,13 +171,23 @@ defmodule Indexer.Block.Fetcher do
transactions: %{params: transactions_with_receipts} transactions: %{params: transactions_with_receipts}
} }
) do ) do
{:ok, %{inserted: inserted, errors: blocks_errors}} result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks])
result
else else
{step, {:error, reason}} -> {:error, {step, reason}} {step, {:error, reason}} -> {:error, {step, reason}}
{:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}} {:import, {:error, step, failed_value, changes_so_far}} -> {:error, {step, failed_value, changes_so_far}}
end end
end end
defp update_block_cache(blocks) do
max_block = Enum.max_by(blocks, fn block -> block.number end)
min_block = Enum.min_by(blocks, fn block -> block.number end)
BlockNumberCache.update(max_block.number)
BlockNumberCache.update(min_block.number)
end
def import( def import(
%__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state,
options options

@ -125,6 +125,7 @@ defmodule Indexer.Fetcher.StakingPools do
pool pool
|> Map.delete(:staking_address) |> Map.delete(:staking_address)
|> Map.put(:mining_address, mining_address) |> Map.put(:mining_address, mining_address)
|> Map.put(:is_pool, true)
%{ %{
name: "anonymous", name: "anonymous",

@ -24,6 +24,7 @@ defmodule Indexer.Supervisor do
} }
alias Indexer.Temporary.{ alias Indexer.Temporary.{
BlocksTransactionsMismatch,
UncatalogedTokenTransfers, UncatalogedTokenTransfers,
UnclesWithoutIndex UnclesWithoutIndex
} }
@ -124,6 +125,8 @@ defmodule Indexer.Supervisor do
# Temporary workers # Temporary workers
{UncatalogedTokenTransfers.Supervisor, [[]]}, {UncatalogedTokenTransfers.Supervisor, [[]]},
{UnclesWithoutIndex.Supervisor, {UnclesWithoutIndex.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]},
{BlocksTransactionsMismatch.Supervisor,
[[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]} [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}
], ],
strategy: :one_for_one strategy: :one_for_one

@ -0,0 +1,115 @@
defmodule Indexer.Temporary.BlocksTransactionsMismatch do
@moduledoc """
Fetches `consensus` `t:Explorer.Chain.Block.t/0` and compares their transaction
number against a node, to revoke `consensus` on mismatch.
This is meant to fix incorrectly strored transactions that happened as a result
of a race condition due to the asynchronicity of indexer's components.
"""
use Indexer.Fetcher
require Logger
import Ecto.Query
alias Ecto.Multi
alias EthereumJSONRPC.Blocks
alias Explorer.Chain.Block
alias Explorer.Repo
alias Indexer.BufferedTask
@behaviour BufferedTask
@defaults [
flush_interval: :timer.seconds(3),
max_batch_size: 10,
max_concurrency: 4,
task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor,
metadata: [fetcher: :blocks_transactions_mismatch]
]
@doc false
def child_spec([init_options, gen_server_options]) when is_list(init_options) do
{state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments)
unless state do
raise ArgumentError,
":json_rpc_named_arguments must be provided to `#{__MODULE__}.child_spec " <>
"to allow for json_rpc calls when running."
end
merged_init_options =
@defaults
|> Keyword.merge(mergeable_init_options)
|> Keyword.put(:state, state)
Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_options}, gen_server_options]}, id: __MODULE__)
end
@impl BufferedTask
def init(initial, reducer, _) do
query =
from(block in Block,
join: transactions in assoc(block, :transactions),
where: block.consensus and block.refetch_needed,
group_by: block.hash,
select: {block, count(transactions.hash)}
)
{:ok, final} = Repo.stream_reduce(query, initial, &reducer.(&1, &2))
final
end
@impl BufferedTask
def run(blocks_data, json_rpc_named_arguments) do
hashes = Enum.map(blocks_data, fn {block, _trans_num} -> block.hash end)
Logger.debug("fetching")
case EthereumJSONRPC.fetch_blocks_by_hash(hashes, json_rpc_named_arguments) do
{:ok, blocks} ->
run_blocks(blocks, blocks_data)
{:error, reason} ->
Logger.error(fn -> ["failed to fetch: ", inspect(reason)] end)
{:retry, blocks_data}
end
end
defp run_blocks(%Blocks{blocks_params: []}, blocks_data), do: {:retry, blocks_data}
defp run_blocks(
%Blocks{transactions_params: transactions_params},
blocks_data
) do
found_blocks_map =
transactions_params
|> Enum.group_by(&Map.fetch!(&1, :block_hash))
|> Map.new(fn {block_hash, trans_lst} -> {block_hash, Enum.count(trans_lst)} end)
{found_blocks_data, missing_blocks_data} =
Enum.split_with(blocks_data, fn {block, _trans_num} ->
Map.has_key?(found_blocks_map, to_string(block.hash))
end)
{:ok, _} =
found_blocks_data
|> Enum.reduce(Multi.new(), fn {block, trans_num}, multi ->
changes = %{
refetch_needed: false,
consensus: found_blocks_map[to_string(block.hash)] == trans_num
}
Multi.update(multi, block.hash, Block.changeset(block, changes))
end)
|> Repo.transaction()
if Enum.empty?(missing_blocks_data) do
:ok
else
{:retry, missing_blocks_data}
end
end
end

@ -129,6 +129,25 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
result: "0x0000000000000000000000000000000000000000000000000000000000000000" result: "0x0000000000000000000000000000000000000000000000000000000000000000"
} }
# stakeAmountMinusOrderedWithdraw
%{
id: id,
jsonrpc: "2.0",
method: "eth_call",
params: [
%{
data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# isValidator # isValidator
%{ %{
id: id, id: id,

Loading…
Cancel
Save