diff --git a/CHANGELOG.md b/CHANGELOG.md index f76fa8f8bacf..1342b69ef710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,205 @@ ## Current ### Features +- [#5105](https://github.com/blockscout/blockscout/pull/5105) - Redesign token page +- [#4690](https://github.com/blockscout/blockscout/pull/4690) - Improve pagination: introduce pagination with random access to pages; Integrate it to the Transactions List page + +### Fixes +- [#5169](https://github.com/blockscout/blockscout/pull/5169) - Fix several UI bugs; Add tooltip to the prev/next block buttons +- [#5184](https://github.com/blockscout/blockscout/pull/5184) - eth_call method: remove from param from the request, if it is null +- [#5172](https://github.com/blockscout/blockscout/pull/5172), [#5182](https://github.com/blockscout/blockscout/pull/5182) - Reduced the size of js bundles +- [#5166](https://github.com/blockscout/blockscout/pull/5166) - Fix contracts verification bugs +- [#5160](https://github.com/blockscout/blockscout/pull/5160) - Fix blocks validated hint +- [#5155](https://github.com/blockscout/blockscout/pull/5155) - Fix get_implementation_abi_from_proxy/2 implementation +- [#5154](https://github.com/blockscout/blockscout/pull/5154) - Fix token counters bug + +### Chore +- [#5171](https://github.com/blockscout/blockscout/pull/5171) - Replace lodash NPM package with tiny lodash modules +- [#5170](https://github.com/blockscout/blockscout/pull/5170) - Token price row name fix +- [#5153](https://github.com/blockscout/blockscout/pull/5153) - Discord link instead of Gitter +- [#5142](https://github.com/blockscout/blockscout/pull/5142) - Updated some outdated npm packages +- [#5140](https://github.com/blockscout/blockscout/pull/5140) - Babel minor and core-js major updates +- [#5139](https://github.com/blockscout/blockscout/pull/5139) - Eslint major update +- [#5138](https://github.com/blockscout/blockscout/pull/5138) - Webpack minor update +- [#5119](https://github.com/blockscout/blockscout/pull/5119) - Inventory controller refactoring +- [#5118](https://github.com/blockscout/blockscout/pull/5118) - Fix top navigation template + + +## 4.1.1-beta + +### Features +- [#5090](https://github.com/blockscout/blockscout/pull/5090) - Allotted rate limit by IP +- [#5080](https://github.com/blockscout/blockscout/pull/5080) - Allotted rate limit by a global API key + +### Fixes +- [#5085](https://github.com/blockscout/blockscout/pull/5085) - Fix wallet style +- [#5088](https://github.com/blockscout/blockscout/pull/5088) - Store address transactions/token transfers in the DB +- [#5071](https://github.com/blockscout/blockscout/pull/5071) - Fix write page contract tuple input +- [#5066](https://github.com/blockscout/blockscout/pull/5066) - Fix read contract page bug +- [#5034](https://github.com/blockscout/blockscout/pull/5034) - Fix broken functions input at transaction page +- [#5025](https://github.com/blockscout/blockscout/pull/5025) - Add standard input JSON files validation +- [#5051](https://github.com/blockscout/blockscout/pull/5051) - Fix 500 response when ABI method was parsed as nil + +### Chore +- [#5092](https://github.com/blockscout/blockscout/pull/5092) - Resolve vulnerable follow-redirects npm dep in ./apps/explorer +- [#5091](https://github.com/blockscout/blockscout/pull/5091) - Refactor search page template +- [#5081](https://github.com/blockscout/blockscout/pull/5081) - Add internal transactions fetcher disabled? config parameter +- [#5063](https://github.com/blockscout/blockscout/pull/5063) - Resolve moderate NPM vulnerabilities with npm audit tool +- [#5053](https://github.com/blockscout/blockscout/pull/5053) - Update ex_keccak lib + + +## 4.1.0-beta + +### Features +- [#5030](https://github.com/blockscout/blockscout/pull/5030) - API rate limiting +- [#4924](https://github.com/blockscout/blockscout/pull/4924) - Add daily bytecode verifcation to prevent metamorphic contracts vulnerablity +- [#4908](https://github.com/blockscout/blockscout/pull/4908) - Add verification via standard JSON input +- [#5004](https://github.com/blockscout/blockscout/pull/5004) - Add ability to set up a separate DB endpoint for the API endpoints +- [#4989](https://github.com/blockscout/blockscout/pull/4989), [#4991](https://github.com/blockscout/blockscout/pull/4991) - Bridged tokens list API endpoint +- [#4931](https://github.com/blockscout/blockscout/pull/4931) - Web3 modal with Wallet Connect for Write contract page and Staking Dapp + +### Fixes +- [#5045](https://github.com/blockscout/blockscout/pull/5045) - Contracts interaction improvements +- [#5032](https://github.com/blockscout/blockscout/pull/5032) - Fix token transfer csv export +- [#5020](https://github.com/blockscout/blockscout/pull/5020) - Token instance image display imrovement +- [#5019](https://github.com/blockscout/blockscout/pull/5019) - Fix fetch_last_token_balance function termination +- [#5011](https://github.com/blockscout/blockscout/pull/5011) - Fix `0x0` implementation address +- [#5008](https://github.com/blockscout/blockscout/pull/5008) - Extend decimals cap in format_according_to_decimals up to 24 +- [#5005](https://github.com/blockscout/blockscout/pull/5005) - Fix falsy appearance `Connection Lost` warning on reload/switch page +- [#5003](https://github.com/blockscout/blockscout/pull/5003) - API router refactoring +- [#4992](https://github.com/blockscout/blockscout/pull/4992) - Fix `type` field in transactions after enabling 1559 +- [#4979](https://github.com/blockscout/blockscout/pull/4979), [#4993](https://github.com/blockscout/blockscout/pull/4993) - Store total gas_used in addresses table +- [#4977](https://github.com/blockscout/blockscout/pull/4977) - Export token transfers on address: include transfers on contract itself +- [#4976](https://github.com/blockscout/blockscout/pull/4976) - Handle :econnrefused in pending transactions fetcher +- [#4965](https://github.com/blockscout/blockscout/pull/4965) - Fix search field appearance on medium size screens +- [#4945](https://github.com/blockscout/blockscout/pull/4945) - Fix `Verify & Publish` button link +- [#4938](https://github.com/blockscout/blockscout/pull/4938) - Fix displaying of nested arrays for contracts read +- [#4888](https://github.com/blockscout/blockscout/pull/4888) - Fix fetch_top_tokens method: add nulls last for token holders desc order +- [#4867](https://github.com/blockscout/blockscout/pull/4867) - Fix bug in quering contracts method and improve contracts interactions + +### Chore +- [#5047](https://github.com/blockscout/blockscout/pull/5047) - At contract write use wei precision +- [#5023](https://github.com/blockscout/blockscout/pull/5023) - Capability to leave an empty logo +- [#5018](https://github.com/blockscout/blockscout/pull/5018) - Resolve npm vulnerabilities via npm audix fix +- [#5014](https://github.com/blockscout/blockscout/pull/5014) - Separate FIRST_BLOCK and TRACE_FIRST_BLOCK option for blocks import and tracing methods +- [#4998](https://github.com/blockscout/blockscout/pull/4998) - API endpoints logger +- [#4983](https://github.com/blockscout/blockscout/pull/4983), [#5038](https://github.com/blockscout/blockscout/pull/5038) - Fix contract verification tests +- [#4861](https://github.com/blockscout/blockscout/pull/4861) - Add separate column for token icons + + +## 4.0.0-beta + +### Features +- [#4807](https://github.com/blockscout/blockscout/pull/4807) - Added support for BeaconProxy pattern +- [#4777](https://github.com/blockscout/blockscout/pull/4777), [#4791](https://github.com/blockscout/blockscout/pull/4791), [#4799](https://github.com/blockscout/blockscout/pull/4799), [#4847](https://github.com/blockscout/blockscout/pull/4847) - Added decoding revert reason +- [#4776](https://github.com/blockscout/blockscout/pull/4776) - Added view for unsuccessfully fetched values from read functions +- [#4761](https://github.com/blockscout/blockscout/pull/4761) - ERC-1155 support +- [#4739](https://github.com/blockscout/blockscout/pull/4739) - Improve logs and inputs decoding +- [#4747](https://github.com/blockscout/blockscout/pull/4747) - Advanced CSV export +- [#4745](https://github.com/blockscout/blockscout/pull/4745) - Vyper contracts verification +- [#4699](https://github.com/blockscout/blockscout/pull/4699), [#4793](https://github.com/blockscout/blockscout/pull/4793), [#4820](https://github.com/blockscout/blockscout/pull/4820), [#4827](https://github.com/blockscout/blockscout/pull/4827) - Address page facelifting +- [#4667](https://github.com/blockscout/blockscout/pull/4667) - Transaction Page: Add expand/collapse button for long contract method data +- [#4641](https://github.com/blockscout/blockscout/pull/4641), [#4733](https://github.com/blockscout/blockscout/pull/4733) - Improve Read Contract page logic +- [#4660](https://github.com/blockscout/blockscout/pull/4660) - Save Sourcify path instead of filename +- [#4656](https://github.com/blockscout/blockscout/pull/4656) - Open in Tenderly button +- [#4655](https://github.com/blockscout/blockscout/pull/4655), [#4676](https://github.com/blockscout/blockscout/pull/4676) - EIP-3091 support +- [#4621](https://github.com/blockscout/blockscout/pull/4621) - Add beacon contract address slot for proxy +- [#4625](https://github.com/blockscout/blockscout/pull/4625) - Contract address page: Add implementation link to the overview of proxy contracts +- [#4624](https://github.com/blockscout/blockscout/pull/4624) - Support HTML tags in alert message +- [#4608](https://github.com/blockscout/blockscout/pull/4608), [#4622](https://github.com/blockscout/blockscout/pull/4622) - Block Details page: Improved style of transactions button +- [#4596](https://github.com/blockscout/blockscout/pull/4596), [#4681](https://github.com/blockscout/blockscout/pull/4681), [#4693](https://github.com/blockscout/blockscout/pull/4693) - Display token icon for bridged with Mainnet tokens or identicons for other tokens +- [#4520](https://github.com/blockscout/blockscout/pull/4520) - Add support for EIP-1559 +- [#4593](https://github.com/blockscout/blockscout/pull/4593) - Add status in `Position` pane for txs have no block +- [#4579](https://github.com/blockscout/blockscout/pull/4579) - Write contract page: Resize inputs; Improve multiplier selector + +### Fixes +- [#4857](https://github.com/blockscout/blockscout/pull/4857) - Fix `tx/raw-trace` Internal Server Error +- [#4854](https://github.com/blockscout/blockscout/pull/4854) - Fix infinite gas usage count loading +- [#4853](https://github.com/blockscout/blockscout/pull/4853) - Allow custom optimizations runs for contract verifications via API +- [#4840](https://github.com/blockscout/blockscout/pull/4840) - Replace Enum.dedup with Enum.uniq where actually uniq items are expected +- [#4835](https://github.com/blockscout/blockscout/pull/4835) - Fix view for broken token icons +- [#4830](https://github.com/blockscout/blockscout/pull/4830) - Speed up txs per day chart data collection +- [#4818](https://github.com/blockscout/blockscout/pull/4818) - Fix for extract_omni_bridged_token_metadata_wrapper method +- [#4812](https://github.com/blockscout/blockscout/pull/4812), [#4815](https://github.com/blockscout/blockscout/pull/4815) - Check if exists custom_cap property of extended token object before access it +- [#4810](https://github.com/blockscout/blockscout/pull/4810) - Show `nil` block.size as `N/A bytes` +- [#4806](https://github.com/blockscout/blockscout/pull/4806) - Get token type for token balance update if it is empty +- [#4802](https://github.com/blockscout/blockscout/pull/4802) - Fix floating tooltip on the main page +- [#4801](https://github.com/blockscout/blockscout/pull/4801) - Added clauses and tests for get_total_staked_and_ordered/1 +- [#4798](https://github.com/blockscout/blockscout/pull/4798) - Token instance View contract icon Safari fix +- [#4796](https://github.com/blockscout/blockscout/pull/4796) - Fix nil.timestamp issue +- [#4764](https://github.com/blockscout/blockscout/pull/4764) - Add cleaning of substrings of `require` messages from parsed constructor arguments +- [#4778](https://github.com/blockscout/blockscout/pull/4778) - Migrate :optimization_runs field type: `int4 -> int8` in `smart_contracts` table +- [#4768](https://github.com/blockscout/blockscout/pull/4768) - Block Details page: handle zero division +- [#4751](https://github.com/blockscout/blockscout/pull/4751) - Change text and link for `trade STAKE` button +- [#4746](https://github.com/blockscout/blockscout/pull/4746) - Fix comparison of decimal value +- [#4711](https://github.com/blockscout/blockscout/pull/4711) - Add trimming to the contract functions inputs +- [#4729](https://github.com/blockscout/blockscout/pull/4729) - Fix bugs with fees in cases of txs with `gas price = 0` +- [#4725](https://github.com/blockscout/blockscout/pull/4725) - Fix hardcoded coin name on transaction's and block's page +- [#4724](https://github.com/blockscout/blockscout/pull/4724), [#4842](https://github.com/blockscout/blockscout/pull/4841) - Sanitizer of "empty" blocks +- [#4717](https://github.com/blockscout/blockscout/pull/4717) - Contract verification fix: check only success creation tx +- [#4713](https://github.com/blockscout/blockscout/pull/4713) - Search input field: sanitize input +- [#4703](https://github.com/blockscout/blockscout/pull/4703) - Block Details page: Fix pagination on the Transactions tab +- [#4686](https://github.com/blockscout/blockscout/pull/4686) - Block page: check gas limit value before division +- [#4678](https://github.com/blockscout/blockscout/pull/4678) - Internal transactions indexer: fix issue of some pending transactions never become confirmed +- [#4668](https://github.com/blockscout/blockscout/pull/4668) - Fix css for dark theme +- [#4654](https://github.com/blockscout/blockscout/pull/4654) - AddressView: Change `@burn_address` to string `0x0000000000000000000000000000000000000000` +- [#4626](https://github.com/blockscout/blockscout/pull/4626) - Refine view of popup for reverted tx +- [#4640](https://github.com/blockscout/blockscout/pull/4640) - Token page: fixes in mobile view +- [#4612](https://github.com/blockscout/blockscout/pull/4612) - Hide error selector in the contract's functions list +- [#4615](https://github.com/blockscout/blockscout/pull/4615) - Fix broken style for `View more transfers` button +- [#4592](https://github.com/blockscout/blockscout/pull/4592) - Add `type` field for `receive` and `fallback` entities of a Smart Contract +- [#4601](https://github.com/blockscout/blockscout/pull/4601) - Fix endless Fetching tokens... message on empty addresses +- [#4591](https://github.com/blockscout/blockscout/pull/4591) - Add step and min value for txValue input field +- [#4589](https://github.com/blockscout/blockscout/pull/4589) - Fix solid outputs on contract read page +- [#4586](https://github.com/blockscout/blockscout/pull/4586) - Fix floating tooltips on the token transfer family blocks +- [#4587](https://github.com/blockscout/blockscout/pull/4587) - Enable navbar menu on Search results page +- [#4582](https://github.com/blockscout/blockscout/pull/4582) - Fix NaN input on write contract page + +### Chore +- [#4876](https://github.com/blockscout/blockscout/pull/4876) - Add missing columns updates when INSERT ... ON CONFLICT DO UPDATE ... happens +- [#4872](https://github.com/blockscout/blockscout/pull/4872) - Set explicit ascending order by hash in acquire transactions query of internal transactions import +- [#4871](https://github.com/blockscout/blockscout/pull/4871) - Remove cumulative gas used update duplicate +- [#4860](https://github.com/blockscout/blockscout/pull/4860) - Node 16 support +- [#4828](https://github.com/blockscout/blockscout/pull/4828) - Logging for txs/day chart +- [#4823](https://github.com/blockscout/blockscout/pull/4823) - Various error handlers with unresponsive JSON RPC endpoint +- [#4821](https://github.com/blockscout/blockscout/pull/4821) - Block Details page: Remove crossing at the Burnt Fee line +- [#4819](https://github.com/blockscout/blockscout/pull/4819) - Add config for GasUsage Cache +- [#4781](https://github.com/blockscout/blockscout/pull/4781) - PGAnalyze index suggestions +- [#4735](https://github.com/blockscout/blockscout/pull/4735) - Code clean up: Remove clauses for outdated ganache bugs +- [#4726](https://github.com/blockscout/blockscout/pull/4726) - Update chart.js +- [#4707](https://github.com/blockscout/blockscout/pull/4707) - Top navigation: Move Accounts tab to Tokens +- [#4704](https://github.com/blockscout/blockscout/pull/4704) - Update to Erlang/OTP 24 +- [#4682](https://github.com/blockscout/blockscout/pull/4682) - Update all possible outdated mix dependencies +- [#4663](https://github.com/blockscout/blockscout/pull/4663) - Migrate to Elixir 1.12.x +- [#4661](https://github.com/blockscout/blockscout/pull/4661) - Update NPM packages to resolve vulnerabilities +- [#4649](https://github.com/blockscout/blockscout/pull/4649) - 1559 Transaction Page: Convert Burnt Fee to ether and add price in USD +- [#4646](https://github.com/blockscout/blockscout/pull/4646) - Transaction page: Rename burned to burnt +- [#4611](https://github.com/blockscout/blockscout/pull/4611) - Ability to hide miner in block views + + +## 3.7.3-beta + +### Features +- [#4569](https://github.com/blockscout/blockscout/pull/4569) - Smart-Contract: remove comment with the submission date +- [#4568](https://github.com/blockscout/blockscout/pull/4568) - TX page: Token transfer and minting section improvements +- [#4540](https://github.com/blockscout/blockscout/pull/4540) - Allign copy buttons for `Block Details` and `Transaction Details` pages +- [#4528](https://github.com/blockscout/blockscout/pull/4528) - Block Details page: rework view +- [#4531](https://github.com/blockscout/blockscout/pull/4531) - Add Arbitrum support +- [#4524](https://github.com/blockscout/blockscout/pull/4524) - Add index position of transaction in the block +- [#4489](https://github.com/blockscout/blockscout/pull/4489) - Search results page - [#4475](https://github.com/blockscout/blockscout/pull/4475) - Tx page facelifting -- [#4452](https://github.com/blockscout/blockscout/pull/4452) - Add names for smart-conrtact's function response +- [#4452](https://github.com/blockscout/blockscout/pull/4452) - Add names for smart-contract's function response ### Fixes -- [#4494](https://github.com/blockscout/blockscout/pull/4494) - Fix empty created_contract_address in transaction +- [#4553](https://github.com/blockscout/blockscout/pull/4553) - Indexer performance update: skip genesis block in requesting of trace_block API endpoint +- [#4546](https://github.com/blockscout/blockscout/pull/4546) - Indexer performance update: async get block rewards +- [#4544](https://github.com/blockscout/blockscout/pull/4544) - Indexer performance update: Add skip_metadata flag for token if indexer failed to get any of [name, symbol, decimals, totalSupply] +- [#4542](https://github.com/blockscout/blockscout/pull/4542) - Indexer performance update: Deduplicate tokens in the indexer token transfers transformer +- [#4535](https://github.com/blockscout/blockscout/pull/4535) - Indexer performance update:: Eliminate multiple updates of the same token while parsing mint/burn token transfers batch +- [#4527](https://github.com/blockscout/blockscout/pull/4527) - Indexer performance update: refactor coin balance daily fetcher +- [#4525](https://github.com/blockscout/blockscout/pull/4525) - Uncataloged token transfers query performance improvement +- [#4513](https://github.com/blockscout/blockscout/pull/4513) - Fix installation with custom default path: add NETWORK_PATH variable to the current_path +- [#4500](https://github.com/blockscout/blockscout/pull/4500) - `/tokens/{addressHash}/instance/{id}/token-transfers`: fix incorrect next page url - [#4493](https://github.com/blockscout/blockscout/pull/4493) - Contract's code page: handle null contracts_creation_transaction - [#4488](https://github.com/blockscout/blockscout/pull/4488) - Tx page: handle empty to_address - [#4483](https://github.com/blockscout/blockscout/pull/4483) - Fix copy-paste typo in `token_transfers_counter.ex` @@ -16,6 +210,11 @@ - [#4401](https://github.com/blockscout/blockscout/pull/4401) - Fix displaying of token holders with the same amount ### Chore +- [#4550](https://github.com/blockscout/blockscout/pull/4550) - Update con_cache package to 1.0 +- [#4523](https://github.com/blockscout/blockscout/pull/4523) - Change order of transations in block's view +- [#4521](https://github.com/blockscout/blockscout/pull/4521) - Rewrite transaction page tooltips +- [#4516](https://github.com/blockscout/blockscout/pull/4516) - Add DB migrations step into Docker start script +- [#4497](https://github.com/blockscout/blockscout/pull/4497) - Handle error in fetch_validators_list method - [#4444](https://github.com/blockscout/blockscout/pull/4444) - Main page performance cumulative update - [#4439](https://github.com/blockscout/blockscout/pull/4439), - [#4465](https://github.com/blockscout/blockscout/pull/4465) - Fix revert response in contract's output diff --git a/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss b/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss index e0371b6cad46..f97e39728a08 100644 --- a/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss +++ b/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss @@ -13,7 +13,7 @@ } h2 { color: #a3a9b5; - font-size: 12px; + font-size: 14px; font-weight: 400; line-height: 1.25; display: inline-flex; @@ -66,18 +66,18 @@ background-repeat: no-repeat; background-position: center; &.etherscan { - @include image-2x('../static/images/icons/etherscan@2x.png', 15px, 16px); - background-image: url("../static/images/icons/etherscan.png"); + @include image-2x('/images/icons/etherscan@2x.png', 15px, 16px); + background-image: url("/images/icons/etherscan.png"); background-size: 15px 16px; } &.blockchair { - @include image-2x('../static/images/icons/blockchair@2x.png', 10px, 16px); - background-image: url("../static/images/icons/blockchair.png"); + @include image-2x('/images/icons/blockchair@2x.png', 10px, 16px); + background-image: url("/images/icons/blockchair.png"); background-size: 10px 16px; } &.etherchain { - @include image-2x('../static/images/icons/etherchain@2x.png', 16px, 16px); - background-image: url("../static/images/icons/etherchain.png"); + @include image-2x('/images/icons/etherchain@2x.png', 16px, 16px); + background-image: url("/images/icons/etherchain.png"); background-size: 16px 16px; } } @@ -168,31 +168,31 @@ &:first-child { .verify-other-explorers-cell.left { .exp-logo { - @include image-2x('../static/images/icons/etherscan@2x.png', 15px, 16px); - background-image: url("../static/images/icons/etherscan.png"); + @include image-2x('/images/icons/blockchair@2x.png', 10px, 16px); + background-image: url("/images/icons/blockchair.png"); background-repeat: no-repeat; padding-left: 25px; - background-size: 15px 16px; + background-size: 10px 16px; + background-position: left 3px center; } } } &:nth-child(2) { .verify-other-explorers-cell.left { .exp-logo { - @include image-2x('../static/images/icons/blockchair@2x.png', 10px, 16px); - background-image: url("../static/images/icons/blockchair.png"); + @include image-2x('/images/icons/etherscan@2x.png', 15px, 16px); + background-image: url("/images/icons/etherscan.png"); background-repeat: no-repeat; padding-left: 25px; - background-size: 10px 16px; - background-position: left 3px center; + background-size: 15px 16px; } } } &:nth-child(3) { .verify-other-explorers-cell.left { .exp-logo { - @include image-2x('../static/images/icons/etherchain@2x.png', 16px, 16px); - background-image: url("../static/images/icons/etherchain.png"); + @include image-2x('/images/icons/etherchain@2x.png', 16px, 16px); + background-image: url("/images/icons/etherchain.png"); background-repeat: no-repeat; padding-left: 25px; background-size: 16px 16px; @@ -209,7 +209,7 @@ } .link { - background-image: url("../static/images/icons/link.svg"); + background-image: url("/images/icons/link.svg"); background-repeat: no-repeat; padding-left: 15px; background-size: 12px 12px; diff --git a/apps/block_scout_web/assets/css/theme/_variables-non-critical.scss b/apps/block_scout_web/assets/css/theme/_variables-non-critical.scss index 6f6f26340c86..4bc7448c6cb7 100644 --- a/apps/block_scout_web/assets/css/theme/_variables-non-critical.scss +++ b/apps/block_scout_web/assets/css/theme/_variables-non-critical.scss @@ -3,7 +3,7 @@ // @import "xusdt_variables-non-critical"; // @import "dai_variables-non-critical"; // @import "ethereum_classic_variables-non-critical"; -// @import "ethereum_variables-non-critical"; +@import "ethereum_variables-non-critical"; // @import "ether1_variables-non-critical"; // @import "expanse_variables-non-critical"; // @import "gochain_variables-non-critical"; diff --git a/apps/block_scout_web/assets/css/theme/_variables.scss b/apps/block_scout_web/assets/css/theme/_variables.scss index 1cdaed4a18d2..5c4fabdd7707 100644 --- a/apps/block_scout_web/assets/css/theme/_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_variables.scss @@ -1,5 +1,5 @@ @import "base_variables"; -// @import "neutral_variables"; +@import "ethereum_variables"; // @import "xusdt_variables"; // @import "dai_variables"; // @import "ethereum_classic_variables"; @@ -21,7 +21,6 @@ // @import "tomochain_variables"; // @import "rsk_variables"; // @import "ethercore_variables"; -@import "optimism_variables"; // responsive breakpoints $breakpoint-xs: 320px; diff --git a/apps/block_scout_web/assets/js/lib/autocomplete.js b/apps/block_scout_web/assets/js/lib/autocomplete.js index c957066873de..130f8197521e 100644 --- a/apps/block_scout_web/assets/js/lib/autocomplete.js +++ b/apps/block_scout_web/assets/js/lib/autocomplete.js @@ -1,11 +1,8 @@ import $ from 'jquery' -// @ts-ignore import AutoComplete from '@tarekraafat/autocomplete.js/dist/autoComplete' import { getTextAdData, fetchTextAdData } from './ad' import { DateTime } from 'luxon' import { appendTokenIcon } from './token_icon' -import { escapeHtml } from './utils' -import { commonPath } from './path_helper' import xss from 'xss' const placeHolder = 'Search by address, token symbol, name, transaction hash, or block number' @@ -15,16 +12,16 @@ const dataSrc = async (query, id) => { const searchInput = document .getElementById(id) - searchInput && searchInput.setAttribute('placeholder', 'Loading...') + searchInput.setAttribute('placeholder', 'Loading...') // Fetch External Data Source const source = await fetch( - `${commonPath}/token-autocomplete?q=${query}` + `/token-autocomplete?q=${query}` ) const data = await source.json() // Post Loading placeholder text - searchInput && searchInput.setAttribute('placeholder', placeHolder) + searchInput.setAttribute('placeholder', placeHolder) // Returns Fetched data return data } catch (error) { @@ -41,24 +38,20 @@ const resultsListElement = (list, data) => { if (data.results.length > 0) { info.innerHTML += `Displaying ${data.results.length} results` } else if (data.query !== '###') { - info.innerHTML += `Found ${data.matches.length} matching results for ` - const strong = document.createElement('strong') - strong.appendChild(document.createTextNode(data.query)) - info.appendChild(strong) + info.innerHTML += `Found ${data.matches.length} matching results for "${data.query}"` } list.prepend(info) fetchTextAdData() } -export const searchEngine = (query, record) => { - const queryLowerCase = query.toLowerCase() +const searchEngine = (query, record) => { if (record && ( - (record.name && record.name.toLowerCase().includes(queryLowerCase)) || - (record.symbol && record.symbol.toLowerCase().includes(queryLowerCase)) || - (record.address_hash && record.address_hash.toLowerCase().includes(queryLowerCase)) || - (record.tx_hash && record.tx_hash.toLowerCase().includes(queryLowerCase)) || - (record.block_hash && record.block_hash.toLowerCase().includes(queryLowerCase)) + (record.name && record.name.toLowerCase().includes(query.toLowerCase())) || + (record.symbol && record.symbol.toLowerCase().includes(query.toLowerCase())) || + (record.address_hash && record.address_hash.toLowerCase().includes(query.toLowerCase())) || + (record.tx_hash && record.tx_hash.toLowerCase().includes(query.toLowerCase())) || + (record.block_hash && record.block_hash.toLowerCase().includes(query.toLowerCase())) ) ) { let searchResult = '
' @@ -69,10 +62,10 @@ export const searchEngine = (query, record) => { } else { searchResult += '
' if (record.name) { - searchResult += `${escapeHtml(record.name)}` + searchResult += `${record.name}` } if (record.symbol) { - searchResult += ` (${escapeHtml(record.symbol)})` + searchResult += ` (${record.symbol})` } if (record.holder_count) { searchResult += ` ${record.holder_count} holder(s)` @@ -104,7 +97,7 @@ const resultItemElement = async (item, data) => { const $searchInput = $('#main-search-autocomplete') const chainID = $searchInput.data('chain-id') const displayTokenIcons = $searchInput.data('display-token-icons') - appendTokenIcon($tokenIconContainer, chainID, data.value.address_hash, displayTokenIcons, 15) + appendTokenIcon($tokenIconContainer, chainID, data.value.address_hash, data.value.foreign_chain_id, data.value.foreign_token_hash, displayTokenIcons, 15) } const config = (id) => { return { @@ -113,7 +106,7 @@ const config = (id) => { src: (query) => dataSrc(query, id), cache: false }, - placeHolder, + placeHolder: placeHolder, searchEngine: (query, record) => searchEngine(query, record), threshold: 2, resultsList: { @@ -132,69 +125,65 @@ const config = (id) => { events: { input: { focus: () => { - // @ts-ignore - if (autoCompleteJS && autoCompleteJS.input.value.length) autoCompleteJS.start() + if (autoCompleteJS.input.value.length) autoCompleteJS.start() } } } } } -const autoCompleteJS = document.querySelector('#main-search-autocomplete') && new AutoComplete(config('main-search-autocomplete')) +const autoCompleteJS = new AutoComplete(config('main-search-autocomplete')) // eslint-disable-next-line -const autoCompleteJSMobile = document.querySelector('#main-search-autocomplete-mobile') && new AutoComplete(config('main-search-autocomplete-mobile')) +const autoCompleteJSMobile = new AutoComplete(config('main-search-autocomplete-mobile')) const selection = (event) => { const selectionValue = event.detail.selection.value + const commonPath = process.env.NETWORK_PATH + if (selectionValue.type === 'contract' || selectionValue.type === 'address' || selectionValue.type === 'label') { - window.location.href = `${commonPath}/address/${selectionValue.address_hash}` + window.location = `${commonPath}/address/${selectionValue.address_hash}` } else if (selectionValue.type === 'token') { - window.location.href = `${commonPath}/tokens/${selectionValue.address_hash}` + window.location = `${commonPath}/tokens/${selectionValue.address_hash}` } else if (selectionValue.type === 'transaction') { - window.location.href = `${commonPath}/tx/${selectionValue.tx_hash}` + window.location = `${commonPath}/tx/${selectionValue.tx_hash}` } else if (selectionValue.type === 'block') { - window.location.href = `${commonPath}/blocks/${selectionValue.block_hash}` + window.location = `${commonPath}/blocks/${selectionValue.block_hash}` } } +document.querySelector('#main-search-autocomplete').addEventListener('selection', function (event) { + selection(event) +}) +document.querySelector('#main-search-autocomplete-mobile').addEventListener('selection', function (event) { + selection(event) +}) + const openOnFocus = (event, type) => { const query = event.target.value if (query) { if (type === 'desktop') { - // @ts-ignore - autoCompleteJS && autoCompleteJS.start(query) + autoCompleteJS.start(query) } else if (type === 'mobile') { - // @ts-ignore - autoCompleteJSMobile && autoCompleteJSMobile.start(query) + autoCompleteJSMobile.start(query) } } else { getTextAdData() .then(({ data: adData, inHouse: _inHouse }) => { if (adData) { if (type === 'desktop') { - // @ts-ignore - autoCompleteJS && autoCompleteJS.start('###') + autoCompleteJS.start('###') } else if (type === 'mobile') { - // @ts-ignore - autoCompleteJSMobile && autoCompleteJSMobile.start('###') + autoCompleteJSMobile.start('###') } } }) } } -const mainSearchAutocompleteObj = document.querySelector('#main-search-autocomplete') -const mainSearchAutocompleteMobileObj = document.querySelector('#main-search-autocomplete-mobile') - -mainSearchAutocompleteObj && mainSearchAutocompleteObj.addEventListener('selection', function (event) { - selection(event) -}) -mainSearchAutocompleteMobileObj && mainSearchAutocompleteMobileObj.addEventListener('selection', function (event) { - selection(event) -}) -mainSearchAutocompleteObj && mainSearchAutocompleteObj.addEventListener('focus', function (event) { +document.querySelector('#main-search-autocomplete').addEventListener('focus', function (event) { openOnFocus(event, 'desktop') }) -mainSearchAutocompleteMobileObj && mainSearchAutocompleteMobileObj.addEventListener('focus', function (event) { + +document.querySelector('#main-search-autocomplete-mobile').addEventListener('focus', function (event) { openOnFocus(event, 'mobile') }) diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js index 06a0f7179c18..835f7e70f48e 100644 --- a/apps/block_scout_web/assets/js/lib/history_chart.js +++ b/apps/block_scout_web/assets/js/lib/history_chart.js @@ -92,7 +92,15 @@ const config = { grid, ticks: { beginAtZero: true, - callback: (value, _index, _values) => `$${numeral(value).format('0,0.00')}`, + callback: (value, _index, _values) => { + const formattedValue = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + notation: 'compact', + compactDisplay: 'short' + }).format(value) + return formattedValue + }, maxTicksLimit: 4, color: sassVariables.dashboardBannerChartAxisFontColor } @@ -112,7 +120,13 @@ const config = { grid, ticks: { beginAtZero: true, - callback: (value, _index, _values) => formatValue(value), + callback: (value, _index, _values) => { + const formattedValue = new Intl.NumberFormat('en-US', { + notation: 'compact', + compactDisplay: 'short' + }).format(value) + return formattedValue + }, maxTicksLimit: 4, color: sassVariables.dashboardBannerChartAxisFontColor } diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js index e12e37c87475..1c62a6b82fb1 100644 --- a/apps/block_scout_web/assets/js/pages/address/logs.js +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -1,11 +1,9 @@ import $ from 'jquery' import omit from 'lodash.omit' +import humps from 'humps' import { connectElements } from '../../lib/redux_helpers.js' -import { createAsyncLoadStore, loadPage } from '../../lib/async_listing_load' -import { commonPath } from '../../lib/path_helper' -import { escapeHtml } from '../../lib/utils' +import { createAsyncLoadStore } from '../../lib/async_listing_load' import '../address' -// @ts-ignore import { utils } from 'web3' export const initialState = { @@ -59,62 +57,34 @@ const elements = { } if ($('[data-page="address-logs"]').length) { - let timer - const waitTime = 500 - const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierLog') const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash const $element = $('[data-async-listing]') connectElements({ store, elements }) - const searchFunc = (_event) => { + store.dispatch({ + type: 'PAGE_LOAD', + addressHash: addressHash + }) + + $element.on('click', '[data-search-button]', (_event) => { store.dispatch({ type: 'START_SEARCH', - addressHash + addressHash: addressHash }) const topic = $('[data-search-field]').val() const addressHashPlain = store.getState().addressHash const addressHashChecksum = addressHashPlain && utils.toChecksumAddress(addressHashPlain) - const path = `${commonPath}/search-logs?topic=${topic}&address_id=${addressHashChecksum}` - changeDownloadButtonHref(topic) - loadPage(store, path) - } - - function changeDownloadButtonHref (filter) { - const currentHref = $('a.download-all-items-link').attr('href') - if (currentHref) { - let hrefWithTopic = currentHref - if (currentHref.includes('filter_type=&')) { - hrefWithTopic = currentHref.replace(/filter_type=.*?&/, 'filter_type=topic&') - } - const href = hrefWithTopic.replace(/filter_value=.*?&/, `filter_value=${escapeHtml(filter)}&`) - $('a.download-all-items-link').attr('href', href) - } - } - - store.dispatch({ - type: 'PAGE_LOAD', - addressHash + const path = `${process.env.NETWORK_PATH}/search-logs?topic=${topic}&address_id=${addressHashChecksum}` + store.dispatch({ type: 'START_REQUEST' }) + $.getJSON(path, { type: 'JSON' }) + .done(response => store.dispatch(Object.assign({ type: 'ITEMS_FETCHED' }, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({ type: 'REQUEST_ERROR' })) + .always(() => store.dispatch({ type: 'FINISH_REQUEST' })) }) - $element.on('click', '[data-search-button]', searchFunc) - $element.on('click', '[data-cancel-search-button]', (_event) => { - $('[data-search-field]').val('') - loadPage(store, window.location.pathname) - }) - - $element.on('input keyup', '[data-search-field]', (event) => { - if (event.type === 'input') { - clearTimeout(timer) - timer = setTimeout(() => { - searchFunc(event) - }, waitTime) - } - if (event.type === 'keyup' && event.keyCode === 13) { - clearTimeout(timer) - searchFunc(event) - } + window.location.replace(window.location.href.split('?')[0]) }) } diff --git a/apps/block_scout_web/assets/js/pages/layout.js b/apps/block_scout_web/assets/js/pages/layout.js index edabc03366cc..03d4a3b57bc3 100644 --- a/apps/block_scout_web/assets/js/pages/layout.js +++ b/apps/block_scout_web/assets/js/pages/layout.js @@ -1,182 +1,17 @@ import $ from 'jquery' import { addChainToMM } from '../lib/add_chain_to_mm' -import * as analytics from '../lib/analytics' -import { commonPath } from '../lib/path_helper' - -analytics.init() - -const simpleEvents = { - '.profile-button': 'Profile click', - '.watchlist-button': 'Watch list click', - '.address-tags-button': 'Address tags click', - '.transaction-tags-button': 'Transaction tags click', - '.api-keys-button': 'API keys click', - '.custom-abi-button': 'Custom ABI click', - '.public-tags-button': 'Public tags click', - '.sign-out-button': 'Sign out click', - '.sign-in-button': 'Sign in click', - '.add-address-button': 'Add address to watch list click', - '.add-address-tag-button': 'Add address tag click', - '.add-transaction-tag-button': 'Add transaction tag click', - '.add-api-key-button': 'Add API key click', - '.add-custom-abi-button': 'Add custom ABI click', - '.add-public-tag-button': 'Request to add public tag click' -} - -if (analytics.mixpanelInitialized || analytics.amplitudeInitialized) { - for (const elementClass in simpleEvents) { - $(elementClass).click((_event) => { - analytics.trackEvent(simpleEvents[elementClass]) - }) - } -} - -$('.save-address-button').click((_event) => { - const eventProperties = { - address_hash: $('#watchlist_address_address_hash').val(), - private_tag: $('#watchlist_address_name').val(), - eth_incoming: $('#watchlist_address_watch_coin_input').prop('checked'), - eth_outgoing: $('#watchlist_address_watch_coin_output').prop('checked'), - erc_20_incoming: $('#watchlist_address_watch_erc_20_input').prop('checked'), - erc_20_outgoing: $('#watchlist_address_watch_erc_20_output').prop('checked'), - erc_721_1155_incoming: $('#watchlist_address_watch_erc_721_input').prop('checked'), - erc_721_1155_outgoing: $('#watchlist_address_watch_erc_721_output').prop('checked'), - email_notifications: $('#watchlist_address_notify_email').prop('checked') - } - const eventName = 'New address to watchlist completed' - - analytics.trackEvent(eventName, eventProperties) -}) - -$('.save-address-tag-button').click((_event) => { - const eventName = 'Add address tag completed' - const eventProperties = { - address_hash: $('#tag_address_address_hash').val(), - private_tag: $('#tag_address_name').val() - } - - analytics.trackEvent(eventName, eventProperties) -}) - -$('.save-transaction-tag-button').click((_event) => { - const eventName = 'Add transaction tag completed' - const eventProperties = { - address_hash: $('#tag_transaction_tx_hash').val(), - private_tag: $('#tag_transaction_name').val() - } - - analytics.trackEvent(eventName, eventProperties) -}) - -$('.save-api-key-button').click((_event) => { - const eventName = 'Generate API key completed' - const eventProperties = { - application_name: $('#key_name').val() - } - - analytics.trackEvent(eventName, eventProperties) -}) - -$('.save-custom-abi-button').click((_event) => { - const eventName = 'Custom ABI completed' - const eventProperties = { - smart_contract_address: $('#custom_abi_address_hash').val(), - project_name: $('#custom_abi_name').val(), - custom_abi: $('#custom_abi_abi').val() - } - - analytics.trackEvent(eventName, eventProperties) -}) - -$('.send-public-tag-request-button').click((_event) => { - const eventName = 'Request a public tag completed' - const eventProperties = { - name: $('#public_tags_request_full_name').val(), - email: $('#public_tags_request_email').val(), - company_name: $('#public_tags_request_company').val(), - company_website: $('#public_tags_request_website').val(), - goal: $('#public_tags_request_is_owner_true').prop('checked') ? 'Add tags' : 'Incorrect public tag', - public_tag: $('#public_tags_request_tags').val(), - smart_contracts: $('*[id=public_tags_request_addresses]').map((_i, el) => { - // @ts-ignore - return el.value - }).get(), - reason: $('#public_tags_request_additional_comment').val() - } - - analytics.trackEvent(eventName, eventProperties) -}) - -$(document).ready(() => { - let timer - const waitTime = 500 - const observer = new MutationObserver((mutations) => { - // @ts-ignore - if (mutations[0].target.hidden) { - return - } - - const $results = $('li[id^="autoComplete_result_"]') - - clearTimeout(timer) - timer = setTimeout(() => { - let eventName = 'Occurs searching according to substring at the nav bar' - let eventProperties = { - search: $('.main-search-autocomplete').val() || $('.main-search-autocomplete-mobile').val() - } - - analytics.trackEvent(eventName, eventProperties) - - eventName = 'Search list displays at the nav bar' - // @ts-ignore - eventProperties = { - resultsNumber: $results.length, - results: $results.map((_i, el) => { - // @ts-ignore - return el.children[1].innerText - }) - } - - analytics.trackEvent(eventName, eventProperties) - }, waitTime) - - $results.click((event) => { - const eventName = 'Search item click at the nav bar' - const eventProperties = { - item: event.currentTarget.innerText - } - - analytics.trackEvent(eventName, eventProperties) - }) - }) - observer.observe($('#autoComplete_list_1')[0], { - attributeFilter: ['hidden'], - childList: true - }) - observer.observe($('#autoComplete_list_2')[0], { - attributeFilter: ['hidden'] - }) -}) $(document).click(function (event) { const clickover = $(event.target) const _opened = $('.navbar-collapse').hasClass('show') - // @ts-ignore if (_opened === true && $('.navbar').find(clickover).length < 1) { $('.navbar-toggler').click() } }) const search = (value) => { - const eventName = 'Occurs searching according to substring at the nav bar' - const eventProperties = { - search: value - } - - analytics.trackEvent(eventName, eventProperties) - if (value) { - window.location.href = `${commonPath}/search?q=${value}` + window.location.href = `${process.env.NETWORK_PATH}/search?q=${value}` } } @@ -200,7 +35,6 @@ $('.main-search-autocomplete').on('keyup', function (event) { } }) if (!selected) { - // @ts-ignore search(event.target.value) } } diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js index faa5a2f32cee..d4d956a87fe7 100644 --- a/apps/block_scout_web/assets/js/pages/transaction.js +++ b/apps/block_scout_web/assets/js/pages/transaction.js @@ -6,7 +6,6 @@ import socket from '../socket' import { createStore, connectElements } from '../lib/redux_helpers.js' import '../lib/transaction_input_dropdown' import '../lib/async_listing_load' -import { commonPath } from '../lib/path_helper' import '../app' import Swal from 'sweetalert2' import { compareChainIDs, formatError } from '../lib/smart_contract/common_helpers' @@ -21,19 +20,11 @@ export function reducer (state = initialState, action) { case 'ELEMENTS_LOAD': { return Object.assign({}, state, omit(action, 'type')) } - case 'RECEIVED_NEW_RAW_TRACE': { - return Object.assign({}, state, { - rawTrace: action.msg.rawTrace - }) - } case 'RECEIVED_NEW_BLOCK': { - if (state.blockNumber) { - // @ts-ignore - if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { - return Object.assign({}, state, { - confirmations: action.msg.blockNumber - state.blockNumber - }) - } else return state + if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) { + return Object.assign({}, state, { + confirmations: action.msg.blockNumber - state.blockNumber + }) } else return state } default: @@ -53,16 +44,6 @@ const elements = { $el.empty().append(numeral(state.confirmations).format()) } } - }, - '[data-selector="raw-trace"]': { - render ($el, state) { - if (state.rawTrace) { - $el[0].innerHTML = state.rawTrace - state.rawTrace = null - return $el - } - return $el - } } } @@ -71,27 +52,6 @@ if ($transactionDetailsPage.length) { const store = createStore(reducer) connectElements({ store, elements }) - const transactionHash = $transactionDetailsPage[0].dataset.pageTransactionHash - const transactionChannel = socket.channel(`transactions:${transactionHash}`, {}) - transactionChannel.join() - transactionChannel.on('collated', () => window.location.reload()) - transactionChannel.on('raw_trace', (msg) => store.dispatch({ - type: 'RECEIVED_NEW_RAW_TRACE', - msg: humps.camelizeKeys(msg) - })) - - const pathParts = window.location.pathname.split('/') - const shouldScroll = pathParts.includes('internal-transactions') || - pathParts.includes('token-transfers') || - pathParts.includes('logs') || - pathParts.includes('token-transfers') || - pathParts.includes('raw-trace') || - pathParts.includes('state') - if (shouldScroll) { - const txTabsObj = document.getElementById('transaction-tabs') - txTabsObj && txTabsObj.scrollIntoView() - } - const blocksChannel = socket.channel('blocks:new_block', {}) blocksChannel.join() blocksChannel.on('new_block', (msg) => store.dispatch({ @@ -99,9 +59,13 @@ if ($transactionDetailsPage.length) { msg: humps.camelizeKeys(msg) })) + const transactionHash = $transactionDetailsPage[0].dataset.pageTransactionHash + const transactionChannel = socket.channel(`transactions:${transactionHash}`, {}) + transactionChannel.join() + transactionChannel.on('collated', () => window.location.reload()) + $('.js-cancel-transaction').on('click', (event) => { const btn = $(event.target) - // @ts-ignore if (!window.ethereum) { btn .attr('data-original-title', `Please unlock ${btn.data('from')} account in Metamask`) @@ -114,7 +78,6 @@ if ($transactionDetailsPage.length) { }, 3000) return } - // @ts-ignore const { chainId: walletChainIdHex } = window.ethereum compareChainIDs(btn.data('chainId'), walletChainIdHex) .then(() => { @@ -124,13 +87,12 @@ if ($transactionDetailsPage.length) { value: 0, nonce: btn.data('nonce').toString() } - // @ts-ignore window.ethereum.request({ method: 'eth_sendTransaction', params: [txParams] }) .then(function (txHash) { - const successMsg = `Canceling transaction successfully sent to the network. The current one will change the status once canceling transaction will be confirmed.` + const successMsg = `Canceling transaction successfully sent to the network. The current one will change the status once canceling transaction will be confirmed.` Swal.fire({ title: 'Success', html: successMsg, @@ -169,26 +131,26 @@ $(function () { $collapseButton.on('click', event => { const $button = event.target const $parent = $button.parentElement - const $collapseButton = $parent && $parent.querySelector('[button-collapse-input]') - const $expandButton = $parent && $parent.querySelector('[button-expand-input]') - const $hiddenText = $parent && $parent.querySelector('[data-hidden-text]') - const $placeHolder = $parent && $parent.querySelector('[data-placeholder-dots]') - $collapseButton && $collapseButton.classList.add('d-none') - $expandButton && $expandButton.classList.remove('d-none') - $hiddenText && $hiddenText.classList.add('d-none') - $placeHolder && $placeHolder.classList.remove('d-none') + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $collapseButton.classList.add('d-none') + $expandButton.classList.remove('d-none') + $hiddenText.classList.add('d-none') + $placeHolder.classList.remove('d-none') }) $expandButton.on('click', event => { const $button = event.target const $parent = $button.parentElement - const $collapseButton = $parent && $parent.querySelector('[button-collapse-input]') - const $expandButton = $parent && $parent.querySelector('[button-expand-input]') - const $hiddenText = $parent && $parent.querySelector('[data-hidden-text]') - const $placeHolder = $parent && $parent.querySelector('[data-placeholder-dots]') - $expandButton && $expandButton.classList.add('d-none') - $collapseButton && $collapseButton.classList.remove('d-none') - $hiddenText && $hiddenText.classList.remove('d-none') - $placeHolder && $placeHolder.classList.add('d-none') + const $collapseButton = $parent.querySelector('[button-collapse-input]') + const $expandButton = $parent.querySelector('[button-expand-input]') + const $hiddenText = $parent.querySelector('[data-hidden-text]') + const $placeHolder = $parent.querySelector('[data-placeholder-dots]') + $expandButton.classList.add('d-none') + $collapseButton.classList.remove('d-none') + $hiddenText.classList.remove('d-none') + $placeHolder.classList.add('d-none') }) }) diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js index 3b90aeec8e33..94fd4fc93727 100644 --- a/apps/block_scout_web/assets/webpack.config.js +++ b/apps/block_scout_web/assets/webpack.config.js @@ -32,13 +32,40 @@ const jsOptimizationParams = { parallel: true } +const dropzoneJs = { + entry: { + dropzone: './js/lib/dropzone.js', + }, + output: { + filename: '[name].min.js', + path: path.resolve(__dirname, '../priv/static/js') + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + } + ] + } + ] + }, + optimization: { + minimizer: [ + new TerserJSPlugin(jsOptimizationParams), + ] + } +} + const appJs = { entry: { - 'app': './js/app.js', - 'app_extra': './js/app_extra.js', + app: './js/app.js', + stakes: './js/pages/stakes.js', 'chart-loader': './js/chart-loader.js', - 'balance-chart-loader': './js/balance-chart-loader.js', 'chain': './js/pages/chain.js', 'blocks': './js/pages/blocks.js', 'address': './js/pages/address.js', @@ -49,7 +76,6 @@ const appJs = 'address-logs': './js/pages/address/logs.js', 'address-validations': './js/pages/address/validations.js', 'validated-transactions': './js/pages/transactions.js', - 'verified-contracts': './js/pages/verified_contracts.js', 'pending-transactions': './js/pages/pending_transactions.js', 'transaction': './js/pages/transaction.js', 'verification-form': './js/pages/verification_form.js', @@ -58,26 +84,22 @@ const appJs = 'admin-tasks': './js/pages/admin/tasks.js', 'token-contract': './js/pages/token_contract.js', 'smart-contract-helpers': './js/lib/smart_contract/index.js', - 'sol2uml': './js/pages/sol2uml.js', 'token-transfers-toggle': './js/lib/token_transfers_toggle.js', 'try-api': './js/lib/try_api.js', 'try-eth-api': './js/lib/try_eth_api.js', 'async-listing-load': './js/lib/async_listing_load', 'non-critical': './css/non-critical.scss', 'main-page': './css/main-page.scss', + 'staking': './css/stakes.scss', 'tokens': './js/pages/token/search.js', - 'text-ad': './js/lib/text_ad.js', + 'ad': './js/lib/ad.js', + 'text_ad': './js/lib/text_ad.js', 'banner': './js/lib/banner.js', 'autocomplete': './js/lib/autocomplete.js', - 'custom-scrollbar': './js/lib/custom_scrollbar.js', - 'custom-scrollbar-styles': './css/custom-scrollbar.scss', 'search-results': './js/pages/search-results/search.js', 'token-overview': './js/pages/token/overview.js', 'export-csv': './css/export-csv.scss', - 'csv-download': './js/lib/csv_download.js', - 'dropzone': './js/lib/dropzone.js', - 'delete-item-handler': './js/pages/account/delete_item_handler.js', - 'public-tags-request-form': './js/lib/public_tags_request_form.js' + 'datepicker': './js/lib/datepicker.js' }, output: { filename: '[name].js', @@ -88,10 +110,6 @@ const appJs = }, module: { rules: [ - { - test: /\.css$/, - use: ["style-loader", "css-loader"], - }, { test: /\.js$/, exclude: /node_modules/, @@ -102,16 +120,9 @@ const appJs = { test: /\.scss$/, use: [ + MiniCssExtractPlugin.loader, { - loader: MiniCssExtractPlugin.loader, - options: { - esModule: false, - }, - }, { - loader: 'css-loader', - options: { - esModule: false, - }, + loader: 'css-loader' }, { loader: 'postcss-loader' }, { @@ -137,11 +148,6 @@ const appJs = publicPath: '../fonts/' } } - }, { - test: /\.(png)$/, - use: { - loader: 'file-loader' - } } ] }, @@ -169,10 +175,13 @@ const appJs = ), new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), new webpack.DefinePlugin({ - 'process.env.MIXPANEL_TOKEN': JSON.stringify(process.env.MIXPANEL_TOKEN), - 'process.env.MIXPANEL_URL': JSON.stringify(process.env.MIXPANEL_URL), - 'process.env.AMPLITUDE_API_KEY': JSON.stringify(process.env.AMPLITUDE_API_KEY), - 'process.env.AMPLITUDE_URL': JSON.stringify(process.env.AMPLITUDE_URL) + 'process.env.SOCKET_ROOT': JSON.stringify(process.env.SOCKET_ROOT), + 'process.env.NETWORK_PATH': JSON.stringify(process.env.NETWORK_PATH), + 'process.env.CHAIN_ID': JSON.stringify(process.env.CHAIN_ID), + 'process.env.JSON_RPC': JSON.stringify(process.env.JSON_RPC), + 'process.env.NETWORK_PATH': JSON.stringify(process.env.NETWORK_PATH), + 'process.env.SUBNETWORK': JSON.stringify(process.env.SUBNETWORK), + 'process.env.COIN_NAME': JSON.stringify(process.env.COIN_NAME) }), new webpack.ProvidePlugin({ process: 'process/browser', @@ -183,4 +192,4 @@ const appJs = const viewScripts = glob.sync('./js/view_specific/**/*.js').map(transpileViewScript) -module.exports = viewScripts.concat(appJs) +module.exports = viewScripts.concat(appJs, dropzoneJs) diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs index bb545430cbe8..c8488421cdfe 100644 --- a/apps/block_scout_web/config/config.exs +++ b/apps/block_scout_web/config/config.exs @@ -1,30 +1,105 @@ # This file is responsible for configuring your application -# and its dependencies with the aid of the Config module. +# and its dependencies with the aid of the Mix.Config module. # # This configuration file is loaded before any dependency and # is restricted to this project. -import Config - -[__DIR__ | ~w(.. .. .. config config_helper.exs)] -|> Path.join() -|> Code.eval_file() +use Mix.Config # General application configuration config :block_scout_web, namespace: BlockScoutWeb, - ecto_repos: ConfigHelper.repos(), - cookie_domain: System.get_env("SESSION_COOKIE_DOMAIN"), - # 604800 seconds, 1 week - session_cookie_ttl: 60 * 60 * 24 * 7, - invalid_session_key: "invalid_session", - api_v2_temp_token_key: "api_v2_temp_token" + ecto_repos: [Explorer.Repo], + version: System.get_env("BLOCKSCOUT_VERSION"), + release_link: System.get_env("RELEASE_LINK"), + decompiled_smart_contract_token: System.get_env("DECOMPILED_SMART_CONTRACT_TOKEN"), + show_percentage: if(System.get_env("SHOW_ADDRESS_MARKETCAP_PERCENTAGE", "true") == "false", do: false, else: true), + checksum_address_hashes: if(System.get_env("CHECKSUM_ADDRESS_HASHES", "true") == "false", do: false, else: true) + +config :block_scout_web, BlockScoutWeb.Chain, + network: System.get_env("NETWORK"), + subnetwork: System.get_env("SUBNETWORK"), + network_icon: System.get_env("NETWORK_ICON"), + logo: System.get_env("LOGO") || "/images/ethereum_logo.svg", + logo_footer: System.get_env("LOGO_FOOTER"), + logo_text: System.get_env("LOGO_TEXT"), + has_emission_funds: false, + staking_enabled: not is_nil(System.get_env("POS_STAKING_CONTRACT")), + staking_enabled_in_menu: System.get_env("ENABLE_POS_STAKING_IN_MENU", "false") == "true", + show_staking_warning: System.get_env("SHOW_STAKING_WARNING", "false") == "true", + show_maintenance_alert: System.get_env("SHOW_MAINTENANCE_ALERT", "false") == "true", + # how often (in blocks) the list of pools should autorefresh in UI (zero turns off autorefreshing) + staking_pool_list_refresh_interval: 5 config :block_scout_web, - admin_panel_enabled: System.get_env("ADMIN_PANEL_ENABLED", "") == "true" + link_to_other_explorers: System.get_env("LINK_TO_OTHER_EXPLORERS") == "true", + other_explorers: System.get_env("OTHER_EXPLORERS"), + other_networks: System.get_env("SUPPORTED_CHAINS"), + webapp_url: System.get_env("WEBAPP_URL"), + api_url: System.get_env("API_URL"), + apps_menu: if(System.get_env("APPS_MENU", "false") == "true", do: true, else: false), + external_apps: System.get_env("EXTERNAL_APPS"), + eth_omni_bridge_mediator: System.get_env("ETH_OMNI_BRIDGE_MEDIATOR"), + bsc_omni_bridge_mediator: System.get_env("BSC_OMNI_BRIDGE_MEDIATOR"), + amb_bridge_mediators: System.get_env("AMB_BRIDGE_MEDIATORS"), + foreign_json_rpc: System.get_env("FOREIGN_JSON_RPC", ""), + gas_price: System.get_env("GAS_PRICE", nil), + restricted_list: System.get_env("RESTRICTED_LIST", nil), + restricted_list_key: System.get_env("RESTRICTED_LIST_KEY", nil), + dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"), + dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"), + circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"), + test_tokens_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN"), + max_size_to_show_array_as_is: Integer.parse(System.get_env("MAX_SIZE_UNLESS_HIDE_ARRAY", "50")), + max_length_to_show_string_without_trimming: System.get_env("MAX_STRING_LENGTH_WITHOUT_TRIMMING", "2040"), + re_captcha_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY", nil), + re_captcha_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY", nil) + +global_api_rate_limit_value = + "API_RATE_LIMIT" + |> System.get_env("50") + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> 50 + end + +api_rate_limit_by_key_value = + "API_RATE_LIMIT_BY_KEY" + |> System.get_env("50") + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> 50 + end + +api_rate_limit_by_ip_value = + "API_RATE_LIMIT_BY_IP" + |> System.get_env("50") + |> Integer.parse() + |> case do + {integer, ""} -> integer + _ -> 50 + end + +config :block_scout_web, :api_rate_limit, + global_limit: global_api_rate_limit_value, + limit_by_key: api_rate_limit_by_key_value, + limit_by_ip: api_rate_limit_by_ip_value, + static_api_key: System.get_env("API_RATE_LIMIT_STATIC_API_KEY", nil), + whitelisted_ips: System.get_env("API_RATE_LIMIT_WHITELISTED_IPS", nil) config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: true -config :block_scout_web, BlockScoutWeb.Counters.InternalTransactionsIndexedCounter, enabled: true +# Configures the endpoint +config :block_scout_web, BlockScoutWeb.Endpoint, + url: [ + scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "http", + host: System.get_env("BLOCKSCOUT_HOST") || "localhost", + path: System.get_env("NETWORK_PATH") || "/", + api_path: System.get_env("API_PATH") || "/" + ], + render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)], + pubsub_server: BlockScoutWeb.PubSub config :block_scout_web, BlockScoutWeb.Tracer, service: :block_scout_web, @@ -40,10 +115,32 @@ config :block_scout_web, BlockScoutWeb.SocialMedia, facebook: "PoaNetwork", instagram: "PoaNetwork" +# Configures History +price_chart_config = + if System.get_env("SHOW_PRICE_CHART", "true") != "false" do + %{market: [:price, :market_cap]} + else + %{} + end + +tx_chart_config = + if System.get_env("SHOW_TXS_CHART", "false") == "true" do + %{transactions: [:transactions_per_day]} + else + %{} + end + +config :block_scout_web, + chart_config: Map.merge(price_chart_config, tx_chart_config) + config :block_scout_web, BlockScoutWeb.Chain.TransactionHistoryChartController, # days history_size: 30 +config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, + # days + coin_balance_history_days: System.get_env("COIN_BALANCE_HISTORY_DAYS", "10") + config :ex_cldr, default_locale: "en", default_backend: BlockScoutWeb.Cldr @@ -56,22 +153,6 @@ config :logger, :block_scout_web, block_number step count error_count shrunk import_id transaction_id)a, metadata_filter: [application: :block_scout_web] -config :logger, :api, - # keep synced with `config/config.exs` - format: "$dateT$time $metadata[$level] $message\n", - metadata: - ~w(application fetcher request_id first_block_number last_block_number missing_block_range_count missing_block_count - block_number step count error_count shrunk import_id transaction_id)a, - metadata_filter: [application: :api] - -config :logger, :api_v2, - # keep synced with `config/config.exs` - format: "$dateT$time $metadata[$level] $message\n", - metadata: - ~w(application fetcher request_id first_block_number last_block_number missing_block_range_count missing_block_count - block_number step count error_count shrunk import_id transaction_id)a, - metadata_filter: [application: :api_v2] - config :prometheus, BlockScoutWeb.Prometheus.Instrumenter, # override default for Phoenix 1.4 compatibility # * `:transport_name` to `:transport` @@ -84,40 +165,21 @@ config :prometheus, BlockScoutWeb.Prometheus.Instrumenter, config :spandex_phoenix, tracer: BlockScoutWeb.Tracer +config :wobserver, + # return only the local node + discovery: :none, + mode: :plug + config :block_scout_web, BlockScoutWeb.ApiRouter, - writing_enabled: System.get_env("API_V1_WRITE_METHODS_DISABLED") != "true", - reading_enabled: System.get_env("API_V1_READ_METHODS_DISABLED") != "true" + writing_enabled: System.get_env("DISABLE_WRITE_API") != "true", + reading_enabled: System.get_env("DISABLE_READ_API") != "true", + wobserver_enabled: System.get_env("WOBSERVER_ENABLED") == "true" config :block_scout_web, BlockScoutWeb.WebRouter, enabled: System.get_env("DISABLE_WEBAPP") != "true" -config :block_scout_web, BlockScoutWeb.CSPHeader, - mixpanel_url: System.get_env("MIXPANEL_URL", "https://api-js.mixpanel.com"), - amplitude_url: System.get_env("AMPLITUDE_URL", "https://api2.amplitude.com/2/httpapi") - -# Configures Ueberauth local settings -config :ueberauth, Ueberauth, - providers: [ - auth0: { - Ueberauth.Strategy.Auth0, - [callback_path: "/auth/auth0/callback", callback_params: ["path"]] - } - ] - -redis_url = System.get_env("API_RATE_LIMIT_HAMMER_REDIS_URL") - -if is_nil(redis_url) or redis_url == "" do - config :hammer, backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]} -else - config :hammer, - backend: - {Hammer.Backend.Redis, - [ - delete_buckets_timeout: 60_000 * 10, - expiry_ms: 60_000 * 60 * 4, - redis_url: redis_url - ]} -end +config :hammer, + backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]} # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" +import_config "#{Mix.env()}.exs" diff --git a/apps/block_scout_web/config/prod.exs b/apps/block_scout_web/config/prod.exs index cab6cb734040..83d244cc67da 100644 --- a/apps/block_scout_web/config/prod.exs +++ b/apps/block_scout_web/config/prod.exs @@ -1,4 +1,4 @@ -import Config +use Mix.Config # For production, we often load configuration from external # sources, such as your system environment. For this reason, @@ -15,25 +15,27 @@ import Config # which you typically run after static files are built. config :block_scout_web, BlockScoutWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json", - force_ssl: false + force_ssl: false, + secret_key_base: System.get_env("SECRET_KEY_BASE"), + check_origin: false, + http: [port: System.get_env("PORT")], + url: [ + scheme: System.get_env("BLOCKSCOUT_PROTOCOL") || "https", + port: System.get_env("PORT"), + host: System.get_env("BLOCKSCOUT_HOST") || "localhost", + path: System.get_env("NETWORK_PATH") || "/", + api_path: System.get_env("API_PATH") || "/" + ] config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true config :logger, :block_scout_web, level: :info, path: Path.absname("logs/prod/block_scout_web.log"), - rotate: %{max_bytes: 52_428_800, keep: 5} + rotate: %{max_bytes: 52_428_800, keep: 19} config :logger, :api, level: :debug, path: Path.absname("logs/prod/api.log"), - metadata_filter: [application: :api], - rotate: %{max_bytes: 52_428_800, keep: 5} - -config :logger, :api_v2, - level: :debug, - path: Path.absname("logs/prod/api_v2.log"), - metadata_filter: [application: :api_v2], - rotate: %{max_bytes: 52_428_800, keep: 5} - -config :block_scout_web, :captcha_helper, BlockScoutWeb.CaptchaHelper + metadata_filter: [fetcher: :api], + rotate: %{max_bytes: 52_428_800, keep: 19} diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index 405a1036ba1e..47d4b212b015 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -13,364 +13,31 @@ defmodule BlockScoutWeb.ApiRouter do Router for API """ use BlockScoutWeb, :router - alias BlockScoutWeb.{AddressTransactionController, APIKeyV2Router, SmartContractsApiV2Router} - alias BlockScoutWeb.Plug.{CheckAccountAPI, CheckApiV2, RateLimit} - - forward("/v2/smart-contracts", SmartContractsApiV2Router) - forward("/v2/key", APIKeyV2Router) pipeline :api do - plug(BlockScoutWeb.Plug.Logger, application: :api) plug(:accepts, ["json"]) end - pipeline :account_api do - plug(:fetch_session) - plug(:protect_from_forgery) - plug(CheckAccountAPI) - end - - pipeline :api_v2 do - plug(BlockScoutWeb.Plug.Logger, application: :api_v2) - plug(:accepts, ["json"]) - plug(CheckApiV2) - plug(:fetch_session) - plug(:protect_from_forgery) - plug(RateLimit) - end - - pipeline :api_v2_no_session do - plug(BlockScoutWeb.Plug.Logger, application: :api_v2) - plug(:accepts, ["json"]) - plug(CheckApiV2) - plug(RateLimit) - end - - alias BlockScoutWeb.Account.Api.V1.{AuthenticateController, EmailController, TagsController, UserController} - alias BlockScoutWeb.API.V2 - - # TODO: Remove /account/v1 paths - scope "/account/v1", as: :account_v1 do - pipe_through(:api) - pipe_through(:account_api) - - get("/authenticate", AuthenticateController, :authenticate_get) - post("/authenticate", AuthenticateController, :authenticate_post) - - get("/get_csrf", UserController, :get_csrf) - - scope "/email" do - get("/resend", EmailController, :resend_email) - end - - scope "/user" do - get("/info", UserController, :info) - - get("/watchlist", UserController, :watchlist_old) - delete("/watchlist/:id", UserController, :delete_watchlist) - post("/watchlist", UserController, :create_watchlist) - put("/watchlist/:id", UserController, :update_watchlist) - - get("/api_keys", UserController, :api_keys) - delete("/api_keys/:api_key", UserController, :delete_api_key) - post("/api_keys", UserController, :create_api_key) - put("/api_keys/:api_key", UserController, :update_api_key) - - get("/custom_abis", UserController, :custom_abis) - delete("/custom_abis/:id", UserController, :delete_custom_abi) - post("/custom_abis", UserController, :create_custom_abi) - put("/custom_abis/:id", UserController, :update_custom_abi) - - get("/public_tags", UserController, :public_tags_requests) - delete("/public_tags/:id", UserController, :delete_public_tags_request) - post("/public_tags", UserController, :create_public_tags_request) - put("/public_tags/:id", UserController, :update_public_tags_request) - - scope "/tags" do - get("/address/", UserController, :tags_address_old) - get("/address/:id", UserController, :tags_address) - delete("/address/:id", UserController, :delete_tag_address) - post("/address/", UserController, :create_tag_address) - put("/address/:id", UserController, :update_tag_address) - - get("/transaction/", UserController, :tags_transaction_old) - get("/transaction/:id", UserController, :tags_transaction) - delete("/transaction/:id", UserController, :delete_tag_transaction) - post("/transaction/", UserController, :create_tag_transaction) - put("/transaction/:id", UserController, :update_tag_transaction) - end - end - end - - # TODO: Remove /account/v1 paths - scope "/account/v1" do - pipe_through(:api) - pipe_through(:account_api) - - scope "/tags" do - get("/address/:address_hash", TagsController, :tags_address) - - get("/transaction/:transaction_hash", TagsController, :tags_transaction) - end - end - - scope "/account/v2", as: :account_v2 do - pipe_through(:api) - pipe_through(:account_api) - - get("/authenticate", AuthenticateController, :authenticate_get) - post("/authenticate", AuthenticateController, :authenticate_post) - - get("/get_csrf", UserController, :get_csrf) - - scope "/email" do - get("/resend", EmailController, :resend_email) - end - - scope "/user" do - get("/info", UserController, :info) - - get("/watchlist", UserController, :watchlist) - delete("/watchlist/:id", UserController, :delete_watchlist) - post("/watchlist", UserController, :create_watchlist) - put("/watchlist/:id", UserController, :update_watchlist) - - get("/api_keys", UserController, :api_keys) - delete("/api_keys/:api_key", UserController, :delete_api_key) - post("/api_keys", UserController, :create_api_key) - put("/api_keys/:api_key", UserController, :update_api_key) - - get("/custom_abis", UserController, :custom_abis) - delete("/custom_abis/:id", UserController, :delete_custom_abi) - post("/custom_abis", UserController, :create_custom_abi) - put("/custom_abis/:id", UserController, :update_custom_abi) - - get("/public_tags", UserController, :public_tags_requests) - delete("/public_tags/:id", UserController, :delete_public_tags_request) - post("/public_tags", UserController, :create_public_tags_request) - put("/public_tags/:id", UserController, :update_public_tags_request) - - scope "/tags" do - get("/address/", UserController, :tags_address) - get("/address/:id", UserController, :tags_address) - delete("/address/:id", UserController, :delete_tag_address) - post("/address/", UserController, :create_tag_address) - put("/address/:id", UserController, :update_tag_address) - - get("/transaction/", UserController, :tags_transaction) - get("/transaction/:id", UserController, :tags_transaction) - delete("/transaction/:id", UserController, :delete_tag_transaction) - post("/transaction/", UserController, :create_tag_transaction) - put("/transaction/:id", UserController, :update_tag_transaction) - end - end - end - - scope "/account/v2" do - pipe_through(:api) - pipe_through(:account_api) - - scope "/tags" do - get("/address/:address_hash", TagsController, :tags_address) - - get("/transaction/:transaction_hash", TagsController, :tags_transaction) - end - end - - scope "/v2/import" do - pipe_through(:api_v2_no_session) - - post("/token-info", V2.ImportController, :import_token_info) - get("/smart-contracts/:address_hash_param", V2.ImportController, :try_to_search_contract) - end - - scope "/v2", as: :api_v2 do - pipe_through(:api_v2) - - scope "/search" do - get("/", V2.SearchController, :search) - get("/check-redirect", V2.SearchController, :check_redirect) - get("/quick", V2.SearchController, :quick_search) - end - - scope "/config" do - get("/json-rpc-url", V2.ConfigController, :json_rpc_url) - get("/backend-version", V2.ConfigController, :backend_version) - end - - scope "/transactions" do - get("/", V2.TransactionController, :transactions) - get("/watchlist", V2.TransactionController, :watchlist_transactions) - - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/zkevm-batch/:batch_number", V2.TransactionController, :zkevm_batch) - end - - if System.get_env("CHAIN_TYPE") == "zksync" do - get("/zksync-batch/:batch_number", V2.TransactionController, :zksync_batch) - end - - if System.get_env("CHAIN_TYPE") == "suave" do - get("/execution-node/:execution_node_hash_param", V2.TransactionController, :execution_node) - end - - get("/:transaction_hash_param", V2.TransactionController, :transaction) - get("/:transaction_hash_param/token-transfers", V2.TransactionController, :token_transfers) - get("/:transaction_hash_param/internal-transactions", V2.TransactionController, :internal_transactions) - get("/:transaction_hash_param/logs", V2.TransactionController, :logs) - get("/:transaction_hash_param/raw-trace", V2.TransactionController, :raw_trace) - get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes) - get("/:transaction_hash_param/summary", V2.TransactionController, :summary) - end - - scope "/blocks" do - get("/", V2.BlockController, :blocks) - get("/:block_hash_or_number", V2.BlockController, :block) - get("/:block_hash_or_number/transactions", V2.BlockController, :transactions) - get("/:block_hash_or_number/withdrawals", V2.BlockController, :withdrawals) - end - - scope "/addresses" do - get("/", V2.AddressController, :addresses_list) - get("/:address_hash_param", V2.AddressController, :address) - get("/:address_hash_param/tabs-counters", V2.AddressController, :tabs_counters) - get("/:address_hash_param/counters", V2.AddressController, :counters) - get("/:address_hash_param/token-balances", V2.AddressController, :token_balances) - get("/:address_hash_param/tokens", V2.AddressController, :tokens) - get("/:address_hash_param/transactions", V2.AddressController, :transactions) - get("/:address_hash_param/token-transfers", V2.AddressController, :token_transfers) - get("/:address_hash_param/internal-transactions", V2.AddressController, :internal_transactions) - get("/:address_hash_param/logs", V2.AddressController, :logs) - get("/:address_hash_param/blocks-validated", V2.AddressController, :blocks_validated) - get("/:address_hash_param/coin-balance-history", V2.AddressController, :coin_balance_history) - get("/:address_hash_param/coin-balance-history-by-day", V2.AddressController, :coin_balance_history_by_day) - get("/:address_hash_param/withdrawals", V2.AddressController, :withdrawals) - get("/:address_hash_param/nft", V2.AddressController, :nft_list) - get("/:address_hash_param/nft/collections", V2.AddressController, :nft_collections) - end - - scope "/tokens" do - get("/", V2.TokenController, :tokens_list) - get("/:address_hash_param", V2.TokenController, :token) - get("/:address_hash_param/counters", V2.TokenController, :counters) - get("/:address_hash_param/transfers", V2.TokenController, :transfers) - get("/:address_hash_param/holders", V2.TokenController, :holders) - get("/:address_hash_param/instances", V2.TokenController, :instances) - get("/:address_hash_param/instances/:token_id", V2.TokenController, :instance) - get("/:address_hash_param/instances/:token_id/transfers", V2.TokenController, :transfers_by_instance) - get("/:address_hash_param/instances/:token_id/holders", V2.TokenController, :holders_by_instance) - get("/:address_hash_param/instances/:token_id/transfers-count", V2.TokenController, :transfers_count_by_instance) - end - - scope "/main-page" do - get("/blocks", V2.MainPageController, :blocks) - get("/transactions", V2.MainPageController, :transactions) - get("/transactions/watchlist", V2.MainPageController, :watchlist_transactions) - get("/indexing-status", V2.MainPageController, :indexing_status) - - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/zkevm/batches/confirmed", V2.ZkevmController, :batches_confirmed) - get("/zkevm/batches/latest-number", V2.ZkevmController, :batch_latest_number) - end - - if System.get_env("CHAIN_TYPE") == "zksync" do - get("/zksync/batches/confirmed", V2.ZkSyncController, :batches_confirmed) - get("/zksync/batches/latest-number", V2.ZkSyncController, :batch_latest_number) - end - end - - scope "/stats" do - get("/", V2.StatsController, :stats) - - scope "/charts" do - get("/transactions", V2.StatsController, :transactions_chart) - get("/market", V2.StatsController, :market_chart) - end - end - - scope "/polygon-edge" do - if System.get_env("CHAIN_TYPE") == "polygon_edge" do - get("/deposits", V2.PolygonEdgeController, :deposits) - get("/deposits/count", V2.PolygonEdgeController, :deposits_count) - get("/withdrawals", V2.PolygonEdgeController, :withdrawals) - get("/withdrawals/count", V2.PolygonEdgeController, :withdrawals_count) - end - end - - scope "/withdrawals" do - get("/", V2.WithdrawalController, :withdrawals_list) - get("/counters", V2.WithdrawalController, :withdrawals_counters) - end - - scope "/zkevm" do - if System.get_env("CHAIN_TYPE") == "polygon_zkevm" do - get("/batches", V2.ZkevmController, :batches) - get("/batches/count", V2.ZkevmController, :batches_count) - get("/batches/:batch_number", V2.ZkevmController, :batch) - end - end - - scope "/proxy" do - scope "/noves-fi" do - get("/transactions/:transaction_hash_param", V2.Proxy.NovesFiController, :transaction) - get("/transactions/:transaction_hash_param/describe", V2.Proxy.NovesFiController, :describe_transaction) - get("/addresses/:address_hash_param/transactions", V2.Proxy.NovesFiController, :address_transactions) - end - end - - scope "/zksync" do - if System.get_env("CHAIN_TYPE") == "zksync" do - get("/batches", V2.ZkSyncController, :batches) - get("/batches/count", V2.ZkSyncController, :batches_count) - get("/batches/:batch_number", V2.ZkSyncController, :batch) - end - end - end - scope "/v1", as: :api_v1 do pipe_through(:api) alias BlockScoutWeb.API.{EthRPC, RPC, V1} alias BlockScoutWeb.API.V1.{GasPriceOracleController, HealthController} - alias BlockScoutWeb.API.V2.SearchController - - # leave the same endpoint in v1 in order to keep backward compatibility - get("/search", SearchController, :search) - - @max_complexity 200 - forward("/graphql", Absinthe.Plug, - schema: BlockScoutWeb.Schema, - analyze_complexity: true, - max_complexity: @max_complexity - ) - - get("/transactions-csv", AddressTransactionController, :transactions_csv) - - get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv) - - get("/internal-transactions-csv", AddressTransactionController, :internal_transactions_csv) - - get("/logs-csv", AddressTransactionController, :logs_csv) - - scope "/health" do - get("/", HealthController, :health) - get("/liveness", HealthController, :liveness) - get("/readiness", HealthController, :readiness) - end + get("/health", HealthController, :health) get("/gas-price-oracle", GasPriceOracleController, :gas_price_oracle) - if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do get("/supply", V1.SupplyController, :supply) post("/eth-rpc", EthRPC.EthController, :eth_request) end - if Application.compile_env(:block_scout_web, __MODULE__)[:writing_enabled] do + if Application.get_env(:block_scout_web, __MODULE__)[:writing_enabled] do post("/decompiled_smart_contract", V1.DecompiledSmartContractController, :create) post("/verified_smart_contracts", V1.VerifiedSmartContractController, :create) end - if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do forward("/", RPC.RPCTranslator, %{ "block" => {RPC.BlockController, []}, "account" => {RPC.AddressController, []}, @@ -388,7 +55,7 @@ defmodule BlockScoutWeb.ApiRouter do pipe_through(:api) alias BlockScoutWeb.API.{EthRPC, RPC} - if Application.compile_env(:block_scout_web, __MODULE__)[:reading_enabled] do + if Application.get_env(:block_scout_web, __MODULE__)[:reading_enabled] do post("/eth-rpc", EthRPC.EthController, :eth_request) forward("/", RPCTranslatorForwarder, %{ diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex index 1c99dcb17136..7b70b4b4276f 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex @@ -30,8 +30,8 @@ defmodule BlockScoutWeb.API.V1.GasPriceOracleController do |> send_resp(status, result) end - defp result(gas_prices) do - %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} + def result(gas_prices) do + gas_prices |> Jason.encode!() end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex index eabc844c7d01..34174a288572 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex @@ -3,45 +3,44 @@ defmodule BlockScoutWeb.ChainController do import BlockScoutWeb.Chain, only: [paging_options: 1] - alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.{ChainView, Controller} alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.Address.Counters alias Explorer.Chain.{Address, Block, Transaction} - alias Explorer.Chain.Cache.Block, as: BlockCache - alias Explorer.Chain.Cache.GasUsage - alias Explorer.Chain.Cache.Transaction, as: TransactionCache - alias Explorer.Chain.Search - alias Explorer.Chain.Supply.RSK + alias Explorer.Chain.Supply.{RSK, TokenBridge} + alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime + alias Explorer.ExchangeRates.Token alias Explorer.Market alias Phoenix.View def show(conn, _params) do - transaction_estimated_count = TransactionCache.estimated_count() - total_gas_usage = GasUsage.total() - block_count = BlockCache.estimated_count() - address_count = Counters.address_estimated_count() + transaction_estimated_count = Chain.transaction_estimated_count() + # total_gas_usage = Chain.total_gas_usage() + block_count = Chain.block_estimated_count() + address_count = Chain.address_estimated_count() market_cap_calculation = case Application.get_env(:explorer, :supply) do RSK -> RSK + TokenBridge -> + TokenBridge + _ -> :standard end - exchange_rate = Market.get_coin_exchange_rate() + exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() - transaction_stats = Helper.get_transaction_stats() + transaction_stats = get_transaction_stats() chart_data_paths = %{ market: market_history_chart_path(conn, :show), transaction: transaction_history_chart_path(conn, :show) } - chart_config = Application.get_env(:block_scout_web, :chart)[:chart_config] + chart_config = Application.get_env(:block_scout_web, :chart_config, %{}) render( conn, @@ -54,7 +53,7 @@ defmodule BlockScoutWeb.ChainController do chart_data_paths: chart_data_paths, market_cap_calculation: market_cap_calculation, transaction_estimated_count: transaction_estimated_count, - total_gas_usage: total_gas_usage, + # total_gas_usage: total_gas_usage, transactions_path: recent_transactions_path(conn, :index), transaction_stats: transaction_stats, block_count: block_count, @@ -62,6 +61,25 @@ defmodule BlockScoutWeb.ChainController do ) end + def get_transaction_stats do + stats_scale = date_range(1) + transaction_stats = TransactionStats.by_date_range(stats_scale.earliest, stats_scale.latest) + + # Need datapoint for legend if none currently available. + if Enum.empty?(transaction_stats) do + [%{number_of_transactions: 0, gas_used: 0}] + else + transaction_stats + end + end + + def date_range(num_days) do + today = Date.utc_today() + latest = Date.add(today, -1) + x_days_back = Date.add(latest, -1 * (num_days - 1)) + %{earliest: x_days_back, latest: latest} + end + def search(conn, %{"q" => ""}) do show(conn, []) end @@ -92,7 +110,7 @@ defmodule BlockScoutWeb.ChainController do results = paging_options - |> Search.joint_search(offset, term) + |> search_by(offset, term) encoded_results = results @@ -126,6 +144,10 @@ defmodule BlockScoutWeb.ChainController do json(conn, "{}") end + def search_by(paging_options, offset, term) do + Chain.joint_search(paging_options, offset, term) + end + def chain_blocks(conn, _params) do if ajax?(conn) do blocks = diff --git a/apps/block_scout_web/lib/block_scout_web/csp_header.ex b/apps/block_scout_web/lib/block_scout_web/csp_header.ex index 10e01eec7c7a..ea5a588f6880 100644 --- a/apps/block_scout_web/lib/block_scout_web/csp_header.ex +++ b/apps/block_scout_web/lib/block_scout_web/csp_header.ex @@ -9,24 +9,16 @@ defmodule BlockScoutWeb.CSPHeader do def init(opts), do: opts def call(conn, _opts) do - config = Application.get_env(:block_scout_web, __MODULE__) - google_url = "https://www.google.com" - czilladx_url = "https://request-global.czilladx.com" - coinzillatag_url = "https://coinzillatag.com" - trustwallet_url = "https://raw.githubusercontent.com/trustwallet/assets/" - walletconnect_urls = "wss://*.bridge.walletconnect.org https://registry.walletconnect.org/data/wallets.json" - json_rpc_url = Application.get_env(:block_scout_web, :json_rpc) - Controller.put_secure_browser_headers(conn, %{ "content-security-policy" => "\ - connect-src 'self' #{json_rpc_url} #{config[:mixpanel_url]} #{config[:amplitude_url]} #{websocket_endpoints(conn)} #{czilladx_url} #{trustwallet_url} #{walletconnect_urls};\ + connect-src 'self' #{websocket_endpoints(conn)} wss://*.bridge.walletconnect.org/ https://request-global.czilladx.com/ https://raw.githubusercontent.com/trustwallet/assets/ https://www.google-analytics.com/ https://stats.g.doubleclick.net/j/collect https://registry.walletconnect.org/data/wallets.json https://*.poa.network;\ default-src 'self';\ - script-src 'self' 'unsafe-inline' 'unsafe-eval' #{coinzillatag_url} #{google_url} https://www.gstatic.com;\ + script-src 'self' 'unsafe-inline' 'unsafe-eval' https://coinzillatag.com https://www.google.com https://www.gstatic.com https://www.googletagmanager.com/ https://www.google-analytics.com/;\ style-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com;\ img-src 'self' * data:;\ media-src 'self' * data:;\ font-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.gstatic.com data:;\ - frame-src 'self' 'unsafe-inline' 'unsafe-eval' #{czilladx_url} #{google_url};\ + frame-src 'self' 'unsafe-inline' 'unsafe-eval' https://request-global.czilladx.com/ https://www.google.com;\ " }) end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex index 21afb575a58f..ab74728d69de 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_verify_other_explorers.html.eex @@ -1,21 +1,21 @@
-
+

Verify with other Explorers:

- <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherscan.io", class: "etherscan", address_link: "https://etherscan.io/address/", tx_link: "https://etherscan.io/tx/" %> <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Blockchair.com", class: "blockchair", address_link: "https://blockchair.com/ethereum/address/", tx_link: "https://blockchair.com/ethereum/transaction/" %> + <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherscan.io", class: "etherscan", address_link: "https://etherscan.io/address/", tx_link: "https://etherscan.io/tx/" %> <%= render "_verify_other_explorer.html", hash: @hash, type: @type, header: "Etherchain.org", class: "etherchain", address_link: "https://www.etherchain.org/account/", tx_link: "https://www.etherchain.org/tx/" %> - +
- diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index 2b8ba6e6aa23..f1e6e08031bc 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -42,9 +42,7 @@

<%= @address %>

- + <%= render "_verify_other_explorers.html", hash: @address.hash, type: "address" %> <% address_name = primary_name(@address) %> <%= cond do %> <% @address.token -> %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex index 882e3dda3970..15d29ee20146 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex @@ -100,17 +100,6 @@ <%= BlockScoutWeb.Cldr.Number.to_string!(@transaction_estimated_count, format: "#,###") %> - <%= if @total_gas_usage > 0 do %> -
" - class="custom-tooltip-total-transactions"> - - - <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 34769f0f0828..8d7a7edd21fd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -62,6 +62,36 @@ 'Tx/day': '<%= gettext("Tx/day") %>' } + + + + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex index 72d19629de38..df4e38d853cd 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex @@ -1,12 +1,12 @@ -
+
-
+
\ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex index c43c0e243f00..167a5b8d6e66 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex @@ -1,4 +1,4 @@ -
+
@@ -10,8 +10,8 @@
<%= link( - @validator.block.hash, - to: block_path(BlockScoutWeb.Endpoint, :show, @validator.block.hash), + @validator.block_hash, + to: block_path(BlockScoutWeb.Endpoint, :show, @validator.block_hash), class: "text-truncate") %> <%= @emission_funds |> BlockScoutWeb.AddressView.address_partial_selector(nil, @current_address) |> BlockScoutWeb.RenderHelper.render_partial() %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index d408342b6469..27aab51cfd4e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -88,8 +88,6 @@ title: gettext("Copy Txn Hash") %> - -
diff --git a/apps/block_scout_web/lib/block_scout_web/views/currency_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/currency_helper.ex index 3093509ef591..15832749a90a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/currency_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/currency_helper.ex @@ -1,4 +1,4 @@ -defmodule BlockScoutWeb.CurrencyHelper do +defmodule BlockScoutWeb.CurrencyHelpers do @moduledoc """ Helper functions for interacting with `t:BlockScoutWeb.ExchangeRates.USD.t/0` values. """ @@ -10,16 +10,10 @@ defmodule BlockScoutWeb.CurrencyHelper do ## Examples - iex> BlockScoutWeb.CurrencyHelper.format_integer_to_currency(1000000) + iex> BlockScoutWeb.CurrencyHelpers.format_integer_to_currency(1000000) "1,000,000" """ - @spec format_integer_to_currency(non_neg_integer() | nil) :: String.t() - def format_integer_to_currency(value) - - def format_integer_to_currency(nil) do - "-" - end - + @spec format_integer_to_currency(non_neg_integer()) :: String.t() def format_integer_to_currency(value) do {:ok, formatted} = Number.to_string(value, format: "#,##0") @@ -75,12 +69,17 @@ defmodule BlockScoutWeb.CurrencyHelper do @spec format_according_to_decimals(Decimal.t(), Decimal.t()) :: String.t() def format_according_to_decimals(value, decimals) do - if Decimal.compare(decimals, 24) == :gt do - format_according_to_decimals(value, Decimal.new(18)) - else - value - |> divide_decimals(decimals) - |> thousands_separator() + cond do + decimals == Decimal.new(0) -> + value + + Decimal.cmp(decimals, 24) == :gt -> + format_according_to_decimals(value, Decimal.new(18)) + + true -> + value + |> divide_decimals(decimals) + |> thousands_separator() end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 44df48cb6dc2..f019c7d5d57d 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -1,10 +1,9 @@ defmodule BlockScoutWeb.TransactionView do use BlockScoutWeb, :view - alias BlockScoutWeb.{AccessHelper, AddressView, BlockView, TabHelper} - alias BlockScoutWeb.Account.AuthController + alias BlockScoutWeb.{AccessHelpers, AddressView, BlockView, TabHelpers} alias BlockScoutWeb.Cldr.Number - alias Explorer.{Chain, CustomContractsHelper, Repo} + alias Explorer.{Chain, CustomContractsHelpers, Repo} alias Explorer.Chain.Block.Reward alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction, Wei} alias Explorer.Counters.AverageBlockTime @@ -12,10 +11,10 @@ defmodule BlockScoutWeb.TransactionView do alias Timex.Duration import BlockScoutWeb.Gettext - import BlockScoutWeb.AddressView, only: [from_address_hash: 1, short_token_id: 2, tag_name_to_label: 1] - import BlockScoutWeb.Tokens.Helper + import BlockScoutWeb.AddressView, only: [from_address_hash: 1, short_token_id: 2] + import BlockScoutWeb.Tokens.Helpers - @tabs ["token-transfers", "internal-transactions", "logs", "raw-trace", "state"] + @tabs ["token-transfers", "internal-transactions", "logs", "raw-trace"] @token_burning_title "Token Burning" @token_minting_title "Token Minting" @@ -32,13 +31,13 @@ defmodule BlockScoutWeb.TransactionView do defdelegate formatted_timestamp(block), to: BlockView def block_number(%Transaction{block_number: nil}), do: gettext("Block Pending") - - def block_number(%Transaction{block_number: number, block_hash: hash}), - do: [view_module: BlockView, partial: "_link.html", block: %Block{number: number, hash: hash}] + def block_number(%Transaction{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] def block_number(%Reward{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] - def block_timestamp(%Transaction{} = transaction), do: Transaction.block_timestamp(transaction) + def block_timestamp(%Transaction{block_number: nil, inserted_at: time}), do: time + def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time + def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time def value_transfer?(%Transaction{input: %{bytes: bytes}}) when bytes in [<<>>, nil] do @@ -64,10 +63,6 @@ defmodule BlockScoutWeb.TransactionView do if type, do: {type, transaction_with_transfers_filtered}, else: {nil, transaction_with_transfers_filtered} end - def transaction_actions(transaction) do - Repo.preload(transaction, :transaction_actions) - end - def aggregate_token_transfers(token_transfers) do %{ transfers: {ft_transfers, nft_transfers}, @@ -147,7 +142,8 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: nil, amounts: [], - token_ids: token_transfer.token_ids, + token_id: token_transfer.token_id, + token_ids: [], to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -161,6 +157,7 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: nil, amounts: amounts, + token_id: nil, token_ids: token_transfer.token_ids, to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash @@ -174,7 +171,8 @@ defmodule BlockScoutWeb.TransactionView do token: token_transfer.token, amount: token_transfer.amount, amounts: [], - token_ids: token_transfer.token_ids, + token_id: token_transfer.token_id, + token_ids: [], to_address_hash: token_transfer.to_address_hash, from_address_hash: token_transfer.from_address_hash } @@ -191,7 +189,18 @@ defmodule BlockScoutWeb.TransactionView do if existing_entry do acc1 |> Enum.map(fn entry -> - process_entry(entry, new_entry, token_transfer) + if entry.to_address_hash == token_transfer.to_address_hash && + entry.from_address_hash == token_transfer.from_address_hash && + entry.token == token_transfer.token do + updated_entry = %{ + entry + | amount: Decimal.add(new_entry.amount, entry.amount) + } + + updated_entry + else + entry + end end) else [new_entry | acc1] @@ -200,21 +209,6 @@ defmodule BlockScoutWeb.TransactionView do {new_acc1, acc2} end - def process_entry(entry, new_entry, token_transfer) do - if entry.to_address_hash == token_transfer.to_address_hash && - entry.from_address_hash == token_transfer.from_address_hash && - entry.token == token_transfer.token do - updated_entry = %{ - entry - | amount: Decimal.add(new_entry.amount, entry.amount) - } - - updated_entry - else - entry - end - end - def token_type_name(type) do case type do :erc20 -> gettext("ERC-20 ") @@ -284,7 +278,7 @@ defmodule BlockScoutWeb.TransactionView do case block do %Block{consensus: true} -> {:ok, confirmations} = Chain.confirmations(block, named_arguments) - Number.to_string!(confirmations, format: "#,###") + BlockScoutWeb.Cldr.Number.to_string!(confirmations, format: "#,###") _ -> 0 @@ -328,26 +322,12 @@ defmodule BlockScoutWeb.TransactionView do end end - def formatted_action_amount(data, field_name) do - data - |> Map.get(field_name) - |> Decimal.new() - |> Number.to_string!(format: "#,##0.##################") - end - - def transaction_action_string_to_address(address) do - case Chain.string_to_address_hash(address) do - {:ok, address_hash} -> Chain.hash_to_address(address_hash) - _ -> {:error, nil} - end - end - def transaction_status(transaction) do Chain.transaction_to_status(transaction) end - def transaction_revert_reason(transaction, options \\ []) do - transaction |> Chain.transaction_to_revert_reason() |> decoded_revert_reason(transaction, options) + def transaction_revert_reason(transaction) do + transaction |> Chain.transaction_to_revert_reason() |> decoded_revert_reason(transaction) end def get_pure_transaction_revert_reason(nil), do: nil @@ -383,7 +363,7 @@ defmodule BlockScoutWeb.TransactionView do end def gas(%type{gas: gas}) when is_transaction_type(type) do - Number.to_string!(gas) + BlockScoutWeb.Cldr.Number.to_string!(gas) end def skip_decoding?(transaction) do @@ -391,12 +371,11 @@ defmodule BlockScoutWeb.TransactionView do end def decoded_input_data(transaction) do - {result, _, _} = Transaction.decoded_input_data(transaction, []) - result + Transaction.decoded_input_data(transaction) end - def decoded_revert_reason(revert_reason, transaction, options) do - Transaction.decoded_revert_reason(transaction, revert_reason, options) + def decoded_revert_reason(revert_reason, transaction) do + Transaction.decoded_revert_reason(transaction, revert_reason) end @doc """ @@ -406,26 +385,16 @@ defmodule BlockScoutWeb.TransactionView do format_wei_value(gas_price, unit) end - def l1_gas_price(%Transaction{l1_gas_price: gas_price}, unit) when unit in ~w(wei gwei ether)a do - format_wei_value(gas_price, unit) - end - def gas_used(%Transaction{gas_used: nil}), do: gettext("Pending") def gas_used(%Transaction{gas_used: gas_used}) do Number.to_string!(gas_used) end - def l1_gas_used(%Transaction{l1_gas_used: nil}), do: gettext("Pending") - - def l1_gas_used(%Transaction{l1_gas_used: gas_used}) do - Number.to_string!(gas_used) - end - def gas_used_perc(%Transaction{gas_used: nil}), do: nil def gas_used_perc(%Transaction{gas_used: gas_used, gas: gas}) do - if Decimal.compare(gas, 0) == :gt do + if Decimal.cmp(gas, 0) == :gt do gas_used |> Decimal.div(gas) |> Decimal.mult(100) @@ -537,7 +506,7 @@ defmodule BlockScoutWeb.TransactionView do """ def current_tab_name(request_path) do @tabs - |> Enum.filter(&TabHelper.tab_active?(&1, request_path)) + |> Enum.filter(&TabHelpers.tab_active?(&1, request_path)) |> tab_name() end @@ -545,7 +514,6 @@ defmodule BlockScoutWeb.TransactionView do defp tab_name(["internal-transactions"]), do: gettext("Internal Transactions") defp tab_name(["logs"]), do: gettext("Logs") defp tab_name(["raw-trace"]), do: gettext("Raw Trace") - defp tab_name(["state"]), do: gettext("State changes") defp get_transaction_type_from_token_transfers(token_transfers) do token_transfers_types = @@ -572,7 +540,7 @@ defmodule BlockScoutWeb.TransactionView do end defp show_tenderly_link? do - Application.get_env(:block_scout_web, :show_tenderly_link) + System.get_env("SHOW_TENDERLY_LINK") == "true" end defp tenderly_chain_path do @@ -580,7 +548,7 @@ defmodule BlockScoutWeb.TransactionView do end def get_max_length do - string_value = Application.get_env(:block_scout_web, :contract)[:max_length_to_show_string_without_trimming] + string_value = Application.get_env(:block_scout_web, :max_length_to_show_string_without_trimming) case Integer.parse(string_value) do {integer, ""} -> integer @@ -601,25 +569,25 @@ defmodule BlockScoutWeb.TransactionView do end # Function decodes revert reason of the transaction - @spec decoded_revert_reason(Transaction.t() | nil) :: binary() | nil + @spec decoded_revert_reason(%Transaction{} | nil) :: binary() | nil def decoded_revert_reason(transaction) do revert_reason = get_pure_transaction_revert_reason(transaction) case revert_reason do "0x" <> hex_part -> - process_hex_revert_reason(hex_part) + proccess_hex_revert_reason(hex_part) hex_part -> - process_hex_revert_reason(hex_part) + proccess_hex_revert_reason(hex_part) end end # Function converts hex revert reason to the binary - @spec process_hex_revert_reason(nil) :: nil - defp process_hex_revert_reason(nil), do: nil + @spec proccess_hex_revert_reason(nil) :: nil + defp proccess_hex_revert_reason(nil), do: nil - @spec process_hex_revert_reason(binary()) :: binary() - defp process_hex_revert_reason(hex_revert_reason) do + @spec proccess_hex_revert_reason(binary()) :: binary() + defp proccess_hex_revert_reason(hex_revert_reason) do case Integer.parse(hex_revert_reason, 16) do {number, ""} -> :binary.encode_unsigned(number) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 38e44c6d5c9d..e7b3b0bc1d55 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -1,3684 +1,3321 @@ +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:6 +msgid "%{blocks} block" +msgid_plural "%{blocks} blocks" +msgstr[0] "" +msgstr[1] "" + +#, elixir-format #: lib/block_scout_web/views/address_token_balance_view.ex:10 -#, elixir-autogen, elixir-format msgid "%{count} token" msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:29 -#, elixir-autogen, elixir-format msgid "%{count} transaction" msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 -#, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:43 +msgid " Working Stake Amount is an amount which is accounted and working at the current staking epoch." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 -#, elixir-autogen, elixir-format msgid " is recommended." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:12 -#, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:55 -#, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:7 -#, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:85 -#, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 -#, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:101 -#, elixir-autogen, elixir-format -msgid "%{qty} of Token ID [%{link_to_id}]" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 -#, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 -#, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_metatags.html.eex:2 +msgid "%{subnetwork} Staking DApp - BlockScout" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:351 msgid "(Awaiting internal transactions for status)" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:26 +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:27 +msgid "(inactive pool)" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 -#, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 -#, elixir-autogen, elixir-format -msgid ") may be added for each contract. Click the Add Library button to add an additional one." -msgstr "" - -#: lib/block_scout_web/templates/layout/app.html.eex:93 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:192 msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 -#, elixir-autogen, elixir-format -msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 -#, elixir-autogen, elixir-format -msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 -#, elixir-autogen, elixir-format -msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 -#, elixir-autogen, elixir-format -msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:195 -#, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 -#, elixir-autogen, elixir-format -msgid "A block producer who successfully included the block onto the blockchain." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:76 +msgid "

Pending stake (stake placed on a candidate pool or placed during the current staking epoch) may be withdrawn now.

\n

Active stake (stake available after the current epoch) can be ordered for withdrawal from the pool, and will be available to claim after the current staking epoch is complete.

\n

If you have already ordered (and the staking window is still open), you may increase your current order by entering a positive value, or decrease your current order by entering a negative value in the box and clicking 'Order Withdrawal'. You must either keep the minimum stake amount in the pool, or order your entire stake for withdrawal.

\n" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "A confirmation email was sent to" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:36 +msgid "

To become a candidate, your staking address must be funded with %{tokenSymbol} tokens and %{coinSymbol} coins, and your OpenEthereum node must be active and configured with the mining address you specify here.

\n

To become a delegator, close this window and select an address from the list of pools you would like to place stake on. Click the Stake button next to the address to begin the process.

" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 -#, elixir-autogen, elixir-format -msgid "A library name called in the .sol file. Multiple libraries (up to " +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:97 +msgid "A block producer who successfully included the block onto the blockchain." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 -#, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 -#, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "ABI" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:62 msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 -#, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 -#, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 -#, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 -#, elixir-autogen, elixir-format -msgid "API key" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:124 +msgid "APIs" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 -#, elixir-autogen, elixir-format -msgid "API keys" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:39 +msgid "APY" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:106 -#, elixir-autogen, elixir-format -msgid "APIs" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:79 +msgid "APY & Predicted Reward" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#, elixir-autogen, elixir-format msgid "Action" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 -#, elixir-autogen, elixir-format -msgid "Actions" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:451 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:423 msgid "Actual gas amount used by the transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:44 msgid "Add" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 -#, elixir-autogen, elixir-format -msgid "Add API key" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 -#, elixir-autogen, elixir-format -msgid "Add Contract Libraries" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 -#, elixir-autogen, elixir-format -msgid "Add Custom ABI" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 -#, elixir-autogen, elixir-format -msgid "Add Library" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Add address" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Add address tag" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Add address to the Watch list" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Add transaction tag" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 -#: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 -#: lib/block_scout_web/views/address_view.ex:109 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:243 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:211 msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:191 msgid "Address (external or contract) sending the transaction." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 -#, elixir-autogen, elixir-format -msgid "Address Tags" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:164 +msgid "Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:149 -#, elixir-autogen, elixir-format -msgid "Address balance in" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:65 msgid "Address of the token contract" msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Address used in token mintings and burnings." -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Address*" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Addresses" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:35 -#, elixir-autogen, elixir-format -msgid "Age" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:88 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 -#: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_transaction_view.ex:11 -#: lib/block_scout_web/views/verified_contracts_view.ex:13 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 lib/block_scout_web/templates/address_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 lib/block_scout_web/templates/layout/_topnav.html.eex:73 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/views/address_token_transfer_view.ex:11 lib/block_scout_web/views/address_transaction_view.ex:11 msgid "All" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 -#, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:174 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:27 +msgid "All pool participant addresses. The top address belongs to the %{pool_type}." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:189 msgid "All tokens in the account and total value." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:27 +msgid "Already Ordered:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:10 lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:14 +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:11 lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:11 msgid "Amount" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:409 msgid "Amount of" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:236 -#, elixir-autogen, elixir-format -msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:41 +msgid "Amount of %{symbol} placed by an address." msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:236 +msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 -#, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:134 -#, elixir-autogen, elixir-format -msgid "Apps" -msgstr "" - -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Average" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:101 -#, elixir-autogen, elixir-format -msgid "Average block time" -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 -#, elixir-autogen, elixir-format -msgid "Back to API keys (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:80 +msgid "Approximate Current Annual Percentage Yield. If you see N/A, please reopen the popup in a few blocks (APY cannot be calculated at the very beginning of a staking epoch). Predicted Reward is the amount of %{symbol} a participant will receive for staking and can claim once the current epoch ends." msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Back to Address Tags (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:39 +msgid "Approximate Current Annual Percentage Yield. If you see N/A, please wait for a few blocks (APY cannot be calculated at the very beginning of a staking epoch)." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 -#, elixir-autogen, elixir-format -msgid "Back to Custom ABI (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:152 +msgid "Apps" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Back to Transaction Tags (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:48 +msgid "Available Now:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 -#, elixir-autogen, elixir-format -msgid "Back to Watch list (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:123 +msgid "Average block time" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:9 -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 -#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Back to home" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 +msgid "Back Home" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 -#: lib/block_scout_web/templates/address/overview.html.eex:150 -#: lib/block_scout_web/templates/address_token/overview.html.eex:51 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:4 +#: lib/block_scout_web/templates/address/overview.html.eex:165 lib/block_scout_web/templates/address_token/overview.html.eex:51 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:42 lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:43 msgid "Balance" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:40 -#, elixir-autogen, elixir-format -msgid "Balance after" +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +msgid "Balances" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Balance before" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:32 +msgid "Banned" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Balances" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:46 +msgid "Banned until block #%{banned_until} (%{estimated_unban_day})" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:207 -#, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Beacon chain withdrawals - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:13 +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:5 +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:33 lib/block_scout_web/templates/stakes/_stakes_top.html.eex:24 +msgid "Become a Candidate" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:472 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:444 msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/block/overview.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:29 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:26 lib/block_scout_web/templates/transaction/overview.html.eex:150 msgid "Block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 -#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 msgid "Block #%{number}" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 -#, elixir-autogen, elixir-format msgid "Block Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:53 -#, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:46 msgid "Block Mined, awaiting import..." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:34 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:33 msgid "Block Pending" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:158 -#, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:15 -#, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:160 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:5 +msgid "Block number" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:149 msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:257 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:272 msgid "Block number in which the address was updated." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 -#, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 -#, elixir-autogen, elixir-format -msgid "Blockchain" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:154 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:165 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:24 lib/block_scout_web/templates/layout/_topnav.html.eex:28 msgid "Blocks" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:45 msgid "Blocks Indexed" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:56 -#: lib/block_scout_web/templates/address/overview.html.eex:275 -#: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:386 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:48 +#: lib/block_scout_web/templates/address/overview.html.eex:289 lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:356 msgid "Blocks Validated" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:48 -#, elixir-autogen, elixir-format -msgid "Blocks With Internal Transactions Indexed" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:22 -#, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Burn address" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:45 +msgid "Bridge STAKE to Ethereum" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/bridged_tokens/index.html.eex:7 +msgid "Bridged Tokens from " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 +msgid "Bridged from BSC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Bridged from Ethereum" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:64 #: lib/block_scout_web/templates/block/overview.html.eex:216 -#, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:65 -#, elixir-autogen, elixir-format -msgid "CRC Worth" -msgstr "" - -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 msgid "CSV" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #: lib/block_scout_web/views/internal_transaction_view.ex:21 -#, elixir-autogen, elixir-format msgid "Call" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:22 -#, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:62 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:231 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:48 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:60 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:85 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 -#, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:43 -#, elixir-autogen, elixir-format -msgid "Change" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:10 +msgid "Candidate and Validator Pool Addresses. Current validator pools are specified by a checkmark." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:47 +msgid "Candidate’s Staked Amount" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Change Network" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:41 msgid "Chat (#blockscout)" msgstr "" -#: lib/block_scout_web/views/block_view.ex:65 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:6 +msgid "Choose Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:22 +msgid "Choose Target Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:63 msgid "Chore Reward" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Circulating Market Cap" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:7 +msgid "Claim Ordered Withdraw" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:6 +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:59 lib/block_scout_web/templates/stakes/_stakes_top.html.eex:30 +msgid "Claim Reward" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:40 +msgid "Claim for" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:18 +msgid "Claim the Amount" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 -#, elixir-autogen, elixir-format msgid "Clear" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 msgid "Close" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:66 -#: 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/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:379 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 +#: 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/_eth_rpc_item.html.eex:126 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 +#: lib/block_scout_web/views/address_view.ex:349 msgid "Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:385 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:355 msgid "Coin Balance History" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 -#, elixir-autogen, elixir-format msgid "Collapse" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Company name" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:32 -#, elixir-autogen, elixir-format -msgid "Company website" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:40 msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:142 -#, elixir-autogen, elixir-format -msgid "Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:71 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:65 msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:364 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:344 msgid "Confirmed" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:127 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:116 msgid "Confirmed by " msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:182 msgid "Confirmed within" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:11 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:6 lib/block_scout_web/templates/tokens/holder/index.html.eex:15 msgid "Connection Lost" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 -#: lib/block_scout_web/templates/transaction/index.html.eex:22 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 lib/block_scout_web/templates/transaction/index.html.eex:22 msgid "Connection Lost, click to load newer transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:96 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:90 msgid "Constructor Arguments" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 -#, elixir-autogen, elixir-format -msgid "Constructor args" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:221 msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:157 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:126 msgid "Contract ABI" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:107 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:23 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:18 lib/block_scout_web/views/address_view.ex:102 msgid "Contract Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:47 -#: lib/block_scout_web/views/address_view.ex:81 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/address_view.ex:42 lib/block_scout_web/views/address_view.ex:76 msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:480 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:459 msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:477 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:456 msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:174 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:142 +#: lib/block_scout_web/templates/address_contract/index.html.eex:157 msgid "Contract Creation Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:100 msgid "Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:75 +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:91 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:29 msgid "Contract Name" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 -#, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 -#, elixir-autogen, elixir-format -msgid "Contract name or address" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:63 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:57 msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:100 msgid "Contract source code" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 -#, elixir-autogen, elixir-format -msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Contracts" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:180 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:148 msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:40 msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:159 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:128 msgid "Copy ABI" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Copy API key" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:39 -#: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:38 lib/block_scout_web/templates/block/overview.html.eex:104 +#: lib/block_scout_web/templates/block/overview.html.eex:105 lib/block_scout_web/templates/tokens/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 msgid "Copy Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:144 -#, elixir-autogen, elixir-format -msgid "Copy Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Copy Contract Address" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:176 -#: lib/block_scout_web/templates/address_contract/index.html.eex:192 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:160 msgid "Copy Contract Creation Code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "Copy Decompiled Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:223 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#: lib/block_scout_web/templates/address_contract/index.html.eex:197 msgid "Copy Deployed ByteCode" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 -#: lib/block_scout_web/templates/transaction/overview.html.eex:234 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:14 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:15 lib/block_scout_web/templates/transaction/overview.html.eex:201 +#: lib/block_scout_web/templates/transaction/overview.html.eex:202 msgid "Copy From Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 -#, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 -#, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 -#, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 msgid "Copy Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:132 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:102 +#: lib/block_scout_web/templates/address_contract/index.html.eex:115 msgid "Copy Source Code" msgstr "" -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:260 -#: lib/block_scout_web/templates/transaction/overview.html.eex:261 -#: lib/block_scout_web/templates/transaction/overview.html.eex:268 -#: lib/block_scout_web/templates/transaction/overview.html.eex:269 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:31 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:32 lib/block_scout_web/templates/transaction/overview.html.eex:230 +#: lib/block_scout_web/templates/transaction/overview.html.eex:231 lib/block_scout_web/templates/transaction/overview.html.eex:240 +#: lib/block_scout_web/templates/transaction/overview.html.eex:241 msgid "Copy To Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 -#, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:87 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:81 msgid "Copy Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:82 msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:498 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:470 msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:504 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:476 msgid "Copy Txn UTF-8 Input" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:497 -#: lib/block_scout_web/templates/transaction/overview.html.eex:503 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:475 msgid "Copy Value" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:25 -#, elixir-autogen, elixir-format msgid "Create" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 -#, elixir-autogen, elixir-format -msgid "Create a Custom ABI to interact with contracts." -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 -#, elixir-autogen, elixir-format -msgid "Create an API key to use with your RPC и EthRPC API requests." -msgstr "" - +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:26 -#, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:102 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:118 msgid "Creator" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 -#, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:97 -#, elixir-autogen, elixir-format -msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:55 +msgid "Current Reward Share" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:63 +msgid "Current Reward Share is calculated based on the Working Stake Amount." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Custom" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:35 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:37 +msgid "Current Stake Amount" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Custom ABI" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:36 +msgid "Current Stake:" msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Custom ABI from account" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:89 +msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:70 -#, elixir-autogen, elixir-format -msgid "Daily Transactions" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_metatags.html.eex:6 +msgid "DApp for Staking %{symbol} tokens" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:120 msgid "Data" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:70 -#, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:179 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:168 msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:145 msgid "Decimals" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:33 lib/block_scout_web/templates/transaction_log/_logs.html.eex:41 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 lib/block_scout_web/templates/transaction_log/_logs.html.eex:72 msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:380 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_view.ex:350 msgid "Decompiled Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:83 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:75 msgid "Decompiled code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "Decompiled contract code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Decompiler version" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:23 -#, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:211 -#: lib/block_scout_web/templates/address_contract/index.html.eex:219 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:31 +#: lib/block_scout_web/templates/stakes/_table.html.eex:43 +msgid "Delegators" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:6 +msgid "Delegators of " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:53 +msgid "Delegators’ Staked Amount" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:185 +#: lib/block_scout_web/templates/address_contract/index.html.eex:193 msgid "Deployed ByteCode" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 -#, elixir-autogen, elixir-format msgid "Description" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:56 -#, elixir-autogen, elixir-format -msgid "Description*" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:30 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 msgid "Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:159 -#, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:149 msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 -#: lib/block_scout_web/templates/csv_export/index.html.eex:33 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/csv_export/index.html.eex:25 msgid "Download" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 -#, elixir-autogen, elixir-format -msgid "Drop all Solidity contract source files into the drop zone." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 -#, elixir-autogen, elixir-format -msgid "Drop all Solidity or Yul contract source files into the drop zone." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:26 msgid "Drop sources and metadata JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 -#, elixir-autogen, elixir-format -msgid "Drop sources or click here" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:33 msgid "Drop the standard input JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 -#, elixir-autogen, elixir-format -msgid "E-mail*" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 +msgid "During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 -#, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:222 -#, elixir-autogen, elixir-format -msgid "ERC-1155 " -msgstr "" - -#: lib/block_scout_web/views/transaction_view.ex:220 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:214 msgid "ERC-20 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 -#, elixir-autogen, elixir-format -msgid "ERC-20 tokens (beta)" -msgstr "" - -#: lib/block_scout_web/views/transaction_view.ex:221 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:215 msgid "ERC-721 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 -#, elixir-autogen, elixir-format -msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:1 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:133 +msgid "ETH" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 -#, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 msgid "EVM Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#, elixir-autogen, elixir-format -msgid "EVM version details" -msgstr "" - +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:7 -#, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 -#, elixir-autogen, elixir-format -msgid "Edit" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Edit Watch list address" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 -#, elixir-autogen, elixir-format -msgid "Email notifications" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 -#, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#: lib/block_scout_web/views/block_view.ex:73 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:71 msgid "Emission Reward" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:82 msgid "Enter the Solidity Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 msgid "Enter the Vyper Contract Code" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#, elixir-autogen, elixir-format -msgid "Error" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:4 +msgid "Epoch number" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Error in internal transactions" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:44 +msgid "Epochs range(s) or enum, e.g.: 5-9,23-27,47,50" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 -#, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:31 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 -#, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:375 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:355 msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:353 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:135 msgid "Error: Could not determine contract creator." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:138 msgid "Eth RPC" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:18 +#: lib/block_scout_web/templates/address/index.html.eex:5 lib/block_scout_web/templates/address/overview.html.eex:178 +#: lib/block_scout_web/templates/block/overview.html.eex:209 lib/block_scout_web/templates/internal_transaction/_tile.html.eex:20 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 lib/block_scout_web/templates/layout/_topnav.html.eex:110 +#: lib/block_scout_web/templates/layout/app.html.eex:45 lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:37 lib/block_scout_web/templates/transaction/overview.html.eex:409 +#: lib/block_scout_web/views/wei_helpers.ex:78 +msgid "Ether" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 -#, elixir-autogen, elixir-format msgid "Example Value" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 -#, elixir-autogen, elixir-format msgid "Execute" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 -#, elixir-autogen, elixir-format msgid "Expand" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/csv_export/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:248 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:228 msgid "External libraries" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 -#, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:37 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Failed to decode log data." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Fast" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:247 -#, elixir-autogen, elixir-format -msgid "Fetching gas used..." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 -#, elixir-autogen, elixir-format -msgid "Fetching holders..." +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24 +msgid "Favorites" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:28 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 -#, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:202 -#, elixir-autogen, elixir-format -msgid "Fetching transactions..." -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:221 -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 -#, elixir-autogen, elixir-format -msgid "Fetching transfers..." -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Filter by compiler:" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 -#, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "For contract" -msgstr "" - -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 msgid "Forked Blocks (Reorgs)" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:45 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:42 msgid "Forum" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 lib/block_scout_web/templates/address_transaction/index.html.eex:34 +#: lib/block_scout_web/templates/transaction/overview.html.eex:192 lib/block_scout_web/views/address_internal_transaction_view.ex:10 +#: lib/block_scout_web/views/address_token_transfer_view.ex:10 lib/block_scout_web/views/address_transaction_view.ex:10 msgid "From" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 -#, elixir-autogen, elixir-format msgid "GET" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:67 -#: lib/block_scout_web/templates/block/overview.html.eex:187 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:181 lib/block_scout_web/templates/transaction/overview.html.eex:371 msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:379 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:351 msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:240 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/overview.html.eex:178 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:255 +#: lib/block_scout_web/templates/block/_tile.html.eex:73 lib/block_scout_web/templates/block/overview.html.eex:172 msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:452 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:424 msgid "Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Gas tracker" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:239 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:254 +#: lib/block_scout_web/templates/address/overview.html.eex:288 msgid "Gas used by the address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:60 -#, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:24 -#, elixir-autogen, elixir-format msgid "Github" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 -#, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "GraphQL" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 -#: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:81 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:52 +#: lib/block_scout_web/views/block_view.ex:21 lib/block_scout_web/views/wei_helpers.ex:77 msgid "Gwei" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:123 -#, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:480 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:452 +#: lib/block_scout_web/templates/transaction/overview.html.eex:456 msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 -#, elixir-autogen, elixir-format -msgid "Highlighted events of the transaction." +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:122 +msgid "Holders" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 -#, elixir-autogen, elixir-format -msgid "Holders" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:77 +msgid "How Many Times this Address has been Banned" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:48 -#, elixir-autogen, elixir-format -msgid "Holders Count" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:71 +msgid "How Many Times this Address has been a Validator" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 -#, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 -#, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:38 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:80 msgid "IN" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 -#, elixir-autogen, elixir-format -msgid "If you enabled optimization during compilation, select yes." -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:133 -#, elixir-autogen, elixir-format -msgid "Implementation" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 +msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:132 -#, elixir-autogen, elixir-format -msgid "Implementation address of the proxy contract." +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 +msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Include nightly builds" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:147 +msgid "Implementation" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 -#, elixir-autogen, elixir-format -msgid "Incoming" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:146 +msgid "Implementation address of the proxy contract." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Index" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:12 +msgid "Inactive Pool Addresses. Current validator pools are specified by a checkmark." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 msgid "Index position of Transaction in the block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:249 -#, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 -#, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:47 +msgid "Indexing Tokens" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 -#, elixir-autogen, elixir-format msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:213 msgid "Interacted With (To)" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:6 msgid "Internal Transaction" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 -#: lib/block_scout_web/views/address_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:535 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:28 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 lib/block_scout_web/templates/transaction/_tabs.html.eex:11 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:346 +#: lib/block_scout_web/views/transaction_view.ex:514 msgid "Internal Transactions" msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Internal server error" +#, elixir-format +#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 +msgid "Invalid Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 -#: lib/block_scout_web/views/tokens/overview_view.ex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 lib/block_scout_web/views/tokens/overview_view.ex:44 msgid "Inventory" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Is Yul contract" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 +msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Last 24h" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:125 +msgid "It's me!" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:258 -#, elixir-autogen, elixir-format -msgid "Last Balance Update" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:828 +msgid "JSON RPC error" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Learn more" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:273 +msgid "Last Balance Update" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:49 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:48 msgid "Less than" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 -#, elixir-autogen, elixir-format -msgid "Library" +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:137 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:159 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:181 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:203 +msgid "Library Address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:105 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:127 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:149 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:171 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:193 +msgid "Library Name" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 -#, elixir-autogen, elixir-format msgid "License Expires" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 -#, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:331 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:83 +msgid "Likelihood of Becoming a Validator on the Next Epoch" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:303 msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:315 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:287 msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:298 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:270 msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:282 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:254 msgid "List of token transferred in the transaction." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Loading chart..." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:79 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:225 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:42 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:54 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:79 lib/block_scout_web/templates/address_read_contract/index.html.eex:12 +#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 lib/block_scout_web/templates/address_write_contract/index.html.eex:12 +#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 lib/block_scout_web/templates/tokens/contract/index.html.eex:16 msgid "Loading..." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 -#, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 msgid "Log Index" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:49 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: 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/views/address_view.ex:387 -#: lib/block_scout_web/views/transaction_view.ex:536 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:41 +#: lib/block_scout_web/templates/address_logs/index.html.eex:10 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/views/address_view.ex:357 +#: lib/block_scout_web/views/transaction_view.ex:515 msgid "Logs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:127 +msgid "ME" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:52 msgid "Main Networks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/layout/app.html.eex:50 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:147 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:61 +#: lib/block_scout_web/templates/layout/app.html.eex:49 lib/block_scout_web/templates/tokens/overview/_details.html.eex:98 +#: lib/block_scout_web/views/address_view.ex:142 msgid "Market Cap" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 -#, elixir-autogen, elixir-format -msgid "Market cap" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:42 +msgid "Max Amount to Move:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:408 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:380 msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:418 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:390 msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:327 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:321 msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:398 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:370 msgid "Maximum gas amount approved for the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:407 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:379 msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:115 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:187 msgid "Metadata" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 -#, elixir-autogen, elixir-format msgid "Method Id" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 -#: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:98 lib/block_scout_web/templates/chain/_block.html.eex:16 msgid "Miner" msgstr "" -#: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:68 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:61 +#: lib/block_scout_web/views/block_view.ex:66 msgid "Miner Reward" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 -#, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:22 +msgid "Minimum Stake Allowed:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:17 +msgid "Minimum Stake:" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:206 -#, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:92 -#, elixir-autogen, elixir-format -msgid "Mint of %{address} To %{to}" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:24 +msgid "Mining Address:" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 -#, elixir-autogen, elixir-format msgid "Model" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#, elixir-autogen, elixir-format msgid "Module" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 -#, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:217 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:228 lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:10 +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:57 +msgid "Move Stake" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 -#, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:34 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:186 +msgid "N/A" msgstr "" -#: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 -#, elixir-autogen, elixir-format -msgid "N/A" -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:116 -#, elixir-autogen, elixir-format -msgid "N/A bytes" -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 -#, elixir-autogen, elixir-format msgid "Name" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Name this API key" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Name this Custom ABI" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Name this address" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Name this transaction" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:44 -#, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:14 msgid "New Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:14 msgid "New Solidity Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#, elixir-autogen, elixir-format -msgid "New Solidity/Yul Smart Contract Verification" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:9 msgid "New Vyper Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:82 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:89 lib/block_scout_web/templates/address_contract_verification/new.html.eex:97 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:105 msgid "Next" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:6 +msgid "Next epoch in" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 lib/block_scout_web/templates/stakes/_rows.html.eex:24 msgid "No" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:17 -#, elixir-autogen, elixir-format -msgid "No trace entries found." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:11 +msgid "No Information" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:196 -#: lib/block_scout_web/templates/transaction/overview.html.eex:462 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:190 +#: lib/block_scout_web/templates/transaction/overview.html.eex:434 msgid "Nonce" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Not unique Token" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 -#, elixir-autogen, elixir-format -msgid "Number of accounts holding the token" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:274 -#, elixir-autogen, elixir-format -msgid "Number of blocks validated by this validator." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 -#, elixir-autogen, elixir-format -msgid "Number of digits that come after the decimal place when displaying token value" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:185 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:200 msgid "Number of transactions related to this address." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 -#, elixir-autogen, elixir-format -msgid "Number of transfers for the token" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:212 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:227 msgid "Number of transfers to/from this address." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:36 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:76 msgid "OUT" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format msgid "Only the first" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 -#, elixir-autogen, elixir-format -msgid "Optimization" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:61 msgid "Optimization enabled" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:70 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 msgid "Optimization runs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:78 -#, elixir-autogen, elixir-format -msgid "Other Explorers" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:62 +msgid "Order Withdrawal" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 -#, elixir-autogen, elixir-format -msgid "Outgoing" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:49 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:50 +msgid "Ordered" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Owner Address" +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:76 +msgid "Other Explorers" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "POA solidity flattener or the" +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 +msgid "Owner Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 -#, elixir-autogen, elixir-format msgid "POST" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 -#, elixir-autogen, elixir-format msgid "Page" msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Page not found" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 -#, elixir-autogen, elixir-format msgid "Parameters" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:139 -#, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:370 -#: lib/block_scout_web/views/transaction_view.ex:409 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:350 +#: lib/block_scout_web/views/transaction_view.ex:388 msgid "Pending" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:45 +msgid "Place stake" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 -#, elixir-autogen, elixir-format msgid "Play" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "Please confirm your email address to use the My Account feature." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:11 +msgid "Please, sign transaction and wait for its mining..." msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 -#, elixir-autogen, elixir-format -msgid "Please select notification methods:" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:19 +#: lib/block_scout_web/templates/stakes/_table.html.eex:15 +msgid "Pool" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Please select what types of notifications you will receive:" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:9 +msgid "Pool description" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:8 +msgid "Pool name" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:884 +msgid "Pools searching is already in progress for this address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 msgid "Position" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:254 -#, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Potential matches from contract method database:" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:55 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:57 +msgid "Potential Reward Share" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 -#, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:27 -#, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:42 -#: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:41 +#: lib/block_scout_web/templates/layout/app.html.eex:50 lib/block_scout_web/templates/tokens/overview/_details.html.eex:109 msgid "Price" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 -#, elixir-autogen, elixir-format -msgid "Price per token on the exchanges" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:378 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:350 msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:428 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:219 +#: lib/block_scout_web/templates/transaction/overview.html.eex:400 msgid "Priority Fee / Tip" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:62 -#, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Profile" -msgstr "" - -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Public Tags" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Public tag" -msgstr "" - -#: lib/block_scout_web/templates/account/common/_nav.html.eex:22 -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Public tags" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:50 -#, elixir-autogen, elixir-format -msgid "Public tags* (2 tags maximum, please use \";\" as a divider)" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 msgid "QR Code" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98 msgid "Query" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:133 msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:473 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:445 msgid "Raw Input" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:537 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:516 msgid "Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:89 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:381 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:81 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 lib/block_scout_web/views/address_view.ex:351 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 msgid "Read Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:96 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:382 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:88 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 lib/block_scout_web/views/address_view.ex:352 msgid "Read Proxy" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Records" -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Remove" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 -#, elixir-autogen, elixir-format -msgid "Remove from Watch list" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:32 +msgid "Reason for Ban: %{ban_reason}" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 -#, elixir-autogen, elixir-format -msgid "Request URL" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:58 +msgid "Recalculate" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request a public tag/label" -msgstr "" - -#: lib/block_scout_web/templates/error422/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request cannot be processed" +#, elixir-format +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +msgid "Records" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Request to add public tag" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:58 +msgid "Refresh now" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request to edit a public tag/label" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:12 +msgid "Remove My Pool" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "Resend verification email" +#, elixir-format +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 +msgid "Request URL" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:228 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:45 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:57 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:82 msgid "Reset" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 -#, elixir-autogen, elixir-format msgid "Response Body" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 -#, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:98 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:90 msgid "Result" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:138 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:127 msgid "Revert reason" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:28 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/chain/_block.html.eex:27 lib/block_scout_web/views/internal_transaction_view.ex:28 msgid "Reward" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Run" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:925 +msgid "Reward calculating is already in progress for this address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:61 +msgid "Reward distribution is based on stake amount. Validator receives at least %{min}% of the pool reward." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 -#, elixir-autogen, elixir-format -msgid "Save" +#, elixir-format +#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +msgid "Run" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:206 -#, elixir-autogen, elixir-format -msgid "Scroll to see more" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:10 +msgid "Save changes" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 -#, elixir-autogen, elixir-format msgid "Search" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/search/results.html.eex:17 -#, elixir-autogen, elixir-format msgid "Search Results" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:3 -#, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 -#, elixir-autogen, elixir-format -msgid "Search tokens" +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 +msgid "Search network" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select Yes if you want to verify Yul contract." +#, elixir-format +#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +msgid "Search tokens" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select yes if you want to show nightly builds." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:9 +msgid "Searching for pools you have ever staked into. Please, wait..." msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:27 -#, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Send request" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 -#, elixir-autogen, elixir-format msgid "Server Response" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:65 +msgid "Share of Pool’s Reward" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 -#, elixir-autogen, elixir-format msgid "Show" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 -#, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:47 +msgid "Show Validator Info" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_title.html.eex:22 +msgid "Show banned only" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_title.html.eex:28 +msgid "Show only those I have stake in" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 -#, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:59 -#, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:66 -#, elixir-autogen, elixir-format -msgid "Shows the total CRC balance in the address." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:45 -#, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:33 -#, elixir-autogen, elixir-format -msgid "Sign in" +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:114 +msgid "Size" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Sign out" +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:113 +msgid "Size of the block in bytes." msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Signed in as " +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 lib/block_scout_web/templates/address_logs/index.html.eex:23 +#: lib/block_scout_web/templates/address_token/index.html.eex:60 lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 lib/block_scout_web/templates/address_validation/index.html.eex:20 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:169 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 lib/block_scout_web/templates/stakes/_table.html.eex:49 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:23 lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 lib/block_scout_web/templates/transaction/index.html.eex:25 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 +msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:114 -#, elixir-autogen, elixir-format -msgid "Size" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:234 +msgid "Something went wrong, click to retry." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:113 -#, elixir-autogen, elixir-format -msgid "Size of the block in bytes." +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 +msgid "Sorry, We are unable to locate this transaction Hash" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Slow" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:7 +msgid "Source Pool" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Smart contract / Address" +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:19 +msgid "Sources and Metadata JSON" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:4 -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Smart contract / Address (0x...)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:7 +msgid "Stake" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/views/verified_contracts_view.ex:10 -#, elixir-autogen, elixir-format -msgid "Solidity" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:43 +msgid "Stake More" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 -#: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 -#: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:158 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 -#: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Something went wrong, click to reload." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:64 +msgid "Stake placed on a candidate pool or an active validator pool during the current staking epoch can be moved from one pool to another. Active stake cannot be moved. To re-delegate active stake: order a withdrawal, claim the amount on the next staking epoch, and stake the amount on a different pool." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:223 -#, elixir-autogen, elixir-format -msgid "Something went wrong, click to retry." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:53 +msgid "Stake placed on a pool is pending for the current staking epoch. It will be applied to the next staking epoch. You may withdraw or move pending stake at any time until it becomes active. Once active (the pool you staked with becomes a validator), a withdrawal order can be placed. This amount will be available to claim after that staking epoch is complete." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Sorry, we are unable to locate this transaction hash" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:45 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:46 +msgid "Staked" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Sources *.sol files" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:28 +msgid "Staked Amount" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Sources *.sol or *.yul files" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:26 +msgid "Staker's Address" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Sources and Metadata JSON" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:154 +msgid "Stakes" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 -#, elixir-autogen, elixir-format -msgid "Stakes" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:59 +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:25 lib/block_scout_web/templates/stakes/_table.html.eex:34 +msgid "Stakes Ratio" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Standard Input JSON" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:160 +msgid "Staking" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:20 +msgid "Staking Address:" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:538 -#, elixir-autogen, elixir-format -msgid "State changes" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:928 +msgid "Staking epochs are not specified or not in the allowed range" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:29 +msgid "Standard Input JSON" +msgstr "" + +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:24 -#, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:102 msgid "Status" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Submission date" -msgstr "" - -#: lib/block_scout_web/templates/layout/_footer.html.eex:41 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:39 msgid "Submit an Issue" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:372 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:352 msgid "Success" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:38 +msgid "Swap STAKE on Honeyswap" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:40 msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:67 +msgid "Target Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:30 msgid "Telegram" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:65 msgid "Test Networks" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 -#, elixir-autogen, elixir-format -msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Testnet" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#, elixir-autogen, elixir-format -msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:89 +msgid "The Number of Delegators in the Pool" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:122 -#, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:39 +msgid "The amount can be claimed after the current epoch finishes." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:51 -#, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "The changes from this transaction have not yet happened since the transaction is still pending." +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 -#, elixir-autogen, elixir-format -msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:23 +msgid "The first amount is the candidate’s own stake, the second is the total amount staked into the pool by the candidate and all delegators." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 -#, elixir-autogen, elixir-format -msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:25 +msgid "The first amount is the pool owner’s stake, the second is the total amount staked into the pool by the pool owner and all delegators." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:21 +msgid "The first amount is the validator’s own stake, the second is the total amount staked into the pool by the validator and all delegators." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:138 -#, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:74 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:90 msgid "The name found in the source code of the Contract." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:85 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:101 msgid "The name of the validator." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:43 +msgid "The number of delegators providing stake to the pool. Click on the number to see more details." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:79 -#, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:34 +msgid "The percentage of stake in a single pool relative to the total amount staked in all active pools. A higher ratio results in a greater likelihood of validator pool selection." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:137 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:105 +msgid "The rest addresses are delegators of its pool." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:126 msgid "The revert reason of the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:109 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:34 +msgid "The staking epochs for which the reward could be claimed (read-only field):" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:101 msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:58 +msgid "The table refreshed block(s) ago." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82 msgid "The total amount of tokens issued" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:177 -#, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:16 -#, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:28 msgid "There are no holders for this Token." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 -#, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:28 -#, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 -#, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 -#, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 -#, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/index.html.eex:65 -#, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 msgid "There are no tokens." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 -#, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#: lib/block_scout_web/templates/block_transaction/index.html.eex:19 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block_transaction/index.html.eex:27 msgid "There are no transactions for this block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:31 -#, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26 msgid "There are no transfers for this Token." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 -#, elixir-autogen, elixir-format -msgid "There are no verified contracts." -msgstr "" - -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals for this address." -msgstr "" - -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals for this block." -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/index.html.eex:53 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 -#, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:29 -#, elixir-autogen, elixir-format -msgid "There is no decompiled contracts for this address." +msgid "There is no decompilded contracts for this address." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:12 +msgid "There is no information currently available for this view. Deselect filters or choose another pool view to see current info. To participate as a delegator, select a pool address and click the Stake icon. To become a candidate, click the button below." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 #: lib/block_scout_web/templates/chain/show.html.eex:9 -#, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:6 -#, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 -#, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:11 -#, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:51 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:45 msgid "This contract has been verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 -#, elixir-autogen, elixir-format -msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:102 +msgid "This is a %{pool_type}." msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:5 +msgid "This is a validator" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:22 -#, elixir-autogen, elixir-format -msgid "This transaction hasn't changed state." +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:64 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:31 +msgid "This pool is banned until block #%{banned_until} (%{estimated_unban_day})" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:58 msgid "This transaction is pending confirmation." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:71 -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:65 +#: lib/block_scout_web/templates/transaction/overview.html.eex:169 msgid "Timestamp" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:247 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:32 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 -#: lib/block_scout_web/views/address_token_transfer_view.ex:9 -#: lib/block_scout_web/views/address_transaction_view.ex:9 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 lib/block_scout_web/templates/address_transaction/index.html.eex:28 +#: lib/block_scout_web/templates/transaction/overview.html.eex:215 lib/block_scout_web/views/address_internal_transaction_view.ex:9 +#: lib/block_scout_web/views/address_token_transfer_view.ex:9 lib/block_scout_web/views/address_transaction_view.ex:9 msgid "To" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 -#, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:13 msgid "Toggle navigation" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/tokens/index.html.eex:31 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:71 msgid "Token" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:471 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:450 msgid "Token Burning" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:472 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:451 msgid "Token Creation" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:42 msgid "Token Details" msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 lib/block_scout_web/views/tokens/overview_view.ex:42 msgid "Token Holders" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 msgid "Token ID" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:470 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:449 msgid "Token Minting" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:473 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:452 msgid "Token Transfer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:13 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:378 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 -#: lib/block_scout_web/views/transaction_view.ex:534 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:348 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:186 lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/transaction_view.ex:513 msgid "Token Transfers" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:70 msgid "Token name and symbol." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:156 msgid "Token type" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:175 -#: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:375 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:190 lib/block_scout_web/templates/address_token/overview.html.eex:58 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 lib/block_scout_web/templates/layout/_topnav.html.eex:69 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:101 lib/block_scout_web/templates/tokens/index.html.eex:10 +#: lib/block_scout_web/views/address_view.ex:345 msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:316 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:288 msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:332 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:304 msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:299 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:271 msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:283 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:255 msgid "Tokens Transferred" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:13 -#, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#, elixir-autogen, elixir-format msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:90 msgid "Topics" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Total" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:169 -#, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:97 msgid "Total Supply" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 -#, elixir-autogen, elixir-format -msgid "Total Supply * Price" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:131 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:142 msgid "Total blocks" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:168 -#, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:186 -#, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 -#, elixir-autogen, elixir-format -msgid "Total supply" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:363 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:335 msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:132 msgid "Total transactions" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:52 +msgid "Trade STAKE on AscendEX" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:483 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:462 msgid "Transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 -#, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:205 -#, elixir-autogen, elixir-format -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:438 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:410 msgid "Transaction Burnt Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:50 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:49 msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:364 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:336 msgid "Transaction Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:80 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:74 msgid "Transaction Hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 -#, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 -#, elixir-autogen, elixir-format -msgid "Transaction Tags" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:388 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:360 msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:461 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:433 msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:387 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:359 msgid "Transaction type, introduced in EIP-2718." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/overview.html.eex:186 -#: lib/block_scout_web/templates/address/overview.html.eex:192 -#: lib/block_scout_web/templates/address/overview.html.eex:200 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:4 -#: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:214 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:377 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:201 lib/block_scout_web/templates/address/overview.html.eex:207 +#: lib/block_scout_web/templates/address/overview.html.eex:215 lib/block_scout_web/templates/address_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/block/overview.html.eex:74 lib/block_scout_web/templates/block_transaction/index.html.eex:10 +#: lib/block_scout_web/templates/chain/show.html.eex:225 lib/block_scout_web/templates/layout/_topnav.html.eex:43 +#: lib/block_scout_web/views/address_view.ex:347 msgid "Transactions" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:101 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:117 msgid "Transactions and address of creation." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:213 -#: lib/block_scout_web/templates/address/overview.html.eex:219 -#: lib/block_scout_web/templates/address/overview.html.eex:227 +#, elixir-format +#: lib/block_scout_web/templates/address/_tile.html.eex:31 +msgid "Transactions sent" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:234 lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 -#, elixir-autogen, elixir-format msgid "Transfers" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 -#, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Try to fetch constructor arguments automatically" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:27 -#, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:53 -#, elixir-autogen, elixir-format -msgid "Tx/day" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:53 +msgid "Tx Gas Limit:" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 -#, elixir-autogen, elixir-format -msgid "Txns" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:92 +#: lib/block_scout_web/templates/layout/app.html.eex:52 +msgid "Tx/day" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 -#, elixir-autogen, elixir-format msgid "Type" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 -#, elixir-autogen, elixir-format -msgid "Type of the token standard" -msgstr "" - -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 -#, elixir-autogen, elixir-format -msgid "UML diagram" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:459 +msgid "UTF-8" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:487 -#, elixir-autogen, elixir-format -msgid "UTF-8" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:69 +msgid "Unable to find any pools you could claim a reward from." msgstr "" -#: lib/block_scout_web/views/block_view.ex:77 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:75 msgid "Uncle Reward" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:250 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:31 msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:363 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:343 msgid "Unconfirmed" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 -#, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:79 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:73 msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Update" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:891 +#: lib/block_scout_web/channels/stakes_channel.ex:938 +msgid "Unknown address of Staking contract. Please, contact support" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:417 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:931 +msgid "Unknown pool staking address. Please, contact support" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:887 +#: lib/block_scout_web/channels/stakes_channel.ex:934 +msgid "Unknown staker address. Please, choose your account in MetaMask" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12 +msgid "Use the search box to find a hosted network, or select from the list of available networks below." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:389 msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:427 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:399 msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:224 -#, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:47 msgid "Validated" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:12 -#, elixir-autogen, elixir-format msgid "Validated Transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 -#, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 -#, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:86 -#, elixir-autogen, elixir-format -msgid "Validator Name" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:51 +msgid "Validator Info" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Validator index" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:102 +msgid "Validator Name" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:349 -#, elixir-autogen, elixir-format -msgid "Value" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:8 +msgid "Validator Pool Addresses." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:348 -#, elixir-autogen, elixir-format -msgid "Value sent in the native token (and USD) if applicable." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:32 +msgid "Validator pools can be banned for misbehavior (such as not revealing secret numbers). Validator and delegator stake contained in a banned pool cannot be withdrawn until the ban is over." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 -#, elixir-autogen, elixir-format -msgid "Verified" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:321 +msgid "Value" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Verified Contracts" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:320 +msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:82 msgid "Verified at" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:69 -#, elixir-autogen, elixir-format -msgid "Verified contracts" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Verified contracts - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Verified contracts, %{subnetwork}, %{coin}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 -#: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:228 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:164 +#: lib/block_scout_web/templates/address_contract/index.html.eex:170 lib/block_scout_web/templates/address_contract/index.html.eex:201 +#: lib/block_scout_web/templates/address_contract/index.html.eex:207 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 msgid "Verify & Publish" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:227 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:44 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:81 msgid "Verify & publish" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "Verify the contract " msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:91 msgid "Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:42 msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:36 msgid "Via Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31 msgid "Via flattened source code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 -#, elixir-autogen, elixir-format -msgid "Via multi-part files" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:153 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:164 msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:213 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:224 msgid "View All Transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 -#, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:61 msgid "View Less Transfers" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:60 msgid "View More Transfers" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:39 -#, elixir-autogen, elixir-format msgid "View next block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:23 -#, elixir-autogen, elixir-format msgid "View previous block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:9 -#, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 -#, elixir-autogen, elixir-format -msgid "View the beacon chain withdrawals on %{subnetwork}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:10 -#, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 -#, elixir-autogen, elixir-format -msgid "View the verified contracts on %{subnetwork}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 -#, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 -#: lib/block_scout_web/views/verified_contracts_view.ex:11 -#, elixir-autogen, elixir-format -msgid "Vyper" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:48 msgid "Vyper contract" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:132 msgid "WEI" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 -#, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:139 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:150 msgid "Wallet addresses" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 -#, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Watch list" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:2 +msgid "We found the following pools you can claim reward from:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" +#, elixir-format +#: lib/block_scout_web/views/wei_helpers.ex:76 +msgid "Wei" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:80 -#, elixir-autogen, elixir-format -msgid "Wei" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:7 +msgid "Withdraw" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:70 +msgid "Withdraw Now" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:52 +msgid "Withdraw after block #%{banned_delegators_until} (%{estimated_unban_day})" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:22 +msgid "Withdrawal orders made during an active staking epoch are available to claim after the epoch is complete." msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:73 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Withdrawals" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:35 +msgid "Working Stake Amount" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98 msgid "Write" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:103 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:383 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:95 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 lib/block_scout_web/views/address_view.ex:353 msgid "Write Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:110 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:384 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:102 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 lib/block_scout_web/views/address_view.ex:354 msgid "Write Proxy" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:61 lib/block_scout_web/templates/stakes/_rows.html.eex:24 msgid "Yes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "You can create 3 API keys per account." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:34 +msgid "You Can Order:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:15 +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:12 +msgid "You Staked:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:11 +msgid "You can get your reward for all staking epochs during which the pool was a validator or specify separate epochs if Tx Gas Limit is too high. Tx Gas Limit depends on how long the pool was a validator and how many staking epochs you held your stake in the pool without movement." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:52 +msgid "You can withdraw this amount right now." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "You can create up to 15 Custom ABIs per account." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:38 +msgid "You will be able to withdraw after block #%{banned_delegators_until} (%{estimated_unban_day})" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:11 -#, elixir-autogen, elixir-format -msgid "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:48 +msgid "You will receive:" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have address tags yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:22 lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:28 +msgid "Your Balance:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have addresses on you watchlist yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:16 +msgid "Your Current Stake:" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have transaction tags yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:12 +msgid "Your Mining Address" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Your name" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:14 +msgid "Your Pool Name" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Your name*" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:16 +msgid "Your Pool Short Description (optional)" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:11 +msgid "Your ordered amount" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:111 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:41 +msgid "all epochs" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:127 msgid "at" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 -#, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:409 +msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:215 +msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#, elixir-autogen, elixir-format msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:256 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:19 +msgid "candidate" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:226 msgid "created" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 -#, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:149 -#, elixir-autogen, elixir-format -msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:30 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_contract_view.ex:24 msgid "false" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "here" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 -#, elixir-autogen, elixir-format msgid "here." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 -#, elixir-autogen, elixir-format -msgid "method Response" +#, elixir-format +#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 +msgid "is not a valid transaction hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 -#, elixir-autogen, elixir-format msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "on sign up. Didn’t receive?" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 -#, elixir-autogen, elixir-format msgid "page" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:20 +msgid "pool owner" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 msgid "receive" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 -#, elixir-autogen, elixir-format msgid "required" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:42 +msgid "specified epochs only" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#, elixir-autogen, elixir-format msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:29 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_contract_view.ex:23 msgid "true" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "truffle flattener" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:18 +msgid "validator" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 -#, elixir-autogen, elixir-format -msgid "New Smart Contract Verification via Standard input JSON" +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:216 +msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 -#, elixir-autogen, elixir-format -msgid "New Smart Contract Verification via metadata JSON" +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +msgid "Not unique Token" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 +msgid "Error" msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 -#, elixir-autogen, elixir-format -msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:65 +msgid "CRC Worth" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:66 +msgid "Shows the total CRC balance in the address." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Export" +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:110 +msgid "N/A bytes" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "for address" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:262 +msgid "Fetching gas used..." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 -#, elixir-autogen, elixir-format -msgid "to CSV file" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:209 +#: lib/block_scout_web/templates/address/overview.html.eex:217 +msgid "Fetching transactions..." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 -#, elixir-autogen, elixir-format -msgid "Yul" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:236 +#: lib/block_scout_web/templates/address/overview.html.eex:244 +msgid "Fetching transfers..." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format -msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +msgid "Loading chart..." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +msgid "method Response" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 328f17c0900d..053983c42f92 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -1,3684 +1,3321 @@ +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:6 +msgid "%{blocks} block" +msgid_plural "%{blocks} blocks" +msgstr[0] "" +msgstr[1] "" + +#, elixir-format #: lib/block_scout_web/views/address_token_balance_view.ex:10 -#, elixir-autogen, elixir-format msgid "%{count} token" msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:29 -#, elixir-autogen, elixir-format msgid "%{count} transaction" msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 -#, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:43 +msgid " Working Stake Amount is an amount which is accounted and working at the current staking epoch." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 -#, elixir-autogen, elixir-format msgid " is recommended." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:12 -#, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:55 -#, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:7 -#, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:85 -#, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 -#, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:101 -#, elixir-autogen, elixir-format -msgid "%{qty} of Token ID [%{link_to_id}]" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 -#, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 -#, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_metatags.html.eex:2 +msgid "%{subnetwork} Staking DApp - BlockScout" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:351 msgid "(Awaiting internal transactions for status)" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:26 +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:27 +msgid "(inactive pool)" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 -#, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 -#, elixir-autogen, elixir-format -msgid ") may be added for each contract. Click the Add Library button to add an additional one." -msgstr "" - -#: lib/block_scout_web/templates/layout/app.html.eex:93 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:192 msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 -#, elixir-autogen, elixir-format -msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 -#, elixir-autogen, elixir-format -msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 -#, elixir-autogen, elixir-format -msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." -msgstr "" - -#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 -#, elixir-autogen, elixir-format -msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:195 -#, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 -#, elixir-autogen, elixir-format -msgid "A block producer who successfully included the block onto the blockchain." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:76 +msgid "

Pending stake (stake placed on a candidate pool or placed during the current staking epoch) may be withdrawn now.

\n

Active stake (stake available after the current epoch) can be ordered for withdrawal from the pool, and will be available to claim after the current staking epoch is complete.

\n

If you have already ordered (and the staking window is still open), you may increase your current order by entering a positive value, or decrease your current order by entering a negative value in the box and clicking 'Order Withdrawal'. You must either keep the minimum stake amount in the pool, or order your entire stake for withdrawal.

\n" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "A confirmation email was sent to" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:36 +msgid "

To become a candidate, your staking address must be funded with %{tokenSymbol} tokens and %{coinSymbol} coins, and your OpenEthereum node must be active and configured with the mining address you specify here.

\n

To become a delegator, close this window and select an address from the list of pools you would like to place stake on. Click the Stake button next to the address to begin the process.

" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 -#, elixir-autogen, elixir-format -msgid "A library name called in the .sol file. Multiple libraries (up to " +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:97 +msgid "A block producer who successfully included the block onto the blockchain." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 -#, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 -#, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "ABI" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:62 msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:4 -#, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 -#, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 -#, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 -#, elixir-autogen, elixir-format -msgid "API key" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:124 +msgid "APIs" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 -#, elixir-autogen, elixir-format -msgid "API keys" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:39 +msgid "APY" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:106 -#, elixir-autogen, elixir-format -msgid "APIs" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:79 +msgid "APY & Predicted Reward" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#, elixir-autogen, elixir-format msgid "Action" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 -#, elixir-autogen, elixir-format -msgid "Actions" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:451 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:423 msgid "Actual gas amount used by the transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:44 msgid "Add" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 -#, elixir-autogen, elixir-format -msgid "Add API key" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 -#, elixir-autogen, elixir-format -msgid "Add Contract Libraries" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 -#, elixir-autogen, elixir-format -msgid "Add Custom ABI" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 -#, elixir-autogen, elixir-format -msgid "Add Library" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Add address" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Add address tag" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Add address to the Watch list" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Add transaction tag" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 -#: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 -#: lib/block_scout_web/views/address_view.ex:109 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:20 lib/block_scout_web/views/address_view.ex:104 msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:243 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:211 msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:191 msgid "Address (external or contract) sending the transaction." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 -#, elixir-autogen, elixir-format -msgid "Address Tags" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:164 +msgid "Address balance in xDAI (doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:149 -#, elixir-autogen, elixir-format -msgid "Address balance in" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:65 msgid "Address of the token contract" msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Address used in token mintings and burnings." -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Address*" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Addresses" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:35 -#, elixir-autogen, elixir-format -msgid "Age" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:88 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 -#: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_transaction_view.ex:11 -#: lib/block_scout_web/views/verified_contracts_view.ex:13 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 lib/block_scout_web/templates/address_transaction/index.html.eex:22 +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 lib/block_scout_web/templates/layout/_topnav.html.eex:73 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/views/address_token_transfer_view.ex:11 lib/block_scout_web/views/address_transaction_view.ex:11 msgid "All" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 -#, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:174 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:27 +msgid "All pool participant addresses. The top address belongs to the %{pool_type}." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:189 msgid "All tokens in the account and total value." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:27 +msgid "Already Ordered:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:10 lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:14 +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:11 lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:11 msgid "Amount" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:409 msgid "Amount of" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:236 -#, elixir-autogen, elixir-format -msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:41 +msgid "Amount of %{symbol} placed by an address." msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:236 +msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 -#, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:134 -#, elixir-autogen, elixir-format -msgid "Apps" -msgstr "" - -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Average" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:101 -#, elixir-autogen, elixir-format -msgid "Average block time" -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 -#, elixir-autogen, elixir-format -msgid "Back to API keys (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:80 +msgid "Approximate Current Annual Percentage Yield. If you see N/A, please reopen the popup in a few blocks (APY cannot be calculated at the very beginning of a staking epoch). Predicted Reward is the amount of %{symbol} a participant will receive for staking and can claim once the current epoch ends." msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Back to Address Tags (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:39 +msgid "Approximate Current Annual Percentage Yield. If you see N/A, please wait for a few blocks (APY cannot be calculated at the very beginning of a staking epoch)." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 -#, elixir-autogen, elixir-format -msgid "Back to Custom ABI (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:152 +msgid "Apps" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Back to Transaction Tags (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:48 +msgid "Available Now:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 -#, elixir-autogen, elixir-format -msgid "Back to Watch list (Cancel)" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:123 +msgid "Average block time" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:9 -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 -#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Back to home" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:30 +msgid "Back Home" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 -#: lib/block_scout_web/templates/address/overview.html.eex:150 -#: lib/block_scout_web/templates/address_token/overview.html.eex:51 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:4 +#: lib/block_scout_web/templates/address/overview.html.eex:165 lib/block_scout_web/templates/address_token/overview.html.eex:51 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:42 lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:43 msgid "Balance" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:40 -#, elixir-autogen, elixir-format -msgid "Balance after" +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 +msgid "Balances" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Balance before" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:32 +msgid "Banned" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Balances" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:46 +msgid "Banned until block #%{banned_until} (%{estimated_unban_day})" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:207 -#, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Beacon chain withdrawals - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:13 +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:5 +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:33 lib/block_scout_web/templates/stakes/_stakes_top.html.eex:24 +msgid "Become a Candidate" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:472 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:444 msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/block/overview.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:29 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:26 lib/block_scout_web/templates/transaction/overview.html.eex:150 msgid "Block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 -#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:28 lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 msgid "Block #%{number}" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 -#, elixir-autogen, elixir-format msgid "Block Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:53 -#, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:46 msgid "Block Mined, awaiting import..." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:34 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:33 msgid "Block Pending" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:158 -#, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:15 -#, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:160 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:5 +msgid "Block number" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:149 msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:257 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:272 msgid "Block number in which the address was updated." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 -#, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 -#, elixir-autogen, elixir-format -msgid "Blockchain" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:154 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:165 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:24 lib/block_scout_web/templates/layout/_topnav.html.eex:28 msgid "Blocks" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:45 msgid "Blocks Indexed" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:56 -#: lib/block_scout_web/templates/address/overview.html.eex:275 -#: lib/block_scout_web/templates/address_validation/index.html.eex:11 -#: lib/block_scout_web/views/address_view.ex:386 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:48 +#: lib/block_scout_web/templates/address/overview.html.eex:289 lib/block_scout_web/templates/address_validation/index.html.eex:11 +#: lib/block_scout_web/views/address_view.ex:356 msgid "Blocks Validated" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:48 -#, elixir-autogen, elixir-format -msgid "Blocks With Internal Transactions Indexed" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:22 -#, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Burn address" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:45 +msgid "Bridge STAKE to Ethereum" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/bridged_tokens/index.html.eex:7 +msgid "Bridged Tokens from " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 +msgid "Bridged from BSC" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:78 +msgid "Bridged from Ethereum" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:64 #: lib/block_scout_web/templates/block/overview.html.eex:216 -#, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:65 -#, elixir-autogen, elixir-format -msgid "CRC Worth" -msgstr "" - -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:2 msgid "CSV" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #: lib/block_scout_web/views/internal_transaction_view.ex:21 -#, elixir-autogen, elixir-format msgid "Call" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:22 -#, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:62 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:114 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:231 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:48 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:60 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:85 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 -#, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:43 -#, elixir-autogen, elixir-format -msgid "Change" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:10 +msgid "Candidate and Validator Pool Addresses. Current validator pools are specified by a checkmark." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:47 +msgid "Candidate’s Staked Amount" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 +msgid "Change Network" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:41 msgid "Chat (#blockscout)" msgstr "" -#: lib/block_scout_web/views/block_view.ex:65 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:6 +msgid "Choose Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:22 +msgid "Choose Target Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:63 msgid "Chore Reward" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:38 -#, elixir-autogen, elixir-format -msgid "Circulating Market Cap" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:7 +msgid "Claim Ordered Withdraw" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:6 +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:59 lib/block_scout_web/templates/stakes/_stakes_top.html.eex:30 +msgid "Claim Reward" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:40 +msgid "Claim for" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:18 +msgid "Claim the Amount" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 -#, elixir-autogen, elixir-format msgid "Clear" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 +#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 msgid "Close" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:66 -#: 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/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 -#: lib/block_scout_web/views/address_view.ex:379 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:58 +#: 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/_eth_rpc_item.html.eex:126 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 +#: lib/block_scout_web/views/address_view.ex:349 msgid "Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:42 -#: lib/block_scout_web/views/address_view.ex:385 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:34 +#: lib/block_scout_web/views/address_view.ex:355 msgid "Coin Balance History" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 -#, elixir-autogen, elixir-format msgid "Collapse" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Company name" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:32 -#, elixir-autogen, elixir-format -msgid "Company website" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:40 msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:142 -#, elixir-autogen, elixir-format -msgid "Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:71 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:65 msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:364 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:344 msgid "Confirmed" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:127 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:116 msgid "Confirmed by " msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:182 msgid "Confirmed within" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:11 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:6 lib/block_scout_web/templates/tokens/holder/index.html.eex:15 msgid "Connection Lost" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 -#: lib/block_scout_web/templates/transaction/index.html.eex:22 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 lib/block_scout_web/templates/transaction/index.html.eex:22 msgid "Connection Lost, click to load newer transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:96 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:90 msgid "Constructor Arguments" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 -#, elixir-autogen, elixir-format -msgid "Constructor args" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:221 msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:157 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:126 msgid "Contract ABI" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:107 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:23 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:9 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:18 lib/block_scout_web/views/address_view.ex:102 msgid "Contract Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:47 -#: lib/block_scout_web/views/address_view.ex:81 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/address_view.ex:42 lib/block_scout_web/views/address_view.ex:76 msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:480 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:459 msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:477 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:456 msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:174 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:142 +#: lib/block_scout_web/templates/address_contract/index.html.eex:157 msgid "Contract Creation Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:100 msgid "Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:75 +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:91 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:29 msgid "Contract Name" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 -#, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 -#, elixir-autogen, elixir-format -msgid "Contract name or address" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:63 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:57 msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:100 msgid "Contract source code" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 -#, elixir-autogen, elixir-format -msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Contracts" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:180 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:148 msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:40 msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:159 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:128 msgid "Copy ABI" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Copy API key" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:39 -#: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:38 lib/block_scout_web/templates/block/overview.html.eex:104 +#: lib/block_scout_web/templates/block/overview.html.eex:105 lib/block_scout_web/templates/tokens/overview/_details.html.eex:50 +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 msgid "Copy Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:144 -#, elixir-autogen, elixir-format -msgid "Copy Compiler Settings" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Copy Contract Address" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:176 -#: lib/block_scout_web/templates/address_contract/index.html.eex:192 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:160 msgid "Copy Contract Creation Code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "Copy Decompiled Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:223 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:187 +#: lib/block_scout_web/templates/address_contract/index.html.eex:197 msgid "Copy Deployed ByteCode" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:233 -#: lib/block_scout_web/templates/transaction/overview.html.eex:234 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:14 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:15 lib/block_scout_web/templates/transaction/overview.html.eex:201 +#: lib/block_scout_web/templates/transaction/overview.html.eex:202 msgid "Copy From Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 -#, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 -#, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 -#, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:15 msgid "Copy Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:132 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:102 +#: lib/block_scout_web/templates/address_contract/index.html.eex:115 msgid "Copy Source Code" msgstr "" -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:260 -#: lib/block_scout_web/templates/transaction/overview.html.eex:261 -#: lib/block_scout_web/templates/transaction/overview.html.eex:268 -#: lib/block_scout_web/templates/transaction/overview.html.eex:269 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:31 +#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:32 lib/block_scout_web/templates/transaction/overview.html.eex:230 +#: lib/block_scout_web/templates/transaction/overview.html.eex:231 lib/block_scout_web/templates/transaction/overview.html.eex:240 +#: lib/block_scout_web/templates/transaction/overview.html.eex:241 msgid "Copy To Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 -#, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:87 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:81 msgid "Copy Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:82 msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:498 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:470 msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:504 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:476 msgid "Copy Txn UTF-8 Input" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:497 -#: lib/block_scout_web/templates/transaction/overview.html.eex:503 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:475 msgid "Copy Value" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:25 -#, elixir-autogen, elixir-format msgid "Create" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 -#, elixir-autogen, elixir-format -msgid "Create a Custom ABI to interact with contracts." -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 -#, elixir-autogen, elixir-format -msgid "Create an API key to use with your RPC и EthRPC API requests." -msgstr "" - +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:26 -#, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:102 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:118 msgid "Creator" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 -#, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:97 -#, elixir-autogen, elixir-format -msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:55 +msgid "Current Reward Share" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:63 +msgid "Current Reward Share is calculated based on the Working Stake Amount." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Custom" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:35 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:37 +msgid "Current Stake Amount" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Custom ABI" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:36 +msgid "Current Stake:" msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Custom ABI from account" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:89 +msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:70 -#, elixir-autogen, elixir-format -msgid "Daily Transactions" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_metatags.html.eex:6 +msgid "DApp for Staking %{symbol} tokens" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:120 msgid "Data" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:70 -#, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:179 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:168 msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:145 msgid "Decimals" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:57 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:73 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 lib/block_scout_web/templates/address_logs/_logs.html.eex:53 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:33 lib/block_scout_web/templates/transaction_log/_logs.html.eex:41 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:56 lib/block_scout_web/templates/transaction_log/_logs.html.eex:72 msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/address_view.ex:380 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_view.ex:350 msgid "Decompiled Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:83 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:75 msgid "Decompiled code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "Decompiled contract code" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Decompiler version" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:23 -#, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:211 -#: lib/block_scout_web/templates/address_contract/index.html.eex:219 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:31 +#: lib/block_scout_web/templates/stakes/_table.html.eex:43 +msgid "Delegators" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:6 +msgid "Delegators of " +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:53 +msgid "Delegators’ Staked Amount" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:185 +#: lib/block_scout_web/templates/address_contract/index.html.eex:193 msgid "Deployed ByteCode" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 -#, elixir-autogen, elixir-format msgid "Description" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:56 -#, elixir-autogen, elixir-format -msgid "Description*" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:30 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 msgid "Details" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:159 -#, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:149 msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 -#: lib/block_scout_web/templates/csv_export/index.html.eex:33 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/csv_export/index.html.eex:25 msgid "Download" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 -#, elixir-autogen, elixir-format -msgid "Drop all Solidity contract source files into the drop zone." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 -#, elixir-autogen, elixir-format -msgid "Drop all Solidity or Yul contract source files into the drop zone." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:26 msgid "Drop sources and metadata JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 -#, elixir-autogen, elixir-format -msgid "Drop sources or click here" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:33 msgid "Drop the standard input JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:27 -#, elixir-autogen, elixir-format -msgid "E-mail*" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:22 +msgid "During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 -#, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:222 -#, elixir-autogen, elixir-format -msgid "ERC-1155 " -msgstr "" - -#: lib/block_scout_web/views/transaction_view.ex:220 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:214 msgid "ERC-20 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 -#, elixir-autogen, elixir-format -msgid "ERC-20 tokens (beta)" -msgstr "" - -#: lib/block_scout_web/views/transaction_view.ex:221 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:215 msgid "ERC-721 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 -#, elixir-autogen, elixir-format -msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:1 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 lib/block_scout_web/templates/smart_contract/_functions.html.eex:93 +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:133 +msgid "ETH" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 -#, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:76 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 msgid "EVM Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#, elixir-autogen, elixir-format -msgid "EVM version details" -msgstr "" - +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:7 -#, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 -#, elixir-autogen, elixir-format -msgid "Edit" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Edit Watch list address" -msgstr "" - -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 -#, elixir-autogen, elixir-format -msgid "Email notifications" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 -#, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#: lib/block_scout_web/views/block_view.ex:73 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:71 msgid "Emission Reward" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:82 msgid "Enter the Solidity Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 msgid "Enter the Vyper Contract Code" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#, elixir-autogen, elixir-format -msgid "Error" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:4 +msgid "Epoch number" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Error in internal transactions" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:44 +msgid "Epochs range(s) or enum, e.g.: 5-9,23-27,47,50" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 -#, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:31 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 -#, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:375 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:355 msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:353 msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:135 msgid "Error: Could not determine contract creator." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:138 msgid "Eth RPC" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:18 +#: lib/block_scout_web/templates/address/index.html.eex:5 lib/block_scout_web/templates/address/overview.html.eex:178 +#: lib/block_scout_web/templates/block/overview.html.eex:209 lib/block_scout_web/templates/internal_transaction/_tile.html.eex:20 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:89 lib/block_scout_web/templates/layout/_topnav.html.eex:110 +#: lib/block_scout_web/templates/layout/app.html.eex:45 lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:37 lib/block_scout_web/templates/transaction/overview.html.eex:409 +#: lib/block_scout_web/views/wei_helpers.ex:78 +msgid "Ether" +msgstr "ETH" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 -#, elixir-autogen, elixir-format msgid "Example Value" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 -#, elixir-autogen, elixir-format msgid "Execute" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:55 -#, elixir-autogen, elixir-format msgid "Expand" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/csv_export/index.html.eex:10 -#, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:248 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:228 msgid "External libraries" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 -#, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:37 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:36 msgid "Failed to decode log data." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Fast" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:247 -#, elixir-autogen, elixir-format -msgid "Fetching gas used..." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 -#, elixir-autogen, elixir-format -msgid "Fetching holders..." +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:24 +msgid "Favorites" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/_balance_card.html.eex:28 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 -#, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:202 -#, elixir-autogen, elixir-format -msgid "Fetching transactions..." -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:221 -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 -#, elixir-autogen, elixir-format -msgid "Fetching transfers..." -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Filter by compiler:" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 -#, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "For contract" -msgstr "" - -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 msgid "Forked Blocks (Reorgs)" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:45 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:42 msgid "Forum" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 lib/block_scout_web/templates/address_transaction/index.html.eex:34 +#: lib/block_scout_web/templates/transaction/overview.html.eex:192 lib/block_scout_web/views/address_internal_transaction_view.ex:10 +#: lib/block_scout_web/views/address_token_transfer_view.ex:10 lib/block_scout_web/views/address_transaction_view.ex:10 msgid "From" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 -#, elixir-autogen, elixir-format msgid "GET" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:67 -#: lib/block_scout_web/templates/block/overview.html.eex:187 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:181 lib/block_scout_web/templates/transaction/overview.html.eex:371 msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:379 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:351 msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:240 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/overview.html.eex:178 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:255 +#: lib/block_scout_web/templates/block/_tile.html.eex:73 lib/block_scout_web/templates/block/overview.html.eex:172 msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:452 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:424 msgid "Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Gas tracker" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:239 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:254 +#: lib/block_scout_web/templates/address/overview.html.eex:288 msgid "Gas used by the address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:60 -#, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:24 -#, elixir-autogen, elixir-format msgid "Github" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 -#, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "GraphQL" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 -#: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:81 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:52 +#: lib/block_scout_web/views/block_view.ex:21 lib/block_scout_web/views/wei_helpers.ex:77 msgid "Gwei" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:123 -#, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:480 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:452 +#: lib/block_scout_web/templates/transaction/overview.html.eex:456 msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 -#, elixir-autogen, elixir-format -msgid "Highlighted events of the transaction." +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:122 +msgid "Holders" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 -#, elixir-autogen, elixir-format -msgid "Holders" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:77 +msgid "How Many Times this Address has been Banned" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:48 -#, elixir-autogen, elixir-format -msgid "Holders Count" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:71 +msgid "How Many Times this Address has been a Validator" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 -#, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 -#, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:38 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:80 msgid "IN" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 -#, elixir-autogen, elixir-format -msgid "If you enabled optimization during compilation, select yes." +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:26 +msgid "If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:133 -#, elixir-autogen, elixir-format -msgid "Implementation" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:132 -#, elixir-autogen, elixir-format -msgid "Implementation address of the proxy contract." +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:12 +msgid "If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Include nightly builds" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:147 +msgid "Implementation" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 -#, elixir-autogen, elixir-format -msgid "Incoming" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:146 +msgid "Implementation address of the proxy contract." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#, elixir-autogen, elixir-format -msgid "Index" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:12 +msgid "Inactive Pool Addresses. Current validator pools are specified by a checkmark." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 msgid "Index position of Transaction in the block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:249 -#, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 -#, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:47 +msgid "Indexing Tokens" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 -#, elixir-autogen, elixir-format msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:213 msgid "Interacted With (To)" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:6 msgid "Internal Transaction" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 -#: lib/block_scout_web/views/address_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:535 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:28 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 lib/block_scout_web/templates/transaction/_tabs.html.eex:11 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 lib/block_scout_web/views/address_view.ex:346 +#: lib/block_scout_web/views/transaction_view.ex:514 msgid "Internal Transactions" msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Internal server error" +#, elixir-format +#: lib/block_scout_web/templates/transaction/invalid.html.eex:6 +msgid "Invalid Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 -#: lib/block_scout_web/views/tokens/overview_view.ex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:15 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 lib/block_scout_web/views/tokens/overview_view.ex:44 msgid "Inventory" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Is Yul contract" +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:16 +msgid "It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Last 24h" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:125 +msgid "It's me!" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:258 -#, elixir-autogen, elixir-format -msgid "Last Balance Update" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:828 +msgid "JSON RPC error" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Learn more" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:273 +msgid "Last Balance Update" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:49 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/app.html.eex:48 msgid "Less than" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 -#, elixir-autogen, elixir-format -msgid "Library" +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:137 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:159 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:181 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:203 +msgid "Library Address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:105 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:127 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:149 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:171 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:193 +msgid "Library Name" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 -#, elixir-autogen, elixir-format msgid "License Expires" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 -#, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:331 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:83 +msgid "Likelihood of Becoming a Validator on the Next Epoch" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:303 msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:315 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:287 msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:298 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:270 msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:282 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:254 msgid "List of token transferred in the transaction." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Loading chart..." -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:79 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:225 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:42 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:54 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:79 lib/block_scout_web/templates/address_read_contract/index.html.eex:12 +#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 lib/block_scout_web/templates/address_write_contract/index.html.eex:12 +#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 lib/block_scout_web/templates/tokens/contract/index.html.eex:16 msgid "Loading..." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 -#, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:131 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 msgid "Log Index" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:49 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: 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/views/address_view.ex:387 -#: lib/block_scout_web/views/transaction_view.ex:536 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:41 +#: lib/block_scout_web/templates/address_logs/index.html.eex:10 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/views/address_view.ex:357 +#: lib/block_scout_web/views/transaction_view.ex:515 msgid "Logs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:127 +msgid "ME" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:52 msgid "Main Networks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/layout/app.html.eex:50 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:147 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 +msgid "Mainnet" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:61 +#: lib/block_scout_web/templates/layout/app.html.eex:49 lib/block_scout_web/templates/tokens/overview/_details.html.eex:98 +#: lib/block_scout_web/views/address_view.ex:142 msgid "Market Cap" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 -#, elixir-autogen, elixir-format -msgid "Market cap" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:42 +msgid "Max Amount to Move:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:408 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:380 msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:418 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:390 msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:327 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:321 msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:398 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:370 msgid "Maximum gas amount approved for the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:407 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:379 msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:115 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 lib/block_scout_web/views/tokens/instance/overview_view.ex:187 msgid "Metadata" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 -#, elixir-autogen, elixir-format msgid "Method Id" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 -#: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:98 lib/block_scout_web/templates/chain/_block.html.eex:16 msgid "Miner" msgstr "" -#: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:68 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:61 +#: lib/block_scout_web/views/block_view.ex:66 msgid "Miner Reward" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 -#, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:22 +msgid "Minimum Stake Allowed:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:17 +msgid "Minimum Stake:" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:206 -#, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:92 -#, elixir-autogen, elixir-format -msgid "Mint of %{address} To %{to}" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:24 +msgid "Mining Address:" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 -#, elixir-autogen, elixir-format msgid "Model" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#, elixir-autogen, elixir-format msgid "Module" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 -#, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:217 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:228 lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:10 +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:57 +msgid "Move Stake" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 -#, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." -msgstr "" - -#: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:34 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:186 msgid "N/A" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:116 -#, elixir-autogen, elixir-format -msgid "N/A bytes" -msgstr "" - -#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 +#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 -#, elixir-autogen, elixir-format msgid "Name" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Name this API key" -msgstr "" - -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Name this Custom ABI" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Name this address" -msgstr "" - -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Name this transaction" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:44 -#, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:14 msgid "New Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:14 msgid "New Solidity Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#, elixir-autogen, elixir-format -msgid "New Solidity/Yul Smart Contract Verification" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:9 msgid "New Vyper Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:82 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:89 lib/block_scout_web/templates/address_contract_verification/new.html.eex:97 +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:105 msgid "Next" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:6 +msgid "Next epoch in" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 lib/block_scout_web/templates/stakes/_rows.html.eex:24 msgid "No" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:17 -#, elixir-autogen, elixir-format -msgid "No trace entries found." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:11 +msgid "No Information" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:196 -#: lib/block_scout_web/templates/transaction/overview.html.eex:462 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:190 +#: lib/block_scout_web/templates/transaction/overview.html.eex:434 msgid "Nonce" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Not unique Token" -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 -#, elixir-autogen, elixir-format -msgid "Number of accounts holding the token" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:274 -#, elixir-autogen, elixir-format -msgid "Number of blocks validated by this validator." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 -#, elixir-autogen, elixir-format -msgid "Number of digits that come after the decimal place when displaying token value" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:185 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:200 msgid "Number of transactions related to this address." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 -#, elixir-autogen, elixir-format -msgid "Number of transfers for the token" -msgstr "" - -#: lib/block_scout_web/templates/address/overview.html.eex:212 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:227 msgid "Number of transfers to/from this address." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:36 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:76 msgid "OUT" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format msgid "Only the first" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 -#, elixir-autogen, elixir-format -msgid "Optimization" -msgstr "" - -#: lib/block_scout_web/templates/address_contract/index.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:61 msgid "Optimization enabled" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:70 +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 msgid "Optimization runs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:78 -#, elixir-autogen, elixir-format -msgid "Other Explorers" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:62 +msgid "Order Withdrawal" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 -#, elixir-autogen, elixir-format -msgid "Outgoing" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:49 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:50 +msgid "Ordered" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Owner Address" +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:76 +msgid "Other Explorers" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "POA solidity flattener or the" +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 +msgid "Owner Address" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 -#, elixir-autogen, elixir-format msgid "POST" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 -#, elixir-autogen, elixir-format msgid "Page" msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Page not found" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 -#, elixir-autogen, elixir-format msgid "Parameters" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:139 -#, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:370 -#: lib/block_scout_web/views/transaction_view.ex:409 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:55 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:184 lib/block_scout_web/views/transaction_view.ex:350 +#: lib/block_scout_web/views/transaction_view.ex:388 msgid "Pending" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 -#, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:45 +msgid "Place stake" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 -#, elixir-autogen, elixir-format msgid "Play" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "Please confirm your email address to use the My Account feature." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:11 +msgid "Please, sign transaction and wait for its mining..." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:19 +#: lib/block_scout_web/templates/stakes/_table.html.eex:15 +msgid "Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:9 +msgid "Pool description" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 -#, elixir-autogen, elixir-format -msgid "Please select notification methods:" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:8 +msgid "Pool name" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Please select what types of notifications you will receive:" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:884 +msgid "Pools searching is already in progress for this address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:464 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 msgid "Position" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:254 -#, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 -#, elixir-autogen, elixir-format -msgid "Potential matches from contract method database:" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:55 +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:57 +msgid "Potential Reward Share" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 -#, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:27 -#, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:42 -#: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:41 +#: lib/block_scout_web/templates/layout/app.html.eex:50 lib/block_scout_web/templates/tokens/overview/_details.html.eex:109 msgid "Price" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 -#, elixir-autogen, elixir-format -msgid "Price per token on the exchanges" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:378 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:350 msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:428 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:219 +#: lib/block_scout_web/templates/transaction/overview.html.eex:400 msgid "Priority Fee / Tip" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:62 -#, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Profile" -msgstr "" - -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Public Tags" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Public tag" -msgstr "" - -#: lib/block_scout_web/templates/account/common/_nav.html.eex:22 -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Public tags" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:50 -#, elixir-autogen, elixir-format -msgid "Public tags* (2 tags maximum, please use \";\" as a divider)" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 msgid "QR Code" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98 msgid "Query" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:133 msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:473 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:445 msgid "Raw Input" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:537 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction_raw_trace/index.html.eex:7 lib/block_scout_web/views/transaction_view.ex:516 msgid "Raw Trace" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:89 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 -#: lib/block_scout_web/views/address_view.ex:381 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:81 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 lib/block_scout_web/views/address_view.ex:351 +#: lib/block_scout_web/views/tokens/overview_view.ex:43 msgid "Read Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:96 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 -#: lib/block_scout_web/views/address_view.ex:382 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:88 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 lib/block_scout_web/views/address_view.ex:352 msgid "Read Proxy" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Records" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:32 +msgid "Reason for Ban: %{ban_reason}" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 -#, elixir-autogen, elixir-format -msgid "Remove" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:58 +msgid "Recalculate" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 -#, elixir-autogen, elixir-format -msgid "Remove from Watch list" -msgstr "" - -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 -#, elixir-autogen, elixir-format -msgid "Request URL" -msgstr "" - -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request a public tag/label" -msgstr "" - -#: lib/block_scout_web/templates/error422/index.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request cannot be processed" +#, elixir-format +#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 +msgid "Records" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:37 -#, elixir-autogen, elixir-format -msgid "Request to add public tag" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:58 +msgid "Refresh now" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Request to edit a public tag/label" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:12 +msgid "Remove My Pool" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "Resend verification email" +#, elixir-format +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 +msgid "Request URL" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:228 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:45 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:57 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:82 msgid "Reset" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 -#, elixir-autogen, elixir-format msgid "Response Body" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 -#, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:98 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:90 msgid "Result" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:138 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:127 msgid "Revert reason" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:28 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/chain/_block.html.eex:27 lib/block_scout_web/views/internal_transaction_view.ex:28 msgid "Reward" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Run" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:925 +msgid "Reward calculating is already in progress for this address" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:61 +msgid "Reward distribution is based on stake amount. Validator receives at least %{min}% of the pool reward." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 -#, elixir-autogen, elixir-format -msgid "Save" +#, elixir-format +#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 +msgid "Run" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:206 -#, elixir-autogen, elixir-format -msgid "Scroll to see more" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:10 +msgid "Save changes" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 -#, elixir-autogen, elixir-format msgid "Search" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/search/results.html.eex:17 -#, elixir-autogen, elixir-format msgid "Search Results" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/layout/_search.html.eex:3 -#, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 -#, elixir-autogen, elixir-format -msgid "Search tokens" +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:18 +msgid "Search network" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select Yes if you want to verify Yul contract." +#, elixir-format +#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 +msgid "Search tokens" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 -#, elixir-autogen, elixir-format -msgid "Select yes if you want to show nightly builds." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:9 +msgid "Searching for pools you have ever staked into. Please, wait..." msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:27 -#, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Send request" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 -#, elixir-autogen, elixir-format msgid "Server Response" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:65 +msgid "Share of Pool’s Reward" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 -#, elixir-autogen, elixir-format msgid "Show" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 -#, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:47 +msgid "Show Validator Info" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_title.html.eex:22 +msgid "Show banned only" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_title.html.eex:28 +msgid "Show only those I have stake in" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 -#, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:59 -#, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:66 -#, elixir-autogen, elixir-format -msgid "Shows the total CRC balance in the address." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:45 -#, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:33 -#, elixir-autogen, elixir-format -msgid "Sign in" +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:114 +msgid "Size" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:113 +msgid "Size of the block in bytes." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 +#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 lib/block_scout_web/templates/address_logs/index.html.eex:23 +#: lib/block_scout_web/templates/address_token/index.html.eex:60 lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 +#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 lib/block_scout_web/templates/address_validation/index.html.eex:20 +#: lib/block_scout_web/templates/block_transaction/index.html.eex:22 lib/block_scout_web/templates/chain/show.html.eex:169 +#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 lib/block_scout_web/templates/stakes/_table.html.eex:49 +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:23 lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 lib/block_scout_web/templates/tokens/inventory/index.html.eex:22 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:21 lib/block_scout_web/templates/transaction/index.html.eex:25 +#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 lib/block_scout_web/templates/transaction_log/index.html.eex:15 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 +msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Sign out" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:234 +msgid "Something went wrong, click to retry." msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 -#, elixir-autogen, elixir-format -msgid "Signed in as " +#, elixir-format +#: lib/block_scout_web/templates/transaction/not_found.html.eex:7 +msgid "Sorry, We are unable to locate this transaction Hash" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:114 -#, elixir-autogen, elixir-format -msgid "Size" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:7 +msgid "Source Pool" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:113 -#, elixir-autogen, elixir-format -msgid "Size of the block in bytes." +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:19 +msgid "Sources and Metadata JSON" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#, elixir-autogen, elixir-format -msgid "Slow" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:7 +msgid "Stake" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:21 -#, elixir-autogen, elixir-format -msgid "Smart contract / Address" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:43 +msgid "Stake More" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:4 -#: lib/block_scout_web/templates/account/public_tags_request/address_field.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Smart contract / Address (0x...)" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:64 +msgid "Stake placed on a candidate pool or an active validator pool during the current staking epoch can be moved from one pool to another. Active stake cannot be moved. To re-delegate active stake: order a withdrawal, claim the amount on the next staking epoch, and stake the amount on a different pool." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/views/verified_contracts_view.ex:10 -#, elixir-autogen, elixir-format -msgid "Solidity" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:53 +msgid "Stake placed on a pool is pending for the current staking epoch. It will be applied to the next staking epoch. You may withdraw or move pending stake at any time until it becomes active. Once active (the pool you staked with becomes a validator), a withdrawal order can be placed. This amount will be available to claim after that staking epoch is complete." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 -#: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 -#: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:158 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 -#: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Something went wrong, click to reload." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:45 +#: lib/block_scout_web/templates/stakes/_stakes_stats_item_account.html.eex:46 +msgid "Staked" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:223 -#, elixir-autogen, elixir-format -msgid "Something went wrong, click to retry." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:28 +msgid "Staked Amount" msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Sorry, we are unable to locate this transaction hash" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:26 +msgid "Staker's Address" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Sources *.sol files" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:154 +msgid "Stakes" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 -#, elixir-autogen, elixir-format -msgid "Sources *.sol or *.yul files" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:59 +#: lib/block_scout_web/templates/stakes/_stakes_progress.html.eex:25 lib/block_scout_web/templates/stakes/_table.html.eex:34 +msgid "Stakes Ratio" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Sources and Metadata JSON" +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:160 +msgid "Staking" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 -#, elixir-autogen, elixir-format -msgid "Stakes" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:20 +msgid "Staking Address:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 -#, elixir-autogen, elixir-format -msgid "Standard Input JSON" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:928 +msgid "Staking epochs are not specified or not in the allowed range" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:538 -#, elixir-autogen, elixir-format -msgid "State changes" +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:29 +msgid "Standard Input JSON" msgstr "" +#, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:24 -#, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:102 msgid "Status" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Submission date" -msgstr "" - -#: lib/block_scout_web/templates/layout/_footer.html.eex:41 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:39 msgid "Submit an Issue" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:372 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:352 msgid "Success" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:38 +msgid "Swap STAKE on Honeyswap" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:40 msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:67 +msgid "Target Pool" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:30 msgid "Telegram" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:65 msgid "Test Networks" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 -#, elixir-autogen, elixir-format -msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 +msgid "Testnet" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#, elixir-autogen, elixir-format -msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:89 +msgid "The Number of Delegators in the Pool" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:122 -#, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:39 +msgid "The amount can be claimed after the current epoch finishes." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:51 -#, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "The changes from this transaction have not yet happened since the transaction is still pending." +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 +msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 -#, elixir-autogen, elixir-format -msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:23 +msgid "The first amount is the candidate’s own stake, the second is the total amount staked into the pool by the candidate and all delegators." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 -#, elixir-autogen, elixir-format -msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:25 +msgid "The first amount is the pool owner’s stake, the second is the total amount staked into the pool by the pool owner and all delegators." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:21 +msgid "The first amount is the validator’s own stake, the second is the total amount staked into the pool by the validator and all delegators." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:138 -#, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:74 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:90 msgid "The name found in the source code of the Contract." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:85 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:101 msgid "The name of the validator." msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:43 +msgid "The number of delegators providing stake to the pool. Click on the number to see more details." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:79 -#, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:34 +msgid "The percentage of stake in a single pool relative to the total amount staked in all active pools. A higher ratio results in a greater likelihood of validator pool selection." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:137 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:105 +msgid "The rest addresses are delegators of its pool." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:126 msgid "The revert reason of the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:109 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:34 +msgid "The staking epochs for which the reward could be claimed (read-only field):" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:101 msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:58 +msgid "The table refreshed block(s) ago." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:82 msgid "The total amount of tokens issued" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:177 -#, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_validation/index.html.eex:16 -#, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:28 msgid "There are no holders for this Token." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 -#, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 -#, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:28 -#, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 -#, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 -#, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 -#, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 -#, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/index.html.eex:65 -#, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:27 msgid "There are no tokens." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 -#, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#: lib/block_scout_web/templates/block_transaction/index.html.eex:19 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block_transaction/index.html.eex:27 msgid "There are no transactions for this block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:31 -#, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 lib/block_scout_web/templates/tokens/transfer/index.html.eex:26 msgid "There are no transfers for this Token." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 -#, elixir-autogen, elixir-format -msgid "There are no verified contracts." -msgstr "" - -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals for this address." -msgstr "" - -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals for this block." -msgstr "" - -#: lib/block_scout_web/templates/withdrawal/index.html.eex:53 -#, elixir-autogen, elixir-format -msgid "There are no withdrawals." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 -#, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:29 -#, elixir-autogen, elixir-format -msgid "There is no decompiled contracts for this address." +msgid "There is no decompilded contracts for this address." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_empty_content.html.eex:12 +msgid "There is no information currently available for this view. Deselect filters or choose another pool view to see current info. To participate as a delegator, select a pool address and click the Stake icon. To become a candidate, click the button below." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 #: lib/block_scout_web/templates/chain/show.html.eex:9 -#, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/index.html.eex:6 -#, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 -#, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" +#, elixir-format #: lib/block_scout_web/views/block_transaction_view.ex:11 -#, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:41 msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:51 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:45 msgid "This contract has been verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 -#, elixir-autogen, elixir-format -msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:102 +msgid "This is a %{pool_type}." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:5 +msgid "This is a validator" msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." +#, elixir-format +#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 +msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:22 -#, elixir-autogen, elixir-format -msgid "This transaction hasn't changed state." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:31 +msgid "This pool is banned until block #%{banned_until} (%{estimated_unban_day})" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:64 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:58 msgid "This transaction is pending confirmation." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:71 -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:65 +#: lib/block_scout_web/templates/transaction/overview.html.eex:169 msgid "Timestamp" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:247 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:32 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:9 -#: lib/block_scout_web/views/address_token_transfer_view.ex:9 -#: lib/block_scout_web/views/address_transaction_view.ex:9 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 lib/block_scout_web/templates/address_transaction/index.html.eex:28 +#: lib/block_scout_web/templates/transaction/overview.html.eex:215 lib/block_scout_web/views/address_internal_transaction_view.ex:9 +#: lib/block_scout_web/views/address_token_transfer_view.ex:9 lib/block_scout_web/views/address_transaction_view.ex:9 msgid "To" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 -#, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:13 msgid "Toggle navigation" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/tokens/index.html.eex:31 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:71 msgid "Token" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:471 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 lib/block_scout_web/views/transaction_view.ex:450 msgid "Token Burning" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:472 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 lib/block_scout_web/views/transaction_view.ex:451 msgid "Token Creation" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:42 msgid "Token Details" msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 +#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 lib/block_scout_web/views/tokens/overview_view.ex:42 msgid "Token Holders" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 msgid "Token ID" msgstr "" -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:470 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 lib/block_scout_web/views/transaction_view.ex:449 msgid "Token Minting" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:473 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 lib/block_scout_web/views/transaction_view.ex:452 msgid "Token Transfer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:13 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:378 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:114 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 -#: lib/block_scout_web/views/transaction_view.ex:534 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 +#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 +#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:14 lib/block_scout_web/templates/transaction/_tabs.html.eex:4 +#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 lib/block_scout_web/views/address_view.ex:348 +#: lib/block_scout_web/views/tokens/instance/overview_view.ex:186 lib/block_scout_web/views/tokens/overview_view.ex:41 +#: lib/block_scout_web/views/transaction_view.ex:513 msgid "Token Transfers" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:54 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:70 msgid "Token name and symbol." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:156 msgid "Token type" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:175 -#: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:375 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:190 lib/block_scout_web/templates/address_token/overview.html.eex:58 +#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 lib/block_scout_web/templates/layout/_topnav.html.eex:69 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:101 lib/block_scout_web/templates/tokens/index.html.eex:10 +#: lib/block_scout_web/views/address_view.ex:345 msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:316 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:288 msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:332 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:304 msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:299 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:271 msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:283 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:255 msgid "Tokens Transferred" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:13 -#, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/index.html.eex:14 -#, elixir-autogen, elixir-format msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:90 msgid "Topics" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 -#, elixir-autogen, elixir-format -msgid "Total" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:169 -#, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:43 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:97 msgid "Total Supply" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 -#, elixir-autogen, elixir-format -msgid "Total Supply * Price" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:131 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:142 msgid "Total blocks" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:168 -#, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:186 -#, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 -#, elixir-autogen, elixir-format -msgid "Total supply" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:363 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:335 msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:110 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:132 msgid "Total transactions" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_top.html.eex:52 +msgid "Trade STAKE on AscendEX" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:483 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:462 msgid "Transaction" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 -#, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 -#, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:205 -#, elixir-autogen, elixir-format -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:438 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:410 msgid "Transaction Burnt Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:50 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:49 msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:364 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:336 msgid "Transaction Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:80 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:74 msgid "Transaction Hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 -#, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 -#, elixir-autogen, elixir-format -msgid "Transaction Tags" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:388 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:360 msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:461 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:433 msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:387 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:359 msgid "Transaction type, introduced in EIP-2718." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:7 -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/overview.html.eex:186 -#: lib/block_scout_web/templates/address/overview.html.eex:192 -#: lib/block_scout_web/templates/address/overview.html.eex:200 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:4 -#: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:214 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 -#: lib/block_scout_web/views/address_view.ex:377 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:201 lib/block_scout_web/templates/address/overview.html.eex:207 +#: lib/block_scout_web/templates/address/overview.html.eex:215 lib/block_scout_web/templates/address_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/block/overview.html.eex:74 lib/block_scout_web/templates/block_transaction/index.html.eex:10 +#: lib/block_scout_web/templates/chain/show.html.eex:225 lib/block_scout_web/templates/layout/_topnav.html.eex:43 +#: lib/block_scout_web/views/address_view.ex:347 msgid "Transactions" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:101 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:117 msgid "Transactions and address of creation." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:213 -#: lib/block_scout_web/templates/address/overview.html.eex:219 -#: lib/block_scout_web/templates/address/overview.html.eex:227 +#, elixir-format +#: lib/block_scout_web/templates/address/_tile.html.eex:31 +msgid "Transactions sent" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:228 +#: lib/block_scout_web/templates/address/overview.html.eex:234 lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 -#, elixir-autogen, elixir-format msgid "Transfers" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 -#, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 -#, elixir-autogen, elixir-format -msgid "Try to fetch constructor arguments automatically" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/layout/_footer.html.eex:27 -#, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:53 -#, elixir-autogen, elixir-format -msgid "Tx/day" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:53 +msgid "Tx Gas Limit:" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 -#, elixir-autogen, elixir-format -msgid "Txns" +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:92 +#: lib/block_scout_web/templates/layout/app.html.eex:52 +msgid "Tx/day" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 -#, elixir-autogen, elixir-format msgid "Type" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 -#, elixir-autogen, elixir-format -msgid "Type of the token standard" -msgstr "" - -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 -#, elixir-autogen, elixir-format -msgid "UML diagram" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:459 +msgid "UTF-8" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:487 -#, elixir-autogen, elixir-format -msgid "UTF-8" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:69 +msgid "Unable to find any pools you could claim a reward from." msgstr "" -#: lib/block_scout_web/views/block_view.ex:77 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/block_view.ex:75 msgid "Uncle Reward" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:250 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:31 msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:363 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:343 msgid "Unconfirmed" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 -#, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:79 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:73 msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Update" +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:891 +#: lib/block_scout_web/channels/stakes_channel.ex:938 +msgid "Unknown address of Staking contract. Please, contact support" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:931 +msgid "Unknown pool staking address. Please, contact support" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:417 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/channels/stakes_channel.ex:887 +#: lib/block_scout_web/channels/stakes_channel.ex:934 +msgid "Unknown staker address. Please, choose your account in MetaMask" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12 +msgid "Use the search box to find a hosted network, or select from the list of available networks below." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:389 msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:427 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:399 msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:224 -#, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_topnav.html.eex:47 msgid "Validated" -msgstr "" +msgstr "Mined" +#, elixir-format #: lib/block_scout_web/templates/transaction/index.html.eex:12 -#, elixir-autogen, elixir-format msgid "Validated Transactions" -msgstr "" +msgstr "Mined Transactions" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 -#, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 -#, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:86 -#, elixir-autogen, elixir-format -msgid "Validator Name" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:51 +msgid "Validator Info" msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#, elixir-autogen, elixir-format -msgid "Validator index" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:102 +msgid "Validator Name" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:349 -#, elixir-autogen, elixir-format -msgid "Value" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:8 +msgid "Validator Pool Addresses." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:348 -#, elixir-autogen, elixir-format -msgid "Value sent in the native token (and USD) if applicable." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_table.html.eex:32 +msgid "Validator pools can be banned for misbehavior (such as not revealing secret numbers). Validator and delegator stake contained in a banned pool cannot be withdrawn until the ban is over." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 -#, elixir-autogen, elixir-format -msgid "Verified" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:321 +msgid "Value" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 -#, elixir-autogen, elixir-format -msgid "Verified Contracts" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:320 +msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:88 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:82 msgid "Verified at" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:69 -#, elixir-autogen, elixir-format -msgid "Verified contracts" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 -#, elixir-autogen, elixir-format -msgid "Verified contracts - %{subnetwork} Explorer" -msgstr "" - -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 -#, elixir-autogen, elixir-format -msgid "Verified contracts, %{subnetwork}, %{coin}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 -#: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:228 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract/index.html.eex:29 lib/block_scout_web/templates/address_contract/index.html.eex:164 +#: lib/block_scout_web/templates/address_contract/index.html.eex:170 lib/block_scout_web/templates/address_contract/index.html.eex:201 +#: lib/block_scout_web/templates/address_contract/index.html.eex:207 lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 msgid "Verify & Publish" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:227 +#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:44 +#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:56 +#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:81 msgid "Verify & publish" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "Verify the contract " msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/layout/_footer.html.eex:91 msgid "Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:42 msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:36 msgid "Via Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:31 msgid "Via flattened source code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 -#, elixir-autogen, elixir-format -msgid "Via multi-part files" -msgstr "" - -#: lib/block_scout_web/templates/chain/show.html.eex:153 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:164 msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:213 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:224 msgid "View All Transactions" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 -#, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:61 msgid "View Less Transfers" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/transaction/_tile.html.eex:60 msgid "View More Transfers" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:39 -#, elixir-autogen, elixir-format msgid "View next block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:23 -#, elixir-autogen, elixir-format msgid "View previous block" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address/_metatags.html.eex:9 -#, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 -#, elixir-autogen, elixir-format -msgid "View the beacon chain withdrawals on %{subnetwork}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/block/_metatags.html.eex:10 -#, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 -#, elixir-autogen, elixir-format -msgid "View the verified contracts on %{subnetwork}" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 -#, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 -#: lib/block_scout_web/views/verified_contracts_view.ex:11 -#, elixir-autogen, elixir-format -msgid "Vyper" -msgstr "" - -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:48 msgid "Vyper contract" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:132 msgid "WEI" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 -#, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:139 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/chain/show.html.eex:150 msgid "Wallet addresses" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 -#, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Watch list" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:2 +msgid "We found the following pools you can claim reward from:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" +#, elixir-format +#: lib/block_scout_web/views/wei_helpers.ex:76 +msgid "Wei" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:80 -#, elixir-autogen, elixir-format -msgid "Wei" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:7 +msgid "Withdraw" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:70 +msgid "Withdraw Now" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_rows.html.eex:52 +msgid "Withdraw after block #%{banned_delegators_until} (%{estimated_unban_day})" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:22 +msgid "Withdrawal orders made during an active staking epoch are available to claim after the epoch is complete." msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:73 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:5 -#, elixir-autogen, elixir-format -msgid "Withdrawals" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:35 +msgid "Working Stake Amount" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:98 msgid "Write" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:103 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 -#: lib/block_scout_web/views/address_view.ex:383 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:95 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 lib/block_scout_web/views/address_view.ex:353 msgid "Write Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:110 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:384 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/address/_tabs.html.eex:102 +#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 lib/block_scout_web/views/address_view.ex:354 msgid "Write Proxy" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:61 lib/block_scout_web/templates/stakes/_rows.html.eex:24 msgid "Yes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "You can create 3 API keys per account." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:34 +msgid "You Can Order:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_move.html.eex:15 +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:12 +msgid "You Staked:" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward.html.eex:11 +msgid "You can get your reward for all staking epochs during which the pool was a validator or specify separate epochs if Tx Gas Limit is too high. Tx Gas Limit depends on how long the pool was a validator and how many staking epochs you held your stake in the pool without movement." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_withdraw.html.eex:52 +msgid "You can withdraw this amount right now." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 -#, elixir-autogen, elixir-format -msgid "You can create up to 15 Custom ABIs per account." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_pool_info.html.eex:38 +msgid "You will be able to withdraw after block #%{banned_delegators_until} (%{estimated_unban_day})" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/index.html.eex:11 -#, elixir-autogen, elixir-format -msgid "You can request a public category tag which is displayed to all Blockscout users. Public tags may be added to contract or external addresses, and any associated transactions will inherit that tag. Clicking a tag opens a page with related information and helps provide context and data organization. Requests are sent to a moderator for review and approval. This process can take several days." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:48 +msgid "You will receive:" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have address tags yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:22 lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:28 +msgid "Your Balance:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have addresses on you watchlist yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_stake.html.eex:16 +msgid "Your Current Stake:" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 -#, elixir-autogen, elixir-format -msgid "You don't have transaction tags yet" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:12 +msgid "Your Mining Address" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:15 -#, elixir-autogen, elixir-format -msgid "Your name" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:14 +msgid "Your Pool Name" msgstr "" -#: lib/block_scout_web/templates/account/public_tags_request/form.html.eex:14 -#, elixir-autogen, elixir-format -msgid "Your name*" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_become_candidate.html.eex:16 +msgid "Your Pool Short Description (optional)" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:8 -#, elixir-autogen, elixir-format -msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_withdrawal.html.eex:11 +msgid "Your ordered amount" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:111 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:41 +msgid "all epochs" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:127 msgid "at" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_token/overview.html.eex:52 -#, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:409 +msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:215 +msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#, elixir-autogen, elixir-format msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:256 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:19 +msgid "candidate" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/overview.html.eex:226 msgid "created" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 -#, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:149 -#, elixir-autogen, elixir-format -msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 -#, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:41 msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:30 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_contract_view.ex:24 msgid "false" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#, elixir-autogen, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 msgid "here" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 -#, elixir-autogen, elixir-format msgid "here." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 -#, elixir-autogen, elixir-format -msgid "method Response" +#, elixir-format +#: lib/block_scout_web/templates/transaction/invalid.html.eex:8 +msgid "is not a valid transaction hash" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 -#, elixir-autogen, elixir-format msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 -#, elixir-autogen, elixir-format -msgid "on sign up. Didn’t receive?" -msgstr "" - +#, elixir-format #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 -#, elixir-autogen, elixir-format msgid "page" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:20 +msgid "pool owner" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:43 msgid "receive" msgstr "" +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 +#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 -#, elixir-autogen, elixir-format msgid "required" msgstr "" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_claim_reward_content.html.eex:42 +msgid "specified epochs only" +msgstr "" + +#, elixir-format #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#, elixir-autogen, elixir-format msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:29 -#, elixir-autogen, elixir-format +#, elixir-format +#: lib/block_scout_web/views/address_contract_view.ex:23 msgid "true" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 -#, elixir-autogen, elixir-format -msgid "truffle flattener" +#, elixir-format +#: lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex:18 +msgid "validator" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 -#, elixir-autogen, elixir-format, fuzzy -msgid "New Smart Contract Verification via Standard input JSON" +#, elixir-format +#: lib/block_scout_web/views/transaction_view.ex:216 +msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 -#, elixir-autogen, elixir-format, fuzzy -msgid "New Smart Contract Verification via metadata JSON" +#, elixir-format +#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 +msgid "Not unique Token" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 +msgid "Error" msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 -#, elixir-autogen, elixir-format, fuzzy -msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:65 +msgid "CRC Worth" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address_token/overview.html.eex:66 +msgid "Shows the total CRC balance in the address." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format, fuzzy -msgid "Export" +#, elixir-format +#: lib/block_scout_web/templates/block/overview.html.eex:110 +msgid "N/A bytes" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 -#, elixir-autogen, elixir-format, fuzzy -msgid "for address" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:262 +msgid "Fetching gas used..." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 -#, elixir-autogen, elixir-format -msgid "to CSV file" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:209 +#: lib/block_scout_web/templates/address/overview.html.eex:217 +msgid "Fetching transactions..." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 -#, elixir-autogen, elixir-format -msgid "Yul" +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:236 +#: lib/block_scout_web/templates/address/overview.html.eex:244 +msgid "Fetching transfers..." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format, fuzzy -msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +#, elixir-format +#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 +msgid "Loading chart..." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format, fuzzy -msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +#, elixir-format +#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 +msgid "method Response" msgstr "" diff --git a/apps/ethereum_jsonrpc/config/config.exs b/apps/ethereum_jsonrpc/config/config.exs index 503b7a3828ed..09dcd54c19ba 100644 --- a/apps/ethereum_jsonrpc/config/config.exs +++ b/apps/ethereum_jsonrpc/config/config.exs @@ -1,4 +1,4 @@ -import Config +use Mix.Config config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator, rolling_window_opts: [ @@ -6,8 +6,13 @@ config :ethereum_jsonrpc, EthereumJSONRPC.RequestCoordinator, duration: :timer.minutes(1), table: EthereumJSONRPC.RequestCoordinator.TimeoutCounter ], + wait_per_timeout: :timer.seconds(2), max_jitter: :timer.seconds(2) +config :ethereum_jsonrpc, + rpc_transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", do: :http, else: :ipc), + ipc_path: System.get_env("IPC_PATH") + # Add this configuration to add global RPC request throttling. # throttle_rate_limit: 250, # throttle_rolling_window_opts: [ @@ -31,4 +36,4 @@ config :logger, :ethereum_jsonrpc, # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" +import_config "#{Mix.env()}.exs" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index cd155ed3d231..426542b84397 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -274,6 +274,12 @@ defmodule EthereumJSONRPC do range |> Enum.map(fn number -> %{number: number} end) |> fetch_blocks_by_params(&Block.ByNumber.request/1, json_rpc_named_arguments) + + # range_list = Enum.to_list(range) + + # if Enum.at(range_list, 0) != Enum.at(range_list, -1) do + # Logger.info(["### fetch_blocks_by_range fetched ", inspect(range), " ###"]) + # end end @doc """ diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex index 8173a98df2a5..e2210e2ee862 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/erigon.ex @@ -1,18 +1,21 @@ # credo:disable-for-this-file defmodule EthereumJSONRPC.Erigon do @moduledoc """ - Ethereum JSONRPC methods that are only supported by Erigon. + Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). """ - import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2] + require Logger + import EthereumJSONRPC, only: [id_to_params: 1, integer_to_quantity: 1, json_rpc: 2, request: 1] - alias EthereumJSONRPC.Nethermind.Traces - alias EthereumJSONRPC.{FetchedBeneficiaries, PendingTransaction, TraceReplayBlockTransactions} + alias EthereumJSONRPC.Parity.{FetchedBeneficiaries, Traces} + alias EthereumJSONRPC.Transactions @behaviour EthereumJSONRPC.Variant @impl EthereumJSONRPC.Variant def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) and is_list(json_rpc_named_arguments) do + # Logger.info(["### Beneficiaries started. Range starts with ", inspect(Enum.at(block_numbers, 0)), " ###"]) + id_to_params = block_numbers |> block_numbers_to_params_list() @@ -22,24 +25,66 @@ defmodule EthereumJSONRPC.Erigon do id_to_params |> FetchedBeneficiaries.requests() |> json_rpc(json_rpc_named_arguments) do + # Logger.info(["### Beneficiaries FINISHED. Range starts with ", inspect(Enum.at(block_numbers, 0)), " ###"]) {:ok, FetchedBeneficiaries.from_responses(responses, id_to_params)} end end + @doc """ + Internal transaction fetching for individual transactions is no longer supported for Parity. + + To signal to the caller that fetching is not supported, `:ignore` is returned. + """ @impl EthereumJSONRPC.Variant def fetch_internal_transactions(_transactions_params, _json_rpc_named_arguments), do: :ignore @doc """ - Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Erigon trace URL. + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. """ @impl EthereumJSONRPC.Variant def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - TraceReplayBlockTransactions.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments, Traces) + id_to_params = id_to_params(block_numbers) + + with {:ok, responses} <- + id_to_params + |> trace_replay_block_transactions_requests() + |> json_rpc(json_rpc_named_arguments) do + trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) + end end @impl EthereumJSONRPC.Variant def fetch_first_trace(transactions_params, json_rpc_named_arguments) when is_list(transactions_params) do - TraceReplayBlockTransactions.fetch_first_trace(transactions_params, json_rpc_named_arguments, Traces) + id_to_params = id_to_params(transactions_params) + + trace_replay_transaction_response = + id_to_params + |> trace_replay_transaction_requests() + |> json_rpc(json_rpc_named_arguments) + + case trace_replay_transaction_response do + {:ok, responses} -> + case trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) do + {:ok, [first_trace]} -> + %{block_hash: block_hash} = + transactions_params + |> Enum.at(0) + + {:ok, + [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} + + {:error, error} -> + Logger.error(inspect(error)) + {:error, error} + end + + {:error, :econnrefused} -> + {:error, :econnrefused} + + {:error, [error]} -> + Logger.error(inspect(error)) + {:error, error} + end end @doc """ @@ -47,10 +92,227 @@ defmodule EthereumJSONRPC.Erigon do """ @impl EthereumJSONRPC.Variant def fetch_pending_transactions(json_rpc_named_arguments) do - PendingTransaction.fetch_pending_transactions_geth(json_rpc_named_arguments) + with {:ok, transaction_data} <- + %{id: 1, method: "txpool_content", params: []} |> request() |> json_rpc(json_rpc_named_arguments) do + transactions_params = + transaction_data["pending"] + |> Enum.flat_map(fn {_address, nonce_transactions_map} -> + nonce_transactions_map + |> Enum.map(fn {_nonce, transaction} -> + transaction + end) + end) + |> Transactions.to_elixir() + |> Transactions.elixir_to_params() + |> Enum.map(fn params -> + # txpool_content always returns transaction with 0x0000000000000000000000000000000000000000000000000000000000000000 value in block hash and index is null. + # https://github.com/ethereum/go-ethereum/issues/19897 + params + |> Map.merge(%{:block_hash => nil, :index => nil}) + end) + + {:ok, transactions_params} + end end defp block_numbers_to_params_list(block_numbers) when is_list(block_numbers) do Enum.map(block_numbers, &%{block_quantity: integer_to_quantity(&1)}) end + + defp trace_replay_block_transactions_responses_to_internal_transactions_params(responses, id_to_params) + when is_list(responses) and is_map(id_to_params) do + with {:ok, traces} <- trace_replay_block_transactions_responses_to_traces(responses, id_to_params) do + params = + traces + |> Traces.to_elixir() + |> Traces.elixir_to_params() + + {:ok, params} + end + end + + defp trace_replay_block_transactions_responses_to_traces(responses, id_to_params) + when is_list(responses) and is_map(id_to_params) do + responses + |> Enum.map(&trace_replay_block_transactions_response_to_traces(&1, id_to_params)) + |> Enum.reduce( + {:ok, []}, + fn + {:ok, traces}, {:ok, acc_traces_list} -> + {:ok, [traces | acc_traces_list]} + + {:ok, _}, {:error, _} = acc_error -> + acc_error + + {:error, reason}, {:ok, _} -> + {:error, [reason]} + + {:error, reason}, {:error, acc_reason} -> + {:error, [reason | acc_reason]} + end + ) + |> case do + {:ok, traces_list} -> + traces = + traces_list + |> Enum.reverse() + |> List.flatten() + + {:ok, traces} + + {:error, reverse_reasons} -> + reasons = Enum.reverse(reverse_reasons) + {:error, reasons} + end + end + + defp trace_replay_block_transactions_response_to_traces(%{id: id, result: results}, id_to_params) + when is_list(results) and is_map(id_to_params) do + block_number = Map.fetch!(id_to_params, id) + + annotated_traces = + results + |> Stream.with_index() + |> Enum.flat_map(fn {%{"trace" => traces, "transactionHash" => transaction_hash}, transaction_index} -> + traces + |> Stream.with_index() + |> Enum.map(fn {trace, index} -> + Map.merge(trace, %{ + "blockNumber" => block_number, + "transactionHash" => transaction_hash, + "transactionIndex" => transaction_index, + "index" => index + }) + end) + end) + + {:ok, annotated_traces} + end + + defp trace_replay_block_transactions_response_to_traces(%{id: id, error: error}, id_to_params) + when is_map(id_to_params) do + block_number = Map.fetch!(id_to_params, id) + + annotated_error = + Map.put(error, :data, %{ + "blockNumber" => block_number + }) + + {:error, annotated_error} + end + + defp trace_replay_block_transactions_requests(id_to_params) when is_map(id_to_params) do + Enum.map(id_to_params, fn {id, block_number} -> + trace_replay_block_transactions_request(%{id: id, block_number: block_number}) + end) + end + + defp trace_replay_block_transactions_request(%{id: id, block_number: block_number}) do + request(%{id: id, method: "trace_replayBlockTransactions", params: [integer_to_quantity(block_number), ["trace"]]}) + end + + def trace_replay_transaction_responses_to_first_trace_params(responses, id_to_params) + when is_list(responses) and is_map(id_to_params) do + with {:ok, traces} <- trace_replay_transaction_responses_to_first_trace(responses, id_to_params) do + params = + traces + |> Traces.to_elixir() + |> Traces.elixir_to_params() + + {:ok, params} + end + end + + defp trace_replay_transaction_responses_to_first_trace(responses, id_to_params) + when is_list(responses) and is_map(id_to_params) do + responses + |> Enum.map(&trace_replay_transaction_response_to_first_trace(&1, id_to_params)) + |> Enum.reduce( + {:ok, []}, + fn + {:ok, traces}, {:ok, acc_traces_list} -> + {:ok, [traces | acc_traces_list]} + + {:ok, _}, {:error, _} = acc_error -> + acc_error + + {:error, reason}, {:ok, _} -> + {:error, [reason]} + + {:error, reason}, {:error, acc_reason} -> + {:error, [reason | acc_reason]} + end + ) + |> case do + {:ok, traces_list} -> + traces = + traces_list + |> Enum.reverse() + |> List.flatten() + + {:ok, traces} + + {:error, reverse_reasons} -> + reasons = Enum.reverse(reverse_reasons) + {:error, reasons} + end + end + + defp trace_replay_transaction_response_to_first_trace(%{id: id, result: %{"trace" => traces}}, id_to_params) + when is_list(traces) and is_map(id_to_params) do + %{ + block_hash: block_hash, + block_number: block_number, + hash_data: transaction_hash, + transaction_index: transaction_index + } = Map.fetch!(id_to_params, id) + + first_trace = + traces + |> Stream.with_index() + |> Enum.map(fn {trace, index} -> + Map.merge(trace, %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "index" => index, + "transactionIndex" => transaction_index, + "transactionHash" => transaction_hash + }) + end) + |> Enum.filter(fn trace -> + Map.get(trace, "index") == 0 + end) + + {:ok, first_trace} + end + + defp trace_replay_transaction_response_to_first_trace(%{id: id, error: error}, id_to_params) + when is_map(id_to_params) do + %{ + block_hash: block_hash, + block_number: block_number, + hash_data: transaction_hash, + transaction_index: transaction_index + } = Map.fetch!(id_to_params, id) + + annotated_error = + Map.put(error, :data, %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "transactionIndex" => transaction_index, + "transactionHash" => transaction_hash + }) + + {:error, annotated_error} + end + + defp trace_replay_transaction_requests(id_to_params) when is_map(id_to_params) do + Enum.map(id_to_params, fn {id, %{hash_data: hash_data}} -> + trace_replay_transaction_request(%{id: id, hash_data: hash_data}) + end) + end + + defp trace_replay_transaction_request(%{id: id, hash_data: hash_data}) do + request(%{id: id, method: "trace_replayTransaction", params: [hash_data, ["trace"]]}) + end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex index 047a4d6bbd8f..71a4918491c8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/nethermind/trace.ex @@ -451,7 +451,7 @@ defmodule EthereumJSONRPC.Nethermind.Trace do # subtraces is an actual integer in JSON and not hex-encoded # traceAddress is a list of actual integers, not a list of hex-encoded defp entry_to_elixir({key, _} = entry) - when key in ~w(subtraces traceAddress transactionHash blockHash type output), + when key in ~w(subtraces traceAddress transactionHash blockHash type output transactionPosition), do: entry defp entry_to_elixir({"action" = key, action}) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index b7ba7412f520..395741229482 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -249,7 +249,6 @@ defmodule EthereumJSONRPC.Transaction do "blockNumber" => block_number, "from" => from_address_hash, "gas" => gas, - "gasPrice" => gas_price, "hash" => hash, "input" => input, "nonce" => nonce, @@ -269,7 +268,7 @@ defmodule EthereumJSONRPC.Transaction do block_number: block_number, from_address_hash: from_address_hash, gas: gas, - gas_price: gas_price, + gas_price: max_fee_per_gas, hash: hash, index: index, input: input, @@ -454,6 +453,55 @@ defmodule EthereumJSONRPC.Transaction do ]) end + def elixir_to_params( + %{ + "blockHash" => block_hash, + "blockNumber" => block_number, + "from" => from_address_hash, + "gas" => gas, + "gasPrice" => gas_price, + "hash" => hash, + "input" => input, + "nonce" => nonce, + "r" => r, + "s" => s, + "to" => to_address_hash, + "transactionIndex" => index, + "v" => v, + "value" => value, + "type" => type, + "maxPriorityFeePerGas" => max_priority_fee_per_gas, + "maxFeePerGas" => max_fee_per_gas + } = transaction + ) do + result = %{ + block_hash: block_hash, + block_number: block_number, + from_address_hash: from_address_hash, + gas: gas, + gas_price: gas_price, + hash: hash, + index: index, + input: input, + nonce: nonce, + r: r, + s: s, + to_address_hash: to_address_hash, + v: v, + value: value, + transaction_index: index, + type: type, + max_priority_fee_per_gas: max_priority_fee_per_gas, + max_fee_per_gas: max_fee_per_gas + } + + if transaction["creates"] do + Map.put(result, :created_contract_address_hash, transaction["creates"]) + else + result + end + end + def elixir_to_params( %{ "blockHash" => block_hash, @@ -533,6 +581,16 @@ defmodule EthereumJSONRPC.Transaction do end end + def elixir_to_params( + %{ + nil => _ + } = transaction + ) do + transaction + |> Map.delete(nil) + |> elixir_to_params() + end + @doc """ Extracts `t:EthereumJSONRPC.hash/0` from transaction `params` diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 117ca2c0ab4a..eac0132626fa 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -1,45 +1,86 @@ # This file is responsible for configuring your application -# and its dependencies with the aid of the Config module. +# and its dependencies with the aid of the Mix.Config module. # # This configuration file is loaded before any dependency and # is restricted to this project. -import Config - -[__DIR__ | ~w(.. .. .. config config_helper.exs)] -|> Path.join() -|> Code.eval_file() +use Mix.Config # General application configuration config :explorer, - chain_type: ConfigHelper.chain_type(), - ecto_repos: ConfigHelper.repos(), - token_functions_reader_max_retries: 3, - # for not fully indexed blockchains - decode_not_a_contract_calls: ConfigHelper.parse_bool_env_var("DECODE_NOT_A_CONTRACT_CALLS") - -config :explorer, Explorer.ChainSpec.GenesisData, enabled: true - -config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true + ecto_repos: [Explorer.Repo], + coin: System.get_env("COIN") || "ETH", + coingecko_coin_id: System.get_env("COINGECKO_COIN_ID"), + token_functions_reader_max_retries: 1, + allowed_evm_versions: + System.get_env("ALLOWED_EVM_VERSIONS") || + "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,default", + include_uncles_in_average_block_time: + if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "true", do: true, else: false), + healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5), + realtime_events_sender: + if(System.get_env("DISABLE_WEBAPP") != "true", + do: Explorer.Chain.Events.SimpleSender, + else: Explorer.Chain.Events.DBSender + ) + +config :explorer, Explorer.Counters.AverageBlockTime, + enabled: true, + period: :timer.minutes(10) + +config :explorer, Explorer.Chain.Events.Listener, + enabled: + if(System.get_env("DISABLE_WEBAPP") == nil && System.get_env("DISABLE_INDEXER") == nil, + do: false, + else: true + ) + +config :explorer, Explorer.ChainSpec.GenesisData, + enabled: true, + chain_spec_path: System.get_env("CHAIN_SPEC_PATH"), + emission_format: System.get_env("EMISSION_FORMAT", "DEFAULT"), + rewards_contract_address: System.get_env("REWARDS_CONTRACT", "0xeca443e8e1ab29971a45a9c57a6a9875701698a5") + +config :explorer, Explorer.Chain.Cache.BlockNumber, + enabled: true, + ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false), + global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) + +address_sum_global_ttl = + "ADDRESS_SUM_CACHE_PERIOD" + |> System.get_env("") + |> Integer.parse() + |> case do + {integer, ""} -> :timer.seconds(integer) + _ -> :timer.minutes(60) + end config :explorer, Explorer.Chain.Cache.AddressSum, enabled: true, - ttl_check_interval: :timer.seconds(1) + ttl_check_interval: :timer.seconds(1), + global_ttl: address_sum_global_ttl config :explorer, Explorer.Chain.Cache.AddressSumMinusBurnt, enabled: true, - ttl_check_interval: :timer.seconds(1) + ttl_check_interval: :timer.seconds(1), + global_ttl: address_sum_global_ttl -update_interval_in_milliseconds = ConfigHelper.parse_time_env_var("CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL", "30m") +balances_update_interval = + if System.get_env("ADDRESS_WITH_BALANCES_UPDATE_INTERVAL") do + case Integer.parse(System.get_env("ADDRESS_WITH_BALANCES_UPDATE_INTERVAL")) do + {integer, ""} -> integer + _ -> nil + end + end config :explorer, Explorer.Counters.AddressesWithBalanceCounter, enabled: false, enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds + update_interval_in_seconds: balances_update_interval || 30 * 60 config :explorer, Explorer.Counters.AddressesCounter, enabled: true, enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds + update_interval_in_seconds: balances_update_interval || 30 * 60 config :explorer, Explorer.Counters.AddressTransactionsGasUsageCounter, enabled: true, @@ -49,41 +90,9 @@ config :explorer, Explorer.Counters.AddressTokenUsdSum, enabled: true, enable_consolidation: true -update_interval_in_milliseconds_default = 30 * 60 * 1000 - -config :explorer, Explorer.Chain.Cache.ContractsCounter, - enabled: true, - enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds_default - -config :explorer, Explorer.Chain.Cache.NewContractsCounter, - enabled: true, - enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds_default - -config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, +config :explorer, Explorer.Chain.Cache.TokenExchangeRate, enabled: true, - enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds_default - -config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, - enabled: true, - enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds_default - -config :explorer, Explorer.Chain.Cache.WithdrawalsSum, - enabled: true, - enable_consolidation: true, - update_interval_in_milliseconds: update_interval_in_milliseconds_default - -config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, enabled: true - -config :explorer, Explorer.Chain.Cache.TransactionActionUniswapPools, enabled: true - -config :explorer, Explorer.ExchangeRates, - cache_period: ConfigHelper.parse_time_env_var("CACHE_EXCHANGE_RATES_PERIOD", "10m") - -config :explorer, Explorer.ExchangeRates.TokenExchangeRates, enabled: true + enable_consolidation: true config :explorer, Explorer.Counters.TokenHoldersCounter, enabled: true, @@ -101,7 +110,7 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter, enabled: true, enable_consolidation: true -config :explorer, Explorer.Counters.BlockBurntFeeCounter, +config :explorer, Explorer.Counters.BlockBurnedFeeCounter, enabled: true, enable_consolidation: true @@ -109,22 +118,52 @@ config :explorer, Explorer.Counters.BlockPriorityFeeCounter, enabled: true, enable_consolidation: true -config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: true - -config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true -config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true -config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true +bridge_market_cap_update_interval = + if System.get_env("BRIDGE_MARKET_CAP_UPDATE_INTERVAL") do + case Integer.parse(System.get_env("BRIDGE_MARKET_CAP_UPDATE_INTERVAL")) do + {integer, ""} -> integer + _ -> nil + end + end -config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true +config :explorer, Explorer.Counters.Bridge, + enabled: if(System.get_env("SUPPLY_MODULE") === "TokenBridge", do: true, else: false), + enable_consolidation: System.get_env("DISABLE_BRIDGE_MARKET_CAP_UPDATER") !== "true", + update_interval_in_seconds: bridge_market_cap_update_interval || 30 * 60, + disable_lp_tokens_in_market_cap: System.get_env("DISABLE_LP_TOKENS_IN_MARKET_CAP") == "true" -config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: true +config :explorer, Explorer.ExchangeRates, enabled: System.get_env("DISABLE_EXCHANGE_RATES") != "true", store: :ets -config :explorer, Explorer.Chain.Cache.GasUsage, - enabled: ConfigHelper.parse_bool_env_var("CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED") +config :explorer, Explorer.KnownTokens, enabled: System.get_env("DISABLE_KNOWN_TOKENS") != "true", store: :ets config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: :timer.seconds(2) -config :explorer, Explorer.Tags.AddressTag.Cataloger, enabled: true +config :explorer, Explorer.Market.History.Cataloger, enabled: System.get_env("DISABLE_INDEXER") != "true" + +txs_stats_init_lag = + System.get_env("TXS_HISTORIAN_INIT_LAG", "0") + |> Integer.parse() + |> elem(0) + |> :timer.minutes() + +txs_stats_days_to_compile_at_init = + System.get_env("TXS_STATS_DAYS_TO_COMPILE_AT_INIT", "40") + |> Integer.parse() + |> elem(0) + +config :explorer, Explorer.Chain.Transaction.History.Historian, + enabled: System.get_env("ENABLE_TXS_STATS", "false") != "false", + init_lag: txs_stats_init_lag, + days_to_compile_at_init: txs_stats_days_to_compile_at_init + +history_fetch_interval = + case Integer.parse(System.get_env("HISTORY_FETCH_INTERVAL", "")) do + {mins, ""} -> mins + _ -> 60 + end + |> :timer.minutes() + +config :explorer, Explorer.History.Process, history_fetch_interval: history_fetch_interval config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime_usec] @@ -133,12 +172,48 @@ config :explorer, Explorer.Tracer, adapter: SpandexDatadog.Adapter, trace_key: :blockscout -config :explorer, - solc_bin_api_url: "https://solc-bin.ethereum.org" - -config :explorer, :http_adapter, HTTPoison +if System.get_env("METADATA_CONTRACT") && System.get_env("VALIDATORS_CONTRACT") do + config :explorer, Explorer.Validator.MetadataRetriever, + metadata_contract_address: System.get_env("METADATA_CONTRACT"), + validators_contract_address: System.get_env("VALIDATORS_CONTRACT") + + config :explorer, Explorer.Validator.MetadataProcessor, enabled: System.get_env("DISABLE_INDEXER") != "true" +else + config :explorer, Explorer.Validator.MetadataProcessor, enabled: false +end + +config :explorer, Explorer.Chain.Block.Reward, + validators_contract_address: System.get_env("VALIDATORS_CONTRACT"), + keys_manager_contract_address: System.get_env("KEYS_MANAGER_CONTRACT") + +if System.get_env("POS_STAKING_CONTRACT") do + config :explorer, Explorer.Staking.ContractState, + enabled: true, + staking_contract_address: System.get_env("POS_STAKING_CONTRACT"), + eth_subscribe_max_delay: System.get_env("POS_ETH_SUBSCRIBE_MAX_DELAY", "60"), + eth_blocknumber_pull_interval: System.get_env("POS_ETH_BLOCKNUMBER_PULL_INTERVAL", "500") +else + config :explorer, Explorer.Staking.ContractState, enabled: false +end + +case System.get_env("SUPPLY_MODULE") do + "TokenBridge" -> + config :explorer, supply: Explorer.Chain.Supply.TokenBridge + + "rsk" -> + config :explorer, supply: Explorer.Chain.Supply.RSK + + _ -> + :ok +end + +if System.get_env("SOURCE_MODULE") == "TokenBridge" do + config :explorer, Explorer.ExchangeRates.Source, source: Explorer.ExchangeRates.Source.TokenBridge +end -config :explorer, Explorer.Chain.BridgedToken, enabled: ConfigHelper.parse_bool_env_var("BRIDGED_TOKENS_ENABLED") +config :explorer, + solc_bin_api_url: "https://solc-bin.ethereum.org", + checksum_function: System.get_env("CHECKSUM_FUNCTION") && String.to_atom(System.get_env("CHECKSUM_FUNCTION")) config :logger, :explorer, # keep synced with `config/config.exs` @@ -153,6 +228,26 @@ config :spandex_ecto, SpandexEcto.EctoLogger, tracer: Explorer.Tracer, otp_app: :explorer +config :explorer, Explorer.Chain.Cache.Transactions, + ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false), + global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.Accounts, + ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false), + global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.Uncles, + ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false), + global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5)) + +config :explorer, Explorer.Chain.Cache.GasUsage, enabled: false + +config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, + server_url: System.get_env("SOURCIFY_SERVER_URL") || "https://sourcify.dev/server", + enabled: System.get_env("ENABLE_SOURCIFY_INTEGRATION") == "true", + chain_id: System.get_env("CHAIN_ID"), + repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" +import_config "#{Mix.env()}.exs" diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index e83c505a1db2..377382765e8f 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -1,35 +1,32 @@ -import Config +use Mix.Config + +database = if System.get_env("DATABASE_URL"), do: nil, else: "explorer_dev" +hostname = if System.get_env("DATABASE_URL"), do: nil, else: "localhost" + +database_api_url = + if System.get_env("DATABASE_READ_ONLY_API_URL"), + do: System.get_env("DATABASE_READ_ONLY_API_URL"), + else: System.get_env("DATABASE_URL") # Configure your database config :explorer, Explorer.Repo, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: String.to_integer(System.get_env("POOL_SIZE", "50")), timeout: :timer.seconds(80), - migration_lock: nil - -# Configure API database -config :explorer, Explorer.Repo.Replica1, timeout: :timer.seconds(80) - -# Configure Account database -config :explorer, Explorer.Repo.Account, timeout: :timer.seconds(80) - -# Configure Polygon Edge database -config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80) - -# Configure Polygon zkEVM database -config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80) + queue_target: 2000 -# Configure ZkSync database -config :explorer, Explorer.Repo.ZkSync, timeout: :timer.seconds(80) +database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database +hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname -config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Shibarium, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80) - -# Configure Arbitrum database -config :explorer, Explorer.Repo.Arbitrum, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80) +# Configure API database +config :explorer, Explorer.Repo.Replica1, + database: database_api, + hostname: hostname_api, + url: database_api_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE_API", "50")), + timeout: :timer.seconds(80) config :explorer, Explorer.Tracer, env: "dev", disabled?: true @@ -46,3 +43,17 @@ config :logger, :token_instances, level: :debug, path: Path.absname("logs/dev/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances] + +variant = + if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do + "parity" + else + System.get_env("ETHEREUM_JSONRPC_VARIANT") + |> String.split(".") + |> List.last() + |> String.downcase() + end + +# Import variant specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "dev/#{variant}.exs" diff --git a/apps/explorer/config/dev/erigon.exs b/apps/explorer/config/dev/erigon.exs index 163f526996f6..197d976cdbb3 100644 --- a/apps/explorer/config/dev/erigon.exs +++ b/apps/explorer/config/dev/erigon.exs @@ -1,11 +1,4 @@ -import Config - -~w(config config_helper.exs) -|> Path.join() -|> Code.eval_file() - -hackney_opts = ConfigHelper.hackney_options() -timeout = ConfigHelper.timeout(1) +use Mix.Config config :explorer, json_rpc_named_arguments: [ @@ -13,14 +6,12 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", - fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), - fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), + eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], - http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Erigon ], diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 3ff639dfce9a..bf2036f9ba27 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -1,68 +1,56 @@ -import Config +use Mix.Config # Configures the database config :explorer, Explorer.Repo, + url: System.get_env("DATABASE_URL"), + pool_size: String.to_integer(System.get_env("POOL_SIZE", "50")), + ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), prepare: :unnamed, timeout: :timer.seconds(60), - migration_lock: nil + queue_target: 2000 + +database_api_url = + if System.get_env("DATABASE_READ_ONLY_API_URL"), + do: System.get_env("DATABASE_READ_ONLY_API_URL"), + else: System.get_env("DATABASE_URL") # Configures API the database config :explorer, Explorer.Repo.Replica1, - prepare: :unnamed, - timeout: :timer.seconds(60) - -# Configures Account database -config :explorer, Explorer.Repo.Account, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.PolygonEdge, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.PolygonZkevm, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.ZkSync, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.RSK, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.Shibarium, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.Suave, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.Arbitrum, - prepare: :unnamed, - timeout: :timer.seconds(60) - -config :explorer, Explorer.Repo.BridgedTokens, + url: database_api_url, + pool_size: String.to_integer(System.get_env("POOL_SIZE_API", "50")), + ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), prepare: :unnamed, timeout: :timer.seconds(60) config :explorer, Explorer.Tracer, env: "production", disabled?: true config :logger, :explorer, - level: :info, + level: :debug, path: Path.absname("logs/prod/explorer.log"), - rotate: %{max_bytes: 52_428_800, keep: 5} + rotate: %{max_bytes: 52_428_800, keep: 19} config :logger, :reading_token_functions, level: :debug, path: Path.absname("logs/prod/explorer/tokens/reading_functions.log"), metadata_filter: [fetcher: :token_functions], - rotate: %{max_bytes: 52_428_800, keep: 5} + rotate: %{max_bytes: 52_428_800, keep: 19} config :logger, :token_instances, level: :debug, path: Path.absname("logs/prod/explorer/tokens/token_instances.log"), metadata_filter: [fetcher: :token_instances], - rotate: %{max_bytes: 52_428_800, keep: 5} + rotate: %{max_bytes: 52_428_800, keep: 19} + +variant = + if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do + "parity" + else + System.get_env("ETHEREUM_JSONRPC_VARIANT") + |> String.split(".") + |> List.last() + |> String.downcase() + end + +# Import variant specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "prod/#{variant}.exs" diff --git a/apps/explorer/config/prod/erigon.exs b/apps/explorer/config/prod/erigon.exs index 1ca954c1ef41..f2f9a0f3b77f 100644 --- a/apps/explorer/config/prod/erigon.exs +++ b/apps/explorer/config/prod/erigon.exs @@ -1,11 +1,4 @@ -import Config - -~w(config config_helper.exs) -|> Path.join() -|> Code.eval_file() - -hackney_opts = ConfigHelper.hackney_options() -timeout = ConfigHelper.timeout(1) +use Mix.Config config :explorer, json_rpc_named_arguments: [ @@ -13,14 +6,12 @@ config :explorer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), - fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), - fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: ConfigHelper.eth_call_url(), + eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], - http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Erigon ], diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index caa7e586dad6..c579fc5f2392 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -9,23 +9,16 @@ defmodule Explorer.Application do alias Explorer.Chain.Cache.{ Accounts, - AddressesTabsCounters, AddressSum, AddressSumMinusBurnt, - BackgroundMigrations, - Block, + BlockCount, BlockNumber, - Blocks, GasPriceOracle, - GasUsage, + # GasUsage, MinMissingBlockNumber, NetVersion, - OptimismFinalizationPeriod, - PendingBlockOperation, - StateChanges, - Transaction, + TransactionCount, Transactions, - TransactionsApiV2, Uncles } @@ -49,117 +42,63 @@ defmodule Explorer.Application do base_children = [ Explorer.Repo, Explorer.Repo.Replica1, - Explorer.Vault, Supervisor.child_spec({SpandexDatadog.ApiServer, datadog_opts()}, id: SpandexDatadog.ApiServer), Supervisor.child_spec({Task.Supervisor, name: Explorer.HistoryTaskSupervisor}, id: Explorer.HistoryTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.MarketTaskSupervisor}, id: Explorer.MarketTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.GenesisDataTaskSupervisor}, id: GenesisDataTaskSupervisor), Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), - Supervisor.child_spec({Task.Supervisor, name: Explorer.LookUpSmartContractSourcesTaskSupervisor}, - id: LookUpSmartContractSourcesTaskSupervisor - ), Explorer.SmartContract.SolcDownloader, Explorer.SmartContract.VyperDownloader, {Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents}, {Admin.Recovery, [[], [name: Admin.Recovery]]}, - Accounts, + TransactionCount, AddressSum, AddressSumMinusBurnt, - BackgroundMigrations, - Block, - BlockNumber, - Blocks, + BlockCount, GasPriceOracle, - GasUsage, + # GasUsage, NetVersion, - OptimismFinalizationPeriod, - PendingBlockOperation, - Transaction, - StateChanges, - Transactions, - TransactionsApiV2, - Uncles, - AddressesTabsCounters, + BlockNumber, con_cache_child_spec(MarketHistoryCache.cache_name()), con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)), - {Redix, redix_opts()}, - {Explorer.Utility.MissingRangesManipulator, []} + Transactions, + Accounts, + Uncles, + MinMissingBlockNumber ] children = base_children ++ configurable_children() - opts = [strategy: :one_for_one, name: Explorer.Supervisor, max_restarts: 1_000] + opts = [strategy: :one_for_one, name: Explorer.Supervisor] Supervisor.start_link(children, opts) end defp configurable_children do - configurable_children_set = - [ - configure(Explorer.ExchangeRates), - configure(Explorer.ExchangeRates.TokenExchangeRates), - configure(Explorer.ChainSpec.GenesisData), - configure(Explorer.Market.History.Cataloger), - configure(Explorer.Chain.Cache.ContractsCounter), - configure(Explorer.Chain.Cache.NewContractsCounter), - configure(Explorer.Chain.Cache.VerifiedContractsCounter), - configure(Explorer.Chain.Cache.NewVerifiedContractsCounter), - configure(Explorer.Chain.Cache.TransactionActionTokensData), - configure(Explorer.Chain.Cache.TransactionActionUniswapPools), - configure(Explorer.Chain.Cache.WithdrawalsSum), - configure(Explorer.Chain.Transaction.History.Historian), - configure(Explorer.Chain.Events.Listener), - configure(Explorer.Counters.AddressesWithBalanceCounter), - configure(Explorer.Counters.AddressesCounter), - configure(Explorer.Counters.AddressTransactionsCounter), - configure(Explorer.Counters.AddressTokenTransfersCounter), - configure(Explorer.Counters.AddressTransactionsGasUsageCounter), - configure(Explorer.Counters.AddressTokenUsdSum), - configure(Explorer.Counters.TokenHoldersCounter), - configure(Explorer.Counters.TokenTransfersCounter), - configure(Explorer.Counters.BlockBurntFeeCounter), - configure(Explorer.Counters.BlockPriorityFeeCounter), - configure(Explorer.Counters.AverageBlockTime), - configure(Explorer.Validator.MetadataProcessor), - configure(Explorer.Tags.AddressTag.Cataloger), - configure(MinMissingBlockNumber), - configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand), - configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand), - configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), - sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), - configure(Explorer.Chain.Cache.RootstockLockedBTC), - configure(Explorer.Migrator.TransactionsDenormalization), - configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), - configure(Explorer.Migrator.AddressTokenBalanceTokenType) - ] - |> List.flatten() - - repos_by_chain_type() ++ account_repo() ++ configurable_children_set - end - - defp repos_by_chain_type do - if Mix.env() == :test do - [ - Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, - Explorer.Repo.ZkSync, - Explorer.Repo.RSK, - Explorer.Repo.Shibarium, - Explorer.Repo.Suave, - Explorer.Repo.Arbitrum, - Explorer.Repo.BridgedTokens - ] - else - [] - end - end - - defp account_repo do - if System.get_env("ACCOUNT_DATABASE_URL") || Mix.env() == :test do - [Explorer.Repo.Account] - else - [] - end + [ + configure(Explorer.ExchangeRates), + configure(Explorer.ChainSpec.GenesisData), + configure(Explorer.KnownTokens), + configure(Explorer.Market.History.Cataloger), + configure(Explorer.Chain.Cache.TokenExchangeRate), + configure(Explorer.Chain.Transaction.History.Historian), + configure(Explorer.Chain.Events.Listener), + configure(Explorer.Counters.AddressesWithBalanceCounter), + configure(Explorer.Counters.AddressesCounter), + configure(Explorer.Counters.AddressTransactionsCounter), + configure(Explorer.Counters.AddressTokenTransfersCounter), + configure(Explorer.Counters.AddressTransactionsGasUsageCounter), + configure(Explorer.Counters.AddressTokenUsdSum), + configure(Explorer.Counters.TokenHoldersCounter), + configure(Explorer.Counters.TokenTransfersCounter), + configure(Explorer.Counters.BlockBurnedFeeCounter), + configure(Explorer.Counters.BlockPriorityFeeCounter), + configure(Explorer.Counters.AverageBlockTime), + configure(Explorer.Counters.Bridge), + configure(Explorer.Validator.MetadataProcessor), + configure(Explorer.Staking.ContractState) + ] + |> List.flatten() end defp should_start?(process) do @@ -174,38 +113,12 @@ defmodule Explorer.Application do end end - defp sc_microservice_configure(process) do - if Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:eth_bytecode_db?] do - process - else - [] - end - end - - defp datadog_port do - Application.get_env(:explorer, :datadog)[:port] - end - - defp spandex_batch_size do - Application.get_env(:explorer, :spandex)[:batch_size] - end - - defp spandex_sync_threshold do - Application.get_env(:explorer, :spandex)[:sync_threshold] - end - defp datadog_opts do - datadog_port = datadog_port() - - spandex_batch_size = spandex_batch_size() - - spandex_sync_threshold = spandex_sync_threshold() - [ host: System.get_env("DATADOG_HOST") || "localhost", - port: datadog_port, - batch_size: spandex_batch_size, - sync_threshold: spandex_sync_threshold, + port: System.get_env("DATADOG_PORT") || 8126, + batch_size: System.get_env("SPANDEX_BATCH_SIZE") || 100, + sync_threshold: System.get_env("SPANDEX_SYNC_THRESHOLD") || 100, http: HTTPoison ] end @@ -221,8 +134,4 @@ defmodule Explorer.Application do id: {ConCache, name} ) end - - defp redix_opts do - {System.get_env("ACCOUNT_REDIS_URL") || "redis://127.0.0.1:6379", [name: :redix]} - end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index a985998e5485..c155404a57da 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5,8 +5,6 @@ defmodule Explorer.Chain do import Ecto.Query, only: [ - dynamic: 1, - dynamic: 2, from: 2, join: 4, join: 5, @@ -16,7 +14,6 @@ defmodule Explorer.Chain do order_by: 2, order_by: 3, preload: 2, - preload: 3, select: 2, select: 3, subquery: 1, @@ -25,19 +22,18 @@ defmodule Explorer.Chain do where: 3 ] - import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2] + import EthereumJSONRPC, only: [integer_to_quantity: 1, json_rpc: 2, fetch_block_internal_transactions: 2] require Logger - alias ABI.TypeDecoder + alias ABI.{TypeDecoder, TypeEncoder} + alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi} + alias EthereumJSONRPC.Contract alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction - alias EthereumJSONRPC.Utility.RangesHelper - alias Explorer.Account.WatchlistAddress - - alias Explorer.Counters.{LastFetchedCounter, TokenHoldersCounter, TokenTransfersCounter} + alias Explorer.Counters.LastFetchedCounter alias Explorer.Chain @@ -48,77 +44,66 @@ defmodule Explorer.Chain do Address.CurrentTokenBalance, Address.TokenBalance, Block, - CurrencyHelper, + BridgedToken, + CurrencyHelpers, Data, DecompiledSmartContract, - DenormalizationHelper, Hash, Import, InternalTransaction, Log, PendingBlockOperation, SmartContract, + SmartContractAdditionalSource, + StakingPool, + StakingPoolsDelegator, Token, Token.Instance, TokenTransfer, Transaction, - Wei, - Withdrawal + Wei } + alias Explorer.Chain.Block.{EmissionReward, Reward} + alias Explorer.Chain.Cache.{ + Accounts, + BlockCount, BlockNumber, - Blocks, - ContractsCounter, - NewContractsCounter, - NewVerifiedContractsCounter, + GasUsage, + TokenExchangeRate, + TransactionCount, Transactions, - Uncles, - VerifiedContractsCounter, - WithdrawalsSum + Uncles } - alias Explorer.Chain.Cache.Block, as: BlockCache - alias Explorer.Chain.Cache.PendingBlockOperation, as: PendingBlockOperationCache - alias Explorer.Chain.Fetcher.{CheckBytecodeMatchingOnDemand, LookUpSmartContractSourcesOnDemand} alias Explorer.Chain.Import.Runner alias Explorer.Chain.InternalTransaction.{CallType, Type} - alias Explorer.Chain.SmartContract.Proxy.EIP1167 - + alias Explorer.Counters.{AddressesCounter, AddressesWithBalanceCounter} alias Explorer.Market.MarketHistoryCache alias Explorer.{PagingOptions, Repo} + alias Explorer.SmartContract.Reader + alias Explorer.Staking.ContractState alias Dataloader.Ecto, as: DataloaderEcto @default_paging_options %PagingOptions{page_size: 50} - @token_transfers_per_transaction_preview 10 - @token_transfers_necessity_by_association %{ - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - token: :optional - } - - @method_name_to_id_map %{ - "approve" => "095ea7b3", - "transfer" => "a9059cbb", - "multicall" => "5ae401dc", - "mint" => "40c10f19", - "commit" => "f14fcbc8" - } + @max_incoming_transactions_count 10_000 @revert_msg_prefix_1 "Revert: " @revert_msg_prefix_2 "revert: " @revert_msg_prefix_3 "reverted " @revert_msg_prefix_4 "Reverted " - # Geth-like node - @revert_msg_prefix_5 "execution reverted: " # keccak256("Error(string)") @revert_error_method_id "08c379a0" - @limit_showing_transactions 10_000 + @burn_address_hash_str "0x0000000000000000000000000000000000000000" + + # seconds + @check_bytecode_interval 86_400 + + @limit_showing_transaсtions 10_000 @default_page_size 50 @typedoc """ @@ -159,10 +144,61 @@ defmodule Explorer.Chain do """ @type necessity_by_association :: %{association => necessity} - @type necessity_by_association_option :: {:necessity_by_association, necessity_by_association} - @type paging_options :: {:paging_options, PagingOptions.t()} + @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} + @typep paging_options :: {:paging_options, PagingOptions.t()} @typep balance_by_day :: %{date: String.t(), value: Wei.t()} - @type api? :: {:api?, true | false} + + @doc """ + Gets from the cache the count of `t:Explorer.Chain.Address.t/0`'s where the `fetched_coin_balance` is > 0 + """ + @spec count_addresses_with_balance_from_cache :: non_neg_integer() + def count_addresses_with_balance_from_cache do + AddressesWithBalanceCounter.fetch() + end + + @doc """ + Estimated count of `t:Explorer.Chain.Address.t/0`. + + Estimated count of addresses. + """ + @spec address_estimated_count() :: non_neg_integer() + def address_estimated_count do + cached_value = AddressesCounter.fetch() + + if is_nil(cached_value) do + %Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'addresses';") + + count + else + cached_value + end + end + + @doc """ + Counts the number of addresses with fetched coin balance > 0. + + This function should be used with caution. In larger databases, it may take a + while to have the return back. + """ + def count_addresses_with_balance do + Repo.one( + Address.count_with_fetched_coin_balance(), + timeout: :infinity + ) + end + + @doc """ + Counts the number of all addresses. + + This function should be used with caution. In larger databases, it may take a + while to have the return back. + """ + def count_addresses do + Repo.one( + Address.count(), + timeout: :infinity + ) + end @doc """ `t:Explorer.Chain.InternalTransaction/0`s from the address with the given `hash`. @@ -196,12 +232,12 @@ defmodule Explorer.Chain do paging_options = Keyword.get(options, :paging_options, @default_paging_options) - if direction == nil || direction == "" do + if direction == nil do query_to_address_hash_wrapped = InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :to_address_hash) - |> where_block_number_in_period(from_block, to_block) + |> InternalTransaction.where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -209,7 +245,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :from_address_hash) - |> where_block_number_in_period(from_block, to_block) + |> InternalTransaction.where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -217,27 +253,35 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :created_contract_address_hash) - |> where_block_number_in_period(from_block, to_block) + |> InternalTransaction.where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() - query_to_address_hash_wrapped - |> union(^query_from_address_hash_wrapped) - |> union(^query_created_contract_address_hash_wrapped) + full_query = + query_to_address_hash_wrapped + |> union(^query_from_address_hash_wrapped) + |> union(^query_created_contract_address_hash_wrapped) + + full_query |> wrapped_union_subquery() - |> common_where_limit_order(paging_options) - |> preload(:block) + |> order_by( + [q], + desc: q.block_number, + desc: q.transaction_index, + desc: q.index + ) + |> preload(transaction: :block) |> join_associations(necessity_by_association) - |> select_repo(options).all() + |> Repo.all() else InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, direction) - |> where_block_number_in_period(from_block, to_block) + |> InternalTransaction.where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) - |> preload(:block) + |> preload(transaction: :block) |> join_associations(necessity_by_association) - |> select_repo(options).all() + |> Repo.all() end end @@ -251,7 +295,8 @@ defmodule Explorer.Chain do defp common_where_limit_order(query, paging_options) do query |> InternalTransaction.where_is_different_from_parent_transaction() - |> page_internal_transaction(paging_options, %{index_int_tx_desc_order: true}) + |> InternalTransaction.where_block_number_is_not_null() + |> page_internal_transaction(paging_options) |> limit(^paging_options.page_size) |> order_by( [it], @@ -261,28 +306,240 @@ defmodule Explorer.Chain do ) end - def address_hashes_to_mined_transactions_without_rewards(address_hashes, options) do + @doc """ + Get the total number of transactions sent by the address with the given hash according to the last block indexed. + + We have to increment +1 in the last nonce result because it works like an array position, the first + nonce has the value 0. When last nonce is nil, it considers that the given address has 0 transactions. + """ + @spec total_transactions_sent_by_address(Hash.Address.t()) :: non_neg_integer() + def total_transactions_sent_by_address(address_hash) do + last_nonce = + address_hash + |> Transaction.last_nonce_by_address_query() + |> Repo.one(timeout: :infinity) + + case last_nonce do + nil -> 0 + value -> value + 1 + end + end + + @doc """ + Fetches the transactions related to the address with the given hash, including + transactions that only have the address in the `token_transfers` related table + and rewards for block validation. + + This query is divided into multiple subqueries intentionally in order to + improve the listing performance. + + The `token_trasfers` table tends to grow exponentially, and the query results + with a `transactions` `join` statement takes too long. + + To solve this the `transaction_hashes` are fetched in a separate query, and + paginated through the `block_number` already present in the `token_transfers` + table. + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. + * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and + `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than + the `block_number` and `index` that are passed. + + """ + @spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: + [ + Transaction.t() + ] + def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do + cond do + Keyword.get(options, :direction) == :from -> + address_to_transactions_without_rewards(address_hash, options) + + address_has_rewards?(address_hash) -> + %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining(address_hash) + + if block_miner_payout_address && address_hash == block_miner_payout_address do + transactions_with_rewards_results(address_hash, options, paging_options) + else + address_to_transactions_without_rewards(address_hash, options) + end + + true -> + address_to_transactions_without_rewards(address_hash, options) + end + else + address_to_transactions_without_rewards(address_hash, options) + end + end + + defp transactions_with_rewards_results(address_hash, options, paging_options) do + blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) + + rewards_task = + Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range) end) + + [rewards_task | address_to_transactions_tasks(address_hash, options)] + |> wait_for_address_transactions() + |> Enum.sort_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {-emission_reward.block.number, 1} + + item -> + block_number = if item.block_number, do: -item.block_number, else: 0 + index = if item.index, do: -item.index, else: 0 + {block_number, index} + end + end) + |> Enum.dedup_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type} + + transaction -> + transaction.hash + end + end) + |> Enum.take(paging_options.page_size) + end + + def address_to_transactions_without_rewards(address_hash, options) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + address_hash + |> address_to_transactions_tasks(options) + |> wait_for_address_transactions() + |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) + |> Enum.dedup_by(& &1.hash) + |> Enum.take(paging_options.page_size) + end + + def address_to_mined_transactions_without_rewards(address_hash, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) - address_hashes - |> address_hashes_to_mined_transactions_tasks(options) - |> Transaction.wait_for_address_transactions() + address_hash + |> address_to_mined_transactions_tasks(options) + |> wait_for_address_transactions() |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) |> Enum.dedup_by(& &1.hash) |> Enum.take(paging_options.page_size) end - defp address_hashes_to_mined_transactions_tasks(address_hashes, options) do + defp address_to_transactions_tasks_query(options) do + from_block = from_block(options) + to_block = to_block(options) + + options + |> Keyword.get(:paging_options, @default_paging_options) + |> fetch_transactions(from_block, to_block) + end + + defp transactions_block_numbers_at_address(address_hash, options) do + direction = Keyword.get(options, :direction) + + options + |> address_to_transactions_tasks_query() + |> Transaction.not_pending_transactions() + |> select([t], t.block_number) + |> Transaction.matching_address_queries_list(direction, address_hash) + end + + defp address_to_transactions_tasks(address_hash, options) do + direction = Keyword.get(options, :direction) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + from_block = from_block(options) + to_block = to_block(options) + + options + |> address_to_transactions_tasks_query() + |> Transaction.not_dropped_or_replaced_transacions() + |> where_block_number_in_period(from_block, to_block) + |> join_associations(necessity_by_association) + |> Transaction.matching_address_queries_list(direction, address_hash) + |> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end) + end + + defp address_to_mined_transactions_tasks(address_hash, options) do direction = Keyword.get(options, :direction) necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) options - |> Transaction.address_to_transactions_tasks_query(true) + |> address_to_transactions_tasks_query() |> Transaction.not_pending_transactions() |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(false) - |> Transaction.matching_address_queries_list(direction, address_hashes) - |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) + |> Transaction.matching_address_queries_list(direction, address_hash) + |> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end) + end + + def address_to_transactions_tasks_range_of_blocks(address_hash, options) do + extremums_list = + address_hash + |> transactions_block_numbers_at_address(options) + |> Enum.map(fn query -> + extremum_query = + from( + q in subquery(query), + select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} + ) + + extremum_query + |> Repo.one!() + end) + + extremums_list + |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ + min_block_number: min_number, + max_block_number: max_number + }, + extremums_result -> + current_min_number = Map.get(extremums_result, :min_block_number) + current_max_number = Map.get(extremums_result, :max_block_number) + + extremums_result = + if is_number(current_min_number) do + if is_number(min_number) and min_number > 0 and min_number < current_min_number do + extremums_result + |> Map.put(:min_block_number, min_number) + else + extremums_result + end + else + extremums_result + |> Map.put(:min_block_number, min_number) + end + + if is_number(max_number) and max_number > 0 and max_number > current_max_number do + extremums_result + |> Map.put(:max_block_number, max_number) + else + extremums_result + end + end) + end + + defp wait_for_address_transactions(tasks) do + tasks + |> Task.yield_many(:timer.seconds(20)) + |> Enum.flat_map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + raise "Query fetching address transactions terminated: #{inspect(reason)}" + + nil -> + raise "Query fetching address transactions timed out." + end + end) end @spec address_hash_to_token_transfers(Hash.Address.t(), Keyword.t()) :: [Transaction.t()] @@ -293,106 +550,122 @@ defmodule Explorer.Chain do direction |> Transaction.transactions_with_token_transfers_direction(address_hash) |> Transaction.preload_token_transfers(address_hash) - |> Transaction.handle_paging_options(paging_options) + |> handle_paging_options(paging_options) |> Repo.all() end - @spec address_hash_to_token_transfers_new(Hash.Address.t() | String.t(), Keyword.t()) :: [TokenTransfer.t()] - def address_hash_to_token_transfers_new(address_hash, options \\ []) do + @doc """ + address_hash_to_token_transfers_including_contract/2 function returns token transfers on address (to/from/contract). + It is used by CSV export of token transfers button. + """ + @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()] + def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) - direction = Keyword.get(options, :direction) - filters = Keyword.get(options, :token_type) - necessity_by_association = Keyword.get(options, :necessity_by_association) + from_block = Keyword.get(options, :from_block) + to_block = Keyword.get(options, :to_block) - direction - |> TokenTransfer.token_transfers_by_address_hash(address_hash, filters) - |> join_associations(necessity_by_association) - |> TokenTransfer.handle_paging_options(paging_options) - |> select_repo(options).all() - end + query = + from_block + |> query_address_hash_to_token_transfers_including_contract(to_block, address_hash) + |> order_by([token_transfer], asc: token_transfer.block_number, asc: token_transfer.log_index) - @spec address_hash_to_token_transfers_by_token_address_hash( - Hash.Address.t() | String.t(), - Hash.Address.t() | String.t(), - Keyword.t() - ) :: [TokenTransfer.t()] - def address_hash_to_token_transfers_by_token_address_hash(address_hash, token_address_hash, options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) + query + |> handle_token_transfer_paging_options(paging_options) + |> preload(transaction: :block) + |> preload(:token) + |> Repo.all() + end - necessity_by_association = Keyword.get(options, :necessity_by_association) + defp query_address_hash_to_token_transfers_including_contract(nil, to_block, address_hash) + when not is_nil(to_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + token_transfer.block_number <= ^to_block + ) + end - address_hash - |> TokenTransfer.token_transfers_by_address_hash_and_token_address_hash(token_address_hash) - |> join_associations(necessity_by_association) - |> TokenTransfer.handle_paging_options(paging_options) - |> select_repo(options).all() + defp query_address_hash_to_token_transfers_including_contract(from_block, nil, address_hash) + when not is_nil(from_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + token_transfer.block_number >= ^from_block + ) end - @spec address_hash_to_withdrawals( - Hash.Address.t(), - [paging_options | necessity_by_association_option] - ) :: [Withdrawal.t()] - def address_hash_to_withdrawals(address_hash, options \\ []) when is_list(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + defp query_address_hash_to_token_transfers_including_contract(from_block, to_block, address_hash) + when not is_nil(from_block) and not is_nil(to_block) do + from( + token_transfer in TokenTransfer, + where: + (token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash) and + (token_transfer.block_number >= ^from_block and token_transfer.block_number <= ^to_block) + ) + end - address_hash - |> Withdrawal.address_hash_to_withdrawals_query() - |> join_associations(necessity_by_association) - |> handle_withdrawals_paging_options(paging_options) - |> select_repo(options).all() + defp query_address_hash_to_token_transfers_including_contract(_, _, address_hash) do + from( + token_transfer in TokenTransfer, + where: + token_transfer.to_address_hash == ^address_hash or + token_transfer.from_address_hash == ^address_hash or + token_transfer.token_contract_address_hash == ^address_hash + ) end @spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()] - def address_to_logs(address_hash, csv_export?, options \\ []) when is_list(options) do + def address_to_logs(address_hash, options \\ []) when is_list(options) do paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} from_block = from_block(options) to_block = to_block(options) - base = - if DenormalizationHelper.denormalization_finished?() do - from(log in Log, - order_by: [desc: log.block_number, desc: log.index], - where: log.address_hash == ^address_hash, - limit: ^paging_options.page_size, - select: log, - inner_join: transaction in assoc(log, :transaction), - where: transaction.block_consensus == true - ) - else - from(log in Log, - order_by: [desc: log.block_number, desc: log.index], - where: log.address_hash == ^address_hash, - limit: ^paging_options.page_size, - select: log, - inner_join: block in Block, - on: block.hash == log.block_hash, - where: block.consensus == true - ) - end + {block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.get_max(), 0, 0} - preloaded_query = - if csv_export? do - base - else - base - |> preload(transaction: [:to_address, :from_address]) - end + base_query = + from(log in Log, + inner_join: transaction in Transaction, + on: transaction.hash == log.transaction_hash, + order_by: [desc: log.block_number, desc: log.index], + where: transaction.block_number < ^block_number, + or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index, + or_where: + transaction.block_number == ^block_number and transaction.index == ^transaction_index and + log.index > ^log_index, + where: log.address_hash == ^address_hash, + limit: ^paging_options.page_size, + select: log + ) + + wrapped_query = + from( + log in subquery(base_query), + inner_join: transaction in Transaction, + preload: [:transaction, transaction: [to_address: :smart_contract]], + where: + log.block_hash == transaction.block_hash and + log.block_number == transaction.block_number and + log.transaction_hash == transaction.hash, + select: log + ) - preloaded_query - |> page_logs(paging_options) - |> filter_topic(Keyword.get(options, :topic)) + wrapped_query + |> filter_topic(options) |> where_block_number_in_period(from_block, to_block) - |> select_repo(options).all() + |> Repo.all() |> Enum.take(paging_options.page_size) end - defp filter_topic(base_query, nil), do: base_query - - defp filter_topic(base_query, ""), do: base_query - - defp filter_topic(base_query, topic) do + defp filter_topic(base_query, topic: topic) do from(log in base_query, where: log.first_topic == ^topic or log.second_topic == ^topic or log.third_topic == ^topic or @@ -400,6 +673,8 @@ defmodule Explorer.Chain do ) end + defp filter_topic(base_query, _), do: base_query + def where_block_number_in_period(base_query, from_block, to_block) when is_nil(from_block) and not is_nil(to_block) do from(q in base_query, where: q.block_number <= ^to_block @@ -413,7 +688,9 @@ defmodule Explorer.Chain do end def where_block_number_in_period(base_query, from_block, to_block) when is_nil(from_block) and is_nil(to_block) do - base_query + from(q in base_query, + where: 1 + ) end def where_block_number_in_period(base_query, from_block, to_block) do @@ -439,7 +716,7 @@ defmodule Explorer.Chain do address_hash |> Transaction.transactions_with_token_transfers(token_hash) |> Transaction.preload_token_transfers(address_hash) - |> Transaction.handle_paging_options(paging_options) + |> handle_paging_options(paging_options) |> Repo.all() end @@ -473,6 +750,37 @@ defmodule Explorer.Chain do Repo.aggregate(Block, :count, :hash) end + @doc """ + Reward for mining a block. + + The block reward is the sum of the following: + + * Sum of the transaction fees (gas_used * gas_price) for the block + * A static reward for miner (this value may change during the life of the chain) + * The reward for uncle blocks (1/32 * static_reward * number_of_uncles) + + *NOTE* + + Uncles are not currently accounted for. + """ + @spec block_reward(Block.block_number()) :: Wei.t() + def block_reward(block_number) do + query = + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + inner_join: emission_reward in EmissionReward, + on: fragment("? <@ ?", block.number, emission_reward.block_range), + where: block.number == ^block_number, + group_by: emission_reward.reward, + select: %Wei{ + value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0) + emission_reward.reward + } + ) + + Repo.one!(query) + end + @doc """ The `t:Explorer.Chain.Wei.t/0` paid to the miners of the `t:Explorer.Chain.Block.t/0`s with `hash` `Explorer.Chain.Hash.Full.t/0` by the signers of the transactions in those blocks to cover the gas fee @@ -481,34 +789,17 @@ defmodule Explorer.Chain do @spec gas_payment_by_block_hash([Hash.Full.t()]) :: %{Hash.Full.t() => Wei.t()} def gas_payment_by_block_hash(block_hashes) when is_list(block_hashes) do query = - if DenormalizationHelper.denormalization_finished?() do - from( - transaction in Transaction, - where: transaction.block_hash in ^block_hashes and transaction.block_consensus == true, - group_by: transaction.block_hash, - select: {transaction.block_hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} - ) - else - from( - block in Block, - left_join: transaction in assoc(block, :transactions), - where: block.hash in ^block_hashes and block.consensus == true, - group_by: block.hash, - select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} - ) - end - - initial_gas_payments = - block_hashes - |> Enum.map(&{&1, %Wei{value: Decimal.new(0)}}) - |> Enum.into(%{}) - - existing_data = - query - |> Repo.all() - |> Enum.into(%{}) + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + where: block.hash in ^block_hashes and block.consensus == true, + group_by: block.hash, + select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} + ) - Map.merge(initial_gas_payments, existing_data) + query + |> Repo.all() + |> Enum.into(%{}) end def timestamp_by_block_hash(block_hashes) when is_list(block_hashes) do @@ -537,11 +828,8 @@ defmodule Explorer.Chain do `:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than the `index` that are passed. """ - @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option | api?()], true | false) :: - [ - Transaction.t() - ] - def block_to_transactions(block_hash, options \\ [], old_ui? \\ true) when is_list(options) do + @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()] + def block_to_transactions(block_hash, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) options @@ -550,45 +838,8 @@ defmodule Explorer.Chain do |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(old_ui?) - |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() - |> select_repo(options).all() - |> (&if(old_ui?, - do: &1, - else: - Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end) - )).() - end - - @spec execution_node_to_transactions(Hash.Address.t(), [paging_options | necessity_by_association_option | api?()]) :: - [Transaction.t()] - def execution_node_to_transactions(execution_node_hash, options \\ []) when is_list(options) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - options - |> Keyword.get(:paging_options, @default_paging_options) - |> fetch_transactions_in_descending_order_by_block_and_index() - |> where(execution_node_hash: ^execution_node_hash) - |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(false) - |> (& &1).() - |> select_repo(options).all() - |> (&Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)).() - end - - @spec block_to_withdrawals( - Hash.Full.t(), - [paging_options | necessity_by_association_option] - ) :: [Withdrawal.t()] - def block_to_withdrawals(block_hash, options \\ []) when is_list(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - block_hash - |> Withdrawal.block_hash_to_withdrawals_query() - |> join_associations(necessity_by_association) - |> handle_withdrawals_paging_options(paging_options) - |> select_repo(options).all() + |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> Repo.all() end @doc """ @@ -600,6 +851,7 @@ defmodule Explorer.Chain do from( tx in Transaction, where: tx.block_hash == ^block_hash, + where: not is_nil(tx.max_priority_fee_per_gas), select: sum(tx.gas_used) ) @@ -613,44 +865,31 @@ defmodule Explorer.Chain do @spec block_to_priority_fee_of_1559_txs(Hash.Full.t()) :: Decimal.t() def block_to_priority_fee_of_1559_txs(block_hash) do block = Repo.get_by(Block, hash: block_hash) + %Wei{value: base_fee_per_gas} = block.base_fee_per_gas - case block.base_fee_per_gas do - %Wei{value: base_fee_per_gas} -> - query = - from( - tx in Transaction, - where: tx.block_hash == ^block_hash, - select: - sum( - fragment( - "CASE - WHEN COALESCE(?,?) = 0 THEN 0 - WHEN COALESCE(?,?) - ? < COALESCE(?,?) THEN (COALESCE(?,?) - ?) * ? - ELSE COALESCE(?,?) * ? END", - tx.max_fee_per_gas, - tx.gas_price, - tx.max_fee_per_gas, - tx.gas_price, - ^base_fee_per_gas, - tx.max_priority_fee_per_gas, - tx.gas_price, - tx.max_fee_per_gas, - tx.gas_price, - ^base_fee_per_gas, - tx.gas_used, - tx.max_priority_fee_per_gas, - tx.gas_price, - tx.gas_used - ) - ) + query = + from( + tx in Transaction, + where: tx.block_hash == ^block_hash, + where: not is_nil(tx.max_priority_fee_per_gas), + select: + sum( + fragment( + "CASE + WHEN ? = 0 THEN 0 + WHEN ? < ? THEN ? + ELSE ? END", + tx.max_fee_per_gas, + tx.max_fee_per_gas - ^base_fee_per_gas, + tx.max_priority_fee_per_gas, + (tx.max_fee_per_gas - ^base_fee_per_gas) * tx.gas_used, + tx.max_priority_fee_per_gas * tx.gas_used + ) ) + ) - result = Repo.one(query) - if result, do: result, else: 0 - - _ -> - 0 - end + result = Repo.one(query) + if result, do: result, else: 0 end @doc """ @@ -667,13 +906,42 @@ defmodule Explorer.Chain do Repo.aggregate(query, :count, :hash) end - @spec check_if_withdrawals_in_block(Hash.Full.t()) :: boolean() - def check_if_withdrawals_in_block(block_hash, options \\ []) do - block_hash - |> Withdrawal.block_hash_to_withdrawals_unordered_query() - |> select_repo(options).exists?() + @spec address_to_incoming_transaction_count(Hash.Address.t()) :: non_neg_integer() + def address_to_incoming_transaction_count(address_hash) do + to_address_query = + from( + transaction in Transaction, + where: transaction.to_address_hash == ^address_hash + ) + + Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity) + end + + @spec address_to_incoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil + def address_to_incoming_transaction_gas_usage(address_hash) do + to_address_query = + from( + transaction in Transaction, + where: transaction.to_address_hash == ^address_hash + ) + + Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity) + end + + @spec address_to_outcoming_transaction_gas_usage(Hash.Address.t()) :: Decimal.t() | nil + def address_to_outcoming_transaction_gas_usage(address_hash) do + to_address_query = + from( + transaction in Transaction, + where: transaction.from_address_hash == ^address_hash + ) + + Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity) end + @spec max_incoming_transactions_count() :: non_neg_integer() + def max_incoming_transactions_count, do: @max_incoming_transactions_count + @doc """ How many blocks have confirmed `block` based on the current `max_block_number` @@ -707,8 +975,8 @@ defmodule Explorer.Chain do iex> Explorer.Chain.confirmations(block, block_height: 0) {:ok, 1} """ - @spec confirmations(Block.t() | nil, [{:block_height, block_height()}]) :: - {:ok, non_neg_integer()} | {:error, :non_consensus | :pending} + @spec confirmations(Block.t(), [{:block_height, block_height()}]) :: + {:ok, non_neg_integer()} | {:error, :non_consensus} def confirmations(%Block{consensus: true, number: number}, named_arguments) when is_list(named_arguments) do max_consensus_block_number = Keyword.fetch!(named_arguments, :block_height) @@ -718,8 +986,6 @@ defmodule Explorer.Chain do def confirmations(%Block{consensus: false}, _), do: {:error, :non_consensus} - def confirmations(nil, _), do: {:error, :pending} - @doc """ Creates an address. @@ -775,33 +1041,6 @@ defmodule Explorer.Chain do end end - defp set_address_decompiled(repo, address_hash) do - query = - from( - address in Address, - where: address.hash == ^address_hash - ) - - case repo.update_all(query, set: [decompiled: true]) do - {1, _} -> {:ok, []} - _ -> {:error, "There was an error annotating that the address has been decompiled."} - end - end - - @spec verified_contracts_top(non_neg_integer()) :: [Hash.Address.t()] - def verified_contracts_top(limit) do - query = - from(contract in SmartContract, - inner_join: address in Address, - on: contract.address_hash == address.hash, - order_by: [desc: address.transactions_count], - limit: ^limit, - select: contract.address_hash - ) - - Repo.all(query) - end - @doc """ Converts the `Explorer.Chain.Data.t:t/0` to `iodata` representation that can be written to users efficiently. @@ -855,9 +1094,7 @@ defmodule Explorer.Chain do {:actual, Decimal.new(4)} """ - @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} - def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} - + @spec fee(%Transaction{gas_used: nil}, :ether | :gwei | :wei) :: {:maximum, Decimal.t()} def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do fee = gas_price @@ -867,8 +1104,7 @@ defmodule Explorer.Chain do {:maximum, fee} end - def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit), do: {:actual, nil} - + @spec fee(%Transaction{gas_used: Decimal.t()}, :ether | :gwei | :wei) :: {:actual, Decimal.t()} def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do fee = gas_price @@ -880,84 +1116,31 @@ defmodule Explorer.Chain do @doc """ Checks to see if the chain is down indexing based on the transaction from the - oldest block and the pending operation + oldest block and the `fetch_internal_transactions` pending operation """ - @spec finished_indexing_internal_transactions?([api?]) :: boolean() - def finished_indexing_internal_transactions?(options \\ []) do - internal_transactions_disabled? = - Application.get_env(:indexer, Indexer.Fetcher.InternalTransaction.Supervisor)[:disabled?] or - not Application.get_env(:indexer, Indexer.Supervisor)[:enabled] + @spec finished_indexing?() :: boolean() + def finished_indexing? do + json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) + variant = Keyword.fetch!(json_rpc_named_arguments, :variant) - if internal_transactions_disabled? do + if variant == EthereumJSONRPC.Ganache || variant == EthereumJSONRPC.Arbitrum do true else - json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) - variant = Keyword.fetch!(json_rpc_named_arguments, :variant) - - if variant == EthereumJSONRPC.Ganache || variant == EthereumJSONRPC.Arbitrum do - true - else - check_left_blocks_to_index_internal_transactions(options) - end - end - end - - defp check_left_blocks_to_index_internal_transactions(options) do - with {:transactions_exist, true} <- {:transactions_exist, select_repo(options).exists?(Transaction)}, - min_block_number when not is_nil(min_block_number) <- - select_repo(options).aggregate(Transaction, :min, :block_number) do - min_block_number = - min_block_number - |> Decimal.max(Application.get_env(:indexer, :trace_first_block)) - |> Decimal.to_integer() - - query = - from( - block in Block, - join: pending_ops in assoc(block, :pending_operations), - where: block.consensus and block.number == ^min_block_number - ) + with {:transactions_exist, true} <- {:transactions_exist, Repo.exists?(Transaction)}, + min_block_number when not is_nil(min_block_number) <- Repo.aggregate(Transaction, :min, :block_number) do + query = + from( + b in Block, + join: pending_ops in assoc(b, :pending_operations), + where: pending_ops.fetch_internal_transactions, + where: b.consensus and b.number == ^min_block_number + ) - if select_repo(options).exists?(query) do - false + !Repo.exists?(query) else - check_indexing_internal_transactions_threshold() - end - else - {:transactions_exist, false} -> true - nil -> false - end - end - - defp check_indexing_internal_transactions_threshold do - pbo_count = PendingBlockOperationCache.estimated_count() - - if pbo_count < - Application.get_env(:indexer, Indexer.Fetcher.InternalTransaction)[:indexing_finished_threshold] do - true - else - false - end - end - - def finished_indexing_from_ratio?(ratio) do - Decimal.compare(ratio, 1) !== :lt - end - - @doc """ - Checks if indexing of blocks and internal transactions finished aka full indexing - """ - @spec finished_indexing?([api?]) :: boolean() - def finished_indexing?(options \\ []) do - if Application.get_env(:indexer, Indexer.Supervisor)[:enabled] do - indexed_ratio = indexed_ratio_blocks() - - case finished_indexing_from_ratio?(indexed_ratio) do - false -> false - _ -> finished_indexing_internal_transactions?(options) + {:transactions_exist, false} -> true + nil -> false end - else - true end end @@ -995,10 +1178,10 @@ defmodule Explorer.Chain do Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not """ - @spec hash_to_address(Hash.Address.t() | binary(), [necessity_by_association_option | api?], boolean()) :: + @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option], boolean()) :: {:ok, Address.t()} | {:error, :not_found} def hash_to_address( - hash, + %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, options \\ [ necessity_by_association: %{ :contracts_creation_internal_transaction => :optional, @@ -1022,7 +1205,7 @@ defmodule Explorer.Chain do query |> join_associations(necessity_by_association) |> with_decompiled_code_flag(hash, query_decompiled_code_flag) - |> select_repo(options).one() + |> Repo.one() address_updated_result = case address_result do @@ -1030,7 +1213,21 @@ defmodule Explorer.Chain do if smart_contract do address_result else - SmartContract.compose_smart_contract(address_result, hash, options) + address_verified_twin_contract = + Chain.get_minimal_proxy_template(hash) || + Chain.get_address_verified_twin_contract(hash).verified_contract + + if address_verified_twin_contract do + address_verified_twin_contract_updated = + address_verified_twin_contract + |> Map.put(:address_hash, hash) + |> Map.put_new(:metadata_from_verified_twin, true) + + address_result + |> Map.put(:smart_contract, address_verified_twin_contract_updated) + else + address_result + end end _ -> @@ -1082,6 +1279,360 @@ defmodule Explorer.Chain do end end + defp prepare_search_term(string) do + case Regex.scan(~r/[a-zA-Z0-9]+/, string) do + [_ | _] = words -> + term_final = + words + |> Enum.map(fn [word] -> word <> ":*" end) + |> Enum.join(" & ") + + {:some, term_final} + + _ -> + :none + end + end + + defp search_token_query(term) do + from(token in Token, + left_join: bridged in BridgedToken, + on: token.contract_address_hash == bridged.home_token_contract_address_hash, + where: fragment("to_tsvector(symbol || ' ' || name ) @@ to_tsquery(?)", ^term), + select: %{ + address_hash: token.contract_address_hash, + tx_hash: fragment("CAST(NULL AS bytea)"), + block_hash: fragment("CAST(NULL AS bytea)"), + foreign_token_hash: bridged.foreign_token_contract_address_hash, + foreign_chain_id: bridged.foreign_chain_id, + type: "token", + name: token.name, + symbol: token.symbol, + holder_count: token.holder_count, + inserted_at: token.inserted_at, + block_number: 0 + } + ) + end + + defp search_contract_query(term) do + from(smart_contract in SmartContract, + left_join: address in Address, + on: smart_contract.address_hash == address.hash, + where: fragment("to_tsvector(name ) @@ to_tsquery(?)", ^term), + select: %{ + address_hash: smart_contract.address_hash, + tx_hash: fragment("CAST(NULL AS bytea)"), + block_hash: fragment("CAST(NULL AS bytea)"), + foreign_token_hash: fragment("CAST(NULL AS bytea)"), + foreign_chain_id: ^nil, + type: "contract", + name: smart_contract.name, + symbol: ^nil, + holder_count: ^nil, + inserted_at: address.inserted_at, + block_number: 0 + } + ) + end + + defp search_address_query(term) do + case Chain.string_to_address_hash(term) do + {:ok, address_hash} -> + from(address in Address, + left_join: address_name in Address.Name, + on: address.hash == address_name.address_hash, + where: address.hash == ^address_hash, + select: %{ + address_hash: address.hash, + tx_hash: fragment("CAST(NULL AS bytea)"), + block_hash: fragment("CAST(NULL AS bytea)"), + foreign_token_hash: fragment("CAST(NULL AS bytea)"), + foreign_chain_id: ^nil, + type: "address", + name: address_name.name, + symbol: ^nil, + holder_count: ^nil, + inserted_at: address.inserted_at, + block_number: 0 + } + ) + + _ -> + nil + end + end + + defp search_tx_query(term) do + case Chain.string_to_transaction_hash(term) do + {:ok, tx_hash} -> + from(transaction in Transaction, + where: transaction.hash == ^tx_hash, + select: %{ + address_hash: fragment("CAST(NULL AS bytea)"), + tx_hash: transaction.hash, + block_hash: fragment("CAST(NULL AS bytea)"), + foreign_token_hash: fragment("CAST(NULL AS bytea)"), + foreign_chain_id: ^nil, + type: "transaction", + name: ^nil, + symbol: ^nil, + holder_count: ^nil, + inserted_at: transaction.inserted_at, + block_number: 0 + } + ) + + _ -> + nil + end + end + + defp search_block_query(term) do + case Chain.string_to_block_hash(term) do + {:ok, block_hash} -> + from(block in Block, + where: block.hash == ^block_hash, + select: %{ + address_hash: fragment("CAST(NULL AS bytea)"), + tx_hash: fragment("CAST(NULL AS bytea)"), + block_hash: block.hash, + foreign_token_hash: fragment("CAST(NULL AS bytea)"), + foreign_chain_id: ^nil, + type: "block", + name: ^nil, + symbol: ^nil, + holder_count: ^nil, + inserted_at: block.inserted_at, + block_number: block.number + } + ) + + _ -> + case Integer.parse(term) do + {block_number, _} -> + from(block in Block, + where: block.number == ^block_number, + select: %{ + address_hash: fragment("CAST(NULL AS bytea)"), + tx_hash: fragment("CAST(NULL AS bytea)"), + block_hash: block.hash, + foreign_token_hash: fragment("CAST(NULL AS bytea)"), + foreign_chain_id: ^nil, + type: "block", + name: ^nil, + symbol: ^nil, + holder_count: ^nil, + inserted_at: block.inserted_at, + block_number: block.number + } + ) + + _ -> + nil + end + end + end + + def joint_search(paging_options, offset, string) do + case prepare_search_term(string) do + {:some, term} -> + tokens_query = search_token_query(term) + contracts_query = search_contract_query(term) + tx_query = search_tx_query(string) + address_query = search_address_query(string) + block_query = search_block_query(string) + + basic_query = + from( + tokens in subquery(tokens_query), + union: ^contracts_query + ) + + query = + cond do + address_query -> + basic_query + |> union(^address_query) + + tx_query -> + basic_query + |> union(^tx_query) + |> union(^block_query) + + block_query -> + basic_query + |> union(^block_query) + + true -> + basic_query + end + + ordered_query = + from(items in subquery(query), + order_by: [desc_nulls_last: items.holder_count, asc: items.name, desc: items.inserted_at], + limit: ^paging_options.page_size, + offset: ^offset + ) + + paginated_ordered_query = + ordered_query + |> page_search_results(paging_options) + + search_results = Repo.all(paginated_ordered_query) + + search_results + |> Enum.map(fn result -> + result_checksummed_address_hash = + if result.address_hash do + result + |> Map.put(:address_hash, Address.checksum(result.address_hash)) + else + result + end + + result_checksummed = + if result_checksummed_address_hash.foreign_token_hash do + result_checksummed_address_hash + |> Map.put(:foreign_token_hash, Address.checksum(result_checksummed_address_hash.foreign_token_hash)) + else + result_checksummed_address_hash + end + + result_checksummed + end) + + _ -> + [] + end + end + + @spec search_token(String.t()) :: [Token.t()] + def search_token(string) do + case prepare_search_term(string) do + {:some, term} -> + query = + from(token in Token, + where: fragment("to_tsvector(symbol || ' ' || name ) @@ to_tsquery(?)", ^term), + select: %{ + link: token.contract_address_hash, + symbol: token.symbol, + name: token.name, + holder_count: token.holder_count, + type: "token" + }, + order_by: [desc: token.holder_count] + ) + + Repo.all(query) + + _ -> + [] + end + end + + @spec search_contract(String.t()) :: [SmartContract.t()] + def search_contract(string) do + case prepare_search_term(string) do + {:some, term} -> + query = + from(smart_contract in SmartContract, + left_join: address in Address, + on: smart_contract.address_hash == address.hash, + where: fragment("to_tsvector(name ) @@ to_tsquery(?)", ^term), + select: %{ + link: smart_contract.address_hash, + name: smart_contract.name, + inserted_at: address.inserted_at, + type: "contract" + }, + order_by: [desc: smart_contract.inserted_at] + ) + + Repo.all(query) + + _ -> + [] + end + end + + def search_tx(term) do + case Chain.string_to_transaction_hash(term) do + {:ok, tx_hash} -> + query = + from(transaction in Transaction, + where: transaction.hash == ^tx_hash, + select: %{ + link: transaction.hash, + type: "transaction" + } + ) + + Repo.all(query) + + _ -> + [] + end + end + + def search_address(term) do + case Chain.string_to_address_hash(term) do + {:ok, address_hash} -> + query = + from(address in Address, + left_join: address_name in Address.Name, + on: address.hash == address_name.address_hash, + where: address.hash == ^address_hash, + select: %{ + name: address_name.name, + link: address.hash, + type: "address" + } + ) + + Repo.all(query) + + _ -> + [] + end + end + + def search_block(term) do + case Chain.string_to_block_hash(term) do + {:ok, block_hash} -> + query = + from(block in Block, + where: block.hash == ^block_hash, + select: %{ + link: block.hash, + block_number: block.number, + type: "block" + } + ) + + Repo.all(query) + + _ -> + case Integer.parse(term) do + {block_number, _} -> + query = + from(block in Block, + where: block.number == ^block_number, + select: %{ + link: block.hash, + block_number: block.number, + type: "block" + } + ) + + Repo.all(query) + + _ -> + [] + end + end + end + @doc """ Converts `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`. @@ -1180,7 +1731,7 @@ defmodule Explorer.Chain do options |> Keyword.get(:necessity_by_association, %{}) |> Map.merge(%{ - [smart_contract: :smart_contract_additional_sources] => :optional + smart_contract_additional_sources: :optional }) query = @@ -1193,27 +1744,32 @@ defmodule Explorer.Chain do query |> join_associations(necessity_by_association) |> with_decompiled_code_flag(hash, query_decompiled_code_flag) - |> select_repo(options).one() + |> Repo.one() address_updated_result = case address_result do %{smart_contract: smart_contract} -> if smart_contract do - CheckBytecodeMatchingOnDemand.trigger_check(address_result, smart_contract) - LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, smart_contract) - SmartContract.check_and_update_constructor_args(address_result) + check_bytecode_matching(address_result) else - LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil) - address_verified_twin_contract = - EIP1167.get_implementation_address(hash, options) || - SmartContract.get_address_verified_twin_contract(hash, options).verified_contract - - SmartContract.add_twin_info_to_contract(address_result, address_verified_twin_contract, hash) + Chain.get_minimal_proxy_template(hash) || + Chain.get_address_verified_twin_contract(hash).verified_contract + + if address_verified_twin_contract do + address_verified_twin_contract_updated = + address_verified_twin_contract + |> Map.put(:address_hash, hash) + |> Map.put_new(:metadata_from_verified_twin, true) + + address_result + |> Map.put(:smart_contract, address_verified_twin_contract_updated) + else + address_result + end end _ -> - LookUpSmartContractSourcesOnDemand.trigger_fetch(address_result, nil) address_result end @@ -1224,6 +1780,46 @@ defmodule Explorer.Chain do end end + defp check_bytecode_matching(address) do + now = DateTime.utc_now() + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + if !address.smart_contract.is_changed_bytecode and + address.smart_contract.bytecode_checked_at + |> DateTime.add(@check_bytecode_interval, :second) + |> DateTime.compare(now) != :gt do + case EthereumJSONRPC.fetch_codes( + [%{block_quantity: "latest", address: address.smart_contract.address_hash}], + json_rpc_named_arguments + ) do + {:ok, %EthereumJSONRPC.FetchedCodes{params_list: fetched_codes}} -> + bytecode_from_node = fetched_codes |> List.first() |> Map.get(:code) + bytecode_from_db = "0x" <> (address.contract_code.bytes |> Base.encode16(case: :lower)) + + if bytecode_from_node == bytecode_from_db do + {:ok, smart_contract} = + address.smart_contract + |> Changeset.change(%{bytecode_checked_at: now}) + |> Repo.update() + + %{address | smart_contract: smart_contract} + else + {:ok, smart_contract} = + address.smart_contract + |> Changeset.change(%{bytecode_checked_at: now, is_changed_bytecode: true}) + |> Repo.update() + + %{address | smart_contract: smart_contract} + end + + _ -> + address + end + else + address + end + end + @spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found} def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do query = @@ -1276,15 +1872,14 @@ defmodule Explorer.Chain do `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. """ - @spec hash_to_block(Hash.Full.t(), [necessity_by_association_option | api?]) :: - {:ok, Block.t()} | {:error, :not_found} + @spec hash_to_block(Hash.Full.t(), [necessity_by_association_option]) :: {:ok, Block.t()} | {:error, :not_found} def hash_to_block(%Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) Block |> where(hash: ^hash) |> join_associations(necessity_by_association) - |> select_repo(options).one() + |> Repo.one() |> case do nil -> {:error, :not_found} @@ -1346,7 +1941,7 @@ defmodule Explorer.Chain do `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. """ - @spec hash_to_transaction(Hash.Full.t(), [necessity_by_association_option | api?]) :: + @spec hash_to_transaction(Hash.Full.t(), [necessity_by_association_option]) :: {:ok, Transaction.t()} | {:error, :not_found} def hash_to_transaction( %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, @@ -1358,7 +1953,7 @@ defmodule Explorer.Chain do Transaction |> where(hash: ^hash) |> join_associations(necessity_by_association) - |> select_repo(options).one() + |> Repo.one() |> case do nil -> {:error, :not_found} @@ -1368,42 +1963,6 @@ defmodule Explorer.Chain do end end - # preload_to_detect_tt?: we don't need to preload more than one token transfer in case the tx inside the list (we don't show any token transfers on tx tile in new UI) - def preload_token_transfers( - %Transaction{hash: tx_hash, block_hash: block_hash} = transaction, - necessity_by_association, - options, - preload_to_detect_tt? \\ true - ) do - if preload_to_detect_tt? do - transaction - else - limit = if(preload_to_detect_tt?, do: 1, else: @token_transfers_per_transaction_preview + 1) - - token_transfers = - TokenTransfer - |> (&if(is_nil(block_hash), - do: where(&1, [token_transfer], token_transfer.transaction_hash == ^tx_hash), - else: - where( - &1, - [token_transfer], - token_transfer.transaction_hash == ^tx_hash and token_transfer.block_hash == ^block_hash - ) - )).() - |> limit(^limit) - |> order_by([token_transfer], asc: token_transfer.log_index) - |> (&if(preload_to_detect_tt?, do: &1, else: join_associations(&1, necessity_by_association))).() - |> select_repo(options).all() - |> flat_1155_batch_token_transfers() - |> Enum.take(limit) - - %Transaction{transaction | token_transfers: token_transfers} - end - end - - def get_token_transfers_per_transaction_preview_count, do: @token_transfers_per_transaction_preview - @doc """ Converts list of `t:Explorer.Chain.Transaction.t/0` `hashes` to the list of `t:Explorer.Chain.Transaction.t/0`s for those `hashes`. @@ -1432,15 +1991,15 @@ defmodule Explorer.Chain do `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. """ - @spec hashes_to_transactions([Hash.Full.t()], [necessity_by_association_option | api?]) :: [Transaction.t()] | [] + @spec hashes_to_transactions([Hash.Full.t()], [necessity_by_association_option]) :: [Transaction.t()] | [] def hashes_to_transactions(hashes, options \\ []) when is_list(hashes) and is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - Transaction.fetch_transactions() + fetch_transactions() |> where([transaction], transaction.hash in ^hashes) |> join_associations(necessity_by_association) |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) - |> select_repo(options).all() + |> Repo.all() end @doc """ @@ -1456,94 +2015,34 @@ defmodule Explorer.Chain do @doc """ The percentage of indexed blocks on the chain. + iex> for index <- 5..9 do + ...> insert(:block, number: index) + ...> Process.sleep(200) + ...> end + iex> Explorer.Chain.indexed_ratio() + Decimal.new(1, 50, -2) + If there are no blocks, the percentage is 0. - iex> Explorer.Chain.indexed_ratio_blocks() + iex> Explorer.Chain.indexed_ratio() Decimal.new(0) """ - @spec indexed_ratio_blocks() :: Decimal.t() - def indexed_ratio_blocks do - if Application.get_env(:indexer, Indexer.Supervisor)[:enabled] do - %{min: min_saved_block_number, max: max_saved_block_number} = BlockNumber.get_all() + @spec indexed_ratio() :: Decimal.t() + def indexed_ratio do + %{min: min, max: max} = BlockNumber.get_all() - min_blockchain_block_number = Application.get_env(:indexer, :first_block) + case {min, max} do + {0, 0} -> + Decimal.new(0) - case {min_saved_block_number, max_saved_block_number} do - {0, 0} -> - Decimal.new(0) - - _ -> - divisor = max_saved_block_number - min_blockchain_block_number + 1 - - ratio = get_ratio(BlockCache.estimated_count(), divisor) - - ratio - |> (&if( - greater_or_equal_0_99(&1) && - min_saved_block_number <= min_blockchain_block_number, - do: Decimal.new(1), - else: &1 - )).() - |> format_indexed_ratio() - end - else - Decimal.new(1) - end - end - - @spec indexed_ratio_internal_transactions() :: Decimal.t() - def indexed_ratio_internal_transactions do - if Application.get_env(:indexer, Indexer.Supervisor)[:enabled] && - not Application.get_env(:indexer, Indexer.Fetcher.InternalTransaction.Supervisor)[:disabled?] do - %{max: max_saved_block_number} = BlockNumber.get_all() - pbo_count = PendingBlockOperationCache.estimated_count() - - min_blockchain_trace_block_number = Application.get_env(:indexer, :trace_first_block) - - case max_saved_block_number do - 0 -> - Decimal.new(0) + _ -> + result = Decimal.div(max - min + 1, max + 1) - _ -> - full_blocks_range = max_saved_block_number - min_blockchain_trace_block_number + 1 - processed_int_txs_for_blocks_count = max(0, full_blocks_range - pbo_count) - - ratio = get_ratio(processed_int_txs_for_blocks_count, full_blocks_range) - - ratio - |> (&if( - greater_or_equal_0_99(&1), - do: Decimal.new(1), - else: &1 - )).() - |> format_indexed_ratio() - end - else - Decimal.new(1) + Decimal.round(result, 2, :down) end end - @spec get_ratio(non_neg_integer(), non_neg_integer()) :: Decimal.t() - defp get_ratio(dividend, divisor) do - if divisor > 0, - do: dividend |> Decimal.div(divisor), - else: Decimal.new(1) - end - - @spec greater_or_equal_0_99(Decimal.t()) :: boolean() - defp greater_or_equal_0_99(value) do - Decimal.compare(value, Decimal.from_float(0.99)) == :gt || - Decimal.compare(value, Decimal.from_float(0.99)) == :eq - end - - @spec format_indexed_ratio(Decimal.t()) :: Decimal.t() - defp format_indexed_ratio(raw_ratio) do - raw_ratio - |> Decimal.round(2, :down) - |> Decimal.min(Decimal.new(1)) - end - @spec fetch_min_block_number() :: non_neg_integer def fetch_min_block_number do query = @@ -1576,20 +2075,30 @@ defmodule Explorer.Chain do 0 end + @spec fetch_count_consensus_block() :: non_neg_integer + def fetch_count_consensus_block do + query = + from(block in Block, + select: count(block.hash), + where: block.consensus == true + ) + + Repo.one!(query, timeout: :infinity) || 0 + end + def fetch_block_by_hash(block_hash) do Repo.get(Block, block_hash) end - def filter_consensus_block_numbers(block_numbers) do + @spec fetch_sum_gas_used() :: non_neg_integer + def fetch_sum_gas_used do query = from( - block in Block, - where: block.number in ^block_numbers, - where: block.consensus == true, - select: block.number + t0 in Transaction, + select: fragment("SUM(t0.gas_used)") ) - Repo.all(query) + Repo.one!(query, timeout: :infinity) || 0 end @doc """ @@ -1624,7 +2133,7 @@ defmodule Explorer.Chain do * ':block_type' - use to filter by type of block; Uncle`, `Reorg`, or `Block` (default). """ - @spec list_blocks([paging_options | necessity_by_association_option | api?]) :: [Block.t()] + @spec list_blocks([paging_options | necessity_by_association_option]) :: [Block.t()] def list_blocks(options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options) || @default_paging_options @@ -1632,22 +2141,22 @@ defmodule Explorer.Chain do cond do block_type == "Block" && !paging_options.key -> - block_from_cache(block_type, paging_options, necessity_by_association, options) + fetch_blocks(block_type, paging_options, necessity_by_association) block_type == "Uncle" && !paging_options.key -> - uncles_from_cache(block_type, paging_options, necessity_by_association, options) + uncles_from_cache(block_type, paging_options, necessity_by_association) true -> - fetch_blocks(block_type, paging_options, necessity_by_association, options) + fetch_blocks(block_type, paging_options, necessity_by_association) end end - defp block_from_cache(block_type, paging_options, necessity_by_association, options) do - case Blocks.take_enough(paging_options.page_size) do + def uncles_from_cache(block_type, paging_options, necessity_by_association) do + case Uncles.take_enough(paging_options.page_size) do nil -> - elements = fetch_blocks(block_type, paging_options, necessity_by_association, options) + elements = fetch_blocks(block_type, paging_options, necessity_by_association) - Blocks.update(elements) + Uncles.update(elements) elements @@ -1656,28 +2165,14 @@ defmodule Explorer.Chain do end end - def uncles_from_cache(block_type, paging_options, necessity_by_association, options) do - case Uncles.take_enough(paging_options.page_size) do - nil -> - elements = fetch_blocks(block_type, paging_options, necessity_by_association, options) - - Uncles.update(elements) - - elements - - blocks -> - blocks - end - end - - defp fetch_blocks(block_type, paging_options, necessity_by_association, options) do + defp fetch_blocks(block_type, paging_options, necessity_by_association) do Block |> Block.block_type_filter(block_type) |> page_blocks(paging_options) |> limit(^paging_options.page_size) |> order_by(desc: :number) |> join_associations(necessity_by_association) - |> select_repo(options).all() + |> Repo.all() end @doc """ @@ -1703,12 +2198,174 @@ defmodule Explorer.Chain do |> Enum.into(%{}) end + @doc """ + Lists the top `t:Explorer.Chain.Address.t/0`'s' in descending order based on coin balance and address hash. + + """ + @spec list_top_addresses :: [{Address.t(), non_neg_integer()}] + def list_top_addresses(options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + if is_nil(paging_options.key) do + paging_options.page_size + |> Accounts.take_enough() + |> case do + nil -> + accounts_with_n = fetch_top_addresses(paging_options) + + accounts_with_n + |> Enum.map(fn {address, _n} -> address end) + |> Accounts.update() + + accounts_with_n + + accounts -> + Enum.map( + accounts, + &{&1, + if is_nil(&1.nonce) do + 0 + else + &1.nonce + 1 + end} + ) + end + else + fetch_top_addresses(paging_options) + end + end + + defp fetch_top_addresses(paging_options) do + base_query = + from(a in Address, + where: a.fetched_coin_balance > ^0, + order_by: [desc: a.fetched_coin_balance, asc: a.hash], + preload: [:names], + select: {a, fragment("coalesce(1 + ?, 0)", a.nonce)} + ) + + base_query + |> page_addresses(paging_options) + |> limit(^paging_options.page_size) + |> Repo.all() + end + + @doc """ + Lists the top `t:Explorer.Chain.Token.t/0`'s'. + + """ + @spec list_top_tokens(String.t()) :: [{Token.t(), non_neg_integer()}] + def list_top_tokens(filter, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + fetch_top_tokens(filter, paging_options) + end + + @spec list_top_bridged_tokens(atom(), String.t() | nil, boolean(), [paging_options | necessity_by_association_option]) :: + [ + {Token.t(), BridgedToken.t()} + ] + def list_top_bridged_tokens(destination, filter, from_api, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + fetch_top_bridged_tokens(destination, paging_options, filter, from_api) + end + + defp fetch_top_tokens(filter, paging_options) do + base_query = + from(t in Token, + where: t.total_supply > ^0, + order_by: [desc_nulls_last: t.holder_count, asc: t.name], + preload: [:contract_address] + ) + + base_query_with_paging = + base_query + |> page_tokens(paging_options) + |> limit(^paging_options.page_size) + + query = + if filter && filter !== "" do + base_query_with_paging + |> where(fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^filter)) + else + base_query_with_paging + end + + query + |> Repo.all() + end + + defp fetch_top_bridged_tokens(destination, paging_options, filter, from_api) do + offset = (max(paging_options.page_number, 1) - 1) * paging_options.page_size + chain_id = translate_destination_to_chain_id(destination) + + if chain_id == :undefined do + [] + else + bridged_tokens_query = + if chain_id do + from(bt in BridgedToken, + select: bt, + where: bt.foreign_chain_id == ^chain_id + ) + else + from(bt in BridgedToken, + select: bt + ) + end + + base_query = + from(t in Token, + right_join: bt in subquery(bridged_tokens_query), + on: t.contract_address_hash == bt.home_token_contract_address_hash, + where: t.total_supply > ^0, + where: t.bridged, + order_by: [desc: t.holder_count, asc: t.name], + select: [t, bt], + preload: [:contract_address] + ) + + base_query_with_paging = + base_query + |> page_tokens(paging_options) + |> limit(^paging_options.page_size) + |> offset(^offset) + + query = + if filter && filter !== "" do + base_query_with_paging + |> where(fragment("to_tsvector('english', symbol || ' ' || name ) @@ to_tsquery(?)", ^filter)) + else + base_query_with_paging + end + + if from_api do + query + |> Repo.replica().all() + else + query + |> Repo.all() + end + end + end + + defp translate_destination_to_chain_id(destination) do + case destination do + :eth -> 1 + :kovan -> 42 + :bsc -> 56 + :poa -> 99 + nil -> nil + _ -> :undefined + end + end + @doc """ Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`. """ - def stream_blocks_without_rewards(initial, reducer, limited? \\ false) when is_function(reducer, 2) do + def stream_blocks_without_rewards(initial, reducer) when is_function(reducer, 2) do Block.blocks_without_reward_query() - |> add_fetcher_limit(limited?) |> Repo.stream_reduce(initial, reducer) end @@ -1748,7 +2405,50 @@ defmodule Explorer.Chain do |> page_blocks(paging_options) |> limit(^paging_options.page_size) |> order_by(desc: :number) - |> select_repo(options).all() + |> Repo.all() + end + + def check_if_validated_blocks_at_address(address_hash) do + Repo.exists?(from(b in Block, where: b.miner_hash == ^address_hash)) + end + + def check_if_logs_at_address(address_hash) do + Repo.exists?(from(l in Log, where: l.address_hash == ^address_hash)) + end + + def check_if_internal_transactions_at_address(address_hash) do + internal_transactions_exists_by_created_contract_address_hash = + Repo.exists?(from(it in InternalTransaction, where: it.created_contract_address_hash == ^address_hash)) + + internal_transactions_exists_by_from_address_hash = + Repo.exists?(from(it in InternalTransaction, where: it.from_address_hash == ^address_hash)) + + internal_transactions_exists_by_to_address_hash = + Repo.exists?(from(it in InternalTransaction, where: it.to_address_hash == ^address_hash)) + + internal_transactions_exists_by_created_contract_address_hash || internal_transactions_exists_by_from_address_hash || + internal_transactions_exists_by_to_address_hash + end + + def check_if_token_transfers_at_address(address_hash) do + token_transfers_exists_by_from_address_hash = + Repo.exists?(from(tt in TokenTransfer, where: tt.from_address_hash == ^address_hash)) + + token_transfers_exists_by_to_address_hash = + Repo.exists?(from(tt in TokenTransfer, where: tt.to_address_hash == ^address_hash)) + + token_transfers_exists_by_from_address_hash || + token_transfers_exists_by_to_address_hash + end + + def check_if_tokens_at_address(address_hash) do + Repo.exists?( + from( + tb in CurrentTokenBalance, + where: tb.address_hash == ^address_hash, + where: tb.value > 0 + ) + ) end @doc """ @@ -1759,7 +2459,7 @@ defmodule Explorer.Chain do from( b in Block, join: addr in Address, - on: b.miner_hash == addr.hash, + where: b.miner_hash == addr.hash, select: {b.miner_hash, count(b.miner_hash)}, group_by: b.miner_hash ) @@ -1768,24 +2468,89 @@ defmodule Explorer.Chain do end @doc """ - Return the balance in usd corresponding to this token. Return nil if the fiat_value of the token is not present. + Counts the number of `t:Explorer.Chain.Block.t/0` validated by the address with the given `hash`. """ - def balance_in_fiat(%{fiat_value: fiat_value} = token_balance) when not is_nil(fiat_value) do - token_balance.fiat_value + @spec address_to_validation_count(Hash.Address.t()) :: non_neg_integer() + def address_to_validation_count(hash) do + query = from(block in Block, where: block.miner_hash == ^hash, select: fragment("COUNT(*)")) + + Repo.one(query) + end + + @spec address_to_transaction_count(Address.t()) :: non_neg_integer() + def address_to_transaction_count(address) do + if contract?(address) do + incoming_transaction_count = address_to_incoming_transaction_count(address.hash) + + if incoming_transaction_count == 0 do + total_transactions_sent_by_address(address.hash) + else + incoming_transaction_count + end + else + total_transactions_sent_by_address(address.hash) + end + end + + @spec address_to_token_transfer_count(Address.t()) :: non_neg_integer() + def address_to_token_transfer_count(address) do + query = + from( + token_transfer in TokenTransfer, + where: token_transfer.to_address_hash == ^address.hash, + or_where: token_transfer.from_address_hash == ^address.hash + ) + + Repo.aggregate(query, :count, timeout: :infinity) + end + + @spec address_to_gas_usage_count(Address.t()) :: Decimal.t() | nil + def address_to_gas_usage_count(address) do + if contract?(address) do + incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash) + + cond do + !incoming_transaction_gas_usage -> + address_to_outcoming_transaction_gas_usage(address.hash) + + Decimal.cmp(incoming_transaction_gas_usage, 0) == :eq -> + address_to_outcoming_transaction_gas_usage(address.hash) + + true -> + incoming_transaction_gas_usage + end + else + address_to_outcoming_transaction_gas_usage(address.hash) + end end - def balance_in_fiat(%{token: %{fiat_value: fiat_value, decimals: decimals}}) when nil in [fiat_value, decimals] do + @doc """ + Return the balance in usd corresponding to this token. Return nil if the usd_value of the token is not present. + """ + def balance_in_usd(%{token: %{usd_value: nil}}) do nil end - def balance_in_fiat(%{token: %{fiat_value: fiat_value, decimals: decimals}} = token_balance) do - tokens = CurrencyHelper.divide_decimals(token_balance.value, decimals) - Decimal.mult(tokens, fiat_value) + def balance_in_usd(token_balance) do + tokens = CurrencyHelpers.divide_decimals(token_balance.value, token_balance.token.decimals) + price = token_balance.token.usd_value + Decimal.mult(tokens, price) + end + + def address_tokens_usd_sum(token_balances) do + token_balances + |> Enum.reduce(Decimal.new(0), fn {token_balance, _, _}, acc -> + if token_balance.value && token_balance.token.usd_value do + Decimal.add(acc, balance_in_usd(token_balance)) + else + acc + end + end) end - def contract?(%{contract_code: nil}), do: false + defp contract?(%{contract_code: nil}), do: false - def contract?(%{contract_code: _}), do: true + defp contract?(%{contract_code: _}), do: true @doc """ Returns a stream of unfetched `t:Explorer.Chain.Address.CoinBalance.t/0`. @@ -1814,11 +2579,10 @@ defmodule Explorer.Chain do @spec stream_unfetched_balances( initial :: accumulator, reducer :: - (entry :: %{address_hash: Hash.Address.t(), block_number: Block.block_number()}, accumulator -> accumulator), - limited? :: boolean() + (entry :: %{address_hash: Hash.Address.t(), block_number: Block.block_number()}, accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_unfetched_balances(initial, reducer, limited? \\ false) when is_function(reducer, 2) do + def stream_unfetched_balances(initial, reducer) when is_function(reducer, 2) do query = from( balance in CoinBalance, @@ -1826,9 +2590,7 @@ defmodule Explorer.Chain do select: %{address_hash: balance.address_hash, block_number: balance.block_number} ) - query - |> add_coin_balances_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + Repo.stream_reduce(query, initial, reducer) end @doc """ @@ -1836,13 +2598,11 @@ defmodule Explorer.Chain do """ @spec stream_unfetched_token_balances( initial :: accumulator, - reducer :: (entry :: TokenBalance.t(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: TokenBalance.t(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_unfetched_token_balances(initial, reducer, limited? \\ false) when is_function(reducer, 2) do + def stream_unfetched_token_balances(initial, reducer) when is_function(reducer, 2) do TokenBalance.unfetched_token_balances() - |> add_token_balances_fetcher_limit(limited?) |> Repo.stream_reduce(initial, reducer) end @@ -1850,36 +2610,44 @@ defmodule Explorer.Chain do Returns a stream of all blocks with unfetched internal transactions, using the `pending_block_operation` table. + Only blocks with consensus are returned. + + iex> non_consensus = insert(:block, consensus: false) + iex> insert(:pending_block_operation, block: non_consensus, fetch_internal_transactions: true) iex> unfetched = insert(:block) - iex> insert(:pending_block_operation, block: unfetched, block_number: unfetched.number) + iex> insert(:pending_block_operation, block: unfetched, fetch_internal_transactions: true) + iex> fetched = insert(:block) + iex> insert(:pending_block_operation, block: fetched, fetch_internal_transactions: false) iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions( ...> MapSet.new(), ...> fn number, acc -> ...> MapSet.put(acc, number) ...> end ...> ) + iex> non_consensus.number in number_set + false iex> unfetched.number in number_set true + iex> fetched.hash in number_set + false """ @spec stream_blocks_with_unfetched_internal_transactions( initial :: accumulator, - reducer :: (entry :: term(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: term(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_blocks_with_unfetched_internal_transactions(initial, reducer, limited? \\ false) - when is_function(reducer, 2) do + def stream_blocks_with_unfetched_internal_transactions(initial, reducer) when is_function(reducer, 2) do query = from( - po in PendingBlockOperation, - where: not is_nil(po.block_number), - select: po.block_number + b in Block, + join: pending_ops in assoc(b, :pending_operations), + where: pending_ops.fetch_internal_transactions, + where: b.consensus, + select: b.number ) - query - |> add_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + Repo.stream_reduce(query, initial, reducer) end def remove_nonconsensus_blocks_from_pending_ops(block_hashes) do @@ -1926,23 +2694,20 @@ defmodule Explorer.Chain do | :value ], initial :: accumulator, - reducer :: (entry :: term(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: term(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_transactions_with_unfetched_created_contract_codes(fields, initial, reducer, limited? \\ false) + def stream_transactions_with_unfetched_created_contract_codes(fields, initial, reducer) when is_function(reducer, 2) do query = from(t in Transaction, where: not is_nil(t.block_hash) and not is_nil(t.created_contract_address_hash) and - is_nil(t.created_contract_code_indexed_at) and t.status == ^1, + is_nil(t.created_contract_code_indexed_at), select: ^fields ) - query - |> add_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + Repo.stream_reduce(query, initial, reducer) end @spec stream_mined_transactions( @@ -1994,16 +2759,14 @@ defmodule Explorer.Chain do | :value ], initial :: accumulator, - reducer :: (entry :: term(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: term(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_pending_transactions(fields, initial, reducer, limited? \\ false) when is_function(reducer, 2) do + def stream_pending_transactions(fields, initial, reducer) when is_function(reducer, 2) do query = Transaction |> pending_transactions_query() |> select(^fields) - |> add_fetcher_limit(limited?) Repo.stream_reduce(query, initial, reducer) end @@ -2018,20 +2781,17 @@ defmodule Explorer.Chain do """ @spec stream_unfetched_uncles( initial :: accumulator, - reducer :: (entry :: term(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: term(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_unfetched_uncles(initial, reducer, limited? \\ false) when is_function(reducer, 2) do + def stream_unfetched_uncles(initial, reducer) when is_function(reducer, 2) do query = from(bsdr in Block.SecondDegreeRelation, where: is_nil(bsdr.uncle_fetched_at) and not is_nil(bsdr.index), select: [:nephew_hash, :index] ) - query - |> add_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + Repo.stream_reduce(query, initial, reducer) end @doc """ @@ -2087,10 +2847,22 @@ defmodule Explorer.Chain do end @spec block_height() :: block_height() - def block_height(options \\ []) do + def block_height do query = from(block in Block, select: coalesce(max(block.number), 0), where: block.consensus == true) - select_repo(options).one!(query) + Repo.one!(query) + end + + def count_db_decompiled_contracts do + query = from(p in "pg_class", select: p.reltuples, where: p.relname == "decompiled_smart_contracts") + + query + |> Repo.one() + |> decompiled_contracts_count() + end + + defp decompiled_contracts_count(count) do + {:ok, count} end def last_db_block_status do @@ -2122,14 +2894,68 @@ defmodule Explorer.Chain do end end - @spec increment_last_fetched_counter(binary(), non_neg_integer()) :: {non_neg_integer(), nil} - def increment_last_fetched_counter(type, value) do - query = - from(counter in LastFetchedCounter, - where: counter.counter_type == ^type + def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do + lates_gas_price_query = + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + where: block.consensus == true, + where: transaction.status == ^1, + where: transaction.gas_price > ^0, + group_by: block.number, + order_by: [desc: block.number], + select: min(transaction.gas_price), + limit: ^num_of_blocks ) - Repo.update_all(query, [inc: [value: value]], timeout: :infinity) + latest_gas_prices = + lates_gas_price_query + |> Repo.all(timeout: :infinity) + + latest_ordered_gas_prices = + latest_gas_prices + |> Enum.map(fn %Explorer.Chain.Wei{value: gas_price} -> Decimal.to_integer(gas_price) end) + + safelow_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, safelow_percentile) + average_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, average_percentile) + fast_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, fast_percentile) + + gas_prices = %{ + "slow" => safelow_gas_price, + "average" => average_gas_price, + "fast" => fast_gas_price + } + + {:ok, gas_prices} + catch + error -> + {:error, error} + end + + defp gas_price_percentile_to_gwei(gas_prices, percentile) do + safelow_gas_price_wei = percentile(gas_prices, percentile) + + if safelow_gas_price_wei do + safelow_gas_price_gwei = Wei.to(%Explorer.Chain.Wei{value: Decimal.from_float(safelow_gas_price_wei)}, :gwei) + Decimal.to_float(safelow_gas_price_gwei) |> Float.floor(1) + else + nil + end + end + + @spec percentile(list, number) :: number | nil + defp percentile([], _), do: nil + defp percentile([x], _), do: x + defp percentile(list, 0), do: Enum.min(list) + defp percentile(list, 100), do: Enum.max(list) + + defp percentile(list, n) when is_list(list) and is_number(n) do + s = Enum.sort(list) + r = n / 100.0 * (length(list) - 1) + f = :erlang.trunc(r) + lower = Enum.at(s, f) + upper = Enum.at(s, f + 1) + lower + (upper - lower) * (r - f) end @spec upsert_last_fetched_counter(map()) :: {:ok, LastFetchedCounter.t()} | {:error, Ecto.Changeset.t()} @@ -2142,7 +2968,7 @@ defmodule Explorer.Chain do ) end - def get_last_fetched_counter(type, options \\ []) do + def get_last_fetched_counter(type) do query = from( last_fetched_counter in LastFetchedCounter, @@ -2150,7 +2976,7 @@ defmodule Explorer.Chain do select: last_fetched_counter.value ) - select_repo(options).one(query) || Decimal.new(0) + Repo.one!(query) || Decimal.new(0) end defp block_status({number, timestamp}) do @@ -2166,31 +2992,30 @@ defmodule Explorer.Chain do defp block_status(nil), do: {:error, :no_blocks} - def fetch_min_missing_block_cache(from \\ nil, to \\ nil) do - from_block_number = from || 0 - to_block_number = to || BlockNumber.get_max() + def fetch_min_missing_block_cache do + max_block_number = BlockNumber.get_max() - if to_block_number > 0 do + if max_block_number > 0 do query = from(b in Block, right_join: missing_range in fragment( """ - (SELECT b1.number - FROM generate_series((?)::integer, (?)::integer) AS b1(number) + (SELECT b1.number + FROM generate_series(0, (?)::integer) AS b1(number) WHERE NOT EXISTS (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) """, - ^from_block_number, - ^to_block_number + ^max_block_number ), on: b.number == missing_range.number, select: min(missing_range.number) ) - Repo.one(query, timeout: :infinity) + query + |> Repo.one(timeout: :infinity) || 0 else - nil + 0 end end @@ -2263,13 +3088,10 @@ defmodule Explorer.Chain do right_join: missing_range in fragment( """ - ( - SELECT distinct b1.number + (SELECT distinct b1.number FROM generate_series((?)::integer, (?)::integer) AS b1(number) WHERE NOT EXISTS - (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus) - ORDER BY b1.number DESC - ) + (SELECT 1 FROM blocks b2 WHERE b2.number=b1.number AND b2.consensus)) """, ^range_min, ^range_max @@ -2308,27 +3130,27 @@ defmodule Explorer.Chain do ordered_block_ranges = final_block_ranges |> Enum.sort(fn %Range{first: first1, last: _}, %Range{first: first2, last: _} -> - if range_start <= range_end, do: first1 <= first2, else: first1 >= first2 + if range_start <= range_end do + first1 <= first2 + else + first1 >= first2 + end end) |> Enum.map(fn %Range{first: first, last: last} = range -> if range_start <= range_end do range else - set_new_range(last, first) + if last > first do + %Range{first: last, last: first, step: -1} + else + %Range{first: last, last: first, step: 1} + end end end) ordered_block_ranges end - defp set_new_range(last, first) do - if last > first, do: set_range(last, first, -1), else: set_range(last, first, 1) - end - - defp set_range(last, first, step) do - %Range{first: last, last: first, step: step} - end - defp block_ranges_extend(block_ranges, block_range_start, block_range_end) do # credo:disable-for-next-line block_ranges ++ [Range.new(block_range_start, block_range_end)] @@ -2344,7 +3166,7 @@ defmodule Explorer.Chain do `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. """ - @spec number_to_block(Block.block_number(), [necessity_by_association_option | api?]) :: + @spec number_to_block(Block.block_number(), [necessity_by_association_option]) :: {:ok, Block.t()} | {:error, :not_found} def number_to_block(number, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) @@ -2352,18 +3174,7 @@ defmodule Explorer.Chain do Block |> where(consensus: true, number: ^number) |> join_associations(necessity_by_association) - |> select_repo(options).one() - |> case do - nil -> {:error, :not_found} - block -> {:ok, block} - end - end - - @spec nonconsensus_block_by_number(Block.block_number(), [api?]) :: {:ok, Block.t()} | {:error, :not_found} - def nonconsensus_block_by_number(number, options) do - Block - |> where(consensus: false, number: ^number) - |> select_repo(options).one() + |> Repo.one() |> case do nil -> {:error, :not_found} block -> {:ok, block} @@ -2394,10 +3205,16 @@ defmodule Explorer.Chain do limit: 1 ) - repo = if from_api, do: Repo.replica(), else: Repo + response = + if from_api do + query + |> Repo.replica().one() + else + query + |> Repo.one() + end - query - |> repo.one(timeout: :infinity) + response |> case do nil -> {:error, :not_found} @@ -2454,7 +3271,7 @@ defmodule Explorer.Chain do iex> newest_first_transactions = 50 |> insert_list(:transaction) |> with_block() |> Enum.reverse() iex> oldest_seen = Enum.at(newest_first_transactions, 9) iex> paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} - iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions(true, paging_options: paging_options) + iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options) iex> length(recent_collated_transactions) 10 iex> hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash @@ -2470,24 +3287,12 @@ defmodule Explorer.Chain do the `block_number` and `index` that are passed. """ - @spec recent_collated_transactions(true | false, [paging_options | necessity_by_association_option | api?]) :: [ - Transaction.t() - ] - def recent_collated_transactions(old_ui?, options \\ []) - when is_list(options) do + @spec recent_collated_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()] + def recent_collated_transactions(options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) - method_id_filter = Keyword.get(options, :method) - type_filter = Keyword.get(options, :type) - - fetch_recent_collated_transactions( - old_ui?, - paging_options, - necessity_by_association, - method_id_filter, - type_filter, - options - ) + + fetch_recent_collated_transactions(paging_options, necessity_by_association) end # RAP - random access pagination @@ -2541,32 +3346,17 @@ defmodule Explorer.Chain do def transactions_available_count do Transaction |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) - |> limit(^@limit_showing_transactions) + |> limit(^@limit_showing_transaсtions) |> Repo.aggregate(:count, :hash) end - def fetch_recent_collated_transactions( - old_ui?, - paging_options, - necessity_by_association, - method_id_filter, - type_filter, - options - ) do + def fetch_recent_collated_transactions(paging_options, necessity_by_association) do paging_options - |> Transaction.fetch_transactions() + |> fetch_transactions() |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) - |> apply_filter_by_method_id_to_transactions(method_id_filter) - |> apply_filter_by_tx_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> Transaction.put_has_token_transfers_to_tx(old_ui?) - |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() - |> select_repo(options).all() - |> (&if(old_ui?, - do: &1, - else: - Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end) - )).() + |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> Repo.all() end @doc """ @@ -2593,26 +3383,19 @@ defmodule Explorer.Chain do Results will be the transactions older than the `inserted_at` and `hash` that are passed. """ - @spec recent_pending_transactions([paging_options | necessity_by_association_option], true | false) :: [ - Transaction.t() - ] - def recent_pending_transactions(options \\ [], old_ui? \\ true) - when is_list(options) do + @spec recent_pending_transactions([paging_options | necessity_by_association_option]) :: [Transaction.t()] + def recent_pending_transactions(options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) - method_id_filter = Keyword.get(options, :method) - type_filter = Keyword.get(options, :type) Transaction - |> Transaction.page_pending_transaction(paging_options) + |> page_pending_transaction(paging_options) |> limit(^paging_options.page_size) |> pending_transactions_query() - |> apply_filter_by_method_id_to_transactions(method_id_filter) - |> apply_filter_by_tx_type_to_transactions(type_filter) - |> order_by([transaction], desc: transaction.inserted_at, asc: transaction.hash) + |> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash) |> join_associations(necessity_by_association) - |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() - |> select_repo(options).all() + |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) + |> Repo.all() end def pending_transactions_query(query) do @@ -2631,6 +3414,32 @@ defmodule Explorer.Chain do |> Repo.all(timeout: :infinity) end + @doc """ + Returns the list of empty blocks from the DB which have not marked with `t:Explorer.Chain.Block.is_empty/0`. + This query used for initializtion of Indexer.EmptyBlocksSanitizer + """ + def unprocessed_empty_blocks_query_list(limit) do + query = + from(block in Block, + as: :block, + where: block.consensus == true, + where: is_nil(block.is_empty), + where: + not exists( + from(transaction in Transaction, + where: transaction.block_number == parent_as(:block).number + ) + ), + select: {block.number, block.hash}, + order_by: [desc: block.number], + limit: ^limit, + offset: 100 + ) + + query + |> Repo.all(timeout: :infinity) + end + @doc """ The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.Address.t/0`. @@ -2668,8 +3477,6 @@ defmodule Explorer.Chain do Hash.Address.cast(string) end - def string_to_address_hash(_), do: :error - @doc """ The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.t/0`. @@ -2695,8 +3502,6 @@ defmodule Explorer.Chain do Hash.Full.cast(string) end - def string_to_block_hash(_), do: :error - @doc """ The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.t/0`. @@ -2722,45 +3527,89 @@ defmodule Explorer.Chain do Hash.Full.cast(string) end - def string_to_transaction_hash(_), do: :error - @doc """ - `t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`. + Estimated count of `t:Explorer.Chain.Transaction.t/0`. - ## Options + Estimated count of both collated and pending transactions using the transactions table statistics. + """ + @spec transaction_estimated_count() :: non_neg_integer() + def transaction_estimated_count do + cached_value = TransactionCount.get_count() - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, - then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. - * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and - `:key` (a tuple of the lowest/oldest `{index}`). Results will be the internal transactions older than - the `index` that is passed. + if is_nil(cached_value) do + %Postgrex.Result{rows: [[rows]]} = + SQL.query!(Repo, "SELECT reltuples::BIGINT AS estimate FROM pg_class WHERE relname='transactions'") - """ + rows + else + cached_value + end + end - @spec all_transaction_to_internal_transactions(Hash.Full.t(), [ - paging_options | necessity_by_association_option | api? - ]) :: [ - InternalTransaction.t() - ] - def all_transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - paging_options = Keyword.get(options, :paging_options, @default_paging_options) + @spec total_gas_usage() :: non_neg_integer() + def total_gas_usage do + cached_value = GasUsage.get_sum() - InternalTransaction - |> for_parent_transaction(hash) + if is_nil(cached_value) do + 0 + else + cached_value + end + end + + @doc """ + Estimated count of `t:Explorer.Chain.Block.t/0`. + + Estimated count of consensus blocks. + """ + @spec block_estimated_count() :: non_neg_integer() + def block_estimated_count do + cached_value = BlockCount.get_count() + + if is_nil(cached_value) do + %Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'blocks';") + + trunc(count * 0.90) + else + cached_value + end + end + + @doc """ + `t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`. + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, + then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. + * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and + `:key` (a tuple of the lowest/oldest `{index}`). Results will be the internal transactions older than + the `index` that is passed. + + """ + + @spec all_transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [ + InternalTransaction.t() + ] + def all_transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + paging_options = Keyword.get(options, :paging_options, @default_paging_options) + + InternalTransaction + |> for_parent_transaction(hash) |> join_associations(necessity_by_association) |> InternalTransaction.where_nonpending_block() |> page_internal_transaction(paging_options) |> limit(^paging_options.page_size) |> order_by([internal_transaction], asc: internal_transaction.index) - |> select_repo(options).all() + |> preload(:transaction) + |> Repo.all() end - @spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option | api?]) :: - [ - InternalTransaction.t() - ] + @spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [ + InternalTransaction.t() + ] def transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) @@ -2774,8 +3623,8 @@ defmodule Explorer.Chain do |> page_internal_transaction(paging_options) |> limit(^paging_options.page_size) |> order_by([internal_transaction], asc: internal_transaction.index) - |> preload(:block) - |> select_repo(options).all() + |> preload(:transaction) + |> Repo.all() end @doc """ @@ -2791,8 +3640,8 @@ defmodule Explorer.Chain do the `index` that are passed. """ - @spec transaction_to_logs(Hash.Full.t(), [paging_options | necessity_by_association_option | api?]) :: [Log.t()] - def transaction_to_logs(transaction_hash, options \\ []) when is_list(options) do + @spec transaction_to_logs(Hash.Full.t(), boolean(), [paging_options | necessity_by_association_option]) :: [Log.t()] + def transaction_to_logs(transaction_hash, from_api, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) paging_options = Keyword.get(options, :paging_options, @default_paging_options) @@ -2807,13 +3656,18 @@ defmodule Explorer.Chain do query = log_with_transactions |> where([_, transaction], transaction.hash == ^transaction_hash) - |> page_transaction_logs(paging_options) + |> page_logs(paging_options) |> limit(^paging_options.page_size) |> order_by([log], asc: log.index) |> join_associations(necessity_by_association) - query - |> select_repo(options).all() + if from_api do + query + |> Repo.replica().all() + else + query + |> Repo.all() + end end @doc """ @@ -2829,13 +3683,12 @@ defmodule Explorer.Chain do the `index` that are passed. """ - @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option | api?()]) :: [ + @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [ TokenTransfer.t() ] def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - paging_options = options |> Keyword.get(:paging_options, @default_paging_options) |> Map.put(:asc_order, true) - token_type = Keyword.get(options, :token_type) + paging_options = Keyword.get(options, :paging_options, @default_paging_options) TokenTransfer |> join(:inner, [token_transfer], transaction in assoc(token_transfer, :transaction)) @@ -2844,14 +3697,11 @@ defmodule Explorer.Chain do transaction.hash == ^transaction_hash and token_transfer.block_hash == transaction.block_hash and token_transfer.block_number == transaction.block_number ) - |> join(:inner, [tt], token in assoc(tt, :token), as: :token) - |> preload([token: token], [{:token, token}]) - |> TokenTransfer.filter_by_type(token_type) |> TokenTransfer.page_token_transfer(paging_options) |> limit(^paging_options.page_size) - |> order_by([token_transfer], asc: token_transfer.log_index) + |> order_by([token_transfer], asc: token_transfer.inserted_at) |> join_associations(necessity_by_association) - |> select_repo(options).all() + |> Repo.all() end @doc """ @@ -2934,20 +3784,16 @@ defmodule Explorer.Chain do Wei.hex_format(value) ) - revert_reason = + data = case EthereumJSONRPC.json_rpc(req, json_rpc_named_arguments) do {:error, %{data: data}} -> data - {:error, %{message: message}} -> - message - _ -> "" end - formatted_revert_reason = - revert_reason |> format_revert_reason_message() |> (&if(String.valid?(&1), do: &1, else: revert_reason)).() + formatted_revert_reason = format_revert_reason_message(data) if byte_size(formatted_revert_reason) > 0 do transaction @@ -2972,9 +3818,6 @@ defmodule Explorer.Chain do @revert_msg_prefix_4 <> rest -> extract_revert_reason_message_wrapper(rest) - @revert_msg_prefix_5 <> rest -> - extract_revert_reason_message_wrapper(rest) - revert_reason_full -> revert_reason_full end @@ -3009,7 +3852,12 @@ defmodule Explorer.Chain do The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in `unit`. """ - @spec value(InternalTransaction.t() | Transaction.t(), :wei | :gwei | :ether) :: Wei.wei() | Wei.gwei() | Wei.ether() + @spec value(InternalTransaction.t(), :wei) :: Wei.wei() + @spec value(InternalTransaction.t(), :gwei) :: Wei.gwei() + @spec value(InternalTransaction.t(), :ether) :: Wei.ether() + @spec value(Transaction.t(), :wei) :: Wei.wei() + @spec value(Transaction.t(), :gwei) :: Wei.gwei() + @spec value(Transaction.t(), :ether) :: Wei.ether() def value(%type{value: value}, unit) when type in [InternalTransaction, Transaction] do Wei.to(value, unit) end @@ -3155,16 +4003,394 @@ defmodule Explorer.Chain do end end - defp fetch_transactions_in_ascending_order_by_index(paging_options) do + @doc """ + Inserts a `t:SmartContract.t/0`. + + As part of inserting a new smart contract, an additional record is inserted for + naming the address for reference. + """ + @spec create_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()} + def create_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do + new_contract = %SmartContract{} + + smart_contract_changeset = + new_contract + |> SmartContract.changeset(attrs) + |> Changeset.put_change(:external_libraries, external_libraries) + + new_contract_additional_source = %SmartContractAdditionalSource{} + + smart_contract_additional_sources_changesets = + if secondary_sources do + secondary_sources + |> Enum.map(fn changeset -> + new_contract_additional_source + |> SmartContractAdditionalSource.changeset(changeset) + end) + else + [] + end + + address_hash = Changeset.get_field(smart_contract_changeset, :address_hash) + + # Enforce ShareLocks tables order (see docs: sharelocks.md) + insert_contract_query = + Multi.new() + |> Multi.run(:set_address_verified, fn repo, _ -> set_address_verified(repo, address_hash) end) + |> Multi.run(:clear_primary_address_names, fn repo, _ -> clear_primary_address_names(repo, address_hash) end) + |> Multi.run(:insert_address_name, fn repo, _ -> + name = Changeset.get_field(smart_contract_changeset, :name) + create_address_name(repo, name, address_hash) + end) + |> Multi.insert(:smart_contract, smart_contract_changeset) + + insert_contract_query_with_additional_sources = + smart_contract_additional_sources_changesets + |> Enum.with_index() + |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> + Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) + end) + + insert_result = + insert_contract_query_with_additional_sources + |> Repo.transaction() + + case insert_result do + {:ok, %{smart_contract: smart_contract}} -> + {:ok, smart_contract} + + {:error, :smart_contract, changeset, _} -> + {:error, changeset} + + {:error, :set_address_verified, message, _} -> + {:error, message} + end + end + + @doc """ + Updates a `t:SmartContract.t/0`. + + Has the similar logic as create_smart_contract/1. + Used in cases when you need to update row in DB contains SmartContract, e.g. in case of changing + status `partially verified` to `fully verified` (re-verify). + """ + @spec update_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()} + def update_smart_contract(attrs \\ %{}, external_libraries \\ [], secondary_sources \\ []) do + address_hash = Map.get(attrs, :address_hash) + + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + query_sources = + from( + source in SmartContractAdditionalSource, + where: source.address_hash == ^address_hash + ) + + _delete_sources = Repo.delete_all(query_sources) + + smart_contract = Repo.one(query) + + smart_contract_changeset = + smart_contract + |> SmartContract.changeset(attrs) + |> Changeset.put_change(:external_libraries, external_libraries) + + new_contract_additional_source = %SmartContractAdditionalSource{} + + smart_contract_additional_sources_changesets = + if secondary_sources do + secondary_sources + |> Enum.map(fn changeset -> + new_contract_additional_source + |> SmartContractAdditionalSource.changeset(changeset) + end) + else + [] + end + + # Enforce ShareLocks tables order (see docs: sharelocks.md) + insert_contract_query = + Multi.new() + |> Multi.update(:smart_contract, smart_contract_changeset) + + insert_contract_query_with_additional_sources = + smart_contract_additional_sources_changesets + |> Enum.with_index() + |> Enum.reduce(insert_contract_query, fn {changeset, index}, multi -> + Multi.insert(multi, "smart_contract_additional_source_#{Integer.to_string(index)}", changeset) + end) + + insert_result = + insert_contract_query_with_additional_sources + |> Repo.transaction() + + case insert_result do + {:ok, %{smart_contract: smart_contract}} -> + {:ok, smart_contract} + + {:error, :smart_contract, changeset, _} -> + {:error, changeset} + + {:error, :set_address_verified, message, _} -> + {:error, message} + end + end + + defp set_address_verified(repo, address_hash) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + case repo.update_all(query, set: [verified: true]) do + {1, _} -> {:ok, []} + _ -> {:error, "There was an error annotating that the address has been verified."} + end + end + + defp set_address_decompiled(repo, address_hash) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + case repo.update_all(query, set: [decompiled: true]) do + {1, _} -> {:ok, []} + _ -> {:error, "There was an error annotating that the address has been decompiled."} + end + end + + defp clear_primary_address_names(repo, address_hash) do + query = + from( + address_name in Address.Name, + where: address_name.address_hash == ^address_hash, + # Enforce Name ShareLocks order (see docs: sharelocks.md) + order_by: [asc: :address_hash, asc: :name], + lock: "FOR UPDATE" + ) + + repo.update_all( + from(n in Address.Name, join: s in subquery(query), on: n.address_hash == s.address_hash and n.name == s.name), + set: [primary: false] + ) + + {:ok, []} + end + + defp create_address_name(repo, name, address_hash) do + params = %{ + address_hash: address_hash, + name: name, + primary: true + } + + %Address.Name{} + |> Address.Name.changeset(params) + |> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name]) + end + + @doc """ + Finds metadata for verification of a contract from verified twins: contracts with the same bytecode + which were verified previously, returns a single t:SmartContract.t/0 + """ + def get_address_verified_twin_contract(address_hash) do + case Repo.get(Address, address_hash) do + nil -> + %{:verified_contract => nil, :additional_sources => nil} + + target_address -> + target_address_hash = target_address.hash + contract_code = target_address.contract_code + + case contract_code do + %Chain.Data{bytes: contract_code_bytes} -> + contract_code_md5 = + Base.encode16(:crypto.hash(:md5, "\\x" <> Base.encode16(contract_code_bytes, case: :lower)), + case: :lower + ) + + verified_contract_twin_query = + from( + address in Address, + inner_join: smart_contract in SmartContract, + on: address.hash == smart_contract.address_hash, + where: fragment("md5(contract_code::text)") == ^contract_code_md5, + where: address.hash != ^target_address_hash, + select: smart_contract, + limit: 1 + ) + + verified_contract_twin = + verified_contract_twin_query + |> Repo.one(timeout: 60_000) + + verified_contract_twin_additional_sources = get_contract_additional_sources(verified_contract_twin) + + %{ + :verified_contract => verified_contract_twin, + :additional_sources => verified_contract_twin_additional_sources + } + + _ -> + %{:verified_contract => nil, :additional_sources => nil} + end + end + end + + def get_minimal_proxy_template(address_hash) do + minimal_proxy_template = + case Repo.get(Address, address_hash) do + nil -> + nil + + target_address -> + contract_code = target_address.contract_code + + case contract_code do + %Chain.Data{bytes: contract_code_bytes} -> + contract_bytecode = Base.encode16(contract_code_bytes, case: :lower) + + get_minimal_proxy_from_template_code(contract_bytecode) + + _ -> + nil + end + end + + minimal_proxy_template + end + + defp get_minimal_proxy_from_template_code(contract_bytecode) do + case contract_bytecode do + "363d3d373d3d3d363d73" <> <> <> _ -> + template_address = "0x" <> template_address + + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^template_address, + select: smart_contract + ) + + template = + query + |> Repo.one(timeout: 10_000) + + template + + _ -> + nil + end + end + + defp get_contract_additional_sources(verified_contract_twin) do + if verified_contract_twin do + verified_contract_twin_additional_sources_query = + from( + s in SmartContractAdditionalSource, + where: s.address_hash == ^verified_contract_twin.address_hash + ) + + verified_contract_twin_additional_sources_query + |> Repo.all() + else + [] + end + end + + @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil + def address_hash_to_smart_contract(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + current_smart_contract = Repo.one(query) + + if current_smart_contract do + current_smart_contract + else + address_verified_twin_contract = + Chain.get_minimal_proxy_template(address_hash) || + Chain.get_address_verified_twin_contract(address_hash).verified_contract + + if address_verified_twin_contract do + Map.put(address_verified_twin_contract, :address_hash, address_hash) + else + current_smart_contract + end + end + end + + def smart_contract_fully_verified?(address_hash_str) when is_binary(address_hash_str) do + case string_to_address_hash(address_hash_str) do + {:ok, address_hash} -> + check_fully_verified(address_hash) + + _ -> + false + end + end + + def smart_contract_fully_verified?(address_hash) do + check_fully_verified(address_hash) + end + + defp check_fully_verified(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + result = Repo.one(query) + + if result, do: !result.partially_verified, else: false + end + + def smart_contract_verified?(address_hash_str) when is_binary(address_hash_str) do + case string_to_address_hash(address_hash_str) do + {:ok, address_hash} -> + check_verified(address_hash) + + _ -> + false + end + end + + def smart_contract_verified?(address_hash) do + check_verified(address_hash) + end + + defp check_verified(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + if Repo.one(query), do: true, else: false + end + + defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil) do Transaction - |> order_by([transaction], asc: transaction.index) - |> handle_block_paging_options(paging_options) + |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + |> where_block_number_in_period(from_block, to_block) + |> handle_paging_options(paging_options) end - defp fetch_transactions_in_descending_order_by_block_and_index(paging_options) do + defp fetch_transactions_in_ascending_order_by_index(paging_options) do Transaction |> order_by([transaction], desc: transaction.block_number, asc: transaction.index) - |> handle_block_paging_options(paging_options) + |> handle_paging_options(paging_options) end defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do @@ -3175,21 +4401,19 @@ defmodule Explorer.Chain do ) end - defp handle_block_paging_options(query, nil), do: query + defp handle_paging_options(query, nil), do: query - defp handle_block_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query - - defp handle_block_paging_options(query, paging_options) do + defp handle_paging_options(query, paging_options) do query - |> page_block_transactions(paging_options) + |> page_transaction(paging_options) |> limit(^paging_options.page_size) end - defp handle_withdrawals_paging_options(query, nil), do: query + defp handle_token_transfer_paging_options(query, nil), do: query - defp handle_withdrawals_paging_options(query, paging_options) do + defp handle_token_transfer_paging_options(query, paging_options) do query - |> Withdrawal.page_withdrawals(paging_options) + |> TokenTransfer.page_token_transfer(paging_options) |> limit(^paging_options.page_size) end @@ -3198,15 +4422,15 @@ defmodule Explorer.Chain do defp handle_random_access_paging_options(query, paging_options) do query - |> (&if(paging_options |> Map.get(:page_number, 1) |> process_page_number() == 1, + |> (&if(paging_options |> Map.get(:page_number, 1) |> proccess_page_number() == 1, do: &1, - else: Transaction.page_transaction(&1, paging_options) + else: page_transaction(&1, paging_options) )).() |> handle_page(paging_options) end defp handle_page(query, paging_options) do - page_number = paging_options |> Map.get(:page_number, 1) |> process_page_number() + page_number = paging_options |> Map.get(:page_number, 1) |> proccess_page_number() page_size = Map.get(paging_options, :page_size, @default_page_size) cond do @@ -3225,14 +4449,14 @@ defmodule Explorer.Chain do end end - defp process_page_number(number) when number < 1, do: 1 + defp proccess_page_number(number) when number < 1, do: 1 - defp process_page_number(number), do: number + defp proccess_page_number(number), do: number defp page_in_bounds?(page_number, page_size), - do: page_size <= @limit_showing_transactions && @limit_showing_transactions - page_number * page_size >= 0 + do: page_size <= @limit_showing_transaсtions && @limit_showing_transaсtions - page_number * page_size >= 0 - def limit_showing_transactions, do: @limit_showing_transactions + def limit_shownig_transactions, do: @limit_showing_transaсtions defp join_association(query, [{association, nested_preload}], necessity) when is_atom(association) and is_atom(nested_preload) do @@ -3243,34 +4467,48 @@ defmodule Explorer.Chain do :required -> from(q in query, inner_join: a in assoc(q, ^association), - as: ^association, left_join: b in assoc(a, ^nested_preload), - as: ^nested_preload, preload: [{^association, {a, [{^nested_preload, b}]}}] ) end end - defp join_association(query, association, necessity) do + defp join_association(query, association, necessity) when is_atom(association) do case necessity do :optional -> preload(query, ^association) :required -> - from(q in query, inner_join: a in assoc(q, ^association), as: ^association, preload: [{^association, a}]) + from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) end end - @spec join_associations(atom() | Ecto.Query.t(), map) :: Ecto.Query.t() - @doc """ - Function to preload entities associated with selected in provided query items - """ - def join_associations(query, necessity_by_association) when is_map(necessity_by_association) do + defp join_associations(query, necessity_by_association) when is_map(necessity_by_association) do Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query -> join_association(acc_query, association, join) end) end + defp page_addresses(query, %PagingOptions{key: nil}), do: query + + defp page_addresses(query, %PagingOptions{key: {coin_balance, hash}}) do + from(address in query, + where: + (address.fetched_coin_balance == ^coin_balance and address.hash > ^hash) or + address.fetched_coin_balance < ^coin_balance + ) + end + + defp page_tokens(query, %PagingOptions{key: nil}), do: query + + defp page_tokens(query, %PagingOptions{key: {holder_count, token_name}}) do + from(token in query, + where: + (token.holder_count == ^holder_count and token.name > ^token_name) or + token.holder_count < ^holder_count + ) + end + defp page_blocks(query, %PagingOptions{key: nil}), do: query defp page_blocks(query, %PagingOptions{key: {block_number}}) do @@ -3283,131 +4521,104 @@ defmodule Explorer.Chain do where(query, [coin_balance], coin_balance.block_number < ^block_number) end - defp page_internal_transaction(_, _, _ \\ %{index_int_tx_desc_order: false}) + defp page_internal_transaction(query, %PagingOptions{key: nil}), do: query - defp page_internal_transaction(query, %PagingOptions{key: nil}, _), do: query - - defp page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}, %{ - index_int_tx_desc_order: desc - }) do - hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, desc) + defp page_internal_transaction(query, %PagingOptions{key: {block_number, transaction_index, index}}) do + where( + query, + [internal_transaction], + internal_transaction.block_number < ^block_number or + (internal_transaction.block_number == ^block_number and + internal_transaction.transaction_index < ^transaction_index) or + (internal_transaction.block_number == ^block_number and + internal_transaction.transaction_index == ^transaction_index and internal_transaction.index < ^index) + ) end - defp page_internal_transaction(query, %PagingOptions{key: {index}}, %{index_int_tx_desc_order: desc}) do - if desc do - where(query, [internal_transaction], internal_transaction.index < ^index) - else - where(query, [internal_transaction], internal_transaction.index > ^index) - end + defp page_internal_transaction(query, %PagingOptions{key: {index}}) do + where(query, [internal_transaction], internal_transaction.index > ^index) end - defp hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, false), - do: - where( - query, - [internal_transaction], - internal_transaction.block_number < ^block_number or - (internal_transaction.block_number == ^block_number and - internal_transaction.transaction_index < ^transaction_index) or - (internal_transaction.block_number == ^block_number and - internal_transaction.transaction_index == ^transaction_index and internal_transaction.index > ^index) - ) - - defp hardcoded_where_for_page_int_tx(query, block_number, transaction_index, index, true), - do: - where( - query, - [internal_transaction], - internal_transaction.block_number < ^block_number or - (internal_transaction.block_number == ^block_number and - internal_transaction.transaction_index < ^transaction_index) or - (internal_transaction.block_number == ^block_number and - internal_transaction.transaction_index == ^transaction_index and internal_transaction.index < ^index) - ) - defp page_logs(query, %PagingOptions{key: nil}), do: query defp page_logs(query, %PagingOptions{key: {index}}) do where(query, [log], log.index > ^index) end - defp page_logs(query, %PagingOptions{key: {block_number, log_index}}) do + defp page_pending_transaction(query, %PagingOptions{key: nil}), do: query + + defp page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do where( query, - [log], - log.block_number < ^block_number or (log.block_number == ^block_number and log.index < ^log_index) + [transaction], + transaction.inserted_at < ^inserted_at or (transaction.inserted_at == ^inserted_at and transaction.hash < ^hash) ) end - defp page_transaction_logs(query, %PagingOptions{key: nil}), do: query + defp page_transaction(query, %PagingOptions{key: nil}), do: query - defp page_transaction_logs(query, %PagingOptions{key: {index}}) do - where(query, [log], log.index > ^index) - end + defp page_transaction(query, %PagingOptions{is_pending_tx: true} = options), + do: page_pending_transaction(query, options) - defp page_transaction_logs(query, %PagingOptions{key: {_block_number, index}}) do - where(query, [log], log.index > ^index) + defp page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index > ^index) + ) end - defp page_block_transactions(query, %PagingOptions{key: nil}), do: query - - defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}, is_index_in_asc_order: true}) do - where(query, [transaction], transaction.index > ^index) + defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index < ^index) + ) end - defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}}) do + defp page_transaction(query, %PagingOptions{key: {index}}) do where(query, [transaction], transaction.index < ^index) end - def page_token_balances(query, %PagingOptions{key: nil}), do: query + defp page_search_results(query, %PagingOptions{key: nil}), do: query - def page_token_balances(query, %PagingOptions{key: {value, address_hash}}) do + # credo:disable-for-next-line + defp page_search_results(query, %PagingOptions{ + key: {_address_hash, _tx_hash, _block_hash, holder_count, name, inserted_at, item_type} + }) do where( query, - [tb], - tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash) + [item], + (item.holder_count < ^holder_count and item.type == ^item_type) or + (item.holder_count == ^holder_count and item.name > ^name and item.type == ^item_type) or + (item.holder_count == ^holder_count and item.name == ^name and item.inserted_at < ^inserted_at and + item.type == ^item_type) or + item.type != ^item_type ) end - def page_current_token_balances(query, keyword) when is_list(keyword), - do: page_current_token_balances(query, Keyword.get(keyword, :paging_options)) - - def page_current_token_balances(query, %PagingOptions{key: nil}), do: query - - def page_current_token_balances(query, %PagingOptions{key: {nil, value, id}}) do - fiat_balance = CurrentTokenBalance.fiat_value_query() - - condition = - dynamic( - [ctb, t], - is_nil(^fiat_balance) and - (ctb.value < ^value or - (ctb.value == ^value and ctb.id < ^id)) - ) + def page_token_balances(query, %PagingOptions{key: nil}), do: query + def page_token_balances(query, %PagingOptions{key: {value, address_hash}}) do where( query, - [ctb, t], - ^condition + [tb], + tb.value < ^value or (tb.value == ^value and tb.address_hash < ^address_hash) ) end - def page_current_token_balances(query, %PagingOptions{key: {fiat_value, value, id}}) do - fiat_balance = CurrentTokenBalance.fiat_value_query() + def page_current_token_balances(query, %PagingOptions{key: nil}), do: query - condition = - dynamic( - [ctb, t], - ^fiat_balance < ^fiat_value or is_nil(^fiat_balance) or - (^fiat_balance == ^fiat_value and - (ctb.value < ^value or - (ctb.value == ^value and ctb.id < ^id))) - ) + def page_current_token_balances(query, paging_options: %PagingOptions{key: nil}), do: query + def page_current_token_balances(query, paging_options: %PagingOptions{key: {name, type, value}}) do where( query, - [ctb, t], - ^condition + [ctb, bt, t], + ctb.value < ^value or (ctb.value == ^value and t.type < ^type) or + (ctb.value == ^value and t.type == ^type and t.name < ^name) ) end @@ -3440,7 +4651,7 @@ defmodule Explorer.Chain do end @doc """ - The current total number of coins minted minus verifiably burnt coins. + The current total number of coins minted minus verifiably burned coins. """ @spec total_supply :: non_neg_integer() | nil def total_supply do @@ -3469,12 +4680,10 @@ defmodule Explorer.Chain do """ @spec stream_uncataloged_token_contract_address_hashes( initial :: accumulator, - reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator), - limited? :: boolean() + reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_uncataloged_token_contract_address_hashes(initial, reducer, limited? \\ false) - when is_function(reducer, 2) do + def stream_uncataloged_token_contract_address_hashes(initial, reducer) when is_function(reducer, 2) do query = from( token in Token, @@ -3482,51 +4691,42 @@ defmodule Explorer.Chain do select: token.contract_address_hash ) - query - |> add_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + Repo.stream_reduce(query, initial, reducer) end - @doc """ - Finds all token instances where metadata never tried to fetch - """ - @spec stream_token_instances_with_unfetched_metadata( + @spec stream_unfetched_token_instances( initial :: accumulator, reducer :: (entry :: map(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_token_instances_with_unfetched_metadata(initial, reducer) when is_function(reducer, 2) do - Instance - |> where([instance], is_nil(instance.error) and is_nil(instance.metadata)) - |> select([instance], %{ - contract_address_hash: instance.token_contract_address_hash, - token_id: instance.token_id - }) - |> Repo.stream_reduce(initial, reducer) - end + def stream_unfetched_token_instances(initial, reducer) when is_function(reducer, 2) do + nft_tokens = + from( + token in Token, + where: token.type == ^"ERC-721" or token.type == ^"ERC-1155", + select: token.contract_address_hash + ) - @spec stream_token_instances_with_error( - initial :: accumulator, - reducer :: (entry :: map(), accumulator -> accumulator), - limited? :: boolean() - ) :: {:ok, accumulator} - when accumulator: term() - def stream_token_instances_with_error(initial, reducer, limited? \\ false) when is_function(reducer, 2) do - # likely to get valid metadata - high_priority = ["request error: 429", ":checkout_timeout", ":econnrefused", ":timeout"] - # almost impossible to get valid metadata - negative_priority = ["VM execution error", "no uri", "invalid json"] - - Instance - |> where([instance], not is_nil(instance.error)) - |> select([instance], %{ - contract_address_hash: instance.token_contract_address_hash, - token_id: instance.token_id, - updated_at: instance.updated_at - }) - |> order_by([instance], desc: instance.error in ^high_priority, asc: instance.error in ^negative_priority) - |> add_fetcher_limit(limited?) - |> Repo.stream_reduce(initial, reducer) + query = + from( + token_transfer in TokenTransfer, + inner_join: token in subquery(nft_tokens), + on: token.contract_address_hash == token_transfer.token_contract_address_hash, + left_join: instance in Instance, + on: + token_transfer.token_id == instance.token_id and + token_transfer.token_contract_address_hash == instance.token_contract_address_hash, + where: is_nil(instance.token_id) and not is_nil(token_transfer.token_id), + select: %{contract_address_hash: token_transfer.token_contract_address_hash, token_id: token_transfer.token_id} + ) + + distinct_query = + from( + q in subquery(query), + distinct: [q.contract_address_hash, q.token_id] + ) + + Repo.stream_reduce(distinct_query, initial, reducer) end @doc """ @@ -3534,20 +4734,322 @@ defmodule Explorer.Chain do """ @spec stream_cataloged_token_contract_address_hashes( initial :: accumulator, - reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator), - some_time_ago_updated :: integer(), - limited? :: boolean() + reducer :: (entry :: Hash.Address.t(), accumulator -> accumulator) ) :: {:ok, accumulator} when accumulator: term() - def stream_cataloged_token_contract_address_hashes(initial, reducer, some_time_ago_updated \\ 2880, limited? \\ false) + def stream_cataloged_token_contract_address_hashes(initial, reducer, some_time_ago_updated \\ 2880) when is_function(reducer, 2) do some_time_ago_updated |> Token.cataloged_tokens() - |> add_fetcher_limit(limited?) |> order_by(asc: :updated_at) |> Repo.stream_reduce(initial, reducer) end + @doc """ + Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an + associated `t:TokenTransfer.t/0` record. + """ + def uncataloged_token_transfer_block_numbers do + query = + from(l in Log, + as: :log, + where: l.first_topic == unquote(TokenTransfer.constant()), + where: + not exists( + from(tf in TokenTransfer, + where: tf.transaction_hash == parent_as(:log).transaction_hash, + where: tf.log_index == parent_as(:log).index + ) + ), + select: l.block_number, + distinct: l.block_number + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end + + @doc """ + Returns a list of token addresses `t:Address.t/0`s that don't have an + bridged property revealed. + """ + def unprocessed_token_addresses_to_reveal_bridged_tokens do + query = + from(t in Token, + where: is_nil(t.bridged), + select: t.contract_address_hash + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end + + @doc """ + Processes AMB tokens from mediators addresses provided + """ + def process_amb_tokens do + amb_bridge_mediators_var = Application.get_env(:block_scout_web, :amb_bridge_mediators) + amb_bridge_mediators = (amb_bridge_mediators_var && String.split(amb_bridge_mediators_var, ",")) || [] + + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + foreign_json_rpc = Application.get_env(:block_scout_web, :foreign_json_rpc) + + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + amb_bridge_mediators + |> Enum.each(fn amb_bridge_mediator_hash -> + with {:ok, bridge_contract_hash_resp} <- + get_bridge_contract_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + bridge_contract_hash <- decode_contract_address_hash_response(bridge_contract_hash_resp), + {:ok, destination_chain_id_resp} <- get_destination_chain_id(bridge_contract_hash, json_rpc_named_arguments), + foreign_chain_id <- decode_contract_integer_response(destination_chain_id_resp), + {:ok, home_token_contract_hash_resp} <- + get_erc677_token_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + home_token_contract_hash_string <- decode_contract_address_hash_response(home_token_contract_hash_resp), + {:ok, home_token_contract_hash} <- Chain.string_to_address_hash(home_token_contract_hash_string), + {:ok, foreign_mediator_contract_hash_resp} <- + get_foreign_mediator_contract_hash(amb_bridge_mediator_hash, json_rpc_named_arguments), + foreign_mediator_contract_hash <- decode_contract_address_hash_response(foreign_mediator_contract_hash_resp), + {:ok, foreign_token_contract_hash_resp} <- + get_erc677_token_hash(foreign_mediator_contract_hash, eth_call_foreign_json_rpc_named_arguments), + foreign_token_contract_hash_string <- + decode_contract_address_hash_response(foreign_token_contract_hash_resp), + {:ok, foreign_token_contract_hash} <- Chain.string_to_address_hash(foreign_token_contract_hash_string) do + insert_bridged_token_metadata(home_token_contract_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_contract_hash, + custom_metadata: nil, + custom_cap: nil, + lp_token: nil, + type: "amb" + }) + + set_token_bridged_status(home_token_contract_hash, true) + else + result -> + Logger.debug([ + "failed to fetch metadata for token bridged with AMB mediator #{amb_bridge_mediator_hash}", + inspect(result) + ]) + end + end) + + :ok + end + + @doc """ + Fetches bridged tokens metadata from OmniBridge. + """ + def fetch_omni_bridged_tokens_metadata(token_addresses) do + Enum.each(token_addresses, fn token_address_hash -> + created_from_int_tx_success_query = + from( + it in InternalTransaction, + inner_join: t in assoc(it, :transaction), + where: it.created_contract_address_hash == ^token_address_hash, + where: t.status == ^1 + ) + + created_from_int_tx_success = + created_from_int_tx_success_query + |> Repo.one() + + created_from_tx_query = + from( + t in Transaction, + where: t.created_contract_address_hash == ^token_address_hash + ) + + created_from_tx = + created_from_tx_query + |> Repo.all() + |> Enum.count() > 0 + + created_from_int_tx_query = + from( + it in InternalTransaction, + where: it.created_contract_address_hash == ^token_address_hash + ) + + created_from_int_tx = + created_from_int_tx_query + |> Repo.all() + |> Enum.count() > 0 + + cond do + created_from_tx -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && !created_from_int_tx_success -> + set_token_bridged_status(token_address_hash, false) + + created_from_int_tx && created_from_int_tx_success -> + proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) + + true -> + :ok + end + end) + + :ok + end + + defp proceed_with_set_omni_status(token_address_hash, created_from_int_tx_success) do + {:ok, eth_omni_status} = + extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_int_tx_success, + :eth_omni_bridge_mediator + ) + + {:ok, bsc_omni_status} = + if eth_omni_status do + {:ok, false} + else + extract_omni_bridged_token_metadata_wrapper( + token_address_hash, + created_from_int_tx_success, + :bsc_omni_bridge_mediator + ) + end + + if !eth_omni_status && !bsc_omni_status do + set_token_bridged_status(token_address_hash, false) + end + end + + defp extract_omni_bridged_token_metadata_wrapper(token_address_hash, created_from_int_tx_success, mediator) do + omni_bridge_mediator = Application.get_env(:block_scout_web, mediator) + %{transaction_hash: transaction_hash} = created_from_int_tx_success + + if omni_bridge_mediator && omni_bridge_mediator !== "" do + {:ok, omni_bridge_mediator_hash} = Chain.string_to_address_hash(omni_bridge_mediator) + + created_by_amb_mediator_query = + from( + it in InternalTransaction, + where: it.transaction_hash == ^transaction_hash, + where: it.to_address_hash == ^omni_bridge_mediator_hash + ) + + created_by_amb_mediator = + created_by_amb_mediator_query + |> Repo.all() + + if Enum.count(created_by_amb_mediator) > 0 do + extract_omni_bridged_token_metadata( + token_address_hash, + omni_bridge_mediator, + omni_bridge_mediator_hash + ) + + {:ok, true} + else + {:ok, false} + end + else + {:ok, false} + end + end + + defp extract_omni_bridged_token_metadata(token_address_hash, omni_bridge_mediator, omni_bridge_mediator_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + with {:ok, _} <- + get_token_interfaces_version_signature(token_address_hash, json_rpc_named_arguments), + {:ok, foreign_token_address_abi_encoded} <- + get_foreign_token_address(omni_bridge_mediator, token_address_hash, json_rpc_named_arguments), + {:ok, bridge_contract_hash_resp} <- + get_bridge_contract_hash(omni_bridge_mediator_hash, json_rpc_named_arguments) do + foreign_token_address_hash_string = decode_contract_address_hash_response(foreign_token_address_abi_encoded) + {:ok, foreign_token_address_hash} = Chain.string_to_address_hash(foreign_token_address_hash_string) + + multi_token_bridge_hash_string = decode_contract_address_hash_response(bridge_contract_hash_resp) + + {:ok, foreign_chain_id_abi_encoded} = + get_destination_chain_id(multi_token_bridge_hash_string, json_rpc_named_arguments) + + foreign_chain_id = decode_contract_integer_response(foreign_chain_id_abi_encoded) + + foreign_json_rpc = Application.get_env(:block_scout_web, :foreign_json_rpc) + + custom_metadata = + get_bridged_token_custom_metadata(foreign_token_address_hash, json_rpc_named_arguments, foreign_json_rpc) + + insert_bridged_token_metadata(token_address_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: nil, + lp_token: nil, + type: "omni" + }) + + set_token_bridged_status(token_address_hash, true) + end + end + + defp get_bridge_contract_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from bridgeContract() + bridge_contract_signature = "0xcd596583" + + perform_eth_call_request(bridge_contract_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_erc677_token_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from erc677token() + erc677_token_signature = "0x18d8f9c9" + + perform_eth_call_request(erc677_token_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_foreign_mediator_contract_hash(mediator_hash, json_rpc_named_arguments) do + # keccak 256 from mediatorContractOnOtherSide() + mediator_contract_on_other_side_signature = "0x871c0760" + + perform_eth_call_request(mediator_contract_on_other_side_signature, mediator_hash, json_rpc_named_arguments) + end + + defp get_destination_chain_id(bridge_contract_hash, json_rpc_named_arguments) do + # keccak 256 from destinationChainId() + destination_chain_id_signature = "0xb0750611" + + perform_eth_call_request(destination_chain_id_signature, bridge_contract_hash, json_rpc_named_arguments) + end + + defp get_token_interfaces_version_signature(token_address_hash, json_rpc_named_arguments) do + # keccak 256 from getTokenInterfacesVersion() + get_token_interfaces_version_signature = "0x859ba28c" + + perform_eth_call_request(get_token_interfaces_version_signature, token_address_hash, json_rpc_named_arguments) + end + + defp get_foreign_token_address(omni_bridge_mediator, token_address_hash, json_rpc_named_arguments) do + # keccak 256 from foreignTokenAddress(address) + foreign_token_address_signature = "0x47ac7d6a" + + token_address_hash_abi_encoded = + [token_address_hash.bytes] + |> TypeEncoder.encode([:address]) + |> Base.encode16() + + foreign_token_address_method = foreign_token_address_signature <> token_address_hash_abi_encoded + + perform_eth_call_request(foreign_token_address_method, omni_bridge_mediator, json_rpc_named_arguments) + end + + defp perform_eth_call_request(method, destination, json_rpc_named_arguments) + when not is_nil(json_rpc_named_arguments) do + method + |> Contract.eth_call_request(destination, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments) + end + + defp perform_eth_call_request(_method, _destination, json_rpc_named_arguments) + when is_nil(json_rpc_named_arguments) do + :error + end + def decode_contract_address_hash_response(resp) do case resp do "0x000000000000000000000000" <> address -> @@ -3569,6 +5071,510 @@ defmodule Explorer.Chain do end end + defp set_token_bridged_status(token_address_hash, status) do + case Repo.get(Token, token_address_hash) do + %Explorer.Chain.Token{bridged: bridged} = target_token -> + if !bridged do + token = Changeset.change(target_token, bridged: status) + + Repo.update(token) + end + + _ -> + :ok + end + end + + defp insert_bridged_token_metadata(token_address_hash, %{ + foreign_chain_id: foreign_chain_id, + foreign_token_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: custom_cap, + lp_token: lp_token, + type: type + }) do + target_token = Repo.get(Token, token_address_hash) + + if target_token do + {:ok, _} = + Repo.insert( + %BridgedToken{ + home_token_contract_address_hash: token_address_hash, + foreign_chain_id: foreign_chain_id, + foreign_token_contract_address_hash: foreign_token_address_hash, + custom_metadata: custom_metadata, + custom_cap: custom_cap, + lp_token: lp_token, + type: type + }, + on_conflict: :nothing + ) + end + end + + # Fetches custom metadata for bridged tokens from the node. + # Currently, gets Balancer token composite tokens with their weights + # from foreign chain + defp get_bridged_token_custom_metadata(foreign_token_address_hash, json_rpc_named_arguments, foreign_json_rpc) + when not is_nil(foreign_json_rpc) and foreign_json_rpc !== "" do + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + balancer_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) || + sushiswap_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) + end + + defp get_bridged_token_custom_metadata(_foreign_token_address_hash, _json_rpc_named_arguments, foreign_json_rpc) + when is_nil(foreign_json_rpc) do + nil + end + + defp get_bridged_token_custom_metadata(_foreign_token_address_hash, _json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc == "" do + nil + end + + defp balancer_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) do + # keccak 256 from getCurrentTokens() + get_current_tokens_signature = "0xcc77828d" + + case get_current_tokens_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + {:ok, "0x"} -> + nil + + {:ok, "0x" <> balancer_current_tokens_encoded} -> + [balancer_current_tokens] = + try do + balancer_current_tokens_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([{:array, :address}]) + rescue + _ -> [] + end + + bridged_token_custom_metadata = + parse_bridged_token_custom_metadata( + balancer_current_tokens, + eth_call_foreign_json_rpc_named_arguments, + foreign_token_address_hash + ) + + if is_map(bridged_token_custom_metadata) do + tokens = Map.get(bridged_token_custom_metadata, :tokens) + weights = Map.get(bridged_token_custom_metadata, :weights) + + if tokens == "" do + nil + else + if weights !== "", do: "#{tokens} #{weights}", else: tokens + end + else + nil + end + + _ -> + nil + end + end + + defp sushiswap_custom_metadata(foreign_token_address_hash, eth_call_foreign_json_rpc_named_arguments) do + # keccak 256 from token0() + token0_signature = "0x0dfe1681" + + # keccak 256 from token1() + token1_signature = "0xd21220a7" + + # keccak 256 from name() + name_signature = "0x06fdde03" + + # keccak 256 from symbol() + symbol_signature = "0x95d89b41" + + with {:ok, "0x" <> token0_encoded} <- + token0_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_encoded} <- + token1_signature + |> Contract.eth_call_request(foreign_token_address_hash, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token0_hash = parse_contract_response(token0_encoded, :address) + token1_hash = parse_contract_response(token1_encoded, :address) + + if token0_hash && token1_hash do + token0_hash_str = "0x" <> Base.encode16(token0_hash, case: :lower) + token1_hash_str = "0x" <> Base.encode16(token1_hash, case: :lower) + + with {:ok, "0x" <> token0_name_encoded} <- + name_signature + |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_name_encoded} <- + name_signature + |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token0_symbol_encoded} <- + symbol_signature + |> Contract.eth_call_request(token0_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> token1_symbol_encoded} <- + symbol_signature + |> Contract.eth_call_request(token1_hash_str, 2, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token0_name = parse_contract_response(token0_name_encoded, :string, {:bytes, 32}) + token1_name = parse_contract_response(token1_name_encoded, :string, {:bytes, 32}) + token0_symbol = parse_contract_response(token0_symbol_encoded, :string, {:bytes, 32}) + token1_symbol = parse_contract_response(token1_symbol_encoded, :string, {:bytes, 32}) + + "#{token0_name}/#{token1_name} (#{token0_symbol}/#{token1_symbol})" + else + _ -> + nil + end + else + nil + end + else + _ -> + nil + end + end + + def calc_lp_tokens_total_liqudity do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + foreign_json_rpc = Application.get_env(:block_scout_web, :foreign_json_rpc) + bridged_mainnet_tokens_list = BridgedToken.get_unprocessed_mainnet_lp_tokens_list() + + Enum.each(bridged_mainnet_tokens_list, fn bridged_token -> + case calc_sushiswap_lp_tokens_cap( + bridged_token.home_token_contract_address_hash, + bridged_token.foreign_token_contract_address_hash, + json_rpc_named_arguments, + foreign_json_rpc + ) do + {:ok, new_custom_cap} -> + bridged_token + |> Changeset.change(%{custom_cap: new_custom_cap, lp_token: true}) + |> Repo.update() + + {:error, :not_lp_token} -> + bridged_token + |> Changeset.change(%{lp_token: false}) + |> Repo.update() + end + end) + + Logger.debug(fn -> "Total liqudity fetched for LP tokens" end) + end + + defp calc_sushiswap_lp_tokens_cap( + home_token_contract_address_hash, + foreign_token_address_hash, + json_rpc_named_arguments, + foreign_json_rpc + ) do + eth_call_foreign_json_rpc_named_arguments = + compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + + # keccak 256 from getReserves() + get_reserves_signature = "0x0902f1ac" + + # keccak 256 from token0() + token0_signature = "0x0dfe1681" + + # keccak 256 from token1() + token1_signature = "0xd21220a7" + + # keccak 256 from totalSupply() + total_supply_signature = "0x18160ddd" + + with {:ok, "0x" <> get_reserves_encoded} <- + get_reserves_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> home_token_total_supply_encoded} <- + total_supply_signature + |> Contract.eth_call_request(home_token_contract_address_hash, 1, nil, nil) + |> json_rpc(json_rpc_named_arguments), + [reserve0, reserve1, _] <- + parse_contract_response(get_reserves_encoded, [{:uint, 112}, {:uint, 112}, {:uint, 32}]), + {:ok, token0_cap_usd} <- + get_lp_token_cap( + home_token_total_supply_encoded, + token0_signature, + reserve0, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ), + {:ok, token1_cap_usd} <- + get_lp_token_cap( + home_token_total_supply_encoded, + token1_signature, + reserve1, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ) do + total_lp_cap = Decimal.add(token0_cap_usd, token1_cap_usd) + {:ok, total_lp_cap} + else + _ -> + {:error, :not_lp_token} + end + end + + defp get_lp_token_cap( + home_token_total_supply_encoded, + token_signature, + reserve, + foreign_token_address_hash, + eth_call_foreign_json_rpc_named_arguments + ) do + # keccak 256 from decimals() + decimals_signature = "0x313ce567" + + # keccak 256 from totalSupply() + total_supply_signature = "0x18160ddd" + + home_token_total_supply = + home_token_total_supply_encoded + |> parse_contract_response({:uint, 256}) + |> Decimal.new() + + with {:ok, "0x" <> token_encoded} <- + token_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token_hash = parse_contract_response(token_encoded, :address) + + if token_hash do + token_hash_str = "0x" <> Base.encode16(token_hash, case: :lower) + + with {:ok, "0x" <> token_decimals_encoded} <- + decimals_signature + |> Contract.eth_call_request(token_hash_str, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments), + {:ok, "0x" <> foreign_token_total_supply_encoded} <- + total_supply_signature + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + token_decimals = parse_contract_response(token_decimals_encoded, {:uint, 256}) + + foreign_token_total_supply = + foreign_token_total_supply_encoded + |> parse_contract_response({:uint, 256}) + |> Decimal.new() + + token_decimals_divider = + 10 + |> :math.pow(token_decimals) + |> Decimal.from_float() + + token_cap = + reserve + |> Decimal.div(foreign_token_total_supply) + |> Decimal.mult(home_token_total_supply) + |> Decimal.div(token_decimals_divider) + + token_price = TokenExchangeRate.fetch_token_exchange_rate_by_address(token_hash_str) + + token_cap_usd = + if token_price do + token_price + |> Decimal.mult(token_cap) + else + 0 + end + + {:ok, token_cap_usd} + end + end + end + end + + defp parse_contract_response(abi_encoded_value, types) when is_list(types) do + values = + try do + abi_encoded_value + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw(types) + rescue + _ -> [nil] + end + + values + end + + defp parse_contract_response(abi_encoded_value, type, emergency_type \\ nil) do + [value] = + try do + [res] = decode_contract_response(abi_encoded_value, type) + + [convert_binary_to_string(res, type)] + rescue + _ -> + if emergency_type do + try do + [res] = decode_contract_response(abi_encoded_value, emergency_type) + + [convert_binary_to_string(res, emergency_type)] + rescue + _ -> + [nil] + end + else + [nil] + end + end + + value + end + + defp decode_contract_response(abi_encoded_value, type) do + abi_encoded_value + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([type]) + end + + defp convert_binary_to_string(binary, type) do + case type do + {:bytes, _} -> + ContractState.binary_to_string(binary) + + _ -> + binary + end + end + + defp compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc != "" do + {_, eth_call_foreign_json_rpc_named_arguments} = + Keyword.get_and_update(json_rpc_named_arguments, :transport_options, fn transport_options -> + {_, updated_transport_options} = + update_transport_options_set_foreign_json_rpc(transport_options, foreign_json_rpc) + + {transport_options, updated_transport_options} + end) + + eth_call_foreign_json_rpc_named_arguments + end + + defp compose_foreign_json_rpc_named_arguments(_json_rpc_named_arguments, foreign_json_rpc) + when foreign_json_rpc == "" do + nil + end + + defp compose_foreign_json_rpc_named_arguments(json_rpc_named_arguments, _foreign_json_rpc) + when is_nil(json_rpc_named_arguments) do + nil + end + + defp update_transport_options_set_foreign_json_rpc(transport_options, foreign_json_rpc) do + Keyword.get_and_update(transport_options, :method_to_url, fn method_to_url -> + {_, updated_method_to_url} = + Keyword.get_and_update(method_to_url, :eth_call, fn eth_call -> + {eth_call, foreign_json_rpc} + end) + + {method_to_url, updated_method_to_url} + end) + end + + defp parse_bridged_token_custom_metadata( + balancer_current_tokens, + eth_call_foreign_json_rpc_named_arguments, + foreign_token_address_hash + ) do + balancer_current_tokens + |> Enum.reduce(%{:tokens => "", :weights => ""}, fn balancer_token_bytes, balancer_tokens_weights -> + balancer_token_hash_without_0x = + balancer_token_bytes + |> Base.encode16(case: :lower) + + balancer_token_hash = "0x" <> balancer_token_hash_without_0x + + # 95d89b41 = keccak256(symbol()) + symbol_signature = "0x95d89b41" + + case symbol_signature + |> Contract.eth_call_request(balancer_token_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) do + {:ok, "0x" <> symbol_encoded} -> + [symbol] = + symbol_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([:string]) + + # f1b8a9b7 = keccak256(getNormalizedWeight(address)) + get_normalized_weight_signature = "0xf1b8a9b7" + + get_normalized_weight_arg_abi_encoded = + [balancer_token_bytes] + |> TypeEncoder.encode([:address]) + |> Base.encode16(case: :lower) + + get_normalized_weight_abi_encoded = get_normalized_weight_signature <> get_normalized_weight_arg_abi_encoded + + get_normalized_weight_resp = + get_normalized_weight_abi_encoded + |> Contract.eth_call_request(foreign_token_address_hash, 1, nil, nil) + |> json_rpc(eth_call_foreign_json_rpc_named_arguments) + + parse_balancer_weights(get_normalized_weight_resp, balancer_tokens_weights, symbol) + + _ -> + nil + end + end) + end + + defp parse_balancer_weights(get_normalized_weight_resp, balancer_tokens_weights, symbol) do + case get_normalized_weight_resp do + {:ok, "0x" <> normalized_weight_encoded} -> + [normalized_weight] = + try do + normalized_weight_encoded + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw([{:uint, 256}]) + rescue + _ -> + [] + end + + normalized_weight_to_100_perc = calc_normalized_weight_to_100_perc(normalized_weight) + + normalized_weight_in_perc = + normalized_weight_to_100_perc + |> div(1_000_000_000_000_000_000) + + current_tokens = Map.get(balancer_tokens_weights, :tokens) + current_weights = Map.get(balancer_tokens_weights, :weights) + + tokens_value = combine_tokens_value(current_tokens, symbol) + weights_value = combine_weights_value(current_weights, normalized_weight_in_perc) + + %{:tokens => tokens_value, :weights => weights_value} + + _ -> + nil + end + end + + defp calc_normalized_weight_to_100_perc(normalized_weight) do + if normalized_weight, do: 100 * normalized_weight, else: 0 + end + + defp combine_tokens_value(current_tokens, symbol) do + if current_tokens == "", do: symbol, else: current_tokens <> "/" <> symbol + end + + defp combine_weights_value(current_weights, normalized_weight_in_perc) do + if current_weights == "", + do: "#{normalized_weight_in_perc}", + else: current_weights <> "/" <> "#{normalized_weight_in_perc}" + end + @doc """ Fetches a `t:Token.t/0` by an address hash. @@ -3578,41 +5584,49 @@ defmodule Explorer.Chain do `:required`, and the `t:Token.t/0` has no associated record for that association, then the `t:Token.t/0` will not be included in the list. """ - @spec token_from_address_hash(Hash.Address.t() | String.t(), [necessity_by_association_option | api?]) :: + @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option]) :: {:ok, Token.t()} | {:error, :not_found} - def token_from_address_hash(hash, options \\ []) do + def token_from_address_hash( + %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, + options \\ [] + ) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) query = from( t in Token, + left_join: bt in BridgedToken, + on: t.contract_address_hash == bt.home_token_contract_address_hash, where: t.contract_address_hash == ^hash, - select: t + select: [t, bt] ) query |> join_associations(necessity_by_association) |> preload(:contract_address) - |> select_repo(options).one() + |> Repo.one() |> case do nil -> {:error, :not_found} - %Token{} = token -> - {:ok, token} - end - end + [%Token{} = token, %BridgedToken{} = bridged_token] -> + foreign_token_contract_address_hash = Map.get(bridged_token, :foreign_token_contract_address_hash) + foreign_chain_id = Map.get(bridged_token, :foreign_chain_id) + custom_metadata = Map.get(bridged_token, :custom_metadata) + custom_cap = Map.get(bridged_token, :custom_cap) - @spec token_from_address_hash_exists?(Hash.Address.t(), [api?]) :: boolean() - def token_from_address_hash_exists?(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, options) do - query = - from( - t in Token, - where: t.contract_address_hash == ^hash, - select: t - ) + extended_token = + token + |> Map.put(:foreign_token_contract_address_hash, foreign_token_contract_address_hash) + |> Map.put(:foreign_chain_id, foreign_chain_id) + |> Map.put(:custom_metadata, custom_metadata) + |> Map.put(:custom_cap, custom_cap) + + {:ok, extended_token} - select_repo(options).exists?(query) + [%Token{} = token, nil] -> + {:ok, token} + end end @spec fetch_token_transfers_from_token_hash(Hash.t(), [paging_options]) :: [] @@ -3620,7 +5634,7 @@ defmodule Explorer.Chain do TokenTransfer.fetch_token_transfers_from_token_hash(token_address_hash, options) end - @spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [paging_options]) :: [] + @spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), binary(), [paging_options]) :: [] def fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options \\ []) do TokenTransfer.fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) end @@ -3630,9 +5644,9 @@ defmodule Explorer.Chain do TokenTransfer.count_token_transfers_from_token_hash(token_address_hash) end - @spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [api?]) :: non_neg_integer() - def count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options \\ []) do - TokenTransfer.count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) + @spec count_token_transfers_from_token_hash_and_token_id(Hash.t(), binary()) :: non_neg_integer() + def count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id) do + TokenTransfer.count_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id) end @spec transaction_has_token_transfers?(Hash.t()) :: boolean() @@ -3642,6 +5656,13 @@ defmodule Explorer.Chain do Repo.exists?(query) end + @spec address_has_rewards?(Address.t()) :: boolean() + def address_has_rewards?(address_hash) do + query = from(r in Reward, where: r.address_hash == ^address_hash) + + Repo.exists?(query) + end + @spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: [] def address_tokens_with_balance(address_hash, paging_options \\ []) do address_hash @@ -3671,7 +5692,7 @@ defmodule Explorer.Chain do ) # Enforce Transaction ShareLocks order (see docs: sharelocks.md) |> order_by(asc: :hash) - |> lock("FOR NO KEY UPDATE") + |> lock("FOR UPDATE") hashes = Enum.map(transactions, & &1.hash) @@ -3716,7 +5737,7 @@ defmodule Explorer.Chain do end) # Enforce Transaction ShareLocks order (see docs: sharelocks.md) |> order_by(asc: :hash) - |> lock("FOR NO KEY UPDATE") + |> lock("FOR UPDATE") Repo.update_all( from(t in Transaction, join: s in subquery(query), on: t.hash == s.hash), @@ -3726,38 +5747,16 @@ defmodule Explorer.Chain do end end - @doc """ - Expects map of change params. Inserts using on_conflict: `token_instance_metadata_on_conflict/0` - !!! Supposed to be used ONLY for import of `metadata` or `error`. - """ @spec upsert_token_instance(map()) :: {:ok, Instance.t()} | {:error, Ecto.Changeset.t()} def upsert_token_instance(params) do changeset = Instance.changeset(%Instance{}, params) Repo.insert(changeset, - on_conflict: token_instance_metadata_on_conflict(), + on_conflict: :replace_all, conflict_target: [:token_id, :token_contract_address_hash] ) end - defp token_instance_metadata_on_conflict do - from( - token_instance in Instance, - update: [ - set: [ - metadata: fragment("EXCLUDED.metadata"), - error: fragment("EXCLUDED.error"), - owner_updated_at_block: token_instance.owner_updated_at_block, - owner_updated_at_log_index: token_instance.owner_updated_at_log_index, - owner_address_hash: token_instance.owner_address_hash, - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_instance.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_instance.updated_at) - ] - ], - where: is_nil(token_instance.metadata) - ) - end - @doc """ Update a new `t:Token.t/0` record. @@ -3766,7 +5765,7 @@ defmodule Explorer.Chain do """ @spec update_token(Token.t(), map()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} def update_token(%Token{contract_address_hash: address_hash} = token, params \\ %{}) do - token_changeset = Token.changeset(token, Map.put(params, :updated_at, DateTime.utc_now())) + token_changeset = Token.changeset(token, params) address_name_changeset = Address.Name.changeset(%Address.Name{}, Map.put(params, :address_hash, address_hash)) stale_error_field = :contract_address_hash @@ -3792,7 +5791,7 @@ defmodule Explorer.Chain do ) |> Multi.run(:token, fn repo, _ -> with {:error, %Changeset{errors: [{^stale_error_field, {^stale_error_message, [_]}}]}} <- - repo.update(token_changeset, token_opts) do + repo.insert(token_changeset, token_opts) do # the original token passed into `update_token/2` as stale error means it is unchanged {:ok, token} end @@ -3808,63 +5807,49 @@ defmodule Explorer.Chain do end end - @spec fetch_last_token_balances_include_unfetched(Hash.Address.t(), [api?]) :: [] - def fetch_last_token_balances_include_unfetched(address_hash, options \\ []) do - address_hash - |> CurrentTokenBalance.last_token_balances_include_unfetched() - |> select_repo(options).all() - end - - @spec fetch_last_token_balances(Hash.Address.t(), [api?]) :: [] - def fetch_last_token_balances(address_hash, options \\ []) do + @spec fetch_last_token_balances(Hash.Address.t()) :: [] + def fetch_last_token_balances(address_hash) do address_hash |> CurrentTokenBalance.last_token_balances() - |> select_repo(options).all() + |> Repo.all() end - @spec fetch_paginated_last_token_balances(Hash.Address.t(), [paging_options]) :: [] - def fetch_paginated_last_token_balances(address_hash, options) do - filter = Keyword.get(options, :token_type) - options = Keyword.delete(options, :token_type) - + @spec fetch_last_token_balances(Hash.Address.t(), [paging_options]) :: [] + def fetch_last_token_balances(address_hash, paging_options) do address_hash - |> CurrentTokenBalance.last_token_balances(options, filter) - |> page_current_token_balances(options) - |> select_repo(options).all() + |> CurrentTokenBalance.last_token_balances(paging_options) + |> page_current_token_balances(paging_options) + |> Repo.all() end - @spec erc721_or_erc1155_token_instance_from_token_id_and_token_address(non_neg_integer(), Hash.Address.t(), [api?]) :: - {:ok, Instance.t()} | {:error, :not_found} - def erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, token_contract_address, options \\ []) do - query = Instance.token_instance_query(token_id, token_contract_address) + @spec erc721_token_instance_from_token_id_and_token_address(binary(), Hash.Address.t()) :: + {:ok, TokenTransfer.t()} | {:error, :not_found} + def erc721_token_instance_from_token_id_and_token_address(token_id, token_contract_address) do + query = + from(tt in TokenTransfer, + left_join: instance in Instance, + on: tt.token_contract_address_hash == instance.token_contract_address_hash and tt.token_id == instance.token_id, + where: tt.token_contract_address_hash == ^token_contract_address and tt.token_id == ^token_id, + limit: 1, + select: %{tt | instance: instance} + ) - case select_repo(options).one(query) do + case Repo.one(query) do nil -> {:error, :not_found} token_instance -> {:ok, token_instance} end end - @spec token_instance_exists?(non_neg_integer, Hash.Address.t(), [api?]) :: boolean - def token_instance_exists?(token_id, token_contract_address, options \\ []) do - query = Instance.token_instance_query(token_id, token_contract_address) - - select_repo(options).exists?(query) - end - - @spec token_instance_with_unfetched_metadata?(non_neg_integer, Hash.Address.t(), [api?]) :: boolean - def token_instance_with_unfetched_metadata?(token_id, token_contract_address, options \\ []) do - Instance - |> where([instance], is_nil(instance.error) and is_nil(instance.metadata)) - |> where( - [instance], - instance.token_id == ^token_id and instance.token_contract_address_hash == ^token_contract_address - ) - |> select_repo(options).exists?() - end + defp fetch_coin_balances(address_hash, paging_options) do + address = Repo.get_by(Address, hash: address_hash) - defp fetch_coin_balances(address, paging_options) do - address.hash - |> CoinBalance.fetch_coin_balances(paging_options) + if contract?(address) do + address_hash + |> CoinBalance.fetch_coin_balances(paging_options) + else + address_hash + |> CoinBalance.fetch_coin_balances_with_txs(paging_options) + end end @spec fetch_last_token_balance(Hash.Address.t(), Hash.Address.t()) :: Decimal.t() @@ -3887,16 +5872,15 @@ defmodule Explorer.Chain do end end - @spec address_to_coin_balances(Address.t(), [paging_options | api?]) :: [] - def address_to_coin_balances(address, options) do + @spec address_to_coin_balances(Hash.Address.t(), [paging_options]) :: [] + def address_to_coin_balances(address_hash, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) balances_raw = - address + address_hash |> fetch_coin_balances(paging_options) |> page_coin_balances(paging_options) - |> select_repo(options).all() - |> preload_transactions(options) + |> Repo.all() if Enum.empty?(balances_raw) do balances_raw @@ -3915,8 +5899,8 @@ defmodule Explorer.Chain do |> Enum.max_by(fn balance -> balance.block_number end, fn -> %{} end) |> Map.get(:block_number) - min_block_timestamp = find_block_timestamp(min_block_number, options) - max_block_timestamp = find_block_timestamp(max_block_number, options) + min_block_timestamp = find_block_timestamp(min_block_number) + max_block_timestamp = find_block_timestamp(max_block_number) min_block_unix_timestamp = min_block_timestamp @@ -3930,15 +5914,26 @@ defmodule Explorer.Chain do balances_with_dates = if blocks_delta > 0 do - add_block_timestamp_to_balances( - balances_raw_filtered, - min_block_number, - min_block_unix_timestamp, - max_block_unix_timestamp, - blocks_delta - ) + balances_raw_filtered + |> Enum.map(fn balance -> + date = + trunc( + min_block_unix_timestamp + + (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) / + blocks_delta + ) + + formatted_date = Timex.from_unix(date) + %{balance | block_timestamp: formatted_date} + end) else - add_min_block_timestamp_to_balances(balances_raw_filtered, min_block_unix_timestamp) + balances_raw_filtered + |> Enum.map(fn balance -> + date = min_block_unix_timestamp + + formatted_date = Timex.from_unix(date) + %{balance | block_timestamp: formatted_date} + end) end balances_with_dates @@ -3946,103 +5941,25 @@ defmodule Explorer.Chain do end end - # Here we fetch from DB one tx per one coin balance. It's much more faster than LEFT OUTER JOIN which was before. - defp preload_transactions(balances, options) do - tasks = - Enum.map(balances, fn balance -> - Task.async(fn -> - Transaction - |> where( - [tx], - tx.block_number == ^balance.block_number and (tx.value > ^0 or (tx.gas_price > ^0 and tx.gas_used > ^0)) and - (tx.to_address_hash == ^balance.address_hash or tx.from_address_hash == ^balance.address_hash) - ) - |> select([tx], tx.hash) - |> limit(1) - |> select_repo(options).one() - end) - end) - - tasks - |> Task.yield_many(120_000) - |> Enum.zip(balances) - |> Enum.map(fn {{task, res}, balance} -> - case res do - {:ok, hash} -> - put_tx_hash(hash, balance) - - {:exit, _reason} -> - balance - - nil -> - Task.shutdown(task, :brutal_kill) - balance - end - end) - end - - defp put_tx_hash(hash, coin_balance), - do: if(hash, do: %CoinBalance{coin_balance | transaction_hash: hash}, else: coin_balance) - - defp add_block_timestamp_to_balances( - balances_raw_filtered, - min_block_number, - min_block_unix_timestamp, - max_block_unix_timestamp, - blocks_delta - ) do - balances_raw_filtered - |> Enum.map(fn balance -> - date = - trunc( - min_block_unix_timestamp + - (balance.block_number - min_block_number) * (max_block_unix_timestamp - min_block_unix_timestamp) / - blocks_delta - ) - - add_date_to_balance(balance, date) - end) - end - - defp add_min_block_timestamp_to_balances(balances_raw_filtered, min_block_unix_timestamp) do - balances_raw_filtered - |> Enum.map(fn balance -> - date = min_block_unix_timestamp - - add_date_to_balance(balance, date) - end) - end - - defp add_date_to_balance(balance, date) do - formatted_date = Timex.from_unix(date) - %{balance | block_timestamp: formatted_date} - end - - def get_token_balance(address_hash, token_contract_address_hash, block_number, token_id \\ nil, options \\ []) do - query = TokenBalance.fetch_token_balance(address_hash, token_contract_address_hash, block_number, token_id) - - select_repo(options).one(query) - end - - def get_coin_balance(address_hash, block_number, options \\ []) do + def get_coin_balance(address_hash, block_number) do query = CoinBalance.fetch_coin_balance(address_hash, block_number) - select_repo(options).one(query) + Repo.one(query) end - @spec address_to_balances_by_day(Hash.Address.t(), [api?]) :: [balance_by_day] - def address_to_balances_by_day(address_hash, options \\ []) do + @spec address_to_balances_by_day(Hash.Address.t()) :: [balance_by_day] + def address_to_balances_by_day(address_hash) do latest_block_timestamp = address_hash |> CoinBalance.last_coin_balance_timestamp() - |> select_repo(options).one() + |> Repo.one() address_hash |> CoinBalanceDaily.balances_by_day() - |> select_repo(options).all() + |> Repo.all() |> Enum.sort_by(fn %{date: d} -> {d.year, d.month, d.day} end) |> replace_last_value(latest_block_timestamp) - |> normalize_balances_by_day(Keyword.get(options, :api?, false)) + |> normalize_balances_by_day() end # https://github.com/blockscout/blockscout/issues/2658 @@ -4052,12 +5969,12 @@ defmodule Explorer.Chain do defp replace_last_value(items, _), do: items - defp normalize_balances_by_day(balances_by_day, api?) do + defp normalize_balances_by_day(balances_by_day) do result = balances_by_day |> Enum.filter(fn day -> day.value end) - |> (&if(api?, do: &1, else: Enum.map(&1, fn day -> Map.update!(day, :date, fn x -> to_string(x) end) end))).() - |> (&if(api?, do: &1, else: Enum.map(&1, fn day -> Map.update!(day, :value, fn x -> Wei.to(x, :ether) end) end))).() + |> Enum.map(fn day -> Map.update!(day, :date, &to_string(&1)) end) + |> Enum.map(fn day -> Map.update!(day, :value, &Wei.to(&1, :ether)) end) today = Date.to_string(NaiveDateTime.utc_now()) @@ -4068,32 +5985,34 @@ defmodule Explorer.Chain do end end - @spec fetch_token_holders_from_token_hash(Hash.Address.t(), [paging_options | api?]) :: [TokenBalance.t()] - def fetch_token_holders_from_token_hash(contract_address_hash, options \\ []) do + @spec fetch_token_holders_from_token_hash(Hash.Address.t(), boolean(), [paging_options]) :: [TokenBalance.t()] + def fetch_token_holders_from_token_hash(contract_address_hash, from_api, options \\ []) do query = contract_address_hash |> CurrentTokenBalance.token_holders_ordered_by_value(options) - query - |> select_repo(options).all() + if from_api do + query + |> Repo.replica().all() + else + query + |> Repo.all() + end end def fetch_token_holders_from_token_hash_and_token_id(contract_address_hash, token_id, options \\ []) do contract_address_hash |> CurrentTokenBalance.token_holders_1155_by_token_id(token_id, options) - |> select_repo(options).all() + |> Repo.all() end - def token_id_1155_is_unique?(contract_address_hash, token_id, options \\ []) + def token_id_1155_is_unique?(_, nil), do: false - def token_id_1155_is_unique?(_, nil, _), do: false - - def token_id_1155_is_unique?(contract_address_hash, token_id, options) do - result = - contract_address_hash |> CurrentTokenBalance.token_balances_by_id_limit_2(token_id) |> select_repo(options).all() + def token_id_1155_is_unique?(contract_address_hash, token_id) do + result = contract_address_hash |> CurrentTokenBalance.token_balances_by_id_limit_2(token_id) |> Repo.all() if length(result) == 1 do - Decimal.compare(Enum.at(result, 0), 1) == :eq + Decimal.cmp(Enum.at(result, 0), 1) == :eq else false end @@ -4109,50 +6028,23 @@ defmodule Explorer.Chain do def count_token_holders_from_token_hash(contract_address_hash) do query = from(ctb in CurrentTokenBalance.token_holders_query_for_count(contract_address_hash), - select: fragment("COUNT(DISTINCT(?))", ctb.address_hash) + select: fragment("COUNT(DISTINCT(address_hash))") ) Repo.one!(query, timeout: :infinity) end - @spec address_to_unique_tokens(Hash.Address.t(), Token.t(), [paging_options | api?]) :: [Instance.t()] - def address_to_unique_tokens(contract_address_hash, token, options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - contract_address_hash - |> Instance.address_to_unique_token_instances() - |> Instance.page_token_instance(paging_options) - |> limit(^paging_options.page_size) - |> preload([_], [:owner]) - |> select_repo(options).all() - |> Enum.map(&put_owner_to_token_instance(&1, token, options)) - end - - @doc """ - Put owner address to unique token instance. If not unique, return original instance. - """ - @spec put_owner_to_token_instance(Instance.t(), Token.t(), [api?]) :: Instance.t() - def put_owner_to_token_instance(token_instance, token, options \\ []) - - def put_owner_to_token_instance(%Instance{is_unique: nil} = token_instance, token, options) do - put_owner_to_token_instance(Instance.put_is_unique(token_instance, token, options), token, options) - end - - def put_owner_to_token_instance( - %Instance{owner: nil, is_unique: true} = token_instance, - %Token{type: "ERC-1155"}, - options - ) do - owner_address_hash = - token_instance - |> Instance.owner_query() - |> select_repo(options).one() + @spec address_to_unique_tokens(Hash.Address.t(), [paging_options]) :: [TokenTransfer.t()] + def address_to_unique_tokens(contract_address_hash, options \\ []) do + paging_options = Keyword.get(options, :paging_options, @default_paging_options) - %{token_instance | owner: select_repo(options).get_by(Address, hash: owner_address_hash)} + contract_address_hash + |> TokenTransfer.address_to_unique_tokens() + |> TokenTransfer.page_token_transfer(paging_options) + |> limit(^paging_options.page_size) + |> Repo.all() end - def put_owner_to_token_instance(%Instance{} = token_instance, _token, _options), do: token_instance - @spec data() :: Dataloader.Ecto.t() def data, do: DataloaderEcto.new(Repo) @@ -4292,6 +6184,200 @@ defmodule Explorer.Chain do value end + @doc "Get staking pools from the DB" + @spec staking_pools( + filter :: :validator | :active | :inactive, + paging_options :: PagingOptions.t() | :all, + address_hash :: Hash.t() | nil, + filter_banned :: boolean() | nil, + filter_my :: boolean() | nil + ) :: [map()] + def staking_pools( + filter, + paging_options \\ @default_paging_options, + address_hash \\ nil, + filter_banned \\ false, + filter_my \\ false + ) do + base_query = + StakingPool + |> where(is_deleted: false) + |> staking_pool_filter(filter) + |> staking_pools_paging_query(paging_options) + + delegator_query = + if address_hash do + base_query + |> join(:left, [p], pd in StakingPoolsDelegator, + on: + p.staking_address_hash == pd.staking_address_hash and pd.address_hash == ^address_hash and + not pd.is_deleted + ) + |> select([p, pd], %{pool: p, delegator: pd}) + else + base_query + |> select([p], %{pool: p, delegator: nil}) + end + + banned_query = + if filter_banned do + where(delegator_query, is_banned: true) + else + delegator_query + end + + filtered_query = + if address_hash && filter_my do + where(banned_query, [..., pd], not is_nil(pd)) + else + banned_query + end + + Repo.all(filtered_query) + end + + defp staking_pools_paging_query(base_query, :all) do + base_query + |> order_by(asc: :staking_address_hash) + end + + defp staking_pools_paging_query(base_query, paging_options) do + paging_query = + base_query + |> limit(^paging_options.page_size) + |> order_by(desc: :stakes_ratio, desc: :is_active, asc: :staking_address_hash) + + case paging_options.key do + {value, address_hash} -> + where( + paging_query, + [p], + p.stakes_ratio < ^value or + (p.stakes_ratio == ^value and p.staking_address_hash > ^address_hash) + ) + + _ -> + paging_query + end + 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 + StakingPool + |> where(is_deleted: false) + |> staking_pool_filter(filter) + |> Repo.aggregate(:count, :staking_address_hash) + end + + @doc "Get sum of delegators count from the DB" + @spec delegators_count_sum(filter :: :validator | :active | :inactive) :: integer + def delegators_count_sum(filter) do + StakingPool + |> where(is_deleted: false) + |> staking_pool_filter(filter) + |> Repo.aggregate(:sum, :delegators_count) + end + + @doc "Get sum of total staked amount from the DB" + @spec total_staked_amount_sum(filter :: :validator | :active | :inactive) :: integer + def total_staked_amount_sum(filter) do + StakingPool + |> where(is_deleted: false) + |> staking_pool_filter(filter) + |> Repo.aggregate(:sum, :total_staked_amount) + end + + defp staking_pool_filter(query, :validator) do + where(query, is_validator: true) + end + + defp staking_pool_filter(query, :active) do + where(query, is_active: true) + end + + defp staking_pool_filter(query, :inactive) do + where(query, is_active: false) + end + + def staking_pool(staking_address_hash) do + Repo.get_by(StakingPool, staking_address_hash: staking_address_hash) + end + + def staking_pool_names(staking_addresses) do + StakingPool + |> where([p], p.staking_address_hash in ^staking_addresses and p.is_deleted == false) + |> select([:staking_address_hash, :name]) + |> Repo.all() + end + + def staking_pool_delegators(staking_address_hash, show_snapshotted_data) do + query = + from( + d in StakingPoolsDelegator, + where: + d.staking_address_hash == ^staking_address_hash and + (d.is_active == true or (^show_snapshotted_data and d.snapshotted_stake_amount > 0 and d.is_active != true)), + order_by: [desc: d.stake_amount] + ) + + query + |> Repo.all() + end + + def staking_pool_snapshotted_delegator_data_for_apy do + query = + from( + d in StakingPoolsDelegator, + select: %{ + :staking_address_hash => fragment("DISTINCT ON (?) ?", d.staking_address_hash, d.staking_address_hash), + :snapshotted_reward_ratio => d.snapshotted_reward_ratio, + :snapshotted_stake_amount => d.snapshotted_stake_amount + }, + where: d.staking_address_hash != d.address_hash and d.snapshotted_stake_amount > 0 + ) + + query + |> Repo.all() + end + + def staking_pool_snapshotted_inactive_delegators_count(staking_address_hash) do + query = + from( + d in StakingPoolsDelegator, + where: + d.staking_address_hash == ^staking_address_hash and + d.snapshotted_stake_amount > 0 and + d.is_active != true, + select: fragment("count(*)") + ) + + query + |> Repo.one() + end + + def staking_pool_delegator(staking_address_hash, address_hash) do + Repo.get_by(StakingPoolsDelegator, + staking_address_hash: staking_address_hash, + address_hash: address_hash, + is_deleted: false + ) + end + + def get_total_staked_and_ordered(""), do: nil + + def get_total_staked_and_ordered(address_hash) when is_binary(address_hash) do + StakingPoolsDelegator + |> where([delegator], delegator.address_hash == ^address_hash and not delegator.is_deleted) + |> select([delegator], %{ + stake_amount: coalesce(sum(delegator.stake_amount), 0), + ordered_withdraw: coalesce(sum(delegator.ordered_withdraw), 0) + }) + |> Repo.one() + end + + def get_total_staked_and_ordered(_), do: nil + defp with_decompiled_code_flag(query, _hash, false), do: query defp with_decompiled_code_flag(query, hash, true) do @@ -4299,16 +6385,12 @@ defmodule Explorer.Chain do from(decompiled_contract in DecompiledSmartContract, where: decompiled_contract.address_hash == ^hash, limit: 1, - select: %{ - address_hash: decompiled_contract.address_hash, - has_decompiled_code?: not is_nil(decompiled_contract.address_hash) - } + select: %{has_decompiled_code?: not is_nil(decompiled_contract.address_hash)} ) from( address in query, left_join: decompiled_code in subquery(has_decompiled_code_query), - on: address.hash == decompiled_code.address_hash, select_merge: %{has_decompiled_code?: decompiled_code.has_decompiled_code?} ) end @@ -4319,7 +6401,6 @@ defmodule Explorer.Chain do |> TypeDecoder.decode_raw(types) end - @spec get_token_type(Hash.Address.t()) :: String.t() | nil def get_token_type(hash) do query = from( @@ -4331,16 +6412,58 @@ defmodule Explorer.Chain do Repo.one(query) end - @spec erc_20_token?(Token.t()) :: bool - def erc_20_token?(token) do - erc_20_token_type?(token.type) + @doc """ + Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. + + Returns `:ok` if found + + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} + ...> ) + iex> Explorer.Chain.check_address_exists(hash) + :ok + + Returns `:not_found` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.check_address_exists(hash) + :not_found + + """ + @spec check_address_exists(Hash.Address.t()) :: :ok | :not_found + def check_address_exists(address_hash) do + address_hash + |> address_exists?() + |> boolean_to_check_result() end - defp erc_20_token_type?(type) do - case type do - "ERC-20" -> true - _ -> false - end + @doc """ + Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists. + + Returns `true` if found + + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} + ...> ) + iex> Explorer.Chain.address_exists?(hash) + true + + Returns `false` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.address_exists?(hash) + false + + """ + @spec address_exists?(Hash.Address.t()) :: boolean() + def address_exists?(address_hash) do + query = + from( + address in Address, + where: address.hash == ^address_hash + ) + + Repo.exists?(query) end @doc """ @@ -4402,6 +6525,36 @@ defmodule Explorer.Chain do Repo.exists?(query) end + @doc """ + Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the + `t:Explorer.Chain.Address.t/0` with the provided `hash`. + + Returns `:ok` if found and `:not_found` otherwise. + """ + @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found + def check_verified_smart_contract_exists(address_hash) do + address_hash + |> verified_smart_contract_exists?() + |> boolean_to_check_result() + end + + @doc """ + Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the + `t:Explorer.Chain.Address.t/0` with the provided `hash`. + + Returns `true` if found and `false` otherwise. + """ + @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean() + def verified_smart_contract_exists?(address_hash) do + query = + from( + smart_contract in SmartContract, + where: smart_contract.address_hash == ^address_hash + ) + + Repo.exists?(query) + end + @doc """ Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists. @@ -4505,83 +6658,357 @@ defmodule Explorer.Chain do end @doc """ - Checks if a `t:Explorer.Chain.Token.Instance.t/0` with the given `hash` and `token_id` exists. + Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists. Returns `:ok` if found - iex> token = insert(:token) - iex> token_id = 10 - iex> insert(:token_instance, - ...> token_contract_address_hash: token.contract_address_hash, - ...> token_id: token_id - ...> ) - iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(token_id, token.contract_address_hash) - :ok + iex> contract_address = insert(:address) + iex> token_id = 10 + iex> insert(:token_transfer, + ...> from_address: contract_address, + ...> token_contract_address: contract_address, + ...> token_id: token_id + ...> ) + iex> Explorer.Chain.check_erc721_token_instance_exists(token_id, contract_address.hash) + :ok + + Returns `:not_found` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.check_erc721_token_instance_exists(10, hash) + :not_found + """ + @spec check_erc721_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: :ok | :not_found + def check_erc721_token_instance_exists(token_id, hash) do + token_id + |> erc721_token_instance_exist?(hash) + |> boolean_to_check_result() + end + + @doc """ + Checks if a `t:Explorer.Chain.TokenTransfer.t/0` with the given `hash` and `token_id` exists. + + Returns `true` if found + + iex> contract_address = insert(:address) + iex> token_id = 10 + iex> insert(:token_transfer, + ...> from_address: contract_address, + ...> token_contract_address: contract_address, + ...> token_id: token_id + ...> ) + iex> Explorer.Chain.erc721_token_instance_exist?(token_id, contract_address.hash) + true + + Returns `false` if not found + + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.erc721_token_instance_exist?(10, hash) + false + """ + @spec erc721_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() + def erc721_token_instance_exist?(token_id, hash) do + query = + from(tt in TokenTransfer, + where: tt.token_contract_address_hash == ^hash and tt.token_id == ^token_id + ) + + Repo.exists?(query) + end + + defp boolean_to_check_result(true), do: :ok + + defp boolean_to_check_result(false), do: :not_found + + def extract_db_name(db_url) do + if db_url == nil do + "" + else + db_url + |> String.split("/") + |> Enum.take(-1) + |> Enum.at(0) + end + end + + def extract_db_host(db_url) do + if db_url == nil do + "" + else + db_url + |> String.split("@") + |> Enum.take(-1) + |> Enum.at(0) + |> String.split(":") + |> Enum.at(0) + end + end + + @doc """ + Fetches the first trace from the Parity trace URL. + """ + def fetch_first_trace(transactions_params, json_rpc_named_arguments) do + case EthereumJSONRPC.fetch_first_trace(transactions_params, json_rpc_named_arguments) do + {:ok, [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} -> + format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) + + {:error, error} -> + {:error, error} + + :ignore -> + :ignore + end + end + + def combine_proxy_implementation_abi(proxy_address_hash, abi) when not is_nil(abi) do + implementation_abi = get_implementation_abi_from_proxy(proxy_address_hash, abi) + + if Enum.empty?(implementation_abi), do: abi, else: implementation_abi ++ abi + end + + def combine_proxy_implementation_abi(_, abi) when is_nil(abi) do + [] + end + + def proxy_contract?(address_hash, abi) when not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + Map.get(method, "name") == "implementation" || + master_copy_pattern?(method) + end) + + if implementation_method_abi || + get_implementation_address_hash_eip_1967(address_hash) !== "0x0000000000000000000000000000000000000000", + do: true, + else: false + end + + def proxy_contract?(_address_hash, abi) when is_nil(abi), do: false + + def gnosis_safe_contract?(abi) when not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + master_copy_pattern?(method) + end) + + if implementation_method_abi, do: true, else: false + end + + def gnosis_safe_contract?(abi) when is_nil(abi), do: false + + def get_implementation_address_hash(proxy_address_hash, abi) + when not is_nil(proxy_address_hash) and not is_nil(abi) do + implementation_method_abi = + abi + |> Enum.find(fn method -> + Map.get(method, "name") == "implementation" && Map.get(method, "stateMutability") == "view" + end) + + master_copy_method_abi = + abi + |> Enum.find(fn method -> + master_copy_pattern?(method) + end) + + cond do + implementation_method_abi -> + get_implementation_address_hash_basic(proxy_address_hash, abi) + + master_copy_method_abi -> + get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) + + true -> + get_implementation_address_hash_eip_1967(proxy_address_hash) + end + end + + def get_implementation_address_hash(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do + nil + end + + defp get_implementation_address_hash_eip_1967(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_logic_contract_address = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc" + + {_status, implementation_address} = + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_logic_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + {:ok, implementation_logic_address} -> + {:ok, implementation_logic_address} + + {:error, _} -> + {:ok, "0x"} + end + + abi_decode_address_output(implementation_address) + end + + # changes requested by https://github.com/blockscout/blockscout/issues/4770 + # for support BeaconProxy pattern + defp fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) do + # https://eips.ethereum.org/EIPS/eip-1967 + storage_slot_beacon_contract_address = "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50" + + implementation_method_abi = [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + + case Contract.eth_get_storage_at_request( + proxy_address_hash, + storage_slot_beacon_contract_address, + nil, + json_rpc_named_arguments + ) do + {:ok, empty_address} + when empty_address in ["0x", "0x0", "0x0000000000000000000000000000000000000000000000000000000000000000"] -> + {:ok, "0x"} + + {:ok, beacon_contract_address} -> + case beacon_contract_address + |> abi_decode_address_output() + |> get_implementation_address_hash_basic(implementation_method_abi) do + <> -> + {:ok, implementation_address} + + _ -> + {:ok, beacon_contract_address} + end + + {:error, _} -> + {:ok, "0x"} + end + end + + defp get_implementation_address_hash_basic(proxy_address_hash, abi) do + # 5c60da1b = keccak256(implementation()) + implementation_address = + case Reader.query_contract( + proxy_address_hash, + abi, + %{ + "5c60da1b" => [] + }, + false + ) do + %{"5c60da1b" => {:ok, [result]}} -> result + _ -> nil + end + + address_to_hex(implementation_address) + end + + defp get_implementation_address_hash_from_master_copy_pattern(proxy_address_hash) do + json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) + + master_copy_storage_pointer = "0x0" - Returns `:not_found` if not found + {:ok, implementation_address} = + Contract.eth_get_storage_at_request( + proxy_address_hash, + master_copy_storage_pointer, + nil, + json_rpc_named_arguments + ) - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.check_erc721_or_erc1155_token_instance_exists(10, hash) - :not_found - """ - @spec check_erc721_or_erc1155_token_instance_exists(binary() | non_neg_integer(), Hash.Address.t()) :: - :ok | :not_found - def check_erc721_or_erc1155_token_instance_exists(token_id, hash) do - token_id - |> erc721_or_erc1155_token_instance_exist?(hash) - |> boolean_to_check_result() + abi_decode_address_output(implementation_address) end - @doc """ - Checks if a `t:Explorer.Chain.Token.Instance.t/0` with the given `hash` and `token_id` exists. + defp master_copy_pattern?(method) do + Map.get(method, "type") == "constructor" && + method + |> Enum.find(fn item -> + case item do + {"inputs", inputs} -> + master_copy_input?(inputs) - Returns `true` if found + _ -> + false + end + end) + end - iex> token = insert(:token) - iex> token_id = 10 - iex> insert(:token_instance, - ...> token_contract_address_hash: token.contract_address_hash, - ...> token_id: token_id - ...> ) - iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(token_id, token.contract_address_hash) - true + defp master_copy_input?(inputs) do + inputs + |> Enum.find(fn input -> + Map.get(input, "name") == "_masterCopy" + end) + end - Returns `false` if not found + defp abi_decode_address_output(nil), do: nil - iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - iex> Explorer.Chain.erc721_or_erc1155_token_instance_exist?(10, hash) - false - """ - @spec erc721_or_erc1155_token_instance_exist?(binary() | non_neg_integer(), Hash.Address.t()) :: boolean() - def erc721_or_erc1155_token_instance_exist?(token_id, hash) do - query = - from(i in Instance, - where: i.token_contract_address_hash == ^hash and i.token_id == ^Decimal.new(token_id) - ) + defp abi_decode_address_output("0x"), do: @burn_address_hash_str - Repo.exists?(query) + defp abi_decode_address_output(address) when is_binary(address) do + if String.length(address) > 42 do + "0x" <> String.slice(address, -40, 40) + else + address + end end - def boolean_to_check_result(true), do: :ok + defp abi_decode_address_output(_), do: nil - def boolean_to_check_result(false), do: :not_found + defp address_to_hex(address) do + if address do + if String.starts_with?(address, "0x") do + address + else + "0x" <> Base.encode16(address, case: :lower) + end + end + end - @doc """ - Fetches the first trace from the Nethermind trace URL. - """ - def fetch_first_trace(transactions_params, json_rpc_named_arguments) do - case EthereumJSONRPC.fetch_first_trace(transactions_params, json_rpc_named_arguments) do - {:ok, [%{first_trace: first_trace, block_hash: block_hash, json_rpc_named_arguments: json_rpc_named_arguments}]} -> - format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) + def get_implementation_abi(implementation_address_hash_string) when not is_nil(implementation_address_hash_string) do + case Chain.string_to_address_hash(implementation_address_hash_string) do + {:ok, implementation_address_hash} -> + implementation_smart_contract = + implementation_address_hash + |> Chain.address_hash_to_smart_contract() - {:error, error} -> - {:error, error} + if implementation_smart_contract do + implementation_smart_contract + |> Map.get(:abi) + else + [] + end - :ignore -> - :ignore + _ -> + [] end end + def get_implementation_abi(implementation_address_hash_string) when is_nil(implementation_address_hash_string) do + [] + end + + def get_implementation_abi_from_proxy(proxy_address_hash, abi) + when not is_nil(proxy_address_hash) and not is_nil(abi) do + implementation_address_hash_string = get_implementation_address_hash(proxy_address_hash, abi) + get_implementation_abi(implementation_address_hash_string) + end + + def get_implementation_abi_from_proxy(proxy_address_hash, abi) when is_nil(proxy_address_hash) or is_nil(abi) do + [] + end + defp format_tx_first_trace(first_trace, block_hash, json_rpc_named_arguments) do {:ok, to_address_hash} = if Map.has_key?(first_trace, :to_address_hash) do @@ -4678,8 +7105,7 @@ defmodule Explorer.Chain do if transaction_index == 0 do 0 else - filtered_block_numbers = RangesHelper.filter_traceable_block_numbers([block_number]) - {:ok, traces} = fetch_block_internal_transactions(filtered_block_numbers, json_rpc_named_arguments) + {:ok, traces} = fetch_block_internal_transactions([block_number], json_rpc_named_arguments) sorted_traces = traces @@ -4697,18 +7123,56 @@ defmodule Explorer.Chain do end end - defp find_block_timestamp(number, options) do + defp find_block_timestamp(number) do Block - |> where([block], block.number == ^number) - |> select([block], block.timestamp) + |> where([b], b.number == ^number) + |> select([b], b.timestamp) |> limit(1) - |> select_repo(options).one() + |> Repo.one() + end + + def bridged_tokens_enabled? do + eth_omni_bridge_mediator = Application.get_env(:block_scout_web, :eth_omni_bridge_mediator) + bsc_omni_bridge_mediator = Application.get_env(:block_scout_web, :bsc_omni_bridge_mediator) + + (eth_omni_bridge_mediator && eth_omni_bridge_mediator !== "") || + (bsc_omni_bridge_mediator && bsc_omni_bridge_mediator !== "") + end + + def bridged_tokens_eth_enabled? do + eth_omni_bridge_mediator = Application.get_env(:block_scout_web, :eth_omni_bridge_mediator) + + eth_omni_bridge_mediator && eth_omni_bridge_mediator !== "" + end + + def bridged_tokens_bsc_enabled? do + bsc_omni_bridge_mediator = Application.get_env(:block_scout_web, :bsc_omni_bridge_mediator) + + bsc_omni_bridge_mediator && bsc_omni_bridge_mediator !== "" + end + + def chain_id_display_name(nil), do: "" + + def chain_id_display_name(chain_id) do + chain_id_int = + if is_integer(chain_id) do + chain_id + else + chain_id + |> Decimal.to_integer() + end + + case chain_id_int do + 1 -> "eth" + 56 -> "bsc" + _ -> "" + end end @spec get_token_transfer_type(TokenTransfer.t()) :: :token_burning | :token_minting | :token_spawning | :token_transfer def get_token_transfer_type(transfer) do - {:ok, burn_address_hash} = Chain.string_to_address_hash(SmartContract.burn_address_hash_string()) + {:ok, burn_address_hash} = Chain.string_to_address_hash(@burn_address_hash_str) cond do transfer.to_address_hash == burn_address_hash && transfer.from_address_hash !== burn_address_hash -> @@ -4752,12 +7216,10 @@ defmodule Explorer.Chain do end end - @spec from_block(keyword) :: any - def from_block(options) do + defp from_block(options) do Keyword.get(options, :from_block) || nil end - @spec to_block(keyword) :: any def to_block(options) do Keyword.get(options, :to_block) || nil end @@ -4787,402 +7249,4 @@ defmodule Explorer.Chain do query |> Repo.one() end - - def address_hash_is_smart_contract?(nil), do: false - - def address_hash_is_smart_contract?(address_hash) do - with %Address{contract_code: bytecode} <- Repo.get_by(Address, hash: address_hash), - false <- is_nil(bytecode) do - true - else - _ -> - false - end - end - - def hash_to_lower_case_string(hash) do - hash - |> to_string() - |> String.downcase() - end - - def recent_transactions(options, [:pending | _]) do - recent_pending_transactions(options, false) - end - - def recent_transactions(options, _) do - recent_collated_transactions(false, options) - end - - def apply_filter_by_method_id_to_transactions(query, nil), do: query - - def apply_filter_by_method_id_to_transactions(query, filter) when is_list(filter) do - method_ids = Enum.flat_map(filter, &map_name_or_method_id_to_method_id/1) - - if method_ids != [] do - query - |> where([tx], fragment("SUBSTRING(? FOR 4)", tx.input) in ^method_ids) - else - query - end - end - - def apply_filter_by_method_id_to_transactions(query, filter), - do: apply_filter_by_method_id_to_transactions(query, [filter]) - - defp map_name_or_method_id_to_method_id(string) when is_binary(string) do - if id = @method_name_to_id_map[string] do - decode_method_id(id) - else - trimmed = - string - |> String.replace("0x", "", global: false) - - decode_method_id(trimmed) - end - end - - defp decode_method_id(method_id) when is_binary(method_id) do - case String.length(method_id) == 8 && Base.decode16(method_id, case: :mixed) do - {:ok, bytes} -> - [bytes] - - _ -> - [] - end - end - - def apply_filter_by_tx_type_to_transactions(query, [_ | _] = filter) do - {dynamic, modified_query} = apply_filter_by_tx_type_to_transactions_inner(filter, query) - - modified_query - |> where(^dynamic) - end - - def apply_filter_by_tx_type_to_transactions(query, _filter), do: query - - def apply_filter_by_tx_type_to_transactions_inner(dynamic \\ dynamic(false), filter, query) - - def apply_filter_by_tx_type_to_transactions_inner(dynamic, [type | remain], query) do - case type do - :contract_call -> - dynamic - |> filter_contract_call_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner( - remain, - join(query, :inner, [tx], address in assoc(tx, :to_address), as: :to_address) - ) - - :contract_creation -> - dynamic - |> filter_contract_creation_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) - - :coin_transfer -> - dynamic - |> filter_transaction_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) - - :token_transfer -> - dynamic - |> filter_token_transfer_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner(remain, query) - - :token_creation -> - dynamic - |> filter_token_creation_dynamic() - |> apply_filter_by_tx_type_to_transactions_inner( - remain, - join(query, :inner, [tx], token in Token, - on: token.contract_address_hash == tx.created_contract_address_hash, - as: :created_token - ) - ) - end - end - - def apply_filter_by_tx_type_to_transactions_inner(dynamic_query, _, query), do: {dynamic_query, query} - - def filter_contract_creation_dynamic(dynamic) do - dynamic([tx], ^dynamic or is_nil(tx.to_address_hash)) - end - - def filter_transaction_dynamic(dynamic) do - dynamic([tx], ^dynamic or tx.value > ^0) - end - - def filter_contract_call_dynamic(dynamic) do - dynamic([tx, to_address: to_address], ^dynamic or not is_nil(to_address.contract_code)) - end - - def filter_token_transfer_dynamic(dynamic) do - # TokenTransfer.__struct__.__meta__.source - dynamic( - [tx], - ^dynamic or - fragment( - "NOT (SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NULL", - tx.hash - ) - ) - end - - def filter_token_creation_dynamic(dynamic) do - dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token)) - end - - def count_verified_contracts do - Repo.aggregate(SmartContract, :count, timeout: :infinity) - end - - def count_new_verified_contracts do - query = - from(contract in SmartContract, - select: contract.inserted_at, - where: fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", contract.inserted_at) - ) - - query - |> Repo.aggregate(:count, timeout: :infinity) - end - - def count_contracts do - query = - from(address in Address, - select: address, - where: not is_nil(address.contract_code) - ) - - query - |> Repo.aggregate(:count, timeout: :infinity) - end - - def count_new_contracts do - query = - from(tx in Transaction, - select: tx, - where: - tx.status == ^:ok and - fragment("NOW() - ? at time zone 'UTC' <= interval '24 hours'", tx.created_contract_code_indexed_at) - ) - - query - |> Repo.aggregate(:count, timeout: :infinity) - end - - def count_verified_contracts_from_cache(options \\ []) do - VerifiedContractsCounter.fetch(options) - end - - def count_new_verified_contracts_from_cache(options \\ []) do - NewVerifiedContractsCounter.fetch(options) - end - - def count_contracts_from_cache(options \\ []) do - ContractsCounter.fetch(options) - end - - def count_new_contracts_from_cache(options \\ []) do - NewContractsCounter.fetch(options) - end - - def fetch_token_counters(address_hash, timeout) do - total_token_transfers_task = - Task.async(fn -> - TokenTransfersCounter.fetch(address_hash) - end) - - total_token_holders_task = - Task.async(fn -> - TokenHoldersCounter.fetch(address_hash) - end) - - [total_token_transfers_task, total_token_holders_task] - |> Task.yield_many(timeout) - |> Enum.map(fn {_task, res} -> - case res do - {:ok, result} -> - result - - {:exit, reason} -> - Logger.warn("Query fetching token counters terminated: #{inspect(reason)}") - 0 - - nil -> - Logger.warn("Query fetching token counters timed out.") - 0 - end - end) - |> List.to_tuple() - end - - @spec flat_1155_batch_token_transfers([TokenTransfer.t()], Decimal.t() | nil) :: [TokenTransfer.t()] - def flat_1155_batch_token_transfers(token_transfers, token_id \\ nil) when is_list(token_transfers) do - Enum.reduce(token_transfers, [], fn tt, acc -> - case tt.token_ids do - [] -> - Enum.reverse([tt | Enum.reverse(acc)]) - - [_token_id] -> - Enum.reverse([tt | Enum.reverse(acc)]) - - token_ids when is_list(token_ids) -> - transfers = flat_1155_batch_token_transfer(tt, tt.amounts, token_ids, token_id) - - acc ++ transfers - - _ -> - Enum.reverse([tt | Enum.reverse(acc)]) - end - end) - end - - defp flat_1155_batch_token_transfer(tt, amounts, token_ids, token_id_to_filter) do - amounts - |> Enum.zip(token_ids) - |> Enum.with_index() - |> Enum.map(fn {{amount, token_id}, index} -> - if is_nil(token_id_to_filter) || token_id == token_id_to_filter do - %TokenTransfer{tt | token_ids: [token_id], amount: amount, amounts: nil, index_in_batch: index} - end - end) - |> Enum.reject(&is_nil/1) - |> squash_token_transfers_in_batch() - end - - defp squash_token_transfers_in_batch(token_transfers) do - token_transfers - |> Enum.group_by(fn tt -> {List.first(tt.token_ids), tt.from_address_hash, tt.to_address_hash} end) - |> Enum.map(fn {_k, v} -> Enum.reduce(v, nil, &group_batch_reducer/2) end) - |> Enum.sort_by(fn tt -> tt.index_in_batch end, :desc) - end - - defp group_batch_reducer(transfer, nil) do - transfer - end - - defp group_batch_reducer(transfer, acc) do - %TokenTransfer{acc | amount: Decimal.add(acc.amount, transfer.amount)} - end - - @spec paginate_1155_batch_token_transfers([TokenTransfer.t()], [paging_options]) :: [TokenTransfer.t()] - def paginate_1155_batch_token_transfers(token_transfers, options) do - paging_options = options |> Keyword.get(:paging_options, nil) - - case paging_options do - %PagingOptions{batch_key: batch_key} when not is_nil(batch_key) -> - filter_previous_page_transfers(token_transfers, batch_key) - - _ -> - token_transfers - end - end - - defp filter_previous_page_transfers( - token_transfers, - {batch_block_hash, batch_transaction_hash, batch_log_index, index_in_batch} - ) do - token_transfers - |> Enum.reverse() - |> Enum.reduce_while([], fn tt, acc -> - if tt.block_hash == batch_block_hash and tt.transaction_hash == batch_transaction_hash and - tt.log_index == batch_log_index and tt.index_in_batch == index_in_batch do - {:halt, acc} - else - {:cont, [tt | acc]} - end - end) - end - - def select_repo(options) do - if Keyword.get(options, :api?, false) do - Repo.replica() - else - Repo - end - end - - def select_watchlist_address_id(watchlist_id, address_hash) - when not is_nil(watchlist_id) and not is_nil(address_hash) do - WatchlistAddress - |> where([wa], wa.watchlist_id == ^watchlist_id and wa.address_hash_hash == ^address_hash) - |> select([wa], wa.id) - |> Repo.account_repo().one() - end - - def select_watchlist_address_id(_watchlist_id, _address_hash), do: nil - - def fetch_watchlist_transactions(watchlist_id, options) do - watchlist_addresses = - watchlist_id - |> WatchlistAddress.watchlist_addresses_by_watchlist_id_query() - |> Repo.account_repo().all() - - address_hashes = Enum.map(watchlist_addresses, fn wa -> wa.address_hash end) - - watchlist_names = - Enum.reduce(watchlist_addresses, %{}, fn wa, acc -> - Map.put(acc, wa.address_hash, %{label: wa.name, display_name: wa.name}) - end) - - {watchlist_names, address_hashes_to_mined_transactions_without_rewards(address_hashes, options)} - end - - def list_withdrawals(options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - Withdrawal.list_withdrawals() - |> join_associations(necessity_by_association) - |> handle_withdrawals_paging_options(paging_options) - |> select_repo(options).all() - end - - def sum_withdrawals do - Repo.aggregate(Withdrawal, :sum, :amount, timeout: :infinity) - end - - def upsert_count_withdrawals(index) do - upsert_last_fetched_counter(%{ - counter_type: "withdrawals_count", - value: index - }) - end - - def sum_withdrawals_from_cache(options \\ []) do - WithdrawalsSum.fetch(options) - end - - def count_withdrawals_from_cache(options \\ []) do - "withdrawals_count" |> get_last_fetched_counter(options) |> Decimal.add(1) - end - - def add_fetcher_limit(query, false), do: query - - def add_fetcher_limit(query, true) do - fetcher_limit = Application.get_env(:indexer, :fetcher_init_limit) - - limit(query, ^fetcher_limit) - end - - defp add_token_balances_fetcher_limit(query, false), do: query - - defp add_token_balances_fetcher_limit(query, true) do - token_balances_fetcher_limit = Application.get_env(:indexer, :token_balances_fetcher_init_limit) - - limit(query, ^token_balances_fetcher_limit) - end - - defp add_coin_balances_fetcher_limit(query, false), do: query - - defp add_coin_balances_fetcher_limit(query, true) do - coin_balances_fetcher_limit = Application.get_env(:indexer, :coin_balances_fetcher_init_limit) - - limit(query, ^coin_balances_fetcher_limit) - end - - @spec default_paging_options() :: map() - def default_paging_options do - @default_paging_options - end end diff --git a/apps/explorer/lib/explorer/chain/cache/accounts.ex b/apps/explorer/lib/explorer/chain/cache/accounts.ex index f24340548467..c8e61696aef0 100644 --- a/apps/explorer/lib/explorer/chain/cache/accounts.ex +++ b/apps/explorer/lib/explorer/chain/cache/accounts.ex @@ -41,6 +41,7 @@ defmodule Explorer.Chain.Cache.Accounts do # The only thing we can safely do when an address in the cache changes its # `fetched_coin_balance` is to invalidate the whole cache and wait for it # to be filled again (by the query that it takes the place of when full). + ConCache.update(cache_name(), ids_list_key(), fn ids -> if drop_needed?(ids, addresses) do # Remove the addresses immediately diff --git a/apps/explorer/lib/explorer/chain/cache/blocks.ex b/apps/explorer/lib/explorer/chain/cache/blocks.ex deleted file mode 100644 index ca968fb56d45..000000000000 --- a/apps/explorer/lib/explorer/chain/cache/blocks.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Explorer.Chain.Cache.Blocks do - @moduledoc """ - Caches the last imported blocks - """ - - alias Explorer.Chain.Block - - use Explorer.Chain.OrderedCache, - name: :blocks, - max_size: 60, - ids_list_key: "block_numbers", - preload: :transactions, - preload: [miner: :names], - preload: :rewards, - ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval], - global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl] - - @type element :: Block.t() - - @type id :: non_neg_integer() - - def element_to_id(%Block{number: number}), do: number - - def drop_nonconsensus(numbers) when is_nil(numbers) or numbers == [], do: :ok - - def drop_nonconsensus(numbers) when is_list(numbers) do - ConCache.update(cache_name(), ids_list_key(), fn ids -> - nonconsensus = MapSet.new(numbers) - - {lost_consensus, kept_consensus} = Enum.split_with(ids, &MapSet.member?(nonconsensus, &1)) - - # immediately delete the blocks that lost consensus - Enum.each(lost_consensus, &ConCache.delete(cache_name(), &1)) - - # ids_list is set to never expire - {:ok, %ConCache.Item{value: kept_consensus, ttl: :infinity}} - end) - end - - def drop_nonconsensus(number) when not is_nil(number), do: drop_nonconsensus([number]) -end diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index d09ce6d41cb0..04086974a803 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -5,367 +5,44 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do require Logger - import Ecto.Query, - only: [ - from: 2 - ] + @default_cache_period :timer.minutes(10) - alias EthereumJSONRPC.Blocks + @num_of_blocks (case Integer.parse(System.get_env("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", "200")) do + {integer, ""} -> integer + _ -> 200 + end) - alias Explorer.Chain.{ - Block, - DenormalizationHelper, - Transaction, - Wei - } + @safelow (case Integer.parse(System.get_env("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", "35")) do + {integer, ""} -> integer + _ -> 35 + end) - alias Explorer.Counters.AverageBlockTime - alias Explorer.{Market, Repo} - alias Timex.Duration + @average (case Integer.parse(System.get_env("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", "60")) do + {integer, ""} -> integer + _ -> 60 + end) + + @fast (case Integer.parse(System.get_env("GAS_PRICE_ORACLE_FAST_PERCENTILE", "90")) do + {integer, ""} -> integer + _ -> 90 + end) use Explorer.Chain.MapCache, name: :gas_price, key: :gas_prices, - key: :gas_prices_acc, - key: :updated_at, - key: :old_gas_prices, - key: :old_updated_at, key: :async_task, - global_ttl: :infinity, - ttl_check_interval: :timer.seconds(1), + global_ttl: cache_period(), + ttl_check_interval: :timer.minutes(5), callback: &async_task_on_deletion(&1) - @doc """ - Calculates how much time left till the next gas prices updated taking into account estimated query running time. - """ - @spec update_in :: non_neg_integer() - def update_in do - case {get_old_updated_at(), get_updated_at()} do - {%DateTime{} = old_updated_at, %DateTime{} = updated_at} -> - time_to_update = DateTime.diff(updated_at, old_updated_at, :millisecond) + 500 - time_since_last_update = DateTime.diff(DateTime.utc_now(), updated_at, :millisecond) - next_update_in = time_to_update - time_since_last_update - if next_update_in <= 0, do: global_ttl(), else: next_update_in - - _ -> - global_ttl() + :timer.seconds(2) - end - end - - @doc """ - Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile. - These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered. - """ - @spec get_average_gas_price(pos_integer(), pos_integer(), pos_integer(), pos_integer()) :: - {{:error, any} | {:ok, %{slow: gas_price, average: gas_price, fast: gas_price}}, - [ - %{ - block_number: non_neg_integer(), - slow_gas_price: nil | Decimal.t(), - fast_gas_price: nil | Decimal.t(), - average_gas_price: nil | Decimal.t(), - slow_priority_fee_per_gas: nil | Decimal.t(), - average_priority_fee_per_gas: nil | Decimal.t(), - fast_priority_fee_per_gas: nil | Decimal.t(), - slow_time: nil | Decimal.t(), - average_time: nil | Decimal.t(), - fast_time: nil | Decimal.t() - } - ]} - when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()} - def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do - safelow_percentile_fraction = safelow_percentile / 100 - average_percentile_fraction = average_percentile / 100 - fast_percentile_fraction = fast_percentile / 100 - - acc = get_gas_prices_acc() - - from_block = - case acc do - [%{block_number: from_block} | _] -> from_block - _ -> -1 - end - - average_block_time = - case AverageBlockTime.average_block_time() do - {:error, _} -> nil - average_block_time -> average_block_time |> Duration.to_milliseconds() - end - - fee_query = - if DenormalizationHelper.denormalization_finished?() do - from( - transaction in Transaction, - where: transaction.block_consensus == true, - where: transaction.status == ^1, - where: transaction.gas_price > ^0, - where: transaction.block_number > ^from_block, - group_by: transaction.block_number, - order_by: [desc: transaction.block_number], - select: %{ - block_number: transaction.block_number, - slow_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.gas_price - ), - average_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^average_percentile_fraction, - transaction.gas_price - ), - fast_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.gas_price - ), - slow_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - average_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^average_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - fast_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - slow_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^safelow_percentile_fraction, - transaction.block_timestamp - transaction.earliest_processing_start, - ^average_block_time - ), - average_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^average_percentile_fraction, - transaction.block_timestamp - transaction.earliest_processing_start, - ^average_block_time - ), - fast_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^fast_percentile_fraction, - transaction.block_timestamp - transaction.earliest_processing_start, - ^average_block_time - ) - }, - limit: ^num_of_blocks - ) - else - from( - block in Block, - left_join: transaction in assoc(block, :transactions), - where: block.consensus == true, - where: transaction.status == ^1, - where: transaction.gas_price > ^0, - where: transaction.block_number > ^from_block, - group_by: transaction.block_number, - order_by: [desc: transaction.block_number], - select: %{ - block_number: transaction.block_number, - slow_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.gas_price - ), - average_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^average_percentile_fraction, - transaction.gas_price - ), - fast_gas_price: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.gas_price - ), - slow_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - average_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^average_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - fast_priority_fee_per_gas: - fragment( - "percentile_disc(? :: real) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - slow_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^safelow_percentile_fraction, - block.timestamp - transaction.earliest_processing_start, - ^average_block_time - ), - average_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^average_percentile_fraction, - block.timestamp - transaction.earliest_processing_start, - ^average_block_time - ), - fast_time: - fragment( - "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", - ^fast_percentile_fraction, - block.timestamp - transaction.earliest_processing_start, - ^average_block_time - ) - }, - limit: ^num_of_blocks - ) - end - - new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks) - - gas_prices = new_acc |> process_fee_data_from_db() - - {{:ok, gas_prices}, new_acc} - catch - error -> - Logger.error("Failed to get gas prices: #{inspect(error)}") - {{:error, error}, get_gas_prices_acc()} - end - - defp merge_gas_prices(new, acc, acc_size), do: Enum.take(new ++ acc, acc_size) - - defp process_fee_data_from_db([]) do - %{ - slow: nil, - average: nil, - fast: nil - } - end - - defp process_fee_data_from_db(fees) do - %{ - slow_gas_price: slow_gas_price, - average_gas_price: average_gas_price, - fast_gas_price: fast_gas_price, - slow_priority_fee_per_gas: slow_priority_fee_per_gas, - average_priority_fee_per_gas: average_priority_fee_per_gas, - fast_priority_fee_per_gas: fast_priority_fee_per_gas, - slow_time: slow_time, - average_time: average_time, - fast_time: fast_time - } = merge_fees(fees) - - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - {slow_fee, average_fee, fast_fee} = - case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] && - EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do - {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) -> - base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei) - - { - priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei), - priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei), - priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei) - } - - _ -> - {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)} - end - - exchange_rate_from_db = Market.get_coin_exchange_rate() - - %{ - slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db), - average: compose_gas_price(average_fee, average_time, exchange_rate_from_db), - fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db) - } - end - - defp merge_fees(fees_from_db) do - fees_from_db - |> Stream.map(&Map.delete(&1, :block_number)) - |> Enum.reduce( - &Map.merge(&1, &2, fn - _, nil, nil -> nil - _, val, nil -> [val] - _, nil, acc -> if is_list(acc), do: acc, else: [acc] - _, val, acc -> if is_list(acc), do: [val | acc], else: [val, acc] - end) - ) - |> Map.new(fn - {key, nil} -> - {key, nil} - - {key, value} -> - value = if is_list(value), do: value, else: [value] - count = Enum.count(value) - {key, value |> Enum.reduce(Decimal.new(0), &Decimal.add/2) |> Decimal.div(count)} - end) - end - - defp compose_gas_price(fee, time, exchange_rate_from_db) do - %{ - price: fee |> format_wei(), - time: time && time |> Decimal.to_float(), - fiat_price: fiat_fee(fee, exchange_rate_from_db) - } - end - - defp fiat_fee(fee, exchange_rate) do - exchange_rate.usd_value && - fee - |> Wei.to(:ether) - |> Decimal.mult(exchange_rate.usd_value) - |> Decimal.mult(simple_transaction_gas()) - |> Decimal.round(2) - end - - defp priority_with_base_fee(priority, base_fee) do - priority |> Wei.from(:wei) |> Wei.sum(base_fee) - end - - defp gas_price(value) do - value |> Wei.from(:wei) - end - - defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) - - defp global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] - - defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas] - - defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks] - - defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile] - - defp average, do: Application.get_env(:explorer, __MODULE__)[:average_percentile] - - defp fast, do: Application.get_env(:explorer, __MODULE__)[:fast_percentile] + alias Explorer.Chain defp handle_fallback(:gas_prices) do # This will get the task PID if one exists and launch a new task if not # See next `handle_fallback` definition get_async_task() - {:return, get_old_gas_prices()} + {:return, nil} end defp handle_fallback(:async_task) do @@ -374,17 +51,13 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:ok, task} = Task.start(fn -> try do - {result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) + result = Chain.get_average_gas_price(@num_of_blocks, @safelow, @average, @fast) - set_gas_prices_acc(acc) - set_gas_prices(%ConCache.Item{ttl: global_ttl(), value: result}) - set_old_updated_at(get_updated_at()) - set_updated_at(DateTime.utc_now()) + set_all(result) rescue e -> - Logger.error([ - "Couldn't update gas used gas_prices", - Exception.format(:error, e, __STACKTRACE__) + Logger.debug([ + "Coudn't update gas used gas_prices #{inspect(e)}" ]) end @@ -394,18 +67,19 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:update, task} end - defp handle_fallback(:gas_prices_acc) do - {:return, []} - end - - defp handle_fallback(_), do: {:return, nil} - # By setting this as a `callback` an async task will be started each time the # `gas_prices` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :gas_prices}) do - set_old_gas_prices(get_gas_prices()) - get_async_task() - end + defp async_task_on_deletion({:delete, _, :gas_prices}), do: get_async_task() defp async_task_on_deletion(_data), do: nil + + defp cache_period do + "GAS_PRICE_ORACLE_CACHE_PERIOD" + |> System.get_env("") + |> Integer.parse() + |> case do + {integer, ""} -> :timer.seconds(integer) + _ -> @default_cache_period + end + end end diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 24f017c39228..e0a20c196d70 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -3,6 +3,8 @@ defmodule Explorer.Chain.Import do Bulk importing of data into `Explorer.Repo` """ + require Logger + alias Ecto.Changeset alias Explorer.Account.Notify alias Explorer.Chain.Events.Publisher @@ -310,6 +312,32 @@ defmodule Explorer.Chain.Import do {:ok, inserted} end + def insert_changes_list_in_batches(_module, repo, changes_list, batch_size, options) + when is_atom(repo) and is_list(changes_list) do + changes_list + |> Stream.chunk_every(batch_size) + |> Enum.map(fn changes_chunk -> + # Logger.info("### #{inspect(module)} changes_chunk size #{Enum.count(changes_chunk)} ###") + + Task.async(fn -> + insert_changes_list(repo, changes_chunk, options) + end) + end) + |> Task.yield_many(:timer.seconds(60)) + |> Enum.reduce_while({:ok, []}, fn {_task, res}, {:ok, acc} -> + # Logger.info("### #{inspect(module)} insert/update result #{inspect(res)} ###") + + case res do + {:ok, {:ok, result}} -> + new_acc = if result, do: result ++ acc, else: acc + {:cont, {:ok, new_acc}} + + error -> + {:halt, error} + end + end) + end + defp timestamp_changes_list(changes_list, timestamps) when is_list(changes_list) do Enum.map(changes_list, ×tamp_params(&1, timestamps)) end @@ -337,14 +365,32 @@ defmodule Explorer.Chain.Import do end defp logged_import(multis, options) when is_list(multis) and is_map(options) do + # Logger.info("### logged_import ###") import_id = :erlang.unique_integer([:positive]) Explorer.Logger.metadata(fn -> import_transactions(multis, options) end, import_id: import_id) end defp import_transactions(multis, options) when is_list(multis) and is_map(options) do - Enum.reduce_while(multis, {:ok, %{}}, fn multi, {:ok, acc_changes} -> - case import_transaction(multi, options) do + # Logger.info("### import_transactions with options keys #{inspect(Map.keys(options))} ###") + # Logger.info("### multis length #{Enum.count(multis)} ###") + # Logger.info("### multis #{inspect(multis)} ###") + + grouped_multis = + multis + |> Enum.chunk_by(& &1.names) + + # Logger.info("### grouped_multis length #{inspect(Enum.count(grouped_multis))} ###") + # Logger.info("### grouped_multis #{inspect(grouped_multis)} ###") + + grouped_multis + |> Enum.map(fn group -> + multis_group_reducer(group, options) + end) + |> Enum.reduce_while({:ok, %{}}, fn res, {:ok, acc_changes} -> + # Logger.info("### import_transactions results #{inspect(res)} ###") + + case res do {:ok, changes} -> {:cont, {:ok, Map.merge(acc_changes, changes)}} {:error, _, _, _} = error -> {:halt, error} end @@ -357,7 +403,38 @@ defmodule Explorer.Chain.Import do end end + defp multis_group_reducer(group, options) do + group + |> Enum.map(fn multi -> + Task.async(fn -> + import_transaction(multi, options) + end) + end) + |> Task.yield_many(:timer.seconds(60)) + |> Enum.map(fn {_task, res} -> res end) + |> Enum.reduce_while({:ok, %{}}, fn res, {:ok, acc_changes} -> + case res do + {:ok, changes} -> + changes_reducer(changes, acc_changes) + + nil -> + {:cont, {:ok, acc_changes}} + end + end) + end + + defp changes_reducer(changes, acc_changes) do + case changes do + {:ok, changes_map} -> + {:cont, {:ok, Map.merge(acc_changes, changes_map)}} + + {:error, _, _, _} = error -> + {:halt, error} + end + end + defp import_transaction(multi, options) when is_map(options) do + # Logger.info("### import_transaction ###") Repo.logged_transaction(multi, timeout: Map.get(options, :timeout, @transaction_timeout)) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex index 62b0cfa9b2e5..11d0dc4de12d 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] @@ -37,6 +38,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do @impl Import.Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info("### Address_coin_balances run STARTED changes_list length #{Enum.count(changes_list)} ###") + insert_options = options |> Map.get(option_key(), %{}) @@ -75,6 +78,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do {:ok, [%{required(:address_hash) => Hash.Address.t(), required(:block_number) => Block.block_number()}]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info("### Address_coin_balances insert started changes_list length #{Enum.count(changes_list)} ###") on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce CoinBalance ShareLocks order (see docs: sharelocks.md) @@ -83,10 +87,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do |> Enum.sort_by(&{&1.address_hash, &1.block_number}) |> Enum.dedup() + # Import.insert_changes_list_in_batches( {:ok, _} = Import.insert_changes_list( + # __MODULE__, repo, ordered_changes_list, + # 100, conflict_target: [:address_hash, :block_number], on_conflict: on_conflict, for: CoinBalance, @@ -94,6 +101,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalances do timestamps: timestamps ) + Logger.info("### Address_coin_balances insert FINISHED changes_list length #{Enum.count(changes_list)} ###") + {:ok, Enum.map(ordered_changes_list, &Map.take(&1, ~w(address_hash block_number)a))} end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex index e610ed3c81d9..3df9cbab7498 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/coin_balances_daily.ex @@ -4,13 +4,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Address.CoinBalanceDaily alias Explorer.Chain.{Hash, Import, Wei} - alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -37,6 +37,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do @impl Import.Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info("### Address_coin_balances_daily run STARTED changes_list length #{Enum.count(changes_list)} ###") + insert_options = options |> Map.get(option_key(), %{}) @@ -45,12 +47,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_coin_balances_daily, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :address_referencing, - :coin_balances_daily, - :address_coin_balances_daily - ) + insert(repo, changes_list, insert_options) end) end @@ -75,17 +72,36 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do {:ok, [%{required(:address_hash) => Hash.Address.t(), required(:day) => Date.t()}]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info("### Address_coin_balances_daily insert started changes_list length #{Enum.count(changes_list)} ###") on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - combined_changes = changes_list |> Enum.reduce(%{}, &compose_change/2) + combined_changes_list = + changes_list + |> Enum.group_by(fn %{ + address_hash: address_hash, + day: day + } -> + {address_hash, day} + end) + |> Enum.map(fn {_, grouped_address_coin_balances} -> + Enum.max_by(grouped_address_coin_balances, fn daily_balance -> + case daily_balance do + %{value: value} -> value + _ -> nil + end + end) + end) # Enforce CoinBalanceDaily ShareLocks order (see docs: sharelocks.md) - ordered_changes_list = combined_changes |> Map.values() |> Enum.sort_by(&{&1.address_hash, &1.day}) + ordered_changes_list = Enum.sort_by(combined_changes_list, &{&1.address_hash, &1.day}) + # Import.insert_changes_list_in_batches( {:ok, _} = Import.insert_changes_list( + # __MODULE__, repo, ordered_changes_list, + # 100, conflict_target: [:address_hash, :day], on_conflict: on_conflict, for: CoinBalanceDaily, @@ -93,18 +109,9 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do timestamps: timestamps ) - {:ok, Enum.map(ordered_changes_list, &Map.take(&1, ~w(address_hash day)a))} - end + Logger.info("### Address_coin_balances_daily insert FINISHED ###") - defp compose_change(change, acc) do - Map.update(acc, {change.address_hash, change.day}, change, fn existing_change -> - if Map.has_key?(change, :value) && Map.has_key?(existing_change, :value) && - change.value > existing_change.value do - change - else - existing_change - end - end) + {:ok, Enum.map(ordered_changes_list, &Map.take(&1, ~w(address_hash day)a))} end def default_on_conflict do @@ -115,14 +122,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CoinBalancesDaily do value: fragment( """ - CASE WHEN EXCLUDED.value IS NOT NULL AND (? IS NULL OR EXCLUDED.value > ?) THEN + CASE WHEN EXCLUDED.value IS NOT NULL AND EXCLUDED.value > ? THEN EXCLUDED.value ELSE ? END """, balance.value, - balance.value, balance.value ), inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", balance.inserted_at), diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex index cc51fb87376c..5bb25f7b23fa 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/current_token_balances.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] @@ -52,6 +53,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do value: Decimal.t() } def token_holder_count_deltas(%{deleted: deleted, inserted: inserted}) when is_list(deleted) and is_list(inserted) do + # Logger.info("### Blocks token_holder_count_deltas started ###") + deleted_holder_address_hash_set_by_token_contract_address_hash = to_holder_address_hash_set_by_token_contract_address_hash(deleted) @@ -82,6 +85,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do [%{contract_address_hash: token_contract_address_hash, delta: holder_count_delta}] end end) + + # Logger.info("### Blocks token_holder_count_deltas FINISHED ###") end @impl Import.Runner @@ -100,6 +105,8 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do @impl Import.Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info("### Address_current_token_balances tun STARTED changes_list length #{Enum.count(changes_list)} ###") + insert_options = options |> Map.get(option_key(), %{}) @@ -206,9 +213,13 @@ defmodule Explorer.Chain.Import.Runner.Address.CurrentTokenBalances do | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_atom(repo) and is_list(changes_list) do + Logger.info("### Address_current_token_balances insert started changes_list length #{Enum.count(changes_list)} ###") + inserted_changes_list = insert_changes_list_with_and_without_token_id(changes_list, repo, timestamps, timeout, options) + Logger.info("### Address_current_token_balances insert FINISHED ###") + {:ok, inserted_changes_list} end diff --git a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex index 618c8a920de2..27430e397d22 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/address/token_balances.ex @@ -4,13 +4,13 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.Import - alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -35,6 +35,8 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do @impl Import.Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info("### Address_token_balances run STARTED changes_list length #{Enum.count(changes_list)} ###") + insert_options = options |> Map.get(option_key(), %{}) @@ -43,12 +45,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do |> Map.put(:timestamps, timestamps) Multi.run(multi, :address_token_balances, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :token_balances, - :address_token_balances - ) + insert(repo, changes_list, insert_options) end) end @@ -63,38 +60,50 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do {:ok, [TokenBalance.t()]} | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info("### Address_token_balances insert started changes_list length #{Enum.count(changes_list)} ###") on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + %{ + changes_list_no_token_id: changes_list_no_token_id, + changes_list_with_token_id: changes_list_with_token_id + } = filter_changes_list(changes_list) + # Enforce TokenBalance ShareLocks order (see docs: sharelocks.md) - ordered_changes_list = - changes_list - |> Enum.map(fn change -> - if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do - change - else - Map.put(change, :token_id, nil) - end - end) - |> Enum.group_by(fn %{ - address_hash: address_hash, - token_contract_address_hash: token_contract_address_hash, - token_id: token_id, - block_number: block_number - } -> - {token_contract_address_hash, token_id, address_hash, block_number} - end) - |> Enum.map(fn {_, grouped_address_token_balances} -> - process_grouped_address_token_balances(grouped_address_token_balances) - end) - |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash, &1.block_number}) - - {:ok, inserted_changes_list} = - if Enum.count(ordered_changes_list) > 0 do + ordered_changes_list_no_token_id = combine_changes_list_no_token_id(changes_list_no_token_id) + # Enforce TokenBalance ShareLocks order (see docs: sharelocks.md) + ordered_changes_list_with_token_id = combine_changes_list_with_token_id(changes_list_with_token_id) + + {:ok, inserted_changes_list_no_token_id} = + if Enum.count(ordered_changes_list_no_token_id) > 0 do + # Import.insert_changes_list_in_batches( + Import.insert_changes_list( + # __MODULE__, + repo, + ordered_changes_list_no_token_id, + # 100, + conflict_target: + {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, block_number) WHERE token_id IS NULL>}, + on_conflict: on_conflict, + for: TokenBalance, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + else + {:ok, []} + end + + {:ok, inserted_changes_list_with_token_id} = + if Enum.count(ordered_changes_list_with_token_id) > 0 do + # Import.insert_changes_list_in_batches( Import.insert_changes_list( + # __MODULE__, repo, - ordered_changes_list, + ordered_changes_list_with_token_id, + # 100, conflict_target: - {:unsafe_fragment, ~s<(address_hash, token_contract_address_hash, COALESCE(token_id, -1), block_number)>}, + {:unsafe_fragment, + ~s<(address_hash, token_contract_address_hash, token_id, block_number) WHERE token_id IS NOT NULL>}, on_conflict: on_conflict, for: TokenBalance, returning: true, @@ -105,15 +114,79 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do {:ok, []} end + inserted_changes_list = inserted_changes_list_no_token_id ++ inserted_changes_list_with_token_id + + Logger.info("### Address_token_balances insert FINISHED ###") {:ok, inserted_changes_list} end - defp process_grouped_address_token_balances(grouped_address_token_balances) do - if Enum.count(grouped_address_token_balances) > 1 do - Enum.max_by(grouped_address_token_balances, fn balance -> Map.get(balance, :value_fetched_at) end) - else - Enum.at(grouped_address_token_balances, 0) - end + defp filter_changes_list(changes_list) do + changes_list + |> Enum.reduce(%{changes_list_no_token_id: [], changes_list_with_token_id: []}, fn change, acc -> + updated_change = + if Map.has_key?(change, :token_id) and Map.get(change, :token_type) == "ERC-1155" do + change + else + Map.put(change, :token_id, nil) + end + + if updated_change.token_id do + changes_list_with_token_id = [updated_change | acc.changes_list_with_token_id] + + %{ + changes_list_no_token_id: acc.changes_list_no_token_id, + changes_list_with_token_id: changes_list_with_token_id + } + else + changes_list_no_token_id = [updated_change | acc.changes_list_no_token_id] + + %{ + changes_list_no_token_id: changes_list_no_token_id, + changes_list_with_token_id: acc.changes_list_with_token_id + } + end + end) + end + + defp combine_changes_list_no_token_id(changes_list_no_token_id) do + changes_list_no_token_id + |> Enum.group_by(fn %{ + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number + } -> + {token_contract_address_hash, address_hash, block_number} + end) + |> Enum.map(fn {_, grouped_address_token_balances} -> + uniq = Enum.uniq(grouped_address_token_balances) + + if Enum.count(uniq) > 1 do + Enum.max_by(uniq, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end) + else + Enum.at(uniq, 0) + end + end) + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.address_hash, &1.block_number}) + end + + defp combine_changes_list_with_token_id(changes_list_with_token_id) do + changes_list_with_token_id + |> Enum.group_by(fn %{ + address_hash: address_hash, + token_contract_address_hash: token_contract_address_hash, + token_id: token_id, + block_number: block_number + } -> + {token_contract_address_hash, token_id, address_hash, block_number} + end) + |> Enum.map(fn {_, grouped_address_token_balances} -> + if Enum.count(grouped_address_token_balances) > 1 do + Enum.max_by(grouped_address_token_balances, fn %{value_fetched_at: value_fetched_at} -> value_fetched_at end) + else + Enum.at(grouped_address_token_balances, 0) + end + end) + |> Enum.sort_by(&{&1.token_contract_address_hash, &1.token_id, &1.address_hash, &1.block_number}) end defp default_on_conflict do @@ -121,7 +194,7 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do token_balance in TokenBalance, update: [ set: [ - value: fragment("COALESCE(EXCLUDED.value, ?)", token_balance.value), + value: fragment("EXCLUDED.value"), value_fetched_at: fragment("EXCLUDED.value_fetched_at"), token_type: fragment("EXCLUDED.token_type"), inserted_at: fragment("LEAST(EXCLUDED.inserted_at, ?)", token_balance.inserted_at), @@ -129,8 +202,9 @@ defmodule Explorer.Chain.Import.Runner.Address.TokenBalances do ] ], where: - is_nil(token_balance.value_fetched_at) or fragment("EXCLUDED.value_fetched_at IS NULL") or - fragment("? < EXCLUDED.value_fetched_at", token_balance.value_fetched_at) + fragment("EXCLUDED.value IS NOT NULL") and + (is_nil(token_balance.value_fetched_at) or + fragment("? < EXCLUDED.value_fetched_at", token_balance.value_fetched_at)) ) end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex index 23a2e7a1d04b..256a169864c2 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/addresses.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/addresses.ex @@ -4,11 +4,11 @@ defmodule Explorer.Chain.Import.Runner.Addresses do """ require Ecto.Query + require Logger alias Ecto.{Multi, Repo} alias Explorer.Chain.{Address, Hash, Import, Transaction} alias Explorer.Chain.Import.Runner - alias Explorer.Prometheus.Instrumenter import Ecto.Query, only: [from: 2] @@ -40,6 +40,9 @@ defmodule Explorer.Chain.Import.Runner.Addresses do @impl Import.Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info("### Addresses run started changes_list length #{inspect(Enum.count(changes_list))} ###") + # Logger.info("### multi #{inspect(multi)} ###") + insert_options = options |> Map.get(option_key(), %{}) @@ -58,45 +61,16 @@ defmodule Explorer.Chain.Import.Runner.Addresses do end) end) - ordered_changes_list = - changes_list_with_defaults - |> Enum.group_by(& &1.hash) - |> Enum.map(fn {_, grouped_addresses} -> - Enum.max_by(grouped_addresses, fn address -> - address_max_by(address) - end) - end) - |> Enum.sort_by(& &1.hash) + # Logger.info("### Addresses run started. Before multi.run #1 #{inspect(Multi.to_list(multi))} ###") multi - |> Multi.run(:filter_addresses, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> filter_addresses(repo, ordered_changes_list) end, - :addresses, - :addresses, - :filter_addresses - ) + |> Multi.run(:addresses, fn repo, _ -> + # Logger.info("### Addresses insert started (internal, outside) ###") + insert(repo, changes_list_with_defaults, insert_options) end) - |> Multi.run(:addresses, fn repo, %{filter_addresses: {addresses, _existing_addresses}} -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, addresses, insert_options) end, - :addresses, - :addresses, - :addresses - ) - end) - |> Multi.run(:created_address_code_indexed_at_transactions, fn repo, - %{ - addresses: addresses, - filter_addresses: {_, existing_addresses_map} - } + |> Multi.run(:created_address_code_indexed_at_transactions, fn repo, %{addresses: addresses} when is_list(addresses) -> - Instrumenter.block_import_stage_runner( - fn -> update_transactions(repo, addresses, existing_addresses_map, update_transactions_options) end, - :addresses, - :addresses, - :created_address_code_indexed_at_transactions - ) + update_transactions(repo, addresses, update_transactions_options) end) end @@ -105,79 +79,49 @@ defmodule Explorer.Chain.Import.Runner.Addresses do ## Private Functions - @spec filter_addresses(Repo.t(), [map()]) :: {:ok, {[map()], map()}} - defp filter_addresses(repo, changes_list) do - hashes = Enum.map(changes_list, & &1.hash) - - existing_addresses_query = - from(a in Address, - where: a.hash in ^hashes, - select: [:hash, :contract_code, :fetched_coin_balance_block_number, :nonce] - ) - - existing_addresses_map = - existing_addresses_query - |> repo.all() - |> Map.new(&{&1.hash, &1}) - - filtered_addresses = - changes_list - |> Enum.reduce([], fn address, acc -> - existing_address = existing_addresses_map[address.hash] - - if should_update?(address, existing_address) do - [address | acc] - else - acc - end - end) - |> Enum.reverse() - - {:ok, {filtered_addresses, existing_addresses_map}} - end - - defp should_update?(new_address, existing_address) do - is_nil(existing_address) or - (not is_nil(new_address[:contract_code]) and new_address[:contract_code] != existing_address.contract_code) or - (not is_nil(new_address[:fetched_coin_balance_block_number]) and - (is_nil(existing_address.fetched_coin_balance_block_number) or - new_address[:fetched_coin_balance_block_number] >= existing_address.fetched_coin_balance_block_number)) or - (not is_nil(new_address[:nonce]) and - (is_nil(existing_address.nonce) or new_address[:nonce] > existing_address.nonce)) - end - @spec insert(Repo.t(), [%{hash: Hash.Address.t()}], %{ optional(:on_conflict) => Import.Runner.on_conflict(), required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: {:ok, [Address.t()]} - defp insert(repo, ordered_changes_list, %{timeout: timeout, timestamps: timestamps} = options) - when is_list(ordered_changes_list) do + defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Addresses insert started changes_list length #{inspect(Enum.count(changes_list))} ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: :hash, - on_conflict: on_conflict, - for: Address, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - end - - defp address_max_by(address) do - cond do - Map.has_key?(address, :address) -> - address.fetched_coin_balance_block_number - - Map.has_key?(address, :nonce) -> - address.nonce + # Enforce Address ShareLocks order (see docs: sharelocks.md) + ordered_changes_list = sort_changes_list(changes_list) + + # Logger.info( + # inspect( + # changes_list + # |> Enum.map( + # &%{ + # hash: &1.hash |> to_string(), + # fetched_coin_balance: if(Map.has_key?(&1, :fetched_coin_balance), do: &1.fetched_coin_balance, else: nil), + # fetched_coin_balance_block_number: + # if(Map.has_key?(&1, :fetched_coin_balance_block_number), + # do: &1.fetched_coin_balance_block_number, + # else: nil + # ) + # } + # ) + # ) + # ) + + res = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :hash, + on_conflict: on_conflict, + for: Address, + returning: true, + timeout: timeout, + timestamps: timestamps + ) - true -> - address - end + Logger.info(["### Addresses insert FINISHED changes_list length #{inspect(Enum.count(changes_list))} ###"]) + res end defp default_on_conflict do @@ -224,14 +168,14 @@ defmodule Explorer.Chain.Import.Runner.Addresses do ) end - defp update_transactions(repo, addresses, existing_addresses_map, %{timeout: timeout, timestamps: timestamps}) do + defp sort_changes_list(changes_list) do + Enum.sort_by(changes_list, & &1.hash) + end + + defp update_transactions(repo, addresses, %{timeout: timeout, timestamps: timestamps}) do ordered_created_contract_hashes = addresses - |> Enum.filter(fn address -> - existing_address = existing_addresses_map[address.hash] - - not is_nil(address.contract_code) and (is_nil(existing_address) or is_nil(existing_address.contract_code)) - end) + |> Enum.filter(& &1.contract_code) |> MapSet.new(& &1.hash) |> Enum.sort() @@ -243,7 +187,7 @@ defmodule Explorer.Chain.Import.Runner.Addresses do where: t.created_contract_address_hash in ^ordered_created_contract_hashes, # Enforce Transaction ShareLocks order (see docs: sharelocks.md) order_by: t.hash, - lock: "FOR NO KEY UPDATE" + lock: "FOR UPDATE" ) try do diff --git a/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex b/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex index cf13ae1b2278..978bd6ab7731 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/block/rewards.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Block.Rewards do """ import Ecto.Query, only: [from: 2] + require Logger alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.Block.Reward @@ -58,21 +59,27 @@ defmodule Explorer.Chain.Import.Runner.Block.Rewards do }) :: {:ok, [Reward.t()]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Block rewards insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce Reward ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.block_hash, &1.address_hash, &1.address_type}) - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: [:address_hash, :address_type, :block_hash], - on_conflict: on_conflict, - for: ecto_schema_module(), - returning: true, - timeout: timeout, - timestamps: timestamps - ) + res = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: [:address_hash, :address_type, :block_hash], + on_conflict: on_conflict, + for: ecto_schema_module(), + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + Logger.info(["### Block rewards insert FINISHED ###"]) + + res end defp default_on_conflict do diff --git a/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex b/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex index 0383e681d31d..ef4e7b4dbed2 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/block/second_degree_relations.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] @@ -66,6 +67,7 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do {:ok, nil | %{nephew_hash: Hash.Full.t(), uncle_hash: Hash.Full.t(), index: non_neg_integer()}} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout} = options) when is_atom(repo) and is_list(changes_list) do + Logger.info(["### Second degree relations insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce SeconDegreeRelation ShareLocks order (see docs: sharelocks.md) @@ -74,15 +76,20 @@ defmodule Explorer.Chain.Import.Runner.Block.SecondDegreeRelations do |> Enum.sort_by(&{&1.nephew_hash, &1.uncle_hash}) |> Enum.dedup() - Import.insert_changes_list(repo, ordered_changes_list, - conflict_target: [:nephew_hash, :uncle_hash], - on_conflict: on_conflict, - for: Block.SecondDegreeRelation, - returning: [:nephew_hash, :uncle_hash, :index], - timeout: timeout, - # block_second_degree_relations doesn't have timestamps - timestamps: %{} - ) + res = + Import.insert_changes_list(repo, ordered_changes_list, + conflict_target: [:nephew_hash, :uncle_hash], + on_conflict: on_conflict, + for: Block.SecondDegreeRelation, + returning: [:nephew_hash, :uncle_hash, :index], + timeout: timeout, + # block_second_degree_relations doesn't have timestamps + timestamps: %{} + ) + + Logger.info(["### Second degree relations insert FINISHED ###"]) + + res end defp default_on_conflict do diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index 78cd1a01183f..f9332388ea2d 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -4,31 +4,17 @@ defmodule Explorer.Chain.Import.Runner.Blocks do """ require Ecto.Query + require Logger - import Ecto.Query, only: [from: 2, where: 3, subquery: 1] + import Ecto.Query, only: [from: 2, subquery: 1] alias Ecto.{Changeset, Multi, Repo} - - alias EthereumJSONRPC.Utility.RangesHelper - - alias Explorer.Chain.{ - Address, - Block, - Import, - PendingBlockOperation, - Token, - Token.Instance, - TokenTransfer, - Transaction - } - + alias Explorer.Chain.{Address, Block, Import, PendingBlockOperation, Transaction} alias Explorer.Chain.Block.Reward alias Explorer.Chain.Import.Runner alias Explorer.Chain.Import.Runner.Address.CurrentTokenBalances - alias Explorer.Chain.Import.Runner.{TokenInstances, Tokens} - alias Explorer.Prometheus.Instrumenter + alias Explorer.Chain.Import.Runner.Tokens alias Explorer.Repo, as: ExplorerRepo - alias Explorer.Utility.MissingRangesManipulator @behaviour Runner @@ -53,6 +39,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do @impl Runner def run(multi, changes_list, %{timestamps: timestamps} = options) do + Logger.info(["### Blocks run STARTED ###"]) + insert_options = options |> Map.get(option_key(), %{}) @@ -61,175 +49,106 @@ defmodule Explorer.Chain.Import.Runner.Blocks do |> Map.put(:timestamps, timestamps) hashes = Enum.map(changes_list, & &1.hash) - - items_for_pending_ops = - changes_list - |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number)) - |> Enum.filter(& &1.consensus) - |> Enum.map(&{&1.number, &1.hash}) - consensus_block_numbers = consensus_block_numbers(changes_list) # Enforce ShareLocks tables order (see docs: sharelocks.md) - run_func = fn repo -> - {:ok, nonconsensus_items} = lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options) - - {:ok, - filter_by_height_range(nonconsensus_items, fn {number, _hash} -> RangesHelper.traceable_block_number?(number) end)} - end - multi |> Multi.run(:lose_consensus, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> run_func.(repo) end, - :address_referencing, - :blocks, - :lose_consensus - ) + lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options) end) |> Multi.run(:blocks, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> - # Note, needs to be executed after `lose_consensus` for lock acquisition - insert(repo, changes_list, insert_options) - end, - :address_referencing, - :blocks, - :blocks - ) + # Note, needs to be executed after `lose_consensus` for lock acquisition + insert(repo, changes_list, insert_options) end) - |> Multi.run(:new_pending_operations, fn repo, %{lose_consensus: nonconsensus_items} -> - Instrumenter.block_import_stage_runner( - fn -> - new_pending_operations(repo, nonconsensus_items, items_for_pending_ops, insert_options) - end, - :address_referencing, - :blocks, - :new_pending_operations - ) + |> Multi.run(:new_pending_operations, fn repo, %{lose_consensus: nonconsensus_hashes} -> + new_pending_operations(repo, nonconsensus_hashes, hashes, insert_options) end) |> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> - update_block_second_degree_relations(repo, hashes, %{ - timeout: - options[Runner.Block.SecondDegreeRelations.option_key()][:timeout] || - Runner.Block.SecondDegreeRelations.timeout(), - timestamps: timestamps - }) - end, - :address_referencing, - :blocks, - :uncle_fetched_block_second_degree_relations - ) + update_block_second_degree_relations(repo, hashes, %{ + timeout: + options[Runner.Block.SecondDegreeRelations.option_key()][:timeout] || + Runner.Block.SecondDegreeRelations.timeout(), + timestamps: timestamps + }) end) |> Multi.run(:delete_rewards, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> delete_rewards(repo, changes_list, insert_options) end, - :address_referencing, - :blocks, - :delete_rewards - ) + delete_rewards(repo, changes_list, insert_options) end) |> Multi.run(:fork_transactions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> - fork_transactions(%{ - repo: repo, - timeout: options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout(), - timestamps: timestamps, - blocks_changes: changes_list - }) - end, - :address_referencing, - :blocks, - :fork_transactions - ) + fork_transactions(%{ + repo: repo, + timeout: options[Runner.Transactions.option_key()][:timeout] || Runner.Transactions.timeout(), + timestamps: timestamps, + blocks_changes: changes_list + }) end) |> Multi.run(:derive_transaction_forks, fn repo, %{fork_transactions: transactions} -> - Instrumenter.block_import_stage_runner( - fn -> - derive_transaction_forks(%{ - repo: repo, - timeout: options[Runner.Transaction.Forks.option_key()][:timeout] || Runner.Transaction.Forks.timeout(), - timestamps: timestamps, - transactions: transactions - }) - end, - :address_referencing, - :blocks, - :derive_transaction_forks - ) + derive_transaction_forks(%{ + repo: repo, + timeout: options[Runner.Transaction.Forks.option_key()][:timeout] || Runner.Transaction.Forks.timeout(), + timestamps: timestamps, + transactions: transactions + }) end) - |> Multi.run(:delete_address_token_balances, fn repo, %{lose_consensus: non_consensus_blocks} -> - Instrumenter.block_import_stage_runner( - fn -> delete_address_token_balances(repo, non_consensus_blocks, insert_options) end, - :address_referencing, - :blocks, - :delete_address_token_balances - ) + |> Multi.run(:acquire_contract_address_tokens, fn repo, _ -> + acquire_contract_address_tokens(repo, consensus_block_numbers) end) - |> Multi.run(:delete_address_current_token_balances, fn repo, %{lose_consensus: non_consensus_blocks} -> - Instrumenter.block_import_stage_runner( - fn -> delete_address_current_token_balances(repo, non_consensus_blocks, insert_options) end, - :address_referencing, - :blocks, - :delete_address_current_token_balances - ) + |> Multi.run(:delete_address_token_balances, fn repo, _ -> + delete_address_token_balances(repo, consensus_block_numbers, insert_options) + end) + |> Multi.run(:delete_address_current_token_balances, fn repo, _ -> + delete_address_current_token_balances(repo, consensus_block_numbers, insert_options) end) |> Multi.run(:derive_address_current_token_balances, fn repo, %{ delete_address_current_token_balances: deleted_address_current_token_balances } -> - Instrumenter.block_import_stage_runner( - fn -> derive_address_current_token_balances(repo, deleted_address_current_token_balances, insert_options) end, - :address_referencing, - :blocks, - :derive_address_current_token_balances - ) - end) - |> Multi.run(:update_token_instances_owner, fn repo, %{derive_transaction_forks: transactions} -> - Instrumenter.block_import_stage_runner( - fn -> update_token_instances_owner(repo, transactions, insert_options) end, - :address_referencing, - :blocks, - :update_token_instances_owner - ) + derive_address_current_token_balances(repo, deleted_address_current_token_balances, insert_options) end) |> Multi.run(:blocks_update_token_holder_counts, fn repo, %{ delete_address_current_token_balances: deleted, derive_address_current_token_balances: inserted } -> - Instrumenter.block_import_stage_runner( - fn -> - deltas = CurrentTokenBalances.token_holder_count_deltas(%{deleted: deleted, inserted: inserted}) - Tokens.update_holder_counts_with_deltas(repo, deltas, insert_options) - end, - :address_referencing, - :blocks, - :blocks_update_token_holder_counts - ) + deltas = CurrentTokenBalances.token_holder_count_deltas(%{deleted: deleted, inserted: inserted}) + Tokens.update_holder_counts_with_deltas(repo, deltas, insert_options) end) end @impl Runner def timeout, do: @timeout + defp acquire_contract_address_tokens(repo, consensus_block_numbers) do + Logger.info(["### Blocks acquire_contract_address_tokens started ###"]) + + query = + from(ctb in Address.CurrentTokenBalance, + where: ctb.block_number in ^consensus_block_numbers, + select: {ctb.token_contract_address_hash, ctb.token_id}, + distinct: [ctb.token_contract_address_hash, ctb.token_id] + ) + + contract_address_hashes_and_token_ids = repo.all(query) + + Tokens.acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids) + end + defp fork_transactions(%{ repo: repo, timeout: timeout, timestamps: %{updated_at: updated_at}, blocks_changes: blocks_changes }) do + Logger.info(["### Blocks fork_transactions started ###"]) + query = from( transaction in where_forked(blocks_changes), select: transaction, # Enforce Transaction ShareLocks order (see docs: sharelocks.md) order_by: [asc: :hash], - lock: "FOR NO KEY UPDATE" + lock: "FOR UPDATE" ) update_query = @@ -246,9 +165,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do index: nil, status: nil, error: nil, - max_priority_fee_per_gas: nil, - max_fee_per_gas: nil, - type: nil, updated_at: ^updated_at ] ], @@ -257,9 +173,12 @@ defmodule Explorer.Chain.Import.Runner.Blocks do {_num, transactions} = repo.update_all(update_query, [], timeout: timeout) + Logger.info(["### Blocks fork_transactions FINISHED ###"]) + {:ok, transactions} rescue postgrex_error in Postgrex.Error -> + Logger.info(["### Blocks fork_transactions ERROR ###"]) {:error, %{exception: postgrex_error}} end @@ -269,6 +188,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timestamps: %{inserted_at: inserted_at, updated_at: updated_at}, transactions: transactions }) do + Logger.info(["### Blocks derive_transaction_forks started ###"]) + transaction_forks = transactions |> Enum.map(fn transaction -> @@ -298,6 +219,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timeout: timeout ) + Logger.info(["### Blocks derive_transaction_forks FINISHED ###"]) + {:ok, Enum.map(forked_transaction, & &1.hash)} end @@ -307,6 +230,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do required(:timestamps) => Import.timestamps() }) :: {:ok, [Block.t()]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Blocks insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce Block ShareLocks order (see docs: sharelocks.md) @@ -315,16 +239,21 @@ defmodule Explorer.Chain.Import.Runner.Blocks do |> Enum.sort_by(& &1.hash) |> Enum.dedup_by(& &1.hash) - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: :hash, - on_conflict: on_conflict, - for: Block, - returning: true, - timeout: timeout, - timestamps: timestamps - ) + res = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :hash, + on_conflict: on_conflict, + for: Block, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + Logger.info(["### Blocks insert finished ###"]) + + res end # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity @@ -344,6 +273,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do size: fragment("EXCLUDED.size"), timestamp: fragment("EXCLUDED.timestamp"), total_difficulty: fragment("EXCLUDED.total_difficulty"), + base_fee_per_gas: fragment("EXCLUDED.base_fee_per_gas"), + is_empty: fragment("EXCLUDED.is_empty"), # Don't update `hash` as it is used for the conflict target inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", block.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", block.updated_at) @@ -369,16 +300,18 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timeout: timeout, timestamps: %{updated_at: updated_at} }) do + Logger.info(["### Blocks lose_consensus started ###"]) + acquire_query = from( - block in where_invalid_neighbor(changes_list), + block in where_invalid_neighbour(changes_list), or_where: block.number in ^consensus_block_numbers, # we also need to acquire blocks that will be upserted here, for ordering or_where: block.hash in ^hashes, select: block.hash, # Enforce Block ShareLocks order (see docs: sharelocks.md) order_by: [asc: block.hash], - lock: "FOR NO KEY UPDATE" + lock: "FOR UPDATE" ) {_, removed_consensus_block_hashes} = @@ -389,32 +322,18 @@ defmodule Explorer.Chain.Import.Runner.Blocks do on: block.hash == s.hash, # we don't want to remove consensus from blocks that will be upserted where: block.hash not in ^hashes, - select: {block.number, block.hash} + select: block.hash ), [set: [consensus: false, updated_at: updated_at]], timeout: timeout ) - repo.update_all( - from( - transaction in Transaction, - join: s in subquery(acquire_query), - on: transaction.block_hash == s.hash, - # we don't want to remove consensus from blocks that will be upserted - where: transaction.block_hash not in ^hashes - ), - [set: [block_consensus: false, updated_at: updated_at]], - timeout: timeout - ) - - removed_consensus_block_hashes - |> Enum.map(fn {number, _hash} -> number end) - |> Enum.reject(&Enum.member?(consensus_block_numbers, &1)) - |> MissingRangesManipulator.add_ranges_by_block_numbers() + Logger.info(["### Blocks lose_consensus FINISHED ###"]) {:ok, removed_consensus_block_hashes} rescue postgrex_error in Postgrex.Error -> + Logger.info(["### Blocks lose_consensus ERROR ###"]) {:error, %{exception: postgrex_error, consensus_block_numbers: consensus_block_numbers}} end @@ -427,39 +346,42 @@ defmodule Explorer.Chain.Import.Runner.Blocks do lose_consensus(ExplorerRepo, [], block_numbers, [], opts) end - defp new_pending_operations(repo, nonconsensus_items, items, %{ - timeout: timeout, - timestamps: timestamps - }) do - sorted_pending_ops = - items - |> MapSet.new() - |> MapSet.difference(MapSet.new(nonconsensus_items)) - |> Enum.sort() - |> Enum.map(fn {number, hash} -> - %{block_hash: hash, block_number: number} - end) + defp new_pending_operations(repo, nonconsensus_hashes, hashes, %{timeout: timeout, timestamps: timestamps}) do + Logger.info(["### Blocks new_pending_operations started ###"]) - Import.insert_changes_list( - repo, - sorted_pending_ops, - conflict_target: :block_hash, - on_conflict: :nothing, - for: PendingBlockOperation, - returning: true, - timeout: timeout, - timestamps: timestamps - ) + if Application.get_env(:explorer, :json_rpc_named_arguments)[:variant] == EthereumJSONRPC.RSK do + {:ok, []} + else + sorted_pending_ops = + nonconsensus_hashes + |> MapSet.new() + |> MapSet.union(MapSet.new(hashes)) + |> Enum.sort() + |> Enum.map(fn hash -> + %{block_hash: hash, fetch_internal_transactions: true} + end) + + Import.insert_changes_list( + repo, + sorted_pending_ops, + conflict_target: :block_hash, + on_conflict: PendingBlockOperation.default_on_conflict(), + for: PendingBlockOperation, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + end end defp delete_address_token_balances(_, [], _), do: {:ok, []} - defp delete_address_token_balances(repo, non_consensus_blocks, %{timeout: timeout}) do - non_consensus_block_numbers = Enum.map(non_consensus_blocks, fn {number, _hash} -> number end) + defp delete_address_token_balances(repo, consensus_block_numbers, %{timeout: timeout}) do + Logger.info(["### Blocks delete_address_token_balances started ###"]) ordered_query = from(tb in Address.TokenBalance, - where: tb.block_number in ^non_consensus_block_numbers, + where: tb.block_number in ^consensus_block_numbers, select: map(tb, [:address_hash, :token_contract_address_hash, :token_id, :block_number]), # Enforce TokenBalance ShareLocks order (see docs: sharelocks.md) order_by: [ @@ -488,21 +410,24 @@ defmodule Explorer.Chain.Import.Runner.Blocks do try do {_count, deleted_address_token_balances} = repo.delete_all(query, timeout: timeout) + Logger.info(["### Blocks delete_address_token_balances FINISHED ###"]) + {:ok, deleted_address_token_balances} rescue postgrex_error in Postgrex.Error -> - {:error, %{exception: postgrex_error, block_numbers: non_consensus_block_numbers}} + Logger.info(["### Blocks delete_address_token_balances ERROR ###"]) + {:error, %{exception: postgrex_error, block_numbers: consensus_block_numbers}} end end defp delete_address_current_token_balances(_, [], _), do: {:ok, []} - defp delete_address_current_token_balances(repo, non_consensus_blocks, %{timeout: timeout}) do - non_consensus_block_numbers = Enum.map(non_consensus_blocks, fn {number, _hash} -> number end) + defp delete_address_current_token_balances(repo, consensus_block_numbers, %{timeout: timeout}) do + Logger.info(["### Blocks delete_address_current_token_balances started ###"]) ordered_query = from(ctb in Address.CurrentTokenBalance, - where: ctb.block_number in ^non_consensus_block_numbers, + where: ctb.block_number in ^consensus_block_numbers, select: map(ctb, [:address_hash, :token_contract_address_hash, :token_id]), # Enforce CurrentTokenBalance ShareLocks order (see docs: sharelocks.md) order_by: [ @@ -518,10 +443,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do select: map(ctb, [ :address_hash, - :block_number, :token_contract_address_hash, :token_id, - :token_type, # Used to determine if `address_hash` was a holder of `token_contract_address_hash` before # `address_current_token_balance` is deleted in `update_tokens_holder_count`. @@ -539,10 +462,12 @@ defmodule Explorer.Chain.Import.Runner.Blocks do try do {_count, deleted_address_current_token_balances} = repo.delete_all(query, timeout: timeout) + Logger.info(["### Blocks delete_address_current_token_balances FINISHED ###"]) {:ok, deleted_address_current_token_balances} rescue postgrex_error in Postgrex.Error -> - {:error, %{exception: postgrex_error, block_numbers: non_consensus_block_numbers}} + Logger.info(["### Blocks delete_address_current_token_balances ERROR ###"]) + {:error, %{exception: postgrex_error, block_numbers: consensus_block_numbers}} end end @@ -554,28 +479,43 @@ defmodule Explorer.Chain.Import.Runner.Blocks do %{timeout: timeout} = options ) when is_list(deleted_address_current_token_balances) do - new_current_token_balances_placeholders = - Enum.map(deleted_address_current_token_balances, fn deleted_balance -> - now = DateTime.utc_now() + Logger.info(["### Blocks derive_address_current_token_balances started ###"]) - %{ - address_hash: deleted_balance.address_hash, - token_contract_address_hash: deleted_balance.token_contract_address_hash, - token_id: deleted_balance.token_id, - token_type: deleted_balance.token_type, - block_number: deleted_balance.block_number, - value: nil, - value_fetched_at: nil, - inserted_at: now, - updated_at: now - } - end) + final_query = derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) + + new_current_token_balance_query = + from(new_current_token_balance in subquery(final_query), + inner_join: tb in Address.TokenBalance, + on: + tb.address_hash == new_current_token_balance.address_hash and + tb.token_contract_address_hash == new_current_token_balance.token_contract_address_hash and + ((is_nil(tb.token_id) and is_nil(new_current_token_balance.token_id)) or + (tb.token_id == new_current_token_balance.token_id and + not is_nil(tb.token_id) and not is_nil(new_current_token_balance.token_id))) and + tb.block_number == new_current_token_balance.block_number, + select: %{ + address_hash: new_current_token_balance.address_hash, + token_contract_address_hash: new_current_token_balance.token_contract_address_hash, + token_id: new_current_token_balance.token_id, + block_number: new_current_token_balance.block_number, + value: tb.value, + inserted_at: over(min(tb.inserted_at), :w), + updated_at: over(max(tb.updated_at), :w) + }, + windows: [ + w: [partition_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id]] + ] + ) + + current_token_balance = + new_current_token_balance_query + |> repo.all() timestamps = Import.timestamps() result = CurrentTokenBalances.insert_changes_list_with_and_without_token_id( - new_current_token_balances_placeholders, + current_token_balance, repo, timestamps, timeout, @@ -585,161 +525,52 @@ defmodule Explorer.Chain.Import.Runner.Blocks do derived_address_current_token_balances = Enum.map(result, &Map.take(&1, [:address_hash, :token_contract_address_hash, :token_id, :block_number, :value])) + Logger.info(["### Blocks derive_address_current_token_balances FINISHED ###"]) {:ok, derived_address_current_token_balances} end - defp update_token_instances_owner(_, [], _), do: {:ok, []} - - defp update_token_instances_owner(repo, forked_transaction_hashes, options) do - forked_transaction_hashes - |> forked_token_transfers_query() - |> repo.all() - |> process_forked_token_transfers(repo, options) - end - - defp process_forked_token_transfers([], _, _), do: {:ok, []} - - defp process_forked_token_transfers(token_transfers, repo, options) do - changes_initial = - Enum.reduce(token_transfers, %{}, fn tt, acc -> - Map.put_new(acc, {tt.token_contract_address_hash, tt.token_id}, %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_id: tt.token_id, - owner_address_hash: tt.from, - owner_updated_at_block: -1, - owner_updated_at_log_index: -1 - }) - end) - - non_consensus_block_numbers = token_transfers |> Enum.map(fn tt -> tt.block_number end) |> Enum.uniq() - - filtered_query = TokenTransfer.only_consensus_transfers_query() - - base_query = - from(token_transfer in subquery(filtered_query), + defp derive_address_current_token_balances_grouped_query(deleted_address_current_token_balances) do + initial_query = + from(tb in Address.TokenBalance, select: %{ - token_contract_address_hash: token_transfer.token_contract_address_hash, - token_id: fragment("(?)[1]", token_transfer.token_ids), - block_number: max(token_transfer.block_number) + address_hash: tb.address_hash, + token_contract_address_hash: tb.token_contract_address_hash, + token_id: tb.token_id, + block_number: max(tb.block_number) }, - group_by: [token_transfer.token_contract_address_hash, fragment("(?)[1]", token_transfer.token_ids)] + group_by: [tb.address_hash, tb.token_contract_address_hash, tb.token_id] ) - historical_token_transfers_query = - Enum.reduce(token_transfers, base_query, fn tt, acc -> - from(token_transfer in acc, + Enum.reduce(deleted_address_current_token_balances, initial_query, fn %{ + address_hash: address_hash, + token_contract_address_hash: + token_contract_address_hash, + token_id: token_id + }, + acc_query -> + if token_id do + from(tb in acc_query, or_where: - token_transfer.token_contract_address_hash == ^tt.token_contract_address_hash and - fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, ^tt.token_id) and - token_transfer.block_number < ^tt.block_number and - token_transfer.block_number not in ^non_consensus_block_numbers + tb.address_hash == ^address_hash and + tb.token_contract_address_hash == ^token_contract_address_hash and + tb.token_id == ^token_id ) - end) - - refs_to_token_transfers = - from(historical_tt in subquery(historical_token_transfers_query), - inner_join: tt in subquery(filtered_query), - on: - tt.token_contract_address_hash == historical_tt.token_contract_address_hash and - tt.block_number == historical_tt.block_number and - fragment("? @> ARRAY[?::decimal]", tt.token_ids, historical_tt.token_id), - select: %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_id: historical_tt.token_id, - log_index: max(tt.log_index), - block_number: tt.block_number - }, - group_by: [tt.token_contract_address_hash, historical_tt.token_id, tt.block_number] - ) - - derived_token_transfers_query = - from(tt in filtered_query, - inner_join: tt_1 in subquery(refs_to_token_transfers), - on: tt_1.log_index == tt.log_index and tt_1.block_number == tt.block_number - ) - - changes = - derived_token_transfers_query - |> repo.all() - |> Enum.reduce(changes_initial, fn tt, acc -> - token_id = List.first(tt.token_ids) - current_key = {tt.token_contract_address_hash, token_id} - - params = %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_id: token_id, - owner_address_hash: tt.to_address_hash, - owner_updated_at_block: tt.block_number, - owner_updated_at_log_index: tt.log_index - } - - Map.put( - acc, - current_key, - Enum.max_by([acc[current_key], params], fn %{ - owner_updated_at_block: block_number, - owner_updated_at_log_index: log_index - } -> - {block_number, log_index} - end) + else + from(tb in acc_query, + or_where: + tb.address_hash == ^address_hash and + tb.token_contract_address_hash == ^token_contract_address_hash and + is_nil(tb.token_id) ) - end) - |> Map.values() - - TokenInstances.insert( - repo, - changes, - options - |> Map.put(:timestamps, Import.timestamps()) - |> Map.put(:on_conflict, token_instances_on_conflict()) - ) - end - - defp forked_token_transfers_query(forked_transaction_hashes) do - from(token_transfer in TokenTransfer, - where: token_transfer.transaction_hash in ^forked_transaction_hashes, - inner_join: token in Token, - on: token.contract_address_hash == token_transfer.token_contract_address_hash, - where: token.type == "ERC-721", - inner_join: instance in Instance, - on: - fragment("? @> ARRAY[?::decimal]", token_transfer.token_ids, instance.token_id) and - instance.token_contract_address_hash == token_transfer.token_contract_address_hash, - # per one token instance we will have only one token transfer - where: - token_transfer.block_number == instance.owner_updated_at_block and - token_transfer.log_index == instance.owner_updated_at_log_index, - select: %{ - from: token_transfer.from_address_hash, - to: token_transfer.to_address_hash, - token_id: instance.token_id, - token_contract_address_hash: token_transfer.token_contract_address_hash, - block_number: token_transfer.block_number, - log_index: token_transfer.log_index - } - ) - end - - defp token_instances_on_conflict do - from( - token_instance in Instance, - update: [ - set: [ - metadata: token_instance.metadata, - error: token_instance.error, - owner_updated_at_block: fragment("EXCLUDED.owner_updated_at_block"), - owner_updated_at_log_index: fragment("EXCLUDED.owner_updated_at_log_index"), - owner_address_hash: fragment("EXCLUDED.owner_address_hash"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_instance.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_instance.updated_at) - ] - ] - ) + end + end) end # `block_rewards` are linked to `blocks.hash`, but fetched by `blocks.number`, so when a block with the same number is # inserted, the old block rewards need to be deleted, so that the old and new rewards aren't combined. defp delete_rewards(repo, blocks_changes, %{timeout: timeout}) do + Logger.info(["### Blocks delete_rewards started ###"]) + {hashes, numbers} = Enum.reduce(blocks_changes, {[], []}, fn %{consensus: false, hash: hash}, {acc_hashes, acc_numbers} -> @@ -771,9 +602,11 @@ defmodule Explorer.Chain.Import.Runner.Blocks do try do {count, nil} = repo.delete_all(delete_query, timeout: timeout) + Logger.info(["### Blocks delete_rewards FINISHED ###"]) {:ok, count} rescue postgrex_error in Postgrex.Error -> + Logger.info(["### Blocks delete_rewards ERROR ###"]) {:error, %{exception: postgrex_error, blocks_changes: blocks_changes}} end end @@ -783,13 +616,15 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timestamps: %{updated_at: updated_at} }) when is_list(uncle_hashes) do + Logger.info(["### Blocks update_block_second_degree_relations started ###"]) + query = from( bsdr in Block.SecondDegreeRelation, where: bsdr.uncle_hash in ^uncle_hashes, # Enforce SeconDegreeRelation ShareLocks order (see docs: sharelocks.md) order_by: [asc: :nephew_hash, asc: :uncle_hash], - lock: "FOR NO KEY UPDATE" + lock: "FOR UPDATE" ) update_query = @@ -804,9 +639,11 @@ defmodule Explorer.Chain.Import.Runner.Blocks do try do {_, result} = repo.update_all(update_query, [], timeout: timeout) + Logger.info(["### Blocks update_block_second_degree_relations FINISHED ###"]) {:ok, result} rescue postgrex_error in Postgrex.Error -> + Logger.info(["### Blocks update_block_second_degree_relations ERROR ###"]) {:error, %{exception: postgrex_error, uncle_hashes: uncle_hashes}} end end @@ -823,36 +660,25 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end) end - defp where_invalid_neighbor(blocks_changes) when is_list(blocks_changes) do + defp where_invalid_neighbour(blocks_changes) when is_list(blocks_changes) do initial = from(b in Block, where: false) - invalid_neighbors_query = - Enum.reduce(blocks_changes, initial, fn %{ - consensus: consensus, - hash: hash, - parent_hash: parent_hash, - number: number - }, - acc -> - if consensus do - from( - block in acc, - or_where: block.number == ^(number - 1) and block.hash != ^parent_hash, - or_where: block.number == ^(number + 1) and block.parent_hash != ^hash - ) - else - acc - end - end) - - where(invalid_neighbors_query, [block], block.consensus) - end - - defp filter_by_height_range(blocks, filter_func) do - if RangesHelper.trace_ranges_present?() do - Enum.filter(blocks, &filter_func.(&1)) - else - blocks - end + Enum.reduce(blocks_changes, initial, fn %{ + consensus: consensus, + hash: hash, + parent_hash: parent_hash, + number: number + }, + acc -> + if consensus do + from( + block in acc, + or_where: block.number == ^(number - 1) and block.hash != ^parent_hash, + or_where: block.number == ^(number + 1) and block.parent_hash != ^hash + ) + else + acc + end + end) end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 9668f43a66c8..70b36f103ae1 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -140,6 +140,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do | {:error, [Changeset.t()]} defp insert(repo, valid_internal_transactions, %{timeout: timeout, timestamps: timestamps} = options) when is_list(valid_internal_transactions) do + Logger.info("### Internal_transactions insert started ###") on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) ordered_changes_list = Enum.sort_by(valid_internal_transactions, &{&1.transaction_hash, &1.index}) @@ -156,6 +157,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do timestamps: timestamps ) + Logger.info("### Internal_transactions insert FINISHED ###") + {:ok, internal_transactions} end diff --git a/apps/explorer/lib/explorer/chain/import/runner/logs.ex b/apps/explorer/lib/explorer/chain/import/runner/logs.ex index 7c0e591a0f92..de4eba5c1ac5 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/logs.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Logs do """ require Ecto.Query + require Logger alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Import, Log} @@ -62,12 +63,13 @@ defmodule Explorer.Chain.Import.Runner.Logs do {:ok, [Log.t()]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Logs insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce Log ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.block_hash, &1.index}) - {:ok, _} = + {:ok, logs} = Import.insert_changes_list( repo, ordered_changes_list, @@ -78,6 +80,9 @@ defmodule Explorer.Chain.Import.Runner.Logs do timeout: timeout, timestamps: timestamps ) + + Logger.info(["### Logs insert FINISHED ###"]) + {:ok, logs} end defp default_on_conflict do diff --git a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex index f50e98691e61..d104c71a0a95 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/token_transfers.ex @@ -4,12 +4,12 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] alias Ecto.{Changeset, Multi, Repo} alias Explorer.Chain.{Import, TokenTransfer} - alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -42,12 +42,7 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do |> Map.put(:timestamps, timestamps) Multi.run(multi, :token_transfers, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :token_transfers, - :token_transfers - ) + insert(repo, changes_list, insert_options) end) end @@ -58,12 +53,13 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do {:ok, [TokenTransfer.t()]} | {:error, [Changeset.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Token transfers insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce TokenTransfer ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.block_hash, &1.log_index}) - {:ok, inserted} = + {:ok, token_transfers} = Import.insert_changes_list( repo, ordered_changes_list, @@ -75,7 +71,9 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do timestamps: timestamps ) - {:ok, inserted} + Logger.info(["### Token transfers insert FINISHED ###"]) + + {:ok, token_transfers} end defp default_on_conflict do @@ -89,19 +87,19 @@ defmodule Explorer.Chain.Import.Runner.TokenTransfers do from_address_hash: fragment("EXCLUDED.from_address_hash"), to_address_hash: fragment("EXCLUDED.to_address_hash"), token_contract_address_hash: fragment("EXCLUDED.token_contract_address_hash"), - token_ids: fragment("EXCLUDED.token_ids"), + token_id: fragment("EXCLUDED.token_id"), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token_transfer.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token_transfer.updated_at) ] ], where: fragment( - "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_ids) IS DISTINCT FROM (?, ? ,? , ?, ?)", + "(EXCLUDED.amount, EXCLUDED.from_address_hash, EXCLUDED.to_address_hash, EXCLUDED.token_contract_address_hash, EXCLUDED.token_id) IS DISTINCT FROM (?, ? ,? , ?, ?)", token_transfer.amount, token_transfer.from_address_hash, token_transfer.to_address_hash, token_transfer.token_contract_address_hash, - token_transfer.token_ids + token_transfer.token_id ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex index 638b59a71095..23f885cba9bf 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/tokens.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/tokens.ex @@ -4,12 +4,12 @@ defmodule Explorer.Chain.Import.Runner.Tokens do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] alias Ecto.{Multi, Repo} alias Explorer.Chain.{Hash, Import, Token} - alias Explorer.Prometheus.Instrumenter @behaviour Import.Runner @@ -22,10 +22,80 @@ defmodule Explorer.Chain.Import.Runner.Tokens do @type holder_count :: non_neg_integer() @type token_holder_count :: %{contract_address_hash: Hash.Address.t(), count: holder_count()} + def acquire_contract_address_tokens(repo, contract_address_hashes_and_token_ids) do + initial_query_no_token_id = + from(token in Token, + select: token + ) + + initial_query_with_token_id = + from(token in Token, + left_join: instance in Token.Instance, + on: token.contract_address_hash == instance.token_contract_address_hash, + select: token + ) + + {query_no_token_id, query_with_token_id} = + contract_address_hashes_and_token_ids + |> Enum.reduce({initial_query_no_token_id, initial_query_with_token_id}, fn {contract_address_hash, token_id}, + {query_no_token_id, + query_with_token_id} -> + if is_nil(token_id) do + {from( + token in query_no_token_id, + or_where: token.contract_address_hash == ^contract_address_hash + ), query_with_token_id} + else + {query_no_token_id, + from( + [token, instance] in query_with_token_id, + or_where: token.contract_address_hash == ^contract_address_hash and instance.token_id == ^token_id + )} + end + end) + + final_query_no_token_id = + if query_no_token_id == initial_query_no_token_id do + nil + else + from( + token in query_no_token_id, + # Enforce Token ShareLocks order (see docs: sharelocks.md) + order_by: [ + token.contract_address_hash + ], + lock: "FOR UPDATE" + ) + end + + final_query_with_token_id = + if query_with_token_id == initial_query_with_token_id do + nil + else + from( + [token, instance] in query_with_token_id, + # Enforce Token ShareLocks order (see docs: sharelocks.md) + order_by: [ + token.contract_address_hash, + instance.token_id + ], + lock: "FOR UPDATE" + ) + end + + tokens_no_token_id = (final_query_no_token_id && repo.all(final_query_no_token_id)) || [] + tokens_with_token_id = (final_query_with_token_id && repo.all(final_query_with_token_id)) || [] + tokens = tokens_no_token_id ++ tokens_with_token_id + + {:ok, tokens} + end + def update_holder_counts_with_deltas(repo, token_holder_count_deltas, %{ timeout: timeout, timestamps: %{updated_at: updated_at} }) do + Logger.info("### update_holder_counts_with_deltas STARTED") + # NOTE that acquire_contract_address_tokens needs to be called before this {hashes, deltas} = token_holder_count_deltas |> Enum.map(fn %{contract_address_hash: contract_address_hash, delta: delta} -> @@ -34,15 +104,6 @@ defmodule Explorer.Chain.Import.Runner.Tokens do end) |> Enum.unzip() - token_query = - from( - token in Token, - where: token.contract_address_hash in ^hashes, - select: token.contract_address_hash, - order_by: token.contract_address_hash, - lock: "FOR NO KEY UPDATE" - ) - query = from( token in Token, @@ -53,8 +114,8 @@ defmodule Explorer.Chain.Import.Runner.Tokens do ^deltas ), on: token.contract_address_hash == deltas.contract_address_hash, - where: token.contract_address_hash in subquery(token_query), where: not is_nil(token.holder_count), + # ShareLocks order already enforced by `acquire_contract_address_tokens` (see docs: sharelocks.md) update: [ set: [ holder_count: token.holder_count + deltas.delta, @@ -68,6 +129,7 @@ defmodule Explorer.Chain.Import.Runner.Tokens do ) {_total, result} = repo.update_all(query, [], timeout: timeout) + Logger.info("### update_holder_counts_with_deltas FINISHED") {:ok, result} end @@ -96,12 +158,7 @@ defmodule Explorer.Chain.Import.Runner.Tokens do |> Map.put(:timestamps, timestamps) Multi.run(multi, :tokens, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :tokens, - :tokens - ) + insert(repo, changes_list, insert_options) end) end @@ -114,6 +171,7 @@ defmodule Explorer.Chain.Import.Runner.Tokens do required(:timestamps) => Import.timestamps() }) :: {:ok, [Token.t()]} def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Tokens insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) ordered_changes_list = @@ -123,7 +181,7 @@ defmodule Explorer.Chain.Import.Runner.Tokens do # Enforce Token ShareLocks order (see docs: sharelocks.md) |> Enum.sort_by(& &1.contract_address_hash) - {:ok, _} = + {:ok, tokens} = Import.insert_changes_list( repo, ordered_changes_list, @@ -134,75 +192,42 @@ defmodule Explorer.Chain.Import.Runner.Tokens do timeout: timeout, timestamps: timestamps ) + + Logger.info(["### Tokens insert FINISHED ###"]) + {:ok, tokens} end - if Application.compile_env(:explorer, Explorer.Chain.BridgedToken)[:enabled] do - def default_on_conflict do - from( - token in Token, - update: [ - set: [ - name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), - symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), - total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), - decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), - type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), - cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), - bridged: fragment("COALESCE(EXCLUDED.bridged, ?)", token.bridged), - skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), - # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR - # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` - # Don't update `contract_address_hash` as it is the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.bridged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", - token.name, - token.symbol, - token.total_supply, - token.decimals, - token.type, - token.cataloged, - token.bridged, - token.skip_metadata - ) - ) - end - else - def default_on_conflict do - from( - token in Token, - update: [ - set: [ - name: fragment("COALESCE(EXCLUDED.name, ?)", token.name), - symbol: fragment("COALESCE(EXCLUDED.symbol, ?)", token.symbol), - total_supply: fragment("COALESCE(EXCLUDED.total_supply, ?)", token.total_supply), - decimals: fragment("COALESCE(EXCLUDED.decimals, ?)", token.decimals), - type: fragment("COALESCE(EXCLUDED.type, ?)", token.type), - cataloged: fragment("COALESCE(EXCLUDED.cataloged, ?)", token.cataloged), - skip_metadata: fragment("COALESCE(EXCLUDED.skip_metadata, ?)", token.skip_metadata), - # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR - # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` - # Don't update `contract_address_hash` as it is the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", - token.name, - token.symbol, - token.total_supply, - token.decimals, - token.type, - token.cataloged, - token.skip_metadata - ) - ) - end + def default_on_conflict do + from( + token in Token, + update: [ + set: [ + name: fragment("EXCLUDED.name"), + symbol: fragment("EXCLUDED.symbol"), + total_supply: fragment("EXCLUDED.total_supply"), + decimals: fragment("EXCLUDED.decimals"), + type: fragment("EXCLUDED.type"), + cataloged: fragment("EXCLUDED.cataloged"), + bridged: fragment("EXCLUDED.bridged"), + skip_metadata: fragment("EXCLUDED.skip_metadata"), + # `holder_count` is not updated as a pre-existing token means the `holder_count` is already initialized OR + # need to be migrated with `priv/repo/migrations/scripts/update_new_tokens_holder_count_in_batches.sql.exs` + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", token.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", token.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.name, EXCLUDED.symbol, EXCLUDED.total_supply, EXCLUDED.decimals, EXCLUDED.type, EXCLUDED.cataloged, EXCLUDED.bridged, EXCLUDED.skip_metadata) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", + token.name, + token.symbol, + token.total_supply, + token.decimals, + token.type, + token.cataloged, + token.bridged, + token.skip_metadata + ) + ) end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex b/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex index 19144b9dca97..c23f9d936112 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transaction/forks.ex @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Import.Runner.Transaction.Forks do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] @@ -62,21 +63,27 @@ defmodule Explorer.Chain.Import.Runner.Transaction.Forks do required(:timestamps) => Import.timestamps() }) :: {:ok, [%{uncle_hash: Hash.t(), hash: Hash.t()}]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + Logger.info(["### Transaction forks insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce Fork ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, &{&1.uncle_hash, &1.index}) - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: [:uncle_hash, :index], - on_conflict: on_conflict, - for: Transaction.Fork, - returning: [:uncle_hash, :hash], - timeout: timeout, - timestamps: timestamps - ) + res = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: [:uncle_hash, :index], + on_conflict: on_conflict, + for: Transaction.Fork, + returning: [:uncle_hash, :hash], + timeout: timeout, + timestamps: timestamps + ) + + Logger.info(["### Transaction forks insert FINISHED ###"]) + + res end defp default_on_conflict do diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 62578969a783..a69673f77dcf 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -4,14 +4,13 @@ defmodule Explorer.Chain.Import.Runner.Transactions do """ require Ecto.Query + require Logger import Ecto.Query, only: [from: 2] alias Ecto.{Multi, Repo} alias Explorer.Chain.{Block, Hash, Import, Transaction} alias Explorer.Chain.Import.Runner.TokenTransfers - alias Explorer.Prometheus.Instrumenter - alias Explorer.Utility.MissingRangesManipulator @behaviour Import.Runner @@ -47,22 +46,10 @@ defmodule Explorer.Chain.Import.Runner.Transactions do # Enforce ShareLocks tables order (see docs: sharelocks.md) multi |> Multi.run(:recollated_transactions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> - discard_blocks_for_recollated_transactions(repo, changes_list, insert_options) - end, - :block_referencing, - :transactions, - :recollated_transactions - ) + discard_blocks_for_recollated_transactions(repo, changes_list, insert_options) end) |> Multi.run(:transactions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :transactions, - :transactions - ) + insert(repo, changes_list, insert_options) end) end @@ -90,187 +77,91 @@ defmodule Explorer.Chain.Import.Runner.Transactions do } = options ) when is_list(changes_list) do + Logger.info(["### Transactions insert started ###"]) on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) # Enforce Transaction ShareLocks order (see docs: sharelocks.md) ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: :hash, - on_conflict: on_conflict, - for: Transaction, - returning: true, - timeout: timeout, - timestamps: timestamps - ) + res = + Import.insert_changes_list( + repo, + ordered_changes_list, + conflict_target: :hash, + on_conflict: on_conflict, + for: Transaction, + returning: true, + timeout: timeout, + timestamps: timestamps + ) + + Logger.info(["### Transactions insert FINISHED ###"]) + res end defp default_on_conflict do - if System.get_env("CHAIN_TYPE") == "suave" do - from( - transaction in Transaction, - update: [ - set: [ - block_hash: fragment("EXCLUDED.block_hash"), - old_block_hash: transaction.block_hash, - block_number: fragment("EXCLUDED.block_number"), - block_consensus: fragment("EXCLUDED.block_consensus"), - block_timestamp: fragment("EXCLUDED.block_timestamp"), - created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), - created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), - cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), - error: fragment("EXCLUDED.error"), - from_address_hash: fragment("EXCLUDED.from_address_hash"), - gas: fragment("EXCLUDED.gas"), - gas_price: fragment("EXCLUDED.gas_price"), - gas_used: fragment("EXCLUDED.gas_used"), - index: fragment("EXCLUDED.index"), - input: fragment("EXCLUDED.input"), - nonce: fragment("EXCLUDED.nonce"), - r: fragment("EXCLUDED.r"), - s: fragment("EXCLUDED.s"), - status: fragment("EXCLUDED.status"), - to_address_hash: fragment("EXCLUDED.to_address_hash"), - v: fragment("EXCLUDED.v"), - value: fragment("EXCLUDED.value"), - earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), - revert_reason: fragment("EXCLUDED.revert_reason"), - max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), - max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), - type: fragment("EXCLUDED.type"), - execution_node_hash: fragment("EXCLUDED.execution_node_hash"), - wrapped_type: fragment("EXCLUDED.wrapped_type"), - wrapped_nonce: fragment("EXCLUDED.wrapped_nonce"), - wrapped_to_address_hash: fragment("EXCLUDED.wrapped_to_address_hash"), - wrapped_gas: fragment("EXCLUDED.wrapped_gas"), - wrapped_gas_price: fragment("EXCLUDED.wrapped_gas_price"), - wrapped_max_priority_fee_per_gas: fragment("EXCLUDED.wrapped_max_priority_fee_per_gas"), - wrapped_max_fee_per_gas: fragment("EXCLUDED.wrapped_max_fee_per_gas"), - wrapped_value: fragment("EXCLUDED.wrapped_value"), - wrapped_input: fragment("EXCLUDED.wrapped_input"), - wrapped_v: fragment("EXCLUDED.wrapped_v"), - wrapped_r: fragment("EXCLUDED.wrapped_r"), - wrapped_s: fragment("EXCLUDED.wrapped_s"), - wrapped_hash: fragment("EXCLUDED.wrapped_hash"), - # Don't update `hash` as it is part of the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - transaction.block_hash, - transaction.block_number, - transaction.block_consensus, - transaction.block_timestamp, - transaction.created_contract_address_hash, - transaction.created_contract_code_indexed_at, - transaction.cumulative_gas_used, - transaction.from_address_hash, - transaction.gas, - transaction.gas_price, - transaction.gas_used, - transaction.index, - transaction.input, - transaction.nonce, - transaction.r, - transaction.s, - transaction.status, - transaction.to_address_hash, - transaction.v, - transaction.value, - transaction.earliest_processing_start, - transaction.revert_reason, - transaction.max_priority_fee_per_gas, - transaction.max_fee_per_gas, - transaction.type, - transaction.execution_node_hash, - transaction.wrapped_type, - transaction.wrapped_nonce, - transaction.wrapped_to_address_hash, - transaction.wrapped_gas, - transaction.wrapped_gas_price, - transaction.wrapped_max_priority_fee_per_gas, - transaction.wrapped_max_fee_per_gas, - transaction.wrapped_value, - transaction.wrapped_input, - transaction.wrapped_v, - transaction.wrapped_r, - transaction.wrapped_s, - transaction.wrapped_hash - ) - ) - else - from( - transaction in Transaction, - update: [ - set: [ - block_hash: fragment("EXCLUDED.block_hash"), - old_block_hash: transaction.block_hash, - block_number: fragment("EXCLUDED.block_number"), - block_consensus: fragment("EXCLUDED.block_consensus"), - block_timestamp: fragment("EXCLUDED.block_timestamp"), - created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), - created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), - cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), - error: fragment("EXCLUDED.error"), - from_address_hash: fragment("EXCLUDED.from_address_hash"), - gas: fragment("EXCLUDED.gas"), - gas_price: fragment("EXCLUDED.gas_price"), - gas_used: fragment("EXCLUDED.gas_used"), - index: fragment("EXCLUDED.index"), - input: fragment("EXCLUDED.input"), - nonce: fragment("EXCLUDED.nonce"), - r: fragment("EXCLUDED.r"), - s: fragment("EXCLUDED.s"), - status: fragment("EXCLUDED.status"), - to_address_hash: fragment("EXCLUDED.to_address_hash"), - v: fragment("EXCLUDED.v"), - value: fragment("EXCLUDED.value"), - earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), - revert_reason: fragment("EXCLUDED.revert_reason"), - max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), - max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), - type: fragment("EXCLUDED.type"), - # Don't update `hash` as it is part of the primary key and used for the conflict target - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - transaction.block_hash, - transaction.block_number, - transaction.block_consensus, - transaction.block_timestamp, - transaction.created_contract_address_hash, - transaction.created_contract_code_indexed_at, - transaction.cumulative_gas_used, - transaction.from_address_hash, - transaction.gas, - transaction.gas_price, - transaction.gas_used, - transaction.index, - transaction.input, - transaction.nonce, - transaction.r, - transaction.s, - transaction.status, - transaction.to_address_hash, - transaction.v, - transaction.value, - transaction.earliest_processing_start, - transaction.revert_reason, - transaction.max_priority_fee_per_gas, - transaction.max_fee_per_gas, - transaction.type - ) - ) - end + from( + transaction in Transaction, + update: [ + set: [ + block_hash: fragment("EXCLUDED.block_hash"), + old_block_hash: transaction.block_hash, + block_number: fragment("EXCLUDED.block_number"), + created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), + created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), + cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), + error: fragment("EXCLUDED.error"), + from_address_hash: fragment("EXCLUDED.from_address_hash"), + gas: fragment("EXCLUDED.gas"), + gas_price: fragment("EXCLUDED.gas_price"), + gas_used: fragment("EXCLUDED.gas_used"), + index: fragment("EXCLUDED.index"), + input: fragment("EXCLUDED.input"), + nonce: fragment("EXCLUDED.nonce"), + r: fragment("EXCLUDED.r"), + s: fragment("EXCLUDED.s"), + status: fragment("EXCLUDED.status"), + to_address_hash: fragment("EXCLUDED.to_address_hash"), + v: fragment("EXCLUDED.v"), + value: fragment("EXCLUDED.value"), + earliest_processing_start: fragment("EXCLUDED.earliest_processing_start"), + revert_reason: fragment("EXCLUDED.revert_reason"), + max_priority_fee_per_gas: fragment("EXCLUDED.max_priority_fee_per_gas"), + max_fee_per_gas: fragment("EXCLUDED.max_fee_per_gas"), + type: fragment("EXCLUDED.type"), + # Don't update `hash` as it is part of the primary key and used for the conflict target + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) + ] + ], + where: + fragment( + "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + transaction.block_hash, + transaction.block_number, + transaction.created_contract_address_hash, + transaction.created_contract_code_indexed_at, + transaction.cumulative_gas_used, + transaction.from_address_hash, + transaction.gas, + transaction.gas_price, + transaction.gas_used, + transaction.index, + transaction.input, + transaction.nonce, + transaction.r, + transaction.s, + transaction.status, + transaction.to_address_hash, + transaction.v, + transaction.value, + transaction.earliest_processing_start, + transaction.revert_reason, + transaction.max_priority_fee_per_gas, + transaction.max_fee_per_gas, + transaction.type + ) + ) end defp discard_blocks_for_recollated_transactions(repo, changes_list, %{ @@ -299,71 +190,47 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ), on: transaction.hash == new_transaction.hash, where: transaction.block_hash != new_transaction.block_hash, - select: %{hash: transaction.hash, block_hash: transaction.block_hash} + select: transaction.block_hash ) block_hashes = blocks_with_recollated_transactions |> repo.all() - |> Enum.map(fn %{block_hash: block_hash} -> block_hash end) |> Enum.uniq() - transaction_hashes = - blocks_with_recollated_transactions - |> repo.all() - |> Enum.map(fn %{hash: hash} -> hash end) - if Enum.empty?(block_hashes) do {:ok, []} else + Logger.info(fn -> + [ + "consensus removing from blocks with hashes from transactions runner: ", + inspect(block_hashes) + ] + end) + query = from( block in Block, where: block.hash in ^block_hashes, # Enforce Block ShareLocks order (see docs: sharelocks.md) order_by: [asc: block.hash], - lock: "FOR NO KEY UPDATE" - ) - - transactions_query = - from( - transaction in Transaction, - where: transaction.block_hash in ^block_hashes, - # Enforce Transaction ShareLocks order (see docs: sharelocks.md) - order_by: [asc: :hash], - lock: "FOR NO KEY UPDATE" + lock: "FOR UPDATE" ) - transactions_replacements = [ - block_hash: nil, - block_number: nil, - gas_used: nil, - cumulative_gas_used: nil, - index: nil, - status: nil, - error: nil, - max_priority_fee_per_gas: nil, - max_fee_per_gas: nil, - type: nil, - updated_at: updated_at - ] - try do {_, result} = repo.update_all( - from(b in Block, join: s in subquery(query), on: b.hash == s.hash, select: b.number), + from(b in Block, join: s in subquery(query), on: b.hash == s.hash), [set: [consensus: false, updated_at: updated_at]], timeout: timeout ) - {_, _transactions_result} = - repo.update_all( - from(t in Transaction, join: s in subquery(transactions_query), on: t.hash == s.hash), - [set: transactions_replacements], - timeout: timeout - ) - - MissingRangesManipulator.add_ranges_by_block_numbers(result) + Logger.info(fn -> + [ + "consensus removed from blocks with hashes from transactions runner: ", + inspect(result) + ] + end) {:ok, result} rescue @@ -371,32 +238,5 @@ defmodule Explorer.Chain.Import.Runner.Transactions do {:error, %{exception: postgrex_error, block_hashes: block_hashes}} end end - - if Enum.empty?(transaction_hashes) do - {:ok, []} - else - query = - from( - transaction in Transaction, - where: transaction.hash in ^transaction_hashes, - # Enforce Block ShareLocks order (see docs: sharelocks.md) - order_by: [asc: transaction.hash], - lock: "FOR UPDATE" - ) - - try do - {_, result} = - repo.update_all( - from(transaction in Transaction, join: s in subquery(query), on: transaction.hash == s.hash), - [set: [block_consensus: false, updated_at: updated_at]], - timeout: timeout - ) - - {:ok, result} - rescue - postgrex_error in Postgrex.Error -> - {:error, %{exception: postgrex_error, transaction_hashes: transaction_hashes}} - end - end end end diff --git a/apps/explorer/lib/explorer/chain/import/stage.ex b/apps/explorer/lib/explorer/chain/import/stage.ex index ed000760ca9b..0a0b6fc72e7f 100644 --- a/apps/explorer/lib/explorer/chain/import/stage.ex +++ b/apps/explorer/lib/explorer/chain/import/stage.ex @@ -52,7 +52,22 @@ defmodule Explorer.Chain.Import.Stage do changes_list |> Stream.chunk_every(chunk_size) |> Enum.map(fn changes_chunk -> - runner.run(Multi.new(), changes_chunk, options) + Task.async(fn -> + runner.run(Multi.new(), changes_chunk, options) + end) + end) + |> Task.yield_many(:timer.seconds(60)) + |> Enum.map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + raise "Addresses insert/update terminated: #{inspect(reason)}" + + nil -> + raise "Addresses insert/update timed out." + end end) end @@ -75,4 +90,58 @@ defmodule Explorer.Chain.Import.Stage do {new_multi, new_remaining_runner_to_changes_list} end) end + + @spec concurrent_multis([Runner.t()], runner_to_changes_list, %{optional(atom()) => term()}) :: + {[Multi.t()], runner_to_changes_list} + def concurrent_multis(runners, runner_to_changes_list, options) do + # IO.inspect("Gimme runner_to_changes_list #{inspect(runner_to_changes_list)}") + # IO.inspect("Gimme runner_to_changes_list keys #{inspect(Map.keys(runner_to_changes_list))}") + + {final_changes_list, final_remaining_runner_to_changes_list} = + runners + |> Enum.reduce({[], runner_to_changes_list}, fn runner, {acc, remaining_runner_to_changes_list} -> + {changes_list, new_remaining_runner_to_changes_list} = Map.pop(remaining_runner_to_changes_list, runner) + + # IO.inspect("Gimme runner #{inspect(runner)}") + # IO.inspect("Gimme changes_list #{inspect(changes_list)}") + + new_acc = + case changes_list do + nil -> + [{runner, []} | acc] + + _ -> + [{runner, changes_list} | acc] + end + + # {new_multi, new_remaining_runner_to_changes_list} + {new_acc, new_remaining_runner_to_changes_list} + end) + + # IO.inspect("Gimme final_changes_list #{inspect(final_changes_list)}") + + acc = + final_changes_list + |> Enum.map(fn {runner, changes_chunk} -> + if Enum.count(changes_chunk) > 0 do + Task.async(fn -> + runner.run(Multi.new(), changes_chunk, options) + end) + else + nil + end + end) + |> Enum.filter(& &1) + |> Task.yield_many(:timer.seconds(60)) + |> Enum.reduce([], fn {_task, res}, acc -> + case res do + {:ok, result} -> + [result | acc] + end + end) + + # IO.inspect("Gimme result of single_multi_2 #{inspect({acc, final_remaining_runner_to_changes_list})}") + + {acc, final_remaining_runner_to_changes_list} + end end diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex index 72b62a2f9be1..2c2b445e277e 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex @@ -11,20 +11,16 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do @impl Stage def runners, do: [ - Runner.Address.CoinBalances, Runner.Blocks, - Runner.Address.CoinBalancesDaily + Runner.Address.CoinBalances, + Runner.Address.CoinBalancesDaily, + Runner.Tokens, + Runner.StakingPools, + Runner.StakingPoolsDelegators ] - @impl Stage - def all_runners, - do: runners() - @impl Stage def multis(runner_to_changes_list, options) do - {final_multi, final_remaining_runner_to_changes_list} = - Stage.single_multi(runners(), runner_to_changes_list, options) - - {[final_multi], final_remaining_runner_to_changes_list} + Stage.concurrent_multis(runners(), runner_to_changes_list, options) end end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex index c8ed699d2af7..3ada089e7387 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_following.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_following.ex @@ -1,7 +1,9 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do @moduledoc """ Imports any tables that follows and cannot be imported at the same time as - those imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances` and `Explorer.Chain.Import.Stage.BlockReferencing` + those imported by `Explorer.Chain.Import.Stage.Addresses`, + `Explorer.Chain.Import.Stage.AddressReferencing` and + `Explorer.Chain.Import.Stage.BlockReferencing` """ alias Explorer.Chain.Import.{Runner, Stage} @@ -13,19 +15,15 @@ defmodule Explorer.Chain.Import.Stage.BlockFollowing do do: [ Runner.Block.SecondDegreeRelations, Runner.Block.Rewards, - Runner.Address.CurrentTokenBalances, - Runner.TokenInstances + Runner.Address.TokenBalances, + Runner.Address.CurrentTokenBalances ] - @impl Stage - def all_runners, - do: runners() - @impl Stage def multis(runner_to_changes_list, options) do {final_multi, final_remaining_runner_to_changes_list} = - Stage.single_multi(runners(), runner_to_changes_list, options) + Stage.concurrent_multis(runners(), runner_to_changes_list, options) - {[final_multi], final_remaining_runner_to_changes_list} + {final_multi, final_remaining_runner_to_changes_list} end end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex index 1ae2cae52184..7cabb436898f 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_referencing.ex @@ -1,71 +1,22 @@ defmodule Explorer.Chain.Import.Stage.BlockReferencing do @moduledoc """ Imports any tables that reference `t:Explorer.Chain.Block.t/0` and that were - imported by `Explorer.Chain.Import.Stage.AddressesBlocksCoinBalances`. + imported by `Explorer.Chain.Import.Stage.Addresses` and + `Explorer.Chain.Import.Stage.AddressReferencing`. """ alias Explorer.Chain.Import.{Runner, Stage} @behaviour Stage - @default_runners [ - Runner.Transactions, - Runner.Transaction.Forks, - Runner.Logs, - Runner.Tokens, - Runner.TokenTransfers, - Runner.Address.TokenBalances, - Runner.TransactionActions, - Runner.Withdrawals - ] - - @polygon_edge_runners [ - Runner.PolygonEdge.Deposits, - Runner.PolygonEdge.DepositExecutes, - Runner.PolygonEdge.Withdrawals, - Runner.PolygonEdge.WithdrawalExits - ] - - @polygon_zkevm_runners [ - Runner.Zkevm.LifecycleTransactions, - Runner.Zkevm.TransactionBatches, - Runner.Zkevm.BatchTransactions - ] - - @zksync_runners [ - Runner.ZkSync.LifecycleTransactions, - Runner.ZkSync.TransactionBatches, - Runner.ZkSync.BatchTransactions, - Runner.ZkSync.BatchBlocks - ] - - @shibarium_runners [ - Runner.Shibarium.BridgeOperations - ] - - @impl Stage - def runners do - case System.get_env("CHAIN_TYPE") do - "polygon_edge" -> - @default_runners ++ @polygon_edge_runners - - "polygon_zkevm" -> - @default_runners ++ @polygon_zkevm_runners - - "shibarium" -> - @default_runners ++ @shibarium_runners - - "zksync" -> - @default_runners ++ @zksync_runners - - _ -> - @default_runners - end - end @impl Stage - def all_runners do - @default_runners ++ @polygon_edge_runners ++ @polygon_zkevm_runners ++ @shibarium_runners ++ @zksync_runners - end + def runners, + do: [ + Runner.Transactions, + Runner.Transaction.Forks, + Runner.Logs, + Runner.TokenTransfers + ] @impl Stage def multis(runner_to_changes_list, options) do diff --git a/apps/explorer/lib/explorer/chain/import/stage/transactions.ex b/apps/explorer/lib/explorer/chain/import/stage/transactions.ex new file mode 100644 index 000000000000..16a0bd43feb0 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/stage/transactions.ex @@ -0,0 +1,22 @@ +defmodule Explorer.Chain.Import.Stage.Transactions do + @moduledoc """ + Imports transactions before anything else that references them because an unused address is still valid and recoverable + if the other stage(s) don't commit. + """ + + alias Explorer.Chain.Import.{Runner, Stage} + + @behaviour Stage + + @runner Runner.Transactions + + @impl Stage + def runners, do: [@runner] + + @chunk_size 50 + + @impl Stage + def multis(runner_to_changes_list, options) do + Stage.chunk_every(runner_to_changes_list, @runner, @chunk_size, options) + end +end diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index b3497d8b4c75..74640392f24d 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -9,6 +9,7 @@ defmodule Explorer.ExchangeRates do require Logger + alias Explorer.Chain.Cache.TokenExchangeRate alias Explorer.Chain.Events.Publisher alias Explorer.Market alias Explorer.ExchangeRates.{Source, Token} @@ -105,6 +106,23 @@ defmodule Explorer.ExchangeRates do end end + @doc """ + Returns a specific rate from the tracked tickers by token address hash + """ + @spec lookup(String.t()) :: Token.t() | nil + def lookup_by_address(token_address_hash, symbol) do + if store() == :ets && enabled?() do + case :ets.lookup(table_name(), symbol) do + [tuple | _] when is_tuple(tuple) -> + Token.from_tuple(tuple) + + _ -> + token_excange_rate = TokenExchangeRate.fetch_token_exchange_rate_by_address(token_address_hash) + %{usd_value: token_excange_rate} + end + end + end + @doc false @spec table_name() :: atom() def table_name do diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index a65b643fa5cb..9e29299a5685 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -3,59 +3,45 @@ defmodule Explorer.Market do Context for data related to the cryptocurrency market. """ + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.{BridgedToken, Hash} + alias Explorer.Chain.Supply.TokenBridge alias Explorer.ExchangeRates.Token alias Explorer.Market.{MarketHistory, MarketHistoryCache} - alias Explorer.{ExchangeRates, Repo} - - import Ecto.Query, only: [from: 2] + alias Explorer.{ExchangeRates, KnownTokens, Repo} @doc """ - Retrieves the history for the recent specified amount of days. - - Today's date is include as part of the day count + Get most recent exchange rate for the given symbol. """ - @spec fetch_recent_history() :: [MarketHistory.t()] - def fetch_recent_history do - MarketHistoryCache.fetch() + @spec get_exchange_rate(String.t()) :: Token.t() | nil + def get_exchange_rate(symbol) do + ExchangeRates.lookup(symbol) + end + + @spec get_exchange_rate(String.t(), String.t()) :: Token.t() | nil + def get_exchange_rate(token_contract_address_hash, symbol) do + ExchangeRates.lookup_by_address(token_contract_address_hash, symbol) end @doc """ - Retrieves today's native coin exchange rate from the database. + Get the address of the token with the given symbol. """ - @spec get_native_coin_exchange_rate_from_db() :: Token.t() - def get_native_coin_exchange_rate_from_db do - today = - case fetch_recent_history() do - [today | _the_rest] -> today - _ -> nil - end - - if today do - %Token{ - usd_value: Map.get(today, :closing_price), - market_cap_usd: Map.get(today, :market_cap), - tvl_usd: Map.get(today, :tvl), - available_supply: nil, - total_supply: nil, - btc_value: nil, - id: nil, - last_updated: nil, - name: nil, - symbol: nil, - volume_24h_usd: nil, - image_url: nil - } - else - Token.null() + @spec get_known_address(String.t()) :: Hash.Address.t() | nil + def get_known_address(symbol) do + case KnownTokens.lookup(symbol) do + {:ok, address} -> address + nil -> nil end end @doc """ - Get most recent exchange rate for the native coin from ETS or from DB. + Retrieves the history for the recent specified amount of days. + + Today's date is include as part of the day count """ - @spec get_coin_exchange_rate() :: Token.t() - def get_coin_exchange_rate do - get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null() + @spec fetch_recent_history() :: [MarketHistory.t()] + def fetch_recent_history do + MarketHistoryCache.fetch() end @doc false @@ -63,87 +49,81 @@ defmodule Explorer.Market do records_without_zeroes = records |> Enum.reject(fn item -> - Map.has_key?(item, :opening_price) && Map.has_key?(item, :closing_price) && - Decimal.equal?(item.closing_price, 0) && - Decimal.equal?(item.opening_price, 0) + Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0) end) # Enforce MarketHistory ShareLocks order (see docs: sharelocks.md) |> Enum.sort_by(& &1.date) - Repo.insert_all(MarketHistory, records_without_zeroes, - on_conflict: market_history_on_conflict(), - conflict_target: [:date] - ) + Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: :nothing, conflict_target: [:date]) end - defp market_history_on_conflict do - from( - market_history in MarketHistory, - update: [ - set: [ - opening_price: - fragment( - """ - CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.opening_price IS NOT NULL AND EXCLUDED.opening_price > 0 - THEN EXCLUDED.opening_price - ELSE ? - END - """, - market_history.opening_price, - market_history.opening_price, - market_history.opening_price - ), - closing_price: - fragment( - """ - CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.closing_price IS NOT NULL AND EXCLUDED.closing_price > 0 - THEN EXCLUDED.closing_price - ELSE ? - END - """, - market_history.closing_price, - market_history.closing_price, - market_history.closing_price - ), - market_cap: - fragment( - """ - CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.market_cap IS NOT NULL AND EXCLUDED.market_cap > 0 - THEN EXCLUDED.market_cap - ELSE ? - END - """, - market_history.market_cap, - market_history.market_cap, - market_history.market_cap - ), - tvl: - fragment( - """ - CASE WHEN (? IS NULL OR ? = 0) AND EXCLUDED.tvl IS NOT NULL AND EXCLUDED.tvl > 0 - THEN EXCLUDED.tvl - ELSE ? - END - """, - market_history.tvl, - market_history.tvl, - market_history.tvl - ) - ] - ], - where: - is_nil(market_history.tvl) or market_history.tvl == 0 or is_nil(market_history.market_cap) or - market_history.market_cap == 0 or is_nil(market_history.opening_price) or - market_history.opening_price == 0 or is_nil(market_history.closing_price) or - market_history.closing_price == 0 - ) + def add_price(%{contract_address_hash: contract_address_hash, symbol: symbol} = token) do + known_address = get_known_address(symbol) + + matches_known_address = known_address && known_address == contract_address_hash + + usd_value = + cond do + matches_known_address -> + fetch_token_usd_value(matches_known_address, symbol, contract_address_hash) + + bridged_token = mainnet_bridged_token?(token) -> + TokenBridge.get_current_price_for_bridged_token( + contract_address_hash, + bridged_token.foreign_token_contract_address_hash + ) + + true -> + nil + end + + Map.put(token, :usd_value, usd_value) + end + + def add_price(%CurrentTokenBalance{token: token} = token_balance) do + token_with_price = add_price(token) + + Map.put(token_balance, :token, token_with_price) end - @spec get_native_coin_exchange_rate_from_cache :: Token.t() | nil - defp get_native_coin_exchange_rate_from_cache do - case ExchangeRates.list() do - [native_coin] -> native_coin - _ -> nil + def add_price(tokens) when is_list(tokens) do + Enum.map(tokens, fn item -> + case item do + {token_balance, bridged_token, token} -> + {add_price(token_balance), bridged_token, token} + + token_balance -> + add_price(token_balance) + end + end) + end + + defp mainnet_bridged_token?(token) do + bridged_prop = Map.get(token, :bridged) || nil + + if bridged_prop do + bridged_token = Repo.get_by(BridgedToken, home_token_contract_address_hash: token.contract_address_hash) + + if bridged_token do + if bridged_token.foreign_chain_id do + if Decimal.cmp(bridged_token.foreign_chain_id, Decimal.new(1)) == :eq, do: bridged_token, else: false + else + false + end + else + false + end + else + false end end + + defp fetch_token_usd_value(true, symbol, contract_address_hash) do + case get_exchange_rate(contract_address_hash, symbol) do + %{usd_value: usd_value} -> usd_value + nil -> nil + end + end + + defp fetch_token_usd_value(_matches_known_address, _symbol, _contract_address_hash), do: nil end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index a53df8fff4da..551034200062 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -30,6 +30,7 @@ defmodule Explorer.Repo do end def logged_transaction(fun_or_multi, opts \\ []) do + # Logger.info("### logged_transaction ###") transaction_id = :erlang.unique_integer([:positive]) Explorer.Logger.metadata( @@ -53,6 +54,8 @@ defmodule Explorer.Repo do def safe_insert_all(kind, elements, opts) do returning = opts[:returning] + # Logger.info("### safe_insert_all elements length #{Enum.count(elements)} #4 ###") + elements |> Enum.chunk_every(500) |> Enum.reduce({0, []}, fn chunk, {total_count, acc} -> diff --git a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex index 8eb602ba3559..6235dae8da54 100644 --- a/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/solidity/verifier.ex @@ -8,148 +8,53 @@ defmodule Explorer.SmartContract.Solidity.Verifier do then Verified. """ - import Explorer.SmartContract.Helper, - only: [ - cast_libraries: 1, - fetch_data_for_verification: 1, - prepare_bytecode_for_microservice: 3 - ] - - alias ABI.{FunctionSelector, TypeDecoder} alias Explorer.Chain - alias Explorer.Chain.{Data, Hash, SmartContract} - alias Explorer.SmartContract.RustVerifierInterface alias Explorer.SmartContract.Solidity.CodeCompiler + alias Explorer.SmartContract.Verifier.ConstructorArguments - require Logger + @metadata_hash_prefix_0_4_23 "a165627a7a72305820" + @metadata_hash_prefix_0_5_family_1 "65627a7a723" + @metadata_hash_prefix_0_5_family_2 "5820" + @metadata_hash_prefix_0_6_0 "a264697066735822" - @bytecode_hash_options ["default", "none", "bzzr1"] + @experimental "6c6578706572696d656e74616cf5" + @metadata_hash_common_suffix "64736f6c63" + + def evaluate_authenticity(_, %{"name" => ""}), do: {:error, :name} def evaluate_authenticity(_, %{"contract_source_code" => ""}), do: {:error, :contract_source_code} def evaluate_authenticity(address_hash, params) do - try do - evaluate_authenticity_inner(RustVerifierInterface.enabled?(), address_hash, params) - rescue - exception -> - Logger.error(fn -> - [ - "Error while verifying smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}: ", - Exception.format(:error, exception, __STACKTRACE__) - ] - end) - end - end + latest_evm_version = List.last(CodeCompiler.allowed_evm_versions()) + evm_version = Map.get(params, "evm_version", latest_evm_version) - defp evaluate_authenticity_inner(true, address_hash, params) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) - - %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) - |> Map.put("sourceFiles", %{ - "#{params["name"]}.#{smart_contract_source_file_extension(parse_boolean(params["is_yul"]))}" => - params["contract_source_code"] - }) - |> Map.put("libraries", params["external_libraries"]) - |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) - |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) - |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part(verifier_metadata) - end + all_versions = [evm_version | previous_evm_versions(evm_version)] - defp evaluate_authenticity_inner(false, address_hash, params) do - if is_nil(params["name"]) or params["name"] == "" do - {:error, :name} - else - latest_evm_version = List.last(CodeCompiler.evm_versions(:solidity)) - evm_version = Map.get(params, "evm_version", latest_evm_version) + all_versions_extra = all_versions ++ [evm_version] - all_versions = [evm_version | previous_evm_versions(evm_version)] + Enum.reduce_while(all_versions_extra, false, fn version, acc -> + case acc do + {:ok, _} = result -> + {:cont, result} - all_versions_extra = all_versions ++ [evm_version] + {:error, :compiler_version} -> + {:halt, acc} - Enum.reduce_while(all_versions_extra, false, fn version, acc -> - case acc do - {:ok, _} = result -> - {:cont, result} + {:error, :name} -> + {:halt, acc} - {:error, error} - when error in [:name, :no_creation_data, :deployed_bytecode, :compiler_version, :constructor_arguments] -> - {:halt, acc} - - _ -> - cur_params = Map.put(params, "evm_version", version) - {:cont, verify(address_hash, cur_params)} - end - end) - end - end - - defp smart_contract_source_file_extension(true), do: "yul" - defp smart_contract_source_file_extension(_), do: "sol" - - defp prepare_optimization_runs(false_, _) when false_ in [false, "false"], do: nil - - defp prepare_optimization_runs(true_, runs) when true_ in [true, "true"] and is_number(runs) do - runs - end - - defp prepare_optimization_runs(true_, runs) when true_ in [true, "true"] do - case Integer.parse(runs) do - {runs_integer, ""} -> - runs_integer - - _ -> - nil - end + _ -> + cur_params = Map.put(params, "evm_version", version) + {:cont, verify(address_hash, cur_params)} + end + end) end def evaluate_authenticity_via_standard_json_input(address_hash, params, json_input) do - try do - evaluate_authenticity_via_standard_json_input_inner( - RustVerifierInterface.enabled?(), - address_hash, - params, - json_input - ) - rescue - exception -> - Logger.error(fn -> - [ - "Error while verifying smart-contract address: #{address_hash}, params: #{inspect(params, limit: :infinity, printable_limit: :infinity)}, json_input: #{inspect(json_input, limit: :infinity, printable_limit: :infinity)}: ", - Exception.format(:error, exception, __STACKTRACE__) - ] - end) - end - end - - def evaluate_authenticity_via_standard_json_input_inner(true, address_hash, params, json_input) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) - - %{"compilerVersion" => params["compiler_version"]} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) - |> Map.put("input", json_input) - |> RustVerifierInterface.verify_standard_json_input(verifier_metadata) - end - - def evaluate_authenticity_via_standard_json_input_inner(false, address_hash, params, json_input) do verify(address_hash, params, json_input) end - def evaluate_authenticity_via_multi_part_files(address_hash, params, files) do - {creation_tx_input, deployed_bytecode, verifier_metadata} = fetch_data_for_verification(address_hash) - - %{} - |> prepare_bytecode_for_microservice(creation_tx_input, deployed_bytecode) - |> Map.put("sourceFiles", files) - |> Map.put("libraries", params["external_libraries"]) - |> Map.put("optimizationRuns", prepare_optimization_runs(params["optimization"], params["optimization_runs"])) - |> Map.put("evmVersion", Map.get(params, "evm_version", "default")) - |> Map.put("compilerVersion", params["compiler_version"]) - |> RustVerifierInterface.verify_multi_part(verifier_metadata) - end - defp verify(address_hash, params, json_input) do name = Map.get(params, "name", "") compiler_version = Map.fetch!(params, "compiler_version") @@ -178,7 +83,9 @@ defmodule Explorer.SmartContract.Solidity.Verifier do candidate, address_hash, constructor_arguments, - autodetect_constructor_arguments + autodetect_constructor_arguments, + source_code, + contract_name ) do {:ok, verified_data} -> secondary_sources = @@ -193,8 +100,6 @@ defmodule Explorer.SmartContract.Solidity.Verifier do |> Map.put("file_path", file_path) |> Map.put("name", contract_name) |> Map.put("secondary_sources", secondary_sources) - |> Map.put("compiler_settings", map_json_input["settings"]) - |> Map.put("external_libraries", cast_libraries(map_json_input["settings"]["libraries"] || %{})) {:halt, {:ok, verified_data, additional_params}} @@ -228,240 +133,140 @@ defmodule Explorer.SmartContract.Solidity.Verifier do constructor_arguments = Map.get(params, "constructor_arguments", "") evm_version = Map.get(params, "evm_version") optimization_runs = Map.get(params, "optimization_runs", 200) - autodetect_constructor_arguments = params |> Map.get("autodetect_constructor_args", "true") |> parse_boolean() - - if is_compiler_version_at_least_0_6_0?(compiler_version) do - Enum.reduce_while(@bytecode_hash_options, false, fn option, acc -> - case acc do - {:ok, _} = result -> - {:halt, result} - - {:error, error} - when error in [:name, :no_creation_data, :deployed_bytecode, :compiler_version, :constructor_arguments] -> - {:halt, acc} + autodetect_constructor_arguments = params |> Map.get("autodetect_constructor_args", "false") |> parse_boolean() - _ -> - solc_output = - CodeCompiler.run( - name: name, - compiler_version: compiler_version, - code: contract_source_code, - optimize: optimization, - optimization_runs: optimization_runs, - evm_version: evm_version, - external_libs: external_libraries, - bytecode_hash: option - ) - - {:cont, - compare_bytecodes( - solc_output, - address_hash, - constructor_arguments, - autodetect_constructor_arguments - )} - end - end) - else - solc_output = - CodeCompiler.run( - name: name, - compiler_version: compiler_version, - code: contract_source_code, - optimize: optimization, - optimization_runs: optimization_runs, - evm_version: evm_version, - external_libs: external_libraries - ) - - compare_bytecodes( - solc_output, - address_hash, - constructor_arguments, - autodetect_constructor_arguments + solc_output = + CodeCompiler.run( + name: name, + compiler_version: compiler_version, + code: contract_source_code, + optimize: optimization, + optimization_runs: optimization_runs, + evm_version: evm_version, + external_libs: external_libraries ) - end - end - defp is_compiler_version_at_least_0_6_0?("latest"), do: true - - defp is_compiler_version_at_least_0_6_0?(compiler_version) do - case compiler_version |> String.split("+", parts: 2) do - [version, _] -> - digits = - version - |> String.replace("v", "") - |> String.split(".") - |> Enum.map(fn str -> - {num, _} = Integer.parse(str) - num - end) - - Enum.fetch!(digits, 0) > 0 || Enum.fetch!(digits, 1) >= 6 - - _ -> - false - end + compare_bytecodes( + solc_output, + address_hash, + constructor_arguments, + autodetect_constructor_arguments, + contract_source_code, + name + ) end - defp compare_bytecodes({:error, :name}, _, _, _), do: {:error, :name} - defp compare_bytecodes({:error, _}, _, _, _), do: {:error, :compilation} + defp compare_bytecodes({:error, :name}, _, _, _, _, _), do: {:error, :name} + defp compare_bytecodes({:error, _}, _, _, _, _, _), do: {:error, :compilation} - defp compare_bytecodes({:error, _, error_message}, _, _, _) do + defp compare_bytecodes({:error, _, error_message}, _, _, _, _, _) do {:error, :compilation, error_message} end defp compare_bytecodes( - %{"abi" => abi, "bytecode" => bytecode, "deployedBytecode" => deployed_bytecode}, + %{"abi" => abi, "bytecode" => bytecode}, address_hash, arguments_data, - autodetect_constructor_arguments + autodetect_constructor_arguments, + contract_source_code, + contract_name ), do: compare_bytecodes( - {:ok, %{"abi" => abi, "bytecode" => bytecode, "deployedBytecode" => deployed_bytecode}}, + {:ok, %{"abi" => abi, "bytecode" => bytecode}}, address_hash, arguments_data, - autodetect_constructor_arguments + autodetect_constructor_arguments, + contract_source_code, + contract_name ) + # credo:disable-for-next-line /Complexity/ defp compare_bytecodes( - {:ok, %{"abi" => abi, "bytecode" => bytecode, "deployedBytecode" => deployed_bytecode}}, + {:ok, %{"abi" => abi, "bytecode" => bytecode}}, address_hash, arguments_data, - autodetect_constructor_arguments + autodetect_constructor_arguments, + contract_source_code, + contract_name ) do %{ - "metadata_hash_with_length" => local_meta, - "trimmed_bytecode" => local_bytecode_without_meta, - "compiler_version" => solc_local - } = extract_bytecode_and_metadata_hash(bytecode, deployed_bytecode) - - bc_deployed_bytecode = Chain.smart_contract_bytecode(address_hash) + "metadata_hash" => _generated_metadata_hash, + "bytecode" => generated_bytecode, + "compiler_version" => generated_compiler_version + } = extract_bytecode_and_metadata_hash(bytecode) - bc_creation_tx_input = + blockchain_created_tx_input = case Chain.smart_contract_creation_tx_bytecode(address_hash) do %{init: init, created_contract_code: _created_contract_code} -> "0x" <> init_without_0x = init init_without_0x _ -> - "" + bytecode end %{ - "metadata_hash_with_length" => bc_meta, - "trimmed_bytecode" => bc_creation_tx_input_without_meta, - "compiler_version" => solc_bc - } = extract_bytecode_and_metadata_hash(bc_creation_tx_input, bc_deployed_bytecode) - - bc_replaced_local = - String.replace(bc_creation_tx_input_without_meta, local_bytecode_without_meta, "", global: false) - - has_constructor_with_params? = has_constructor_with_params?(abi) - - is_constructor_args_valid? = - if has_constructor_with_params?, do: parse_constructor_and_return_check_function(abi), else: fn _ -> false end + "metadata_hash" => _metadata_hash, + "bytecode" => blockchain_bytecode_without_whisper, + "compiler_version" => compiler_version_from_input + } = extract_bytecode_and_metadata_hash(blockchain_created_tx_input) empty_constructor_arguments = arguments_data == "" or arguments_data == nil cond do - bc_creation_tx_input == "" -> - {:error, :no_creation_data} - - !String.contains?(bc_creation_tx_input, bc_meta) || bc_deployed_bytecode in ["", "0x"] -> - {:error, :deployed_bytecode} - - solc_local != solc_bc -> + compiler_version_from_input != generated_compiler_version -> {:error, :compiler_version} - !String.contains?(bc_creation_tx_input_without_meta, local_bytecode_without_meta) -> + generated_bytecode != blockchain_bytecode_without_whisper && + !try_library_verification(generated_bytecode, blockchain_bytecode_without_whisper) -> {:error, :generated_bytecode} - bc_replaced_local == "" && !has_constructor_with_params? -> - {:ok, %{abi: abi}} - - bc_replaced_local != "" && has_constructor_with_params? && is_constructor_args_valid?.(bc_replaced_local) && - autodetect_constructor_arguments -> - {:ok, %{abi: abi, constructor_arguments: bc_replaced_local}} + has_constructor_with_params?(abi) && autodetect_constructor_arguments -> + result_1 = + try_to_verify_with_unknown_constructor_args( + blockchain_created_tx_input, + bytecode, + blockchain_bytecode_without_whisper, + abi + ) - has_constructor_with_params? && autodetect_constructor_arguments && - ((bc_replaced_local != "" && !is_constructor_args_valid?.(bc_replaced_local)) || bc_replaced_local == "") -> - {:error, :autodetect_constructor_arguments_failed} + result_2 = + ConstructorArguments.find_constructor_arguments(address_hash, abi, contract_source_code, contract_name) - has_constructor_with_params? && - (empty_constructor_arguments || !String.contains?(bc_creation_tx_input, arguments_data)) -> - {:error, :constructor_arguments} - - has_constructor_with_params? && is_constructor_args_valid?.(arguments_data) && - (bc_replaced_local == arguments_data || - check_users_constructor_args_validity(bc_creation_tx_input, bytecode, bc_meta, local_meta, arguments_data)) -> - {:ok, %{abi: abi, constructor_arguments: arguments_data}} - - try_library_verification(local_bytecode_without_meta, bc_creation_tx_input_without_meta) -> - {:ok, %{abi: abi}} - - true -> - {:error, :unknown_error} - end - end - - defp check_users_constructor_args_validity(bc_bytecode, local_bytecode, bc_splitter, local_splitter, user_arguments) do - clear_bc_bytecode = bc_bytecode |> replace_last_occurrence(user_arguments) |> replace_last_occurrence(bc_splitter) - clear_local_bytecode = replace_last_occurrence(local_bytecode, local_splitter) - clear_bc_bytecode == clear_local_bytecode - end - - defp replace_last_occurrence(where, what) when is_binary(where) and is_binary(what) do - where - |> String.reverse() - |> String.replace(String.reverse(what), "", global: false) - |> String.reverse() - end - - defp replace_last_occurrence(_, _), do: nil + cond do + result_1 -> + {:ok, %{abi: abi, constructor_arguments: result_1}} - defp parse_constructor_and_return_check_function(abi) do - constructor_abi = Enum.find(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) + result_2 -> + {:ok, %{abi: abi, constructor_arguments: result_2}} - input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1) + true -> + {:error, :constructor_arguments} + end - fn assumed_arguments -> - try do - _ = - assumed_arguments - |> Base.decode16!(case: :mixed) - |> TypeDecoder.decode_raw(input_types) + has_constructor_with_params?(abi) && empty_constructor_arguments -> + {:error, :constructor_arguments} - assumed_arguments - rescue - _ -> - false - end - end - end + has_constructor_with_params?(abi) && + !ConstructorArguments.verify( + address_hash, + blockchain_bytecode_without_whisper, + arguments_data, + contract_source_code, + contract_name + ) -> + {:error, :constructor_arguments} - defp extract_meta_from_deployed_bytecode(code_unknown_case) do - with true <- is_binary(code_unknown_case), - code <- String.downcase(code_unknown_case), - last_2_bytes <- code |> String.slice(-4..-1), - {meta_length, ""} <- last_2_bytes |> Integer.parse(16), - meta <- String.slice(code, (-(meta_length + 2) * 2)..-5) do - {meta, last_2_bytes} - else - _ -> - {"", ""} + true -> + {:ok, %{abi: abi}} end end - defp decode_meta(meta) do - with {:ok, meta_raw_binary} <- Base.decode16(meta, case: :lower), - {:ok, decoded_meta, _remain} <- CBOR.decode(meta_raw_binary) do - decoded_meta - else - _ -> - %{} - end + defp try_to_verify_with_unknown_constructor_args(creation_code, generated_bytecode, trimmed_bytecode, abi) do + ["", rest_blockchain] = String.split(creation_code, trimmed_bytecode) + ["", rest_generated] = String.split(generated_bytecode, trimmed_bytecode) + ConstructorArguments.experimental_find_constructor_args(rest_blockchain, rest_generated, abi) end # 730000000000000000000000000000000000000000 - default library address that returned by the compiler @@ -483,40 +288,179 @@ defmodule Explorer.SmartContract.Solidity.Verifier do For more information on the swarm hash, check out: https://solidity.readthedocs.io/en/v0.5.3/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode """ - def extract_bytecode_and_metadata_hash("0x" <> bytecode, deployed_bytecode) do - extract_bytecode_and_metadata_hash(bytecode, deployed_bytecode) + def extract_bytecode_and_metadata_hash(nil) do + %{"metadata_hash" => nil, "bytecode" => nil, "compiler_version" => nil} + end + + def extract_bytecode_and_metadata_hash("0x" <> code) do + %{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version} = + extract_bytecode_and_metadata_hash(code) + + %{"metadata_hash" => metadata_hash, "bytecode" => "0x" <> bytecode, "compiler_version" => compiler_version} end - def extract_bytecode_and_metadata_hash(bytecode, deployed_bytecode) do - {meta, meta_length} = extract_meta_from_deployed_bytecode(deployed_bytecode) + def extract_bytecode_and_metadata_hash(code) do + do_extract_bytecode_and_metadata_hash([], String.downcase(code), nil, nil) + end - solc = decode_meta(meta)["solc"] + defp do_extract_bytecode_and_metadata_hash(extracted, remaining, metadata_hash, compiler_version) do + case remaining do + <<>> -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + @metadata_hash_prefix_0_4_23 <> <> <> "0029" <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + # Solidity >= 0.5 family && experimantal + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @experimental <> + @metadata_hash_common_suffix <> + "43" <> <> <> <<_::binary-size(4)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @experimental <> + <<_::binary-size(4)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @metadata_hash_common_suffix <> + "43" <> <> <> "0032" <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + # Solidity >= 0.6.0 https://github.com/ethereum/solidity/blob/develop/Changelog.md#060-2019-12-17 + # https://github.com/ethereum/solidity/blob/26b700771e9cc9c956f0503a05de69a1be427963/docs/metadata.rst#encoding-of-the-metadata-hash-in-the-bytecode + # IPFS is used instead of Swarm + # The current version of the Solidity compiler usually adds the following to the end of the deployed bytecode: + # 0xa2 + # 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> + # 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> + # 0x00 0x32 + # Note: there is a bug in the docs. Instead of 0x32, 0x33 should be used. + # Fixing PR has been created https://github.com/ethereum/solidity/pull/8174 + @metadata_hash_prefix_0_6_0 <> + <> <> + @metadata_hash_common_suffix <> + "43" <> <> <> "0033" <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + @metadata_hash_prefix_0_6_0 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + @metadata_hash_prefix_0_6_0 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + @metadata_hash_prefix_0_6_0 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + @metadata_hash_prefix_0_6_0 <> + <> <> + @metadata_hash_common_suffix <> + "78" <> + <<_::binary-size(2)>> <> + <> <> "00" <> <<_::binary-size(2)>> <> _constructor_arguments -> + do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) + + <> <> rest -> + do_extract_bytecode_and_metadata_hash([next | extracted], rest, metadata_hash, compiler_version) + end + end - bytecode_without_meta = - bytecode - |> replace_last_occurrence(meta <> meta_length) + defp do_extract_bytecode_and_metadata_hash_output(metadata_hash, extracted, compiler_version) do + bytecode = + extracted + |> Enum.reverse() + |> :binary.list_to_bin() - %{ - "metadata_hash_with_length" => meta <> meta_length, - "trimmed_bytecode" => bytecode_without_meta, - "compiler_version" => solc - } + %{"metadata_hash" => metadata_hash, "bytecode" => bytecode, "compiler_version" => compiler_version} end def previous_evm_versions(current_evm_version) do - index = Enum.find_index(CodeCompiler.evm_versions(:solidity), fn el -> el == current_evm_version end) + index = Enum.find_index(CodeCompiler.allowed_evm_versions(), fn el -> el == current_evm_version end) cond do index == 0 -> [] index == 1 -> - [List.first(CodeCompiler.evm_versions(:solidity))] + [List.first(CodeCompiler.allowed_evm_versions())] true -> [ - Enum.at(CodeCompiler.evm_versions(:solidity), index - 1), - Enum.at(CodeCompiler.evm_versions(:solidity), index - 2) + Enum.at(CodeCompiler.allowed_evm_versions(), index - 1), + Enum.at(CodeCompiler.allowed_evm_versions(), index - 2) ] end end @@ -525,60 +469,9 @@ defmodule Explorer.SmartContract.Solidity.Verifier do Enum.any?(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) end - def parse_boolean("true"), do: true - def parse_boolean("false"), do: false - - def parse_boolean(true), do: true - def parse_boolean(false), do: false - - def parse_boolean(_), do: false + defp parse_boolean("true"), do: true + defp parse_boolean("false"), do: false - @doc """ - Function tries to parse constructor args from smart contract creation input. - 1. using `extract_meta_from_deployed_bytecode/1` we derive CBOR metadata string - 2. using metadata we split creation_tx_input and try to decode resulting constructor arguments - 2.1. if we successfully decoded args using constructor's abi, then return constructor args - 2.2 otherwise return nil - """ - @spec parse_constructor_arguments_for_sourcify_contract(Hash.Address.t(), SmartContract.abi()) :: nil | binary - def parse_constructor_arguments_for_sourcify_contract(address_hash, abi) do - parse_constructor_arguments_for_sourcify_contract(address_hash, abi, Chain.smart_contract_bytecode(address_hash)) - end - - @doc """ - Clause for cases when we already can pass deployed bytecode to this function (in order to avoid excessive read DB accesses) - """ - @spec parse_constructor_arguments_for_sourcify_contract( - Hash.Address.t(), - SmartContract.abi(), - binary | Explorer.Chain.Data.t() - ) :: nil | binary - def parse_constructor_arguments_for_sourcify_contract(address_hash, abi, deployed_bytecode) - when is_binary(deployed_bytecode) do - creation_tx_input = - case Chain.smart_contract_creation_tx_bytecode(address_hash) do - %{init: init, created_contract_code: _created_contract_code} -> - "0x" <> init_without_0x = init - init_without_0x - - _ -> - nil - end - - with true <- has_constructor_with_params?(abi), - check_function <- parse_constructor_and_return_check_function(abi), - false <- is_nil(creation_tx_input) || deployed_bytecode == "0x", - {meta, meta_length} <- extract_meta_from_deployed_bytecode(deployed_bytecode), - [_bytecode, constructor_args] <- String.split(creation_tx_input, meta <> meta_length), - ^constructor_args <- check_function.(constructor_args) do - constructor_args - else - _ -> - nil - end - end - - def parse_constructor_arguments_for_sourcify_contract(address_hash, abi, deployed_bytecode) do - parse_constructor_arguments_for_sourcify_contract(address_hash, abi, Data.to_string(deployed_bytecode)) - end + defp parse_boolean(true), do: true + defp parse_boolean(false), do: false end diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex new file mode 100644 index 000000000000..769364c59ca4 --- /dev/null +++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex @@ -0,0 +1,525 @@ +# credo:disable-for-this-file +defmodule Explorer.SmartContract.Verifier.ConstructorArguments do + @moduledoc """ + Smart contract contrstructor arguments verification logic. + """ + alias ABI.{FunctionSelector, TypeDecoder} + alias Explorer.Chain + + @metadata_hash_prefix_0_4_23 "a165627a7a72305820" + @metadata_hash_prefix_0_5_family_1 "65627a7a723" + @metadata_hash_prefix_0_5_family_2 "5820" + @metadata_hash_prefix_0_6_0 "a264697066735822" + + @experimental "6c6578706572696d656e74616cf5" + @metadata_hash_common_suffix "64736f6c63" + + def verify(address_hash, contract_code, arguments_data, contract_source_code, contract_name) do + arguments_data = arguments_data |> String.trim_trailing() |> String.trim_leading("0x") + + creation_code = + address_hash + |> Chain.contract_creation_input_data() + |> String.replace("0x", "") + + check_func = fn assumed_arguments -> assumed_arguments == arguments_data end + + if verify_older_version(creation_code, contract_code, check_func) do + true + else + extract_constructor_arguments(creation_code, check_func, contract_source_code, contract_name) + end + end + + # Earlier versions of Solidity didn't have whisper code. + # constructor argument were directly appended to source code + defp verify_older_version(creation_code, contract_code, check_func) do + creation_code + |> String.split(contract_code) + |> List.last() + |> check_func.() + end + + defp extract_constructor_arguments(code, check_func, contract_source_code, contract_name) do + case code do + # Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html + @metadata_hash_prefix_0_4_23 <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_4_23 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @experimental <> + @metadata_hash_common_suffix <> + "43" <> <<_::binary-size(6)>> <> <<_::binary-size(4)>> <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @experimental <> + <<_::binary-size(4)>> <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + # Solidity >= 0.5.10 https://solidity.readthedocs.io/en/v0.5.10/metadata.html + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + <<_::binary-size(2)>> <> + @metadata_hash_prefix_0_5_family_1 <> + <<_::binary-size(1)>> <> + @metadata_hash_prefix_0_5_family_2 <> + <<_::binary-size(64)>> <> + @metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_5_family_1 + ) + + # Solidity >= 0.6.0 https://github.com/ethereum/solidity/blob/develop/Changelog.md#060-2019-12-17 + # https://github.com/ethereum/solidity/blob/26b700771e9cc9c956f0503a05de69a1be427963/docs/metadata.rst#encoding-of-the-metadata-hash-in-the-bytecode + # IPFS is used instead of Swarm + # The current version of the Solidity compiler usually adds the following to the end of the deployed bytecode: + # 0xa2 + # 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> + # 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> + # 0x00 0x32 + # Note: there is a bug in the docs. Instead of 0x32, 0x33 should be used. + # Fixing PR has been created https://github.com/ethereum/solidity/pull/8174 + @metadata_hash_prefix_0_6_0 <> + <<_::binary-size(68)>> <> + @metadata_hash_common_suffix <> "43" <> <<_::binary-size(6)>> <> "0033" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_6_0 + ) + + @metadata_hash_prefix_0_6_0 <> + <<_::binary-size(68)>> <> + @metadata_hash_common_suffix <> "7826" <> <<_::binary-size(76)>> <> "0057" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_6_0 + ) + + @metadata_hash_prefix_0_6_0 <> + <<_::binary-size(68)>> <> + @metadata_hash_common_suffix <> "7827" <> <<_::binary-size(78)>> <> "0057" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_6_0 + ) + + @metadata_hash_prefix_0_6_0 <> + <<_::binary-size(68)>> <> + @metadata_hash_common_suffix <> "7828" <> <<_::binary-size(80)>> <> "0058" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_6_0 + ) + + @metadata_hash_prefix_0_6_0 <> + <<_::binary-size(68)>> <> + @metadata_hash_common_suffix <> "7829" <> <<_::binary-size(82)>> <> "0059" <> constructor_arguments -> + split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + @metadata_hash_prefix_0_6_0 + ) + + <<>> -> + false + + <<_::binary-size(2)>> <> rest -> + extract_constructor_arguments(rest, check_func, contract_source_code, contract_name) + end + end + + defp is_constructor_arguments_still_has_metadata_prefix(constructor_arguments, prefix) do + constructor_arguments_parts = + constructor_arguments + |> String.split(prefix) + |> Enum.count() + + constructor_arguments_parts > 1 + end + + defp split_constructor_arguments_and_extract_check_func(constructor_arguments, nil, nil, nil, _metadata_hash_prefix), + do: constructor_arguments + + defp split_constructor_arguments_and_extract_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name, + metadata_hash_prefix + ) do + if is_constructor_arguments_still_has_metadata_prefix(constructor_arguments, metadata_hash_prefix) do + {ind, _} = :binary.match(constructor_arguments, metadata_hash_prefix) + offset = if ind > 2, do: ind - 2, else: 0 + + processed_constructor_arguments = + if offset > 0 do + <<_::binary-size(offset)>> <> rest = constructor_arguments + rest + else + constructor_arguments + end + + extract_constructor_arguments(processed_constructor_arguments, check_func, contract_source_code, contract_name) + else + extract_constructor_arguments_check_func( + constructor_arguments, + check_func, + contract_source_code, + contract_name + ) + end + end + + defp extract_constructor_arguments_check_func(constructor_arguments, check_func, contract_source_code, contract_name) do + check_func_result_before_removing_strings = check_func.(constructor_arguments) + + constructor_arguments = + remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) + + filtered_constructor_arguments = + remove_keccak256_strings_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) + + check_func_result = check_func.(filtered_constructor_arguments) + + cond do + check_func_result_before_removing_strings -> + check_func_result_before_removing_strings + + check_func_result -> + check_func_result + + true -> + output = + extract_constructor_arguments(filtered_constructor_arguments, check_func, contract_source_code, contract_name) + + if output do + output + else + # https://github.com/blockscout/blockscout/pull/4764 + clean_constructor_arguments = + remove_substring_of_require_messages(filtered_constructor_arguments, contract_source_code, contract_name) + + check_func.(clean_constructor_arguments) + end + end + end + + defp remove_substring_of_require_messages(constructor_arguments, contract_source_code, contract_name) do + require_msgs = + contract_source_code + |> extract_require_messages_from_constructor(contract_name) + |> Enum.filter(fn require_msg -> require_msg != nil end) + + longest_substring_to_delete = + Enum.reduce(require_msgs, "", fn msg, best_match -> + case String.myers_difference(constructor_arguments, msg) do + [{:eq, match} | _] -> + if String.length(match) > String.length(best_match) do + match + else + best_match + end + + _ -> + best_match + end + end) + + [_, cleaned_constructor_arguments] = String.split(constructor_arguments, longest_substring_to_delete, parts: 2) + cleaned_constructor_arguments + end + + def remove_require_messages_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) do + all_msgs = + contract_source_code + |> extract_require_messages_from_constructor(contract_name) + + filtered_msgs = + all_msgs + |> Enum.filter(fn require_msg -> require_msg != nil end) + + msgs_list = + filtered_msgs + |> Enum.reverse() + + Enum.reduce(msgs_list, constructor_arguments, fn msg, pure_constructor_arguments -> + case String.split(pure_constructor_arguments, msg, parts: 2) do + [_, constructor_arguments_part] -> + constructor_arguments_part + + [_] -> + pure_constructor_arguments + end + end) + end + + def remove_keccak256_strings_from_constructor_arguments(contract_source_code, constructor_arguments, contract_name) do + all_strings = + contract_source_code + |> extract_strings_from_constructor(contract_name) + + strings_list = + all_strings + |> Enum.reverse() + + Enum.reduce(strings_list, constructor_arguments, fn msg, pure_constructor_arguments -> + case String.split(pure_constructor_arguments, msg, parts: 2) do + [_, constructor_arguments_part] -> + constructor_arguments_part + + [_] -> + pure_constructor_arguments + end + end) + end + + def find_constructor_arguments(address_hash, abi, contract_source_code, contract_name) do + creation_code = + address_hash + |> Chain.contract_creation_input_data() + |> String.replace("0x", "") + + check_func = parse_constructor_and_return_check_func(abi) + + extract_constructor_arguments(creation_code, check_func, contract_source_code, contract_name) + end + + defp parse_constructor_and_return_check_func(abi) do + constructor_abi = Enum.find(abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end) + + input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1) + + fn assumed_arguments -> + try do + _ = + assumed_arguments + |> Base.decode16!(case: :mixed) + |> TypeDecoder.decode_raw(input_types) + + assumed_arguments + rescue + _ -> + false + end + end + end + + def extract_require_messages_from_constructor(contract_source_code, _contract_name) do + # todo: _contract_name is for parsing of actually used constructor for concrete contract + require_contents = find_all_requires(contract_source_code) + + messages_list = + Enum.reduce(require_contents, [], fn require_content, msgs -> + msg = get_require_message_hex(require_content) + if msg, do: [msg | msgs], else: msgs + end) + + if messages_list, do: messages_list, else: [] + end + + def extract_strings_from_constructor(contract_source_code, _contract_name) do + keccak256_contents = find_all_strings(contract_source_code) + + strings_list = + Enum.reduce(keccak256_contents, [], fn keccak256_content, strs -> + str = get_keccak256_string_hex(keccak256_content) + if str, do: [str | strs], else: strs + end) + + if strings_list, do: strings_list, else: [] + end + + def find_constructor_content(contract_source_code) do + case String.split(contract_source_code, "constructor", parts: 2) do + [_, right_from_contstructor] -> + [_, right_from_contstructor_inside] = String.split(right_from_contstructor, "{", parts: 2) + [constructor, _] = String.split(right_from_contstructor_inside, "}", parts: 2) + constructor + + [_] -> + nil + end + end + + def find_all_requires(contract_source_code) do + if contract_source_code do + trimmed_source_code = + contract_source_code + |> String.replace(~r/ +/, " ") + |> String.replace("require(", "require (") + + [_ | requires] = + trimmed_source_code + |> String.split("require (") + + Enum.reduce(requires, [], fn right_from_require, requires_list -> + [require_content | _] = String.split(right_from_require, ");", parts: 2) + [require_content | requires_list] + end) + else + [] + end + end + + def find_all_strings(contract_source_code) do + if contract_source_code do + [_ | keccak256s] = String.split(contract_source_code, "keccak256") + + Enum.reduce(keccak256s, [], fn right_from_keccak256, keccak256s_list -> + parts = String.split(right_from_keccak256, "\"") + + if Enum.count(parts) >= 3 do + [_ | [right_from_keccak256_inside]] = String.split(right_from_keccak256, "\"", parts: 2) + [keccak256_content | _] = String.split(right_from_keccak256_inside, "\"", parts: 2) + [keccak256_content | keccak256s_list] + else + keccak256s_list + end + end) + else + [] + end + end + + def get_require_message_hex(require_content) do + parts = String.split(require_content, ",") + + if Enum.count(parts) > 1 do + [msg] = Enum.take(parts, -1) + + msg + |> String.trim() + |> String.trim_leading("\"") + |> String.trim_trailing("\"") + |> String.trim_leading("'") + |> String.trim_trailing("'") + |> Base.encode16(case: :lower) + else + nil + end + end + + def get_keccak256_string_hex(keccak256_content) do + if keccak256_content !== "" do + keccak256_content + |> String.trim() + |> String.trim_leading("\"") + |> String.trim_trailing("\"") + |> String.trim_leading("'") + |> String.trim_trailing("'") + |> Base.encode16(case: :lower) + else + nil + end + end + + def experimental_find_constructor_args(rest_creation, rest_generated, abi) do + got_args = + rest_creation + |> String.split(extract_constructor_arguments(rest_generated, nil, nil, nil)) + |> List.last() + + abi + |> parse_constructor_and_return_check_func() + |> (& &1.(got_args)).() + end +end diff --git a/apps/explorer/priv/repo/migrations/20200410183115_create_index_token_instances_token_contract_address_hash_token.exs b/apps/explorer/priv/repo/migrations/20200410183115_create_index_token_instances_token_contract_address_hash_token.exs new file mode 100644 index 000000000000..226595d4aa3d --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20200410183115_create_index_token_instances_token_contract_address_hash_token.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.CreateIndexTokenInstancesTokenContractAddressHashToken do + use Ecto.Migration + + def change do + create_if_not_exists(index(:token_instances, [:token_contract_address_hash, :token_id])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20211018072347_add_is_empty_index.exs b/apps/explorer/priv/repo/migrations/20211018072347_add_is_empty_index.exs index 8631b31307d7..8691c9ea320d 100644 --- a/apps/explorer/priv/repo/migrations/20211018072347_add_is_empty_index.exs +++ b/apps/explorer/priv/repo/migrations/20211018072347_add_is_empty_index.exs @@ -1,7 +1,5 @@ defmodule Explorer.Repo.Migrations.AddIsEmptyIndex do use Ecto.Migration - @disable_ddl_transaction true - @disable_migration_lock true def change do create( @@ -9,8 +7,7 @@ defmodule Explorer.Repo.Migrations.AddIsEmptyIndex do :blocks, ~w(consensus)a, name: :empty_consensus_blocks, - where: "is_empty IS NULL", - concurrently: true + where: "is_empty IS NULL" ) ) end diff --git a/apps/explorer/priv/repo/migrations/20211018073904_add_transactions_hash_block_number_block_hash_index.exs b/apps/explorer/priv/repo/migrations/20211018073904_add_transactions_hash_block_number_block_hash_index.exs new file mode 100644 index 000000000000..453d5424e797 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20211018073904_add_transactions_hash_block_number_block_hash_index.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.AddTransactionsHashBlockNumberBlockHashIndex do + use Ecto.Migration + + def change do + create( + index( + :transactions, + ~w(hash block_number block_hash)a + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20211018163205_add_block_rewards_address_type_block_hash_index.exs b/apps/explorer/priv/repo/migrations/20211018163205_add_block_rewards_address_type_block_hash_index.exs new file mode 100644 index 000000000000..c21e144d36b0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20211018163205_add_block_rewards_address_type_block_hash_index.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.AddBlockRewardsAddressTypeBlockHashIndex do + use Ecto.Migration + + def change do + create( + index( + :block_rewards, + ~w(address_type block_hash)a + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20211018164721_add_blocks_inserted_at_miner_hash_index.exs b/apps/explorer/priv/repo/migrations/20211018164721_add_blocks_inserted_at_miner_hash_index.exs new file mode 100644 index 000000000000..b1152f1c3fcc --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20211018164721_add_blocks_inserted_at_miner_hash_index.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.AddBlocksInsertedAtMinerHashIndex do + use Ecto.Migration + + def change do + create( + index( + :blocks, + ~w(inserted_at miner_hash)a + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20211114204400_alter_unfetched_token_balances_index.exs b/apps/explorer/priv/repo/migrations/20211114204400_alter_unfetched_token_balances_index.exs new file mode 100644 index 000000000000..b2a35dc7bd78 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20211114204400_alter_unfetched_token_balances_index.exs @@ -0,0 +1,32 @@ +defmodule Explorer.Repo.Migrations.AlterUnfetchedTokenBalancesIndex do + use Ecto.Migration + + def change do + drop_if_exists( + index( + :address_token_balances, + [:address_hash, :token_contract_address_hash, :block_number], + name: "unfetched_token_balances", + where: "value_fetched_at IS NULL and token_id IS NULL" + ) + ) + + drop_if_exists( + index( + :address_token_balances, + [:address_hash, :token_contract_address_hash, :token_id, :block_number], + name: "unfetched_token_balances_with_token_id", + where: "value_fetched_at IS NULL and token_id IS NOT NULL" + ) + ) + + create( + index( + :address_token_balances, + ~w(address_hash token_contract_address_hash token_id block_number)a, + name: :unfetched_token_balances, + where: "value_fetched_at IS NULL" + ) + ) + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index be01544eeecc..cf36c2521c21 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -23,24 +23,17 @@ defmodule Explorer.ChainTest do Token, TokenTransfer, Transaction, + SmartContract, Wei } alias Explorer.{Chain, Etherscan} - alias Explorer.Chain.Address.Counters - alias Explorer.Chain.Cache.Block, as: BlockCache - alias Explorer.Chain.Cache.Transaction, as: TransactionCache - alias Explorer.Chain.Cache.PendingBlockOperation, as: PendingBlockOperationCache alias Explorer.Chain.InternalTransaction.Type alias Explorer.Chain.Supply.ProofOfAuthority alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesCounter - @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" - @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" - doctest Explorer.Chain setup :set_mox_global @@ -50,10 +43,10 @@ defmodule Explorer.ChainTest do describe "remove_nonconsensus_blocks_from_pending_ops/0" do test "removes pending ops for nonconsensus blocks" do block = insert(:block) - insert(:pending_block_operation, block: block, block_number: block.number) + insert(:pending_block_operation, block: block, fetch_internal_transactions: true) nonconsensus_block = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block, block_number: nonconsensus_block.number) + insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true) :ok = Chain.remove_nonconsensus_blocks_from_pending_ops() @@ -63,13 +56,13 @@ defmodule Explorer.ChainTest do test "removes pending ops for nonconsensus blocks by block hashes" do block = insert(:block) - insert(:pending_block_operation, block: block, block_number: block.number) + insert(:pending_block_operation, block: block, fetch_internal_transactions: true) nonconsensus_block = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block, block_number: nonconsensus_block.number) + insert(:pending_block_operation, block: nonconsensus_block, fetch_internal_transactions: true) nonconsensus_block1 = insert(:block, consensus: false) - insert(:pending_block_operation, block: nonconsensus_block1, block_number: nonconsensus_block1.number) + insert(:pending_block_operation, block: nonconsensus_block1, fetch_internal_transactions: true) :ok = Chain.remove_nonconsensus_blocks_from_pending_ops([nonconsensus_block1.hash]) @@ -88,7 +81,7 @@ defmodule Explorer.ChainTest do start_supervised!(AddressesWithBalanceCounter) AddressesWithBalanceCounter.consolidate() - addresses_with_balance = Counters.count_addresses_with_balance_from_cache() + addresses_with_balance = Chain.count_addresses_with_balance_from_cache() assert is_integer(addresses_with_balance) assert addresses_with_balance == 2 @@ -104,16 +97,11 @@ defmodule Explorer.ChainTest do start_supervised!(AddressesCounter) AddressesCounter.consolidate() - addresses_with_balance = Counters.address_estimated_count() + addresses_with_balance = Chain.address_estimated_count() assert is_integer(addresses_with_balance) assert addresses_with_balance == 3 end - - test "returns 0 on empty table" do - start_supervised!(AddressesCounter) - assert 0 == Counters.address_estimated_count() - end end describe "last_db_block_status/0" do @@ -148,22 +136,235 @@ defmodule Explorer.ChainTest do end end - describe "ERC721_or_ERC1155_token_instance_from_token_id_and_token_address/2" do + describe "get_average_gas_price/4" do + test "returns nil percentile values if no blocks in the DB" do + assert {:ok, + %{ + "slow" => nil, + "average" => nil, + "fast" => nil + }} = Chain.get_average_gas_price(3, 35, 60, 90) + end + + test "returns nil percentile values if blocks are empty in the DB" do + insert(:block) + insert(:block) + insert(:block) + + assert {:ok, + %{ + "slow" => nil, + "average" => nil, + "fast" => nil + }} = Chain.get_average_gas_price(3, 35, 60, 90) + end + + test "returns nil percentile values for blocks with failed txs in the DB" do + block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + + :transaction + |> insert( + error: "Reverted", + status: :error, + block_hash: block.hash, + block_number: block.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 100, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + assert {:ok, + %{ + "slow" => nil, + "average" => nil, + "fast" => nil + }} = Chain.get_average_gas_price(3, 35, 60, 90) + end + + test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do + block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 0, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 0, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + assert {:ok, + %{ + "slow" => nil, + "average" => nil, + "fast" => nil + }} = Chain.get_average_gas_price(2, 35, 60, 90) + end + + test "returns the same percentile values if gas price is the same over transactions" do + block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + assert {:ok, + %{ + "slow" => 1.0, + "average" => 1.0, + "fast" => 1.0 + }} = Chain.get_average_gas_price(2, 35, 60, 90) + end + + test "returns correct min gas price from the block" do + block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 1, + gas_price: 3_000_000_000, + hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" + ) + + assert {:ok, + %{ + "slow" => 1.0, + "average" => 1.0, + "fast" => 1.0 + }} = Chain.get_average_gas_price(3, 35, 60, 90) + end + + test "returns correct average percentile" do + block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") + block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") + block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0") + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 2_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 4_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block3.hash, + block_number: block3.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 4_000_000_000, + hash: "0x7d4bc5569053fc29f471901e967c9e60205ac7a122b0e9a789683652c34cc11a" + ) + + assert {:ok, + %{ + "average" => 4.0 + }} = Chain.get_average_gas_price(3, 35, 60, 90) + end + end + + describe "ERC721_token_instance_from_token_id_and_token_address/2" do test "return ERC721 token instance" do - token = insert(:token) + contract_address = insert(:address) token_id = 10 - insert(:token_instance, - token_contract_address_hash: token.contract_address_hash, + insert(:token_transfer, + from_address: contract_address, + token_contract_address: contract_address, token_id: token_id ) assert {:ok, result} = - Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address( - token_id, - token.contract_address_hash - ) + Chain.erc721_token_instance_from_token_id_and_token_address(token_id, contract_address.hash) assert result.token_id == Decimal.new(token_id) end @@ -186,6 +387,30 @@ defmodule Explorer.ChainTest do assert result.token_contract_address_hash == token.contract_address_hash end + test "replaces existing token instance record" do + token = insert(:token) + + params = %{ + token_id: 1, + token_contract_address_hash: token.contract_address_hash, + metadata: %{uri: "http://example.com"} + } + + {:ok, _} = Chain.upsert_token_instance(params) + + params1 = %{ + token_id: 1, + token_contract_address_hash: token.contract_address_hash, + metadata: %{uri: "http://example1.com"} + } + + {:ok, result} = Chain.upsert_token_instance(params1) + + assert result.token_id == Decimal.new(1) + assert result.metadata == params1.metadata + assert result.token_contract_address_hash == token.contract_address_hash + end + test "fails to import with invalid params" do params = %{ token_id: 1, @@ -222,8 +447,7 @@ defmodule Explorer.ChainTest do insert(:token_instance, token_id: 1, token_contract_address_hash: token.contract_address_hash, - error: "no uri", - metadata: nil + error: "no uri" ) params = %{ @@ -269,7 +493,7 @@ defmodule Explorer.ChainTest do address: address ) - assert Enum.count(Chain.address_to_logs(address_hash, false)) == 2 + assert Enum.count(Chain.address_to_logs(address_hash)) == 2 end test "paginates logs" do @@ -280,7 +504,7 @@ defmodule Explorer.ChainTest do |> insert(to_address: address) |> with_block() - _log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number) + log1 = insert(:log, transaction: transaction, index: 1, address: address, block_number: transaction.block_number) 2..51 |> Enum.map(fn index -> @@ -296,11 +520,11 @@ defmodule Explorer.ChainTest do paging_options1 = %PagingOptions{page_size: 1} - [_log] = Chain.address_to_logs(address_hash, false, paging_options: paging_options1) + [_log] = Chain.address_to_logs(address_hash, paging_options: paging_options1) - paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, 51}} + paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}} - assert Enum.count(Chain.address_to_logs(address_hash, false, paging_options: paging_options2)) == 50 + assert Enum.count(Chain.address_to_logs(address_hash, paging_options: paging_options2)) == 50 end test "searches logs by topic when the first topic matches" do @@ -319,9 +543,6 @@ defmodule Explorer.ChainTest do block_number: transaction1.block_number ) - first_topic_hex_string = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" - {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(first_topic_hex_string) - transaction2 = :transaction |> insert(from_address: address) @@ -332,11 +553,11 @@ defmodule Explorer.ChainTest do transaction: transaction2, index: 2, address: address, - first_topic: first_topic, + first_topic: "test", block_number: transaction2.block_number ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: first_topic_hex_string) + [found_log] = Chain.address_to_logs(address_hash, topic: "test") assert found_log.transaction.hash == transaction2.hash end @@ -349,16 +570,13 @@ defmodule Explorer.ChainTest do |> insert(to_address: address) |> with_block() - fourth_topic_hex_string = "0x927abf391899d10d331079a63caffa905efa7075a44a7bbd52b190db4c4308fb" - {:ok, fourth_topic} = Explorer.Chain.Hash.Full.cast(fourth_topic_hex_string) - insert(:log, block: transaction1.block, block_number: transaction1.block_number, transaction: transaction1, index: 1, address: address, - fourth_topic: fourth_topic + fourth_topic: "test" ) transaction2 = @@ -374,220 +592,575 @@ defmodule Explorer.ChainTest do address: address ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: fourth_topic_hex_string) + [found_log] = Chain.address_to_logs(address_hash, topic: "test") assert found_log.transaction.hash == transaction1.hash end end - describe "total_transactions_sent_by_address/1" do - test "increments +1 in the last nonce result" do - address = insert(:address) + describe "address_to_transactions_with_rewards/2" do + test "without transactions" do + %Address{hash: address_hash} = insert(:address) - :transaction - |> insert(nonce: 100, from_address: address) - |> with_block(insert(:block, number: 1000)) + assert Repo.aggregate(Transaction, :count, :hash) == 0 - assert Counters.total_transactions_sent_by_address(address.hash) == 101 + assert [] == Chain.address_to_transactions_with_rewards(address_hash) end - test "returns 0 when the address did not send transactions" do - address = insert(:address) + test "with from transactions" do + %Address{hash: address_hash} = address = insert(:address) - :transaction - |> insert(nonce: 100, to_address: address) - |> with_block(insert(:block, number: 1000)) + transaction = + :transaction + |> insert(from_address: address) + |> with_block() - assert Counters.total_transactions_sent_by_address(address.hash) == 0 + assert [transaction] == + Chain.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) end - end - describe "balance/2" do - test "with Address.t with :wei" do - assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1) - assert Chain.balance(%Address{fetched_coin_balance: nil}, :wei) == nil + test "with to transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Chain.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) end - test "with Address.t with :gwei" do - assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9") - assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1) - assert Chain.balance(%Address{fetched_coin_balance: nil}, :gwei) == nil + test "with to and from transactions and direction: :from" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + # only contains "from" transaction + assert [transaction] == + Chain.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) end - test "with Address.t with :ether" do - assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18") - assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1) - assert Chain.balance(%Address{fetched_coin_balance: nil}, :ether) == nil + test "with to and from transactions and direction: :to" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Chain.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) end - end - describe "block_to_transactions/2" do - test "without transactions" do + test "with to and from transactions and no :direction option" do + %Address{hash: address_hash} = address = insert(:address) block = insert(:block) - assert Repo.aggregate(Transaction, :count, :hash) == 0 + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block(block) - assert [] = Chain.block_to_transactions(block.hash) + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block(block) + + assert [transaction2, transaction1] == + Chain.address_to_transactions_with_rewards(address_hash) + |> Repo.preload([:block, :to_address, :from_address]) end - test "with transactions" do - %Transaction{block: block, hash: transaction_hash} = + test "does not include non-contract-creation parent transactions" do + transaction = + %Transaction{} = :transaction |> insert() |> with_block() - assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block.hash) + %InternalTransaction{created_contract_address: address} = + insert(:internal_transaction_create, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + assert [] == Chain.address_to_transactions_with_rewards(address.hash) end - test "with transactions can be paginated by {index}" do - block = insert(:block) + test "returns transactions that have token transfers for the given to_address" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address, to_address_hash: address.hash) + |> with_block() + + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) + + assert [transaction.hash] == + Chain.address_to_transactions_with_rewards(address_hash) + |> Enum.map(& &1.hash) + end + + test "with transactions can be paginated" do + %Address{hash: address_hash} = address = insert(:address) second_page_hashes = - 50 - |> insert_list(:transaction) - |> with_block(block) + 2 + |> insert_list(:transaction, from_address: address) + |> with_block() |> Enum.map(& &1.hash) - %Transaction{index: index} = + %Transaction{block_number: block_number, index: index} = :transaction - |> insert() - |> with_block(block) + |> insert(from_address: address) + |> with_block() assert second_page_hashes == - block.hash - |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {block.number, index}, page_size: 50}) + address_hash + |> Chain.address_to_transactions_with_rewards( + paging_options: %PagingOptions{ + key: {block_number, index}, + page_size: 2 + } + ) |> Enum.map(& &1.hash) + |> Enum.reverse() end - test "returns transactions with token_transfers preloaded" do - address = insert(:address) - block = insert(:block) - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address) + test "returns results in reverse chronological order by block number and transaction index" do + %Address{hash: address_hash} = address = insert(:address) - transaction = + a_block = insert(:block, number: 6000) + + %Transaction{hash: first} = :transaction - |> insert() - |> with_block(block) + |> insert(to_address: address) + |> with_block(a_block) - insert_list( - 2, - :token_transfer, - to_address: address, - transaction: transaction, - token_contract_address: token_contract_address, - token: token - ) + %Transaction{hash: second} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) - fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block.hash)) - assert fetched_transaction.hash == transaction.hash - assert length(fetched_transaction.token_transfers) == 2 + %Transaction{hash: third} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: fourth} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + b_block = insert(:block, number: 2000) + + %Transaction{hash: fifth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + %Transaction{hash: sixth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + result = + address_hash + |> Chain.address_to_transactions_with_rewards() + |> Enum.map(& &1.hash) + + assert [fourth, third, second, first, sixth, fifth] == result end - end - describe "block_to_gas_used_by_1559_txs/1" do - test "sum of gas_usd from all transactions including legacy" do - block = insert(:block, base_fee_per_gas: 4) + test "with emission rewards" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - insert(:transaction, - gas_used: 4, - cumulative_gas_used: 3, - block_number: block.number, + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + + insert( + :reward, + address_hash: block.miner_hash, block_hash: block.hash, - index: 1, - max_fee_per_gas: 0, - max_priority_fee_per_gas: 3 + address_type: :validator ) - insert(:transaction, - gas_used: 6, - cumulative_gas_used: 3, - block_number: block.number, + insert( + :reward, + address_hash: block.miner_hash, block_hash: block.hash, - index: 2 + address_type: :emission_funds ) - assert Decimal.new(10) == Chain.block_to_gas_used_by_1559_txs(block.hash) + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + res = Chain.address_to_transactions_with_rewards(block.miner.hash) + + assert [{_, _}] = res + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) end - end - describe "block_to_priority_fee_of_1559_txs/1" do - test "with transactions: tx.max_fee_per_gas = 0" do - block = insert(:block, base_fee_per_gas: 4) + test "with emission rewards and transactions" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - insert(:transaction, - gas_used: 4, - cumulative_gas_used: 3, - block_number: block.number, + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + + insert( + :reward, + address_hash: block.miner_hash, block_hash: block.hash, - index: 1, - max_fee_per_gas: 0, - max_priority_fee_per_gas: 3 + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(to_address: block.miner) + |> with_block(block) + |> Repo.preload(:token_transfers) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end ) - assert Decimal.new(0) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :to) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) end - test "with transactions: tx.max_fee_per_gas - block.base_fee_per_gas >= tx.max_priority_fee_per_gas" do - block = insert(:block, base_fee_per_gas: 1) + test "with transactions if rewards are not in the range of blocks" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - insert(:transaction, - gas_used: 3, - cumulative_gas_used: 3, - block_number: block.number, + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, block_hash: block.hash, - index: 1, - max_fee_per_gas: 5, - max_priority_fee_per_gas: 1 + address_type: :validator ) - assert Decimal.new(3) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(from_address: block.miner) + |> with_block() + |> Repo.preload(:token_transfers) + + assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) end - test "with transactions: tx.max_fee_per_gas - block.base_fee_per_gas < tx.max_priority_fee_per_gas" do - block = insert(:block, base_fee_per_gas: 4) + test "with emissions rewards, but feature disabled" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - insert(:transaction, - gas_used: 4, - cumulative_gas_used: 3, - block_number: block.number, + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, block_hash: block.hash, - index: 1, - max_fee_per_gas: 5, - max_priority_fee_per_gas: 3 + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds ) - assert Decimal.new(4) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash) + end + end + + describe "address_to_transactions_tasks_range_of_blocks/2" do + test "returns empty extremums if no transactions" do + address = insert(:address) + + extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => nil, + :max_block_number => 0 + } + end + + test "returns correct extremums for from_address" do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for to_address" do + address = insert(:address) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for created_contract_address" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for multiple number of transactions" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 999)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1003)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1001)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1004)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1002)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 998)) + + extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 998, + :max_block_number => 1004 + } + end + end + + describe "total_transactions_sent_by_address/1" do + test "increments +1 in the last nonce result" do + address = insert(:address) + + :transaction + |> insert(nonce: 100, from_address: address) + |> with_block(insert(:block, number: 1000)) + + assert Chain.total_transactions_sent_by_address(address.hash) == 101 + end + + test "returns 0 when the address did not send transactions" do + address = insert(:address) + + :transaction + |> insert(nonce: 100, to_address: address) + |> with_block(insert(:block, number: 1000)) + + assert Chain.total_transactions_sent_by_address(address.hash) == 0 + end + end + + describe "balance/2" do + test "with Address.t with :wei" do + assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :wei) == Decimal.new(1) + assert Chain.balance(%Address{fetched_coin_balance: nil}, :wei) == nil + end + + test "with Address.t with :gwei" do + assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :gwei) == Decimal.new("1e-9") + assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e9")}}, :gwei) == Decimal.new(1) + assert Chain.balance(%Address{fetched_coin_balance: nil}, :gwei) == nil + end + + test "with Address.t with :ether" do + assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new(1)}}, :ether) == Decimal.new("1e-18") + assert Chain.balance(%Address{fetched_coin_balance: %Wei{value: Decimal.new("1e18")}}, :ether) == Decimal.new(1) + assert Chain.balance(%Address{fetched_coin_balance: nil}, :ether) == nil end + end + + describe "block_to_transactions/2" do + test "without transactions" do + block = insert(:block) + + assert Repo.aggregate(Transaction, :count, :hash) == 0 - test "with legacy transactions" do - block = insert(:block, base_fee_per_gas: 1) + assert [] = Chain.block_to_transactions(block.hash) + end + + test "with transactions" do + %Transaction{block: block, hash: transaction_hash} = + :transaction + |> insert() + |> with_block() + + assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block.hash) + end + + test "with transactions can be paginated by {index}" do + block = insert(:block) + + second_page_hashes = + 50 + |> insert_list(:transaction) + |> with_block(block) + |> Enum.map(& &1.hash) - insert(:transaction, - gas_price: 5, - gas_used: 6, - cumulative_gas_used: 6, - block_number: block.number, - block_hash: block.hash, - index: 1 - ) + %Transaction{index: index} = + :transaction + |> insert() + |> with_block(block) - assert Decimal.new(24) == Chain.block_to_priority_fee_of_1559_txs(block.hash) + assert second_page_hashes == + block.hash + |> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50}) + |> Enum.map(& &1.hash) end - test "0 in blockchain with no EIP-1559 implemented" do - block = insert(:block, base_fee_per_gas: nil) + test "returns transactions with token_transfers preloaded" do + address = insert(:address) + block = insert(:block) + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address) - insert(:transaction, - gas_price: 1, - gas_used: 4, - cumulative_gas_used: 4, - block_number: block.number, - block_hash: block.hash, - index: 1 + transaction = + :transaction + |> insert() + |> with_block(block) + + insert_list( + 2, + :token_transfer, + to_address: address, + transaction: transaction, + token_contract_address: token_contract_address, + token: token ) - assert 0 == Chain.block_to_priority_fee_of_1559_txs(block.hash) + fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block.hash)) + assert fetched_transaction.hash == transaction.hash + assert length(fetched_transaction.token_transfers) == 2 end end @@ -612,13 +1185,13 @@ defmodule Explorer.ChainTest do test "without transactions" do %Address{hash: address_hash} = insert(:address) - assert Counters.address_to_incoming_transaction_count(address_hash) == 0 + assert Chain.address_to_incoming_transaction_count(address_hash) == 0 end test "with transactions" do %Transaction{to_address: to_address} = insert(:transaction) - assert Counters.address_to_incoming_transaction_count(to_address.hash) == 1 + assert Chain.address_to_incoming_transaction_count(to_address.hash) == 1 end end @@ -720,13 +1293,7 @@ defmodule Explorer.ChainTest do end end - describe "finished_indexing_internal_transactions?/0" do - setup do - Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) - Supervisor.restart_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) - on_exit(fn -> Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) end) - end - + describe "finished_indexing?/0" do test "finished indexing" do block = insert(:block, number: 1) @@ -734,11 +1301,11 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - assert Chain.finished_indexing_internal_transactions?() + assert Chain.finished_indexing?() end test "finished indexing (no txs)" do - assert Chain.finished_indexing_internal_transactions?() + assert Chain.finished_indexing?() end test "not finished indexing" do @@ -748,9 +1315,9 @@ defmodule Explorer.ChainTest do |> insert() |> with_block(block) - insert(:pending_block_operation, block: block, block_number: block.number) + insert(:pending_block_operation, block: block, fetch_internal_transactions: true) - refute Chain.finished_indexing_internal_transactions?() + refute Chain.finished_indexing?() end end @@ -878,9 +1445,7 @@ defmodule Explorer.ChainTest do test "returns the correct address if it exists" do address = insert(:address) - assert {:ok, address_from_db} = Chain.hash_to_address(address.hash) - assert address_from_db.hash == address.hash - assert address_from_db.inserted_at == address.inserted_at + assert {:ok, address} = Chain.hash_to_address(address.hash) end test "has_decompiled_code? is true if there are decompiled contracts" do @@ -929,16 +1494,14 @@ defmodule Explorer.ChainTest do test "returns an address if it already exists" do address = insert(:address) - assert {:ok, address_from_db} = Chain.find_or_insert_address_from_hash(address.hash) - assert address_from_db.hash == address.hash - assert address_from_db.inserted_at == address.inserted_at + assert {:ok, address} = Chain.find_or_insert_address_from_hash(address.hash) end test "returns an address if it doesn't exist" do hash_str = "0xcbbcd5ac86f9a50e13313633b262e16f695a90c2" {:ok, hash} = Chain.string_to_address_hash(hash_str) - assert {:ok, %Chain.Address{hash: ^hash}} = Chain.find_or_insert_address_from_hash(hash) + assert {:ok, %Chain.Address{hash: hash}} = Chain.find_or_insert_address_from_hash(hash) end end @@ -1007,100 +1570,26 @@ defmodule Explorer.ChainTest do end end - describe "indexed_ratio_blocks/0" do - setup do - Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Block.child_id()) - Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Block.child_id()) - - on_exit(fn -> - Application.put_env(:indexer, :first_block, 0) - end) - end - + describe "indexed_ratio/0" do test "returns indexed ratio" do for index <- 5..9 do - insert(:block, number: index, consensus: true) + insert(:block, number: index) end - BlockCache.estimated_count() - - assert Decimal.compare(Chain.indexed_ratio_blocks(), Decimal.from_float(0.5)) == :eq + assert Decimal.cmp(Chain.indexed_ratio(), Decimal.from_float(0.5)) == :eq end test "returns 0 if no blocks" do - assert Decimal.new(0) == Chain.indexed_ratio_blocks() + assert Decimal.new(0) == Chain.indexed_ratio() end test "returns 1.0 if fully indexed blocks" do for index <- 0..9 do - insert(:block, number: index, consensus: true) - Process.sleep(200) - end - - BlockCache.estimated_count() - - assert Decimal.compare(Chain.indexed_ratio_blocks(), 1) == :eq - end - - test "returns 1.0 if fully indexed blocks starting from given FIRST_BLOCK" do - Application.put_env(:indexer, :first_block, 5) - - for index <- 5..9 do - insert(:block, number: index, consensus: true) - Process.sleep(200) - end - - BlockCache.estimated_count() - - assert Decimal.compare(Chain.indexed_ratio_blocks(), 1) == :eq - end - end - - describe "indexed_ratio_internal_transactions/0" do - setup do - Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) - Supervisor.restart_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) - - on_exit(fn -> - Application.put_env(:indexer, :trace_first_block, 0) - Supervisor.terminate_child(Explorer.Supervisor, PendingBlockOperationCache.child_id()) - end) - end - - test "returns indexed ratio" do - for index <- 0..9 do - block = insert(:block, number: index) - - if index === 0 || index === 5 || index === 7 do - insert(:pending_block_operation, block: block, block_number: block.number) - end - end - - Chain.indexed_ratio_internal_transactions() - - assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), Decimal.from_float(0.7)) == :eq - end - - test "returns 0 if no blocks" do - assert Decimal.new(0) == Chain.indexed_ratio_internal_transactions() - end - - test "returns 1.0 if no pending block operations" do - for index <- 0..9 do - insert(:block, number: index) - end - - assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), 1) == :eq - end - - test "returns 1.0 if fully indexed blocks with internal transactions starting from given TRACE_FIRST_BLOCK" do - Application.put_env(:indexer, :trace_first_block, 5) - - for index <- 5..9 do insert(:block, number: index) + Process.sleep(200) end - assert Decimal.compare(Chain.indexed_ratio_internal_transactions(), 1) == :eq + assert Decimal.cmp(Chain.indexed_ratio(), 1) == :eq end end @@ -1247,10 +1736,6 @@ defmodule Explorer.ChainTest do # Full tests in `test/explorer/import_test.exs` describe "import/1" do - {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) - {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) - {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) - @import_data %{ blocks: %{ params: [ @@ -1306,12 +1791,13 @@ defmodule Explorer.ChainTest do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: first_topic, - second_topic: second_topic, - third_topic: third_topic, + first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "mined" } ] }, @@ -1374,9 +1860,6 @@ defmodule Explorer.ChainTest do } test "with valid data" do - {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) - {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) - {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) @@ -1479,9 +1962,9 @@ defmodule Explorer.ChainTest do 167, 100, 0, 0>> }, index: 0, - first_topic: ^first_topic, - second_topic: ^second_topic, - third_topic: ^third_topic, + first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", fourth_topic: nil, transaction_hash: %Hash{ byte_count: 32, @@ -1489,6 +1972,7 @@ defmodule Explorer.ChainTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, + type: "mined", inserted_at: %{}, updated_at: %{} } @@ -1598,6 +2082,76 @@ defmodule Explorer.ChainTest do end end + describe "list_top_addresses/0" do + test "without addresses with balance > 0" do + insert(:address, fetched_coin_balance: 0) + assert [] = Chain.list_top_addresses() + end + + test "with top addresses in order" do + address_hashes = + 4..1 + |> Enum.map(&insert(:address, fetched_coin_balance: &1)) + |> Enum.map(& &1.hash) + + assert address_hashes == + Chain.list_top_addresses() + |> Enum.map(fn {address, _transaction_count} -> address end) + |> Enum.map(& &1.hash) + end + + # flaky test + # test "with top addresses in order with matching value" do + # test_hashes = + # 4..0 + # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) + # |> Enum.map(&elem(&1, 1)) + + # tail = + # 4..1 + # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) + # |> Enum.map(& &1.hash) + + # first_result_hash = + # :address + # |> insert(fetched_coin_balance: 4, hash: Enum.fetch!(test_hashes, 4)) + # |> Map.fetch!(:hash) + + # assert [first_result_hash | tail] == + # Chain.list_top_addresses() + # |> Enum.map(fn {address, _transaction_count} -> address end) + # |> Enum.map(& &1.hash) + # end + + # flaky test + # test "paginates addresses" do + # test_hashes = + # 4..0 + # |> Enum.map(&Explorer.Chain.Hash.cast(Explorer.Chain.Hash.Address, &1)) + # |> Enum.map(&elem(&1, 1)) + + # result = + # 4..1 + # |> Enum.map(&insert(:address, fetched_coin_balance: &1, hash: Enum.fetch!(test_hashes, &1 - 1))) + # |> Enum.map(& &1.hash) + + # options = [paging_options: %PagingOptions{page_size: 1}] + + # [{top_address, _}] = Chain.list_top_addresses(options) + # assert top_address.hash == List.first(result) + + # tail_options = [ + # paging_options: %PagingOptions{key: {top_address.fetched_coin_balance.value, top_address.hash}, page_size: 3} + # ] + + # tail_result = tail_options |> Chain.list_top_addresses() |> Enum.map(fn {address, _} -> address.hash end) + + # [_ | expected_tail] = result + + # assert tail_result == expected_tail + # end + end + describe "stream_blocks_without_rewards/2" do test "includes consensus blocks" do %Block{hash: consensus_hash} = insert(:block, consensus: true) @@ -1766,7 +2320,7 @@ defmodule Explorer.ChainTest do %InternalTransaction{ from_address: %Ecto.Association.NotLoaded{}, to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + transaction: %Transaction{} } | _ ] = Chain.address_to_internal_transactions(address_hash) @@ -2174,7 +2728,7 @@ defmodule Explorer.ChainTest do describe "transaction_estimated_count/1" do test "returns integer" do - assert is_integer(TransactionCache.estimated_count()) + assert is_integer(Chain.transaction_estimated_count()) end end @@ -2226,7 +2780,7 @@ defmodule Explorer.ChainTest do ]) ) - assert internal_transaction.block_number == block.number + assert internal_transaction.transaction.block_number == block.number end test "with transaction with internal transactions loads associations with in necessity_by_association" do @@ -2248,7 +2802,7 @@ defmodule Explorer.ChainTest do %InternalTransaction{ from_address: %Ecto.Association.NotLoaded{}, to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + transaction: %Transaction{block: %Ecto.Association.NotLoaded{}} } ] = Chain.transaction_to_internal_transactions(transaction.hash) @@ -2371,7 +2925,7 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: transaction_hash_1, index: index_1} = + %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = insert(:internal_transaction, transaction: transaction, index: 1, @@ -2381,23 +2935,13 @@ defmodule Explorer.ChainTest do transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: transaction_hash_2, index: index_2} = - insert(:internal_transaction, - transaction: transaction, - index: 2, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 2, - transaction_index: transaction.index - ) - result = transaction.hash |> Chain.transaction_to_internal_transactions() |> Enum.map(&{&1.transaction_hash, &1.index}) # excluding of internal transactions with type=call and index=0 - assert [{transaction_hash_1, index_1}, {transaction_hash_2, index_2}] == result + assert [{second_transaction_hash, second_index}] == result end test "pages by index" do @@ -2500,7 +3044,7 @@ defmodule Explorer.ChainTest do ]) ) - assert internal_transaction.block_number == block.number + assert internal_transaction.transaction.block_number == block.number end test "with transaction with internal transactions loads associations with in necessity_by_association" do @@ -2522,7 +3066,7 @@ defmodule Explorer.ChainTest do %InternalTransaction{ from_address: %Ecto.Association.NotLoaded{}, to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + transaction: %Transaction{block: %Ecto.Association.NotLoaded{}} } ] = Chain.all_transaction_to_internal_transactions(transaction.hash) @@ -2724,7 +3268,7 @@ defmodule Explorer.ChainTest do test "without logs" do transaction = insert(:transaction) - assert [] = Chain.transaction_to_logs(transaction.hash) + assert [] = Chain.transaction_to_logs(transaction.hash, false) end test "with logs" do @@ -2736,7 +3280,8 @@ defmodule Explorer.ChainTest do %Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction, block: transaction.block, block_number: transaction.block_number) - assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash) + assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = + Chain.transaction_to_logs(transaction.hash, false) end test "with logs can be paginated" do @@ -2767,7 +3312,7 @@ defmodule Explorer.ChainTest do assert second_page_indexes == transaction.hash - |> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50}) + |> Chain.transaction_to_logs(false, paging_options: %PagingOptions{key: {log.index}, page_size: 50}) |> Enum.map(& &1.index) end @@ -2782,6 +3327,7 @@ defmodule Explorer.ChainTest do assert [%Log{address: %Address{}, transaction: %Transaction{}}] = Chain.transaction_to_logs( transaction.hash, + false, necessity_by_association: %{ address: :optional, transaction: :optional @@ -2793,7 +3339,7 @@ defmodule Explorer.ChainTest do address: %Ecto.Association.NotLoaded{}, transaction: %Ecto.Association.NotLoaded{} } - ] = Chain.transaction_to_logs(transaction.hash) + ] = Chain.transaction_to_logs(transaction.hash, false) end end @@ -2825,205 +3371,25 @@ defmodule Explorer.ChainTest do transaction = :transaction |> insert() - |> with_block() - - insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number) - - assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] = - Chain.transaction_to_token_transfers( - transaction.hash, - necessity_by_association: %{ - token: :optional, - transaction: :optional - } - ) - - assert [ - %TokenTransfer{ - token: %Token{}, - transaction: %Ecto.Association.NotLoaded{} - } - ] = Chain.transaction_to_token_transfers(transaction.hash) - end - - test "token transfers ordered by ASC log_index" do - transaction = - :transaction - |> insert() - |> with_block() - - token_transfer_0 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 0 - ) - - token_transfer_4 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 4 - ) - - token_transfer_2 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 2 - ) - - token_transfer_1 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 1 - ) - - token_transfer_3 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 3 - ) - - token_transfers_sorted = - [token_transfer_0, token_transfer_1, token_transfer_2, token_transfer_3, token_transfer_4] - |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - token_transfers_unsorted = - [token_transfer_1, token_transfer_0, token_transfer_2, token_transfer_3, token_transfer_4] - |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - assert token_transfers_sorted == - transaction.hash - |> Chain.transaction_to_token_transfers( - necessity_by_association: %{ - token: :optional, - transaction: :optional - } - ) - |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - assert token_transfers_unsorted != - transaction.hash - |> Chain.transaction_to_token_transfers( - necessity_by_association: %{ - token: :optional, - transaction: :optional - } - ) - |> Enum.map(&{&1.transaction_hash, &1.log_index}) - end - - test "token transfers can be paginated" do - transaction = - :transaction - |> insert() - |> with_block() - - token_transfer_0 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 0 - ) - - token_transfer_4 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 4 - ) - - token_transfer_2 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 2 - ) - - token_transfer_1 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 1 - ) - - token_transfer_3 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 3 - ) - - token_transfer_6 = - insert(:token_transfer, - transaction: transaction, - block: transaction.block, - block_number: transaction.block_number, - log_index: 6 - ) - - token_transfers_first_page = - [token_transfer_0, token_transfer_1, token_transfer_2] |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - token_transfers_second_page = - [token_transfer_2, token_transfer_3, token_transfer_4] |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - token_transfers_third_page = - [token_transfer_4, token_transfer_6] |> Enum.map(&{&1.transaction_hash, &1.log_index}) - - assert token_transfers_first_page == - transaction.hash - |> Chain.transaction_to_token_transfers( - necessity_by_association: %{ - token: :optional, - transaction: :optional - }, - paging_options: %PagingOptions{ - page_size: 3 - } - ) - |> Enum.map(&{&1.transaction_hash, &1.log_index}) + |> with_block() - assert token_transfers_second_page == - transaction.hash - |> Chain.transaction_to_token_transfers( - necessity_by_association: %{ - token: :optional, - transaction: :optional - }, - paging_options: %PagingOptions{ - key: {transaction.block_number, 1}, - page_size: 3 - } - ) - |> Enum.map(&{&1.transaction_hash, &1.log_index}) + insert(:token_transfer, transaction: transaction, block: transaction.block, block_number: transaction.block_number) - assert token_transfers_third_page == - transaction.hash - |> Chain.transaction_to_token_transfers( + assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] = + Chain.transaction_to_token_transfers( + transaction.hash, necessity_by_association: %{ token: :optional, transaction: :optional - }, - paging_options: %PagingOptions{ - key: {transaction.block_number, 3}, - page_size: 3 } ) - |> Enum.map(&{&1.transaction_hash, &1.log_index}) + + assert [ + %TokenTransfer{ + token: %Ecto.Association.NotLoaded{}, + transaction: %Ecto.Association.NotLoaded{} + } + ] = Chain.transaction_to_token_transfers(transaction.hash) end end @@ -3083,7 +3449,7 @@ defmodule Explorer.ChainTest do :contracts_creation_internal_transaction, :contracts_creation_transaction, :token, - [smart_contract: :smart_contract_additional_sources] + :smart_contract_additional_sources ]) options = [ @@ -3114,29 +3480,58 @@ defmodule Explorer.ChainTest do end end + describe "block_reward/1" do + setup do + %{block_range: range} = emission_reward = insert(:emission_reward) + + block = insert(:block, number: Enum.random(Range.new(range.from, range.to))) + insert(:transaction) + + {:ok, block: block, emission_reward: emission_reward} + end + + test "with block containing transactions", %{block: block, emission_reward: emission_reward} do + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 1) + + :transaction + |> insert(gas_price: 1) + |> with_block(block, gas_used: 2) + + expected = + emission_reward.reward + |> Wei.to(:wei) + |> Decimal.add(Decimal.new(3)) + |> Wei.from(:wei) + + assert expected == Chain.block_reward(block.number) + end + + test "with block without transactions", %{block: block, emission_reward: emission_reward} do + assert emission_reward.reward == Chain.block_reward(block.number) + end + end + describe "gas_payment_by_block_hash/1" do setup do number = 1 - block = insert(:block, number: number, consensus: true) - - %{consensus_block: block, number: number} + %{consensus_block: insert(:block, number: number, consensus: true), number: number} end - test "without consensus block hash has key with 0 value", %{consensus_block: consensus_block, number: number} do + test "without consensus block hash has no key", %{consensus_block: consensus_block, number: number} do non_consensus_block = insert(:block, number: number, consensus: false) :transaction - |> insert(gas_price: 1, block_consensus: false) + |> insert(gas_price: 1) |> with_block(consensus_block, gas_used: 1) :transaction - |> insert(gas_price: 1, block_consensus: false) + |> insert(gas_price: 1) |> with_block(consensus_block, gas_used: 2) - assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{ - non_consensus_block.hash => %Wei{value: Decimal.new(0)} - } + assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{} end test "with consensus block hash without transactions has key with 0 value", %{ @@ -3289,12 +3684,12 @@ defmodule Explorer.ChainTest do describe "recent_collated_transactions/1" do test "with no collated transactions it returns an empty list" do - assert [] == Explorer.Chain.recent_collated_transactions(true) + assert [] == Explorer.Chain.recent_collated_transactions() end test "it excludes pending transactions" do insert(:transaction) - assert [] == Explorer.Chain.recent_collated_transactions(true) + assert [] == Explorer.Chain.recent_collated_transactions() end test "returns a list of recent collated transactions" do @@ -3306,7 +3701,7 @@ defmodule Explorer.ChainTest do oldest_seen = Enum.at(newest_first_transactions, 9) paging_options = %Explorer.PagingOptions{page_size: 10, key: {oldest_seen.block_number, oldest_seen.index}} - recent_collated_transactions = Explorer.Chain.recent_collated_transactions(true, paging_options: paging_options) + recent_collated_transactions = Explorer.Chain.recent_collated_transactions(paging_options: paging_options) assert length(recent_collated_transactions) == 10 assert hd(recent_collated_transactions).hash == Enum.at(newest_first_transactions, 10).hash @@ -3328,11 +3723,10 @@ defmodule Explorer.ChainTest do to_address: address, transaction: transaction, token_contract_address: token_contract_address, - token: token, - block: transaction.block + token: token ) - fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions(true)) + fetched_transaction = List.first(Explorer.Chain.recent_collated_transactions()) assert fetched_transaction.hash == transaction.hash assert length(fetched_transaction.token_transfers) == 2 end @@ -3435,6 +3829,269 @@ defmodule Explorer.ChainTest do end end + describe "create_smart_contract/1" do + setup do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "SimpleStorage", + compiler_version: "0.4.23", + optimization: false, + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + } + + {:ok, valid_attrs: valid_attrs, address: created_contract_address} + end + + test "with valid data creates a smart contract", %{valid_attrs: valid_attrs} do + assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) + assert smart_contract.name == "SimpleStorage" + assert smart_contract.compiler_version == "0.4.23" + assert smart_contract.optimization == false + assert smart_contract.contract_source_code != "" + assert smart_contract.abi != "" + + assert Repo.get_by( + Address.Name, + address_hash: smart_contract.address_hash, + name: smart_contract.name, + primary: true + ) + end + + test "clears an existing primary name and sets the new one", %{valid_attrs: valid_attrs, address: address} do + insert(:address_name, address: address, primary: true) + assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) + + assert Repo.get_by( + Address.Name, + address_hash: smart_contract.address_hash, + name: smart_contract.name, + primary: true + ) + end + + test "trims whitespace from address name", %{valid_attrs: valid_attrs} do + attrs = %{valid_attrs | name: " SimpleStorage "} + assert {:ok, _} = Chain.create_smart_contract(attrs) + assert Repo.get_by(Address.Name, name: "SimpleStorage") + end + + test "sets the address verified field to true", %{valid_attrs: valid_attrs} do + assert {:ok, %SmartContract{} = smart_contract} = Chain.create_smart_contract(valid_attrs) + + assert Repo.get_by(Address, hash: smart_contract.address_hash).verified == true + end + end + + describe "update_smart_contract/1" do + setup do + smart_contract_bytecode = + "0x608060405234801561001057600080fd5b5060df8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b114604e5780636d4ce63c146078575b600080fd5b348015605957600080fd5b5060766004803603810190808035906020019092919050505060a0565b005b348015608357600080fd5b50608a60aa565b6040518082815260200191505060405180910390f35b8060008190555050565b600080549050905600a165627a7a7230582040d82a7379b1ee1632ad4d8a239954fd940277b25628ead95259a85c5eddb2120029" + + created_contract_address = + insert( + :address, + hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + contract_code: smart_contract_bytecode + ) + + transaction = + :transaction + |> insert() + |> with_block() + + insert( + :internal_transaction_create, + transaction: transaction, + index: 0, + created_contract_address: created_contract_address, + created_contract_code: smart_contract_bytecode, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + valid_attrs = %{ + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c", + name: "SimpleStorage", + compiler_version: "0.4.23", + optimization: false, + contract_source_code: + "pragma solidity ^0.4.23; contract SimpleStorage {uint storedData; function set(uint x) public {storedData = x; } function get() public constant returns (uint) {return storedData; } }", + abi: [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ], + partially_verified: true + } + + secondary_sources = [ + %{ + file_name: "storage.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + }, + %{ + file_name: "storage_1.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_1 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + } + ] + + changed_sources = [ + %{ + file_name: "storage_2.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_2 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + }, + %{ + file_name: "storage_3.sol", + contract_source_code: + "pragma solidity >=0.7.0 <0.9.0;contract Storage_3 {uint256 number;function store(uint256 num) public {number = num;}function retrieve_() public view returns (uint256){return number;}}", + address_hash: "0x0f95fa9bc0383e699325f2658d04e8d96d87b90c" + } + ] + + _ = Chain.create_smart_contract(valid_attrs, [], secondary_sources) + + {:ok, + valid_attrs: valid_attrs, + address: created_contract_address, + secondary_sources: secondary_sources, + changed_sources: changed_sources} + end + + test "change partially_verified field", %{valid_attrs: valid_attrs, address: address} do + sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_before_call.name == Map.get(valid_attrs, :name) + assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) + + assert {:ok, %SmartContract{} = smart_contract} = + Chain.update_smart_contract(%{address_hash: address.hash, partially_verified: false}) + + sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_after_call.name == Map.get(valid_attrs, :name) + assert sc_after_call.partially_verified == false + assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) + assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) + assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code) + end + + test "check nothing changed", %{valid_attrs: valid_attrs, address: address} do + sc_before_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_before_call.name == Map.get(valid_attrs, :name) + assert sc_before_call.partially_verified == Map.get(valid_attrs, :partially_verified) + + assert {:ok, %SmartContract{} = smart_contract} = Chain.update_smart_contract(%{address_hash: address.hash}) + + sc_after_call = Repo.get_by(SmartContract, address_hash: address.hash) + assert sc_after_call.name == Map.get(valid_attrs, :name) + assert sc_after_call.partially_verified == Map.get(valid_attrs, :partially_verified) + assert sc_after_call.compiler_version == Map.get(valid_attrs, :compiler_version) + assert sc_after_call.optimization == Map.get(valid_attrs, :optimization) + assert sc_after_call.contract_source_code == Map.get(valid_attrs, :contract_source_code) + end + + test "check additional sources update", %{ + address: address, + secondary_sources: secondary_sources, + changed_sources: changed_sources + } do + sc_before_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + + assert sc_before_call.smart_contract_additional_sources + |> Enum.with_index() + |> Enum.all?(fn {el, ind} -> + {:ok, src} = Enum.fetch(secondary_sources, ind) + + el.file_name == Map.get(src, :file_name) and + el.contract_source_code == Map.get(src, :contract_source_code) + end) + + assert {:ok, %SmartContract{} = smart_contract} = + Chain.update_smart_contract(%{address_hash: address.hash}, [], changed_sources) + + sc_after_call = Repo.get_by(Address, hash: address.hash) |> Repo.preload(:smart_contract_additional_sources) + + assert sc_after_call.smart_contract_additional_sources + |> Enum.with_index() + |> Enum.all?(fn {el, ind} -> + {:ok, src} = Enum.fetch(changed_sources, ind) + + el.file_name == Map.get(src, :file_name) and + el.contract_source_code == Map.get(src, :contract_source_code) + end) + end + end + describe "stream_unfetched_balances/2" do test "with `t:Explorer.Chain.Address.CoinBalance.t/0` with value_fetched_at with same `address_hash` and `block_number` " <> "does not return `t:Explorer.Chain.Block.t/0` `miner_hash`" do @@ -3949,6 +4606,14 @@ defmodule Explorer.ChainTest do assert Chain.circulating_supply() == ProofOfAuthority.circulating() end + describe "address_hash_to_smart_contract/1" do + test "fetches a smart contract" do + smart_contract = insert(:smart_contract) + + assert ^smart_contract = Chain.address_hash_to_smart_contract(smart_contract.address_hash) + end + end + describe "token_from_address_hash/1" do test "with valid hash" do token = insert(:token) @@ -3969,43 +4634,151 @@ defmodule Explorer.ChainTest do assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options) - assert address.smart_contract.address_hash == result.contract_address.smart_contract.address_hash - assert address.smart_contract.contract_code_md5 == result.contract_address.smart_contract.contract_code_md5 - assert address.smart_contract.abi == result.contract_address.smart_contract.abi - assert address.smart_contract.contract_source_code == result.contract_address.smart_contract.contract_source_code - assert address.smart_contract.name == result.contract_address.smart_contract.name + assert smart_contract = result.contract_address.smart_contract + end + end + + test "stream_uncataloged_token_contract_address_hashes/2 reduces with given reducer and accumulator" do + insert(:token, cataloged: true) + %Token{contract_address_hash: uncatalog_address} = insert(:token, cataloged: false) + assert Chain.stream_uncataloged_token_contract_address_hashes([], &[&1 | &2]) == {:ok, [uncatalog_address]} + end + + describe "stream_cataloged_token_contract_address_hashes/2" do + test "reduces with given reducer and accumulator" do + today = DateTime.utc_now() + yesterday = Timex.shift(today, days: -1) + %Token{contract_address_hash: catalog_address} = insert(:token, cataloged: true, updated_at: yesterday) + insert(:token, cataloged: false) + assert Chain.stream_cataloged_token_contract_address_hashes([], &[&1 | &2], 1) == {:ok, [catalog_address]} + end + + test "sorts the tokens by updated_at in ascending order" do + today = DateTime.utc_now() + yesterday = Timex.shift(today, days: -1) + two_days_ago = Timex.shift(today, days: -2) + + token1 = insert(:token, %{cataloged: true, updated_at: yesterday}) + token2 = insert(:token, %{cataloged: true, updated_at: two_days_ago}) + + expected_response = + [token1, token2] + |> Enum.sort(&(Timex.to_unix(&1.updated_at) < Timex.to_unix(&2.updated_at))) + |> Enum.map(& &1.contract_address_hash) + + assert Chain.stream_cataloged_token_contract_address_hashes([], &(&2 ++ [&1]), 12) == {:ok, expected_response} + end + end + + describe "stream_unfetched_token_instances/2" do + test "reduces wuth given reducer and accumulator" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 11 + ) + + assert {:ok, [result]} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) + assert result.token_id == token_transfer.token_id + assert result.contract_address_hash == token_transfer.token_contract_address_hash + end + + test "does not fetch token transfers without token id" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: nil + ) + + assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) + end + + test "do not fetch records with token instances" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_id: 11 + ) + + insert(:token_instance, + token_id: token_transfer.token_id, + token_contract_address_hash: token_transfer.token_contract_address_hash + ) + + assert {:ok, []} = Chain.stream_unfetched_token_instances([], &[&1 | &2]) + end + end + + describe "search_token/1" do + test "finds by part of the name" do + token = insert(:token, name: "magic token", symbol: "MAGIC") + + [result] = Chain.search_token("magic") + + assert result.link == token.contract_address_hash end - end - test "stream_uncataloged_token_contract_address_hashes/2 reduces with given reducer and accumulator" do - insert(:token, cataloged: true) - %Token{contract_address_hash: uncatalog_address} = insert(:token, cataloged: false) - assert Chain.stream_uncataloged_token_contract_address_hashes([], &[&1 | &2]) == {:ok, [uncatalog_address]} - end + test "finds multiple results in different columns" do + insert(:token, name: "magic token", symbol: "TOKEN") + insert(:token, name: "token", symbol: "MAGIC") - describe "stream_cataloged_token_contract_address_hashes/2" do - test "reduces with given reducer and accumulator" do - today = DateTime.utc_now() - yesterday = Timex.shift(today, days: -1) - %Token{contract_address_hash: catalog_address} = insert(:token, cataloged: true, updated_at: yesterday) - insert(:token, cataloged: false) - assert Chain.stream_cataloged_token_contract_address_hashes([], &[&1 | &2], 1) == {:ok, [catalog_address]} + result = Chain.search_token("magic") + + assert Enum.count(result) == 2 end - test "sorts the tokens by updated_at in ascending order" do - today = DateTime.utc_now() - yesterday = Timex.shift(today, days: -1) - two_days_ago = Timex.shift(today, days: -2) + test "do not returns wrong tokens" do + insert(:token, name: "token", symbol: "TOKEN") - token1 = insert(:token, %{cataloged: true, updated_at: yesterday}) - token2 = insert(:token, %{cataloged: true, updated_at: two_days_ago}) + result = Chain.search_token("magic") - expected_response = - [token1, token2] - |> Enum.sort(&(Timex.to_unix(&1.updated_at) < Timex.to_unix(&2.updated_at))) - |> Enum.map(& &1.contract_address_hash) + assert Enum.empty?(result) + end - assert Chain.stream_cataloged_token_contract_address_hashes([], &(&2 ++ [&1]), 12) == {:ok, expected_response} + test "finds record by the term in the second word" do + insert(:token, name: "token magic", symbol: "TOKEN") + + result = Chain.search_token("magic") + + assert Enum.count(result) == 1 end end @@ -4110,7 +4883,7 @@ defmodule Explorer.ChainTest do token_balances = address.hash |> Chain.fetch_last_token_balances() - |> Enum.map(fn token_balance -> token_balance.address_hash end) + |> Enum.map(fn {token_balance, _, _} -> token_balance.address_hash end) assert token_balances == [current_token_balance.address_hash] end @@ -4139,7 +4912,7 @@ defmodule Explorer.ChainTest do token_holders_count = contract_address_hash - |> Chain.fetch_token_holders_from_token_hash([]) + |> Chain.fetch_token_holders_from_token_hash(false, []) |> Enum.count() assert token_holders_count == 2 @@ -4249,48 +5022,6 @@ defmodule Explorer.ChainTest do assert result == [transaction.hash] end - - test "correct ordering for token transfers (ASC log_index)" do - address = insert(:address) - token = insert(:token) - - transaction = - :transaction - |> insert() - |> with_block() - - insert( - :token_transfer, - amount: 2, - to_address: address, - token_contract_address: token.contract_address, - transaction: transaction, - log_index: 2 - ) - - insert( - :token_transfer, - amount: 1, - to_address: address, - token_contract_address: token.contract_address, - transaction: transaction, - log_index: 0 - ) - - insert( - :token_transfer, - amount: 1, - to_address: address, - token_contract_address: token.contract_address, - transaction: transaction, - log_index: 1 - ) - - assert [result] = Chain.address_to_transactions_with_token_transfers(address.hash, token.contract_address_hash) - - assert [{transaction.hash, 0}, {transaction.hash, 1}, {transaction.hash, 2}] == - result.token_transfers |> Enum.map(&{&1.transaction_hash, &1.log_index}) - end end describe "address_to_unique_tokens/2" do @@ -4323,7 +5054,7 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_ids: [29] + token_id: 29 ) second_page = @@ -4334,17 +5065,41 @@ defmodule Explorer.ChainTest do transaction: transaction, token_contract_address: token_contract_address, token: token, - token_ids: [11] + token_id: 11 ) - paging_options = %PagingOptions{key: {List.first(first_page.token_ids)}, page_size: 1} + paging_options = %PagingOptions{key: {first_page.token_id}, page_size: 1} unique_tokens_ids_paginated = token_contract_address.hash - |> Chain.address_to_unique_tokens(token, paging_options: paging_options) + |> Chain.address_to_unique_tokens(paging_options: paging_options) |> Enum.map(& &1.token_id) - assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)] + assert unique_tokens_ids_paginated == [second_page.token_id] + end + end + + describe "uncataloged_token_transfer_block_numbers/0" do + test "returns a list of block numbers" do + block = insert(:block) + address = insert(:address) + + log = + insert(:token_transfer_log, + transaction: + insert(:transaction, + block_number: block.number, + block_hash: block.hash, + cumulative_gas_used: 0, + gas_used: 0, + index: 0 + ), + block: block, + address_hash: address.hash + ) + + block_number = log.block_number + assert {:ok, [^block_number]} = Chain.uncataloged_token_transfer_block_numbers() end end @@ -4580,7 +5335,7 @@ defmodule Explorer.ChainTest do refute Chain.contract_address?(to_string(address.hash), 1) end - @tag :no_nethermind + @tag :no_parity @tag :no_geth test "returns true if fetched code from json rpc", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -4603,7 +5358,7 @@ defmodule Explorer.ChainTest do assert Chain.contract_address?(to_string(hash), 1, json_rpc_named_arguments) end - @tag :no_nethermind + @tag :no_parity @tag :no_geth test "returns false if no fetched code from json rpc", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -4627,6 +5382,207 @@ defmodule Explorer.ChainTest do end end + describe "staking_pools/3" do + test "validators staking pools" do + inserted_validator = insert(:staking_pool, is_active: true, is_validator: true) + insert(:staking_pool, is_active: true, is_validator: false) + + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [%{pool: gotten_validator}] = Chain.staking_pools(:validator, options) + assert inserted_validator.staking_address_hash == gotten_validator.staking_address_hash + end + + test "active staking pools" do + inserted_pool = insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) + + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [%{pool: gotten_pool}] = Chain.staking_pools(:active, options) + assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash + end + + test "all active staking pools ordered by staking_address" do + address1 = Factory.address_hash() + address2 = Factory.address_hash() + address3 = Factory.address_hash() + + assert address1 < address2 and address2 < address3 + + # insert pools in descending order + insert(:staking_pool, is_active: true, staking_address_hash: address3) + insert(:staking_pool, is_active: true, staking_address_hash: address2) + insert(:staking_pool, is_active: true, staking_address_hash: address1) + + # get all active pools in ascending order + assert [%{pool: pool1}, %{pool: pool2}, %{pool: pool3}] = Chain.staking_pools(:active, :all) + assert pool1.staking_address_hash == address1 + assert pool2.staking_address_hash == address2 + assert pool3.staking_address_hash == address3 + end + + test "staking pools ordered by stakes_ratio, is_active, and staking_address_hash" do + address1 = Factory.address_hash() + address2 = Factory.address_hash() + address3 = Factory.address_hash() + address4 = Factory.address_hash() + address5 = Factory.address_hash() + address6 = Factory.address_hash() + + assert address1 < address2 and address2 < address3 and address3 < address4 and address4 < address5 and + address5 < address6 + + # insert pools in descending order + insert(:staking_pool, is_validator: true, is_active: false, staking_address_hash: address6, stakes_ratio: 0) + insert(:staking_pool, is_validator: true, is_active: false, staking_address_hash: address5, stakes_ratio: 0) + insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address4, stakes_ratio: 30) + insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address3, stakes_ratio: 60) + insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address2, stakes_ratio: 5) + insert(:staking_pool, is_validator: true, is_active: true, staking_address_hash: address1, stakes_ratio: 5) + + # get all pools in the order `desc: :stakes_ratio, desc: :is_active, asc: :staking_address_hash` + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [ + %{pool: pool1}, + %{pool: pool2}, + %{pool: pool3}, + %{pool: pool4}, + %{pool: pool5}, + %{pool: pool6} + ] = Chain.staking_pools(:validator, options) + + assert pool1.staking_address_hash == address3 + assert pool2.staking_address_hash == address4 + assert pool3.staking_address_hash == address1 + assert pool4.staking_address_hash == address2 + assert pool5.staking_address_hash == address5 + assert pool6.staking_address_hash == address6 + end + + test "inactive staking pools" do + insert(:staking_pool, is_active: true) + inserted_pool = insert(:staking_pool, is_active: false) + + options = %PagingOptions{page_size: 20, page_number: 1} + + assert [%{pool: gotten_pool}] = Chain.staking_pools(:inactive, options) + assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash + end + end + + describe "staking_pools_count/1" do + test "validators staking pools" do + insert(:staking_pool, is_active: true, is_validator: true) + insert(:staking_pool, is_active: true, is_validator: false) + + assert Chain.staking_pools_count(:validator) == 1 + end + + test "active staking pools" do + insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) + + assert Chain.staking_pools_count(:active) == 1 + end + + test "inactive staking pools" do + insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) + + assert Chain.staking_pools_count(:inactive) == 1 + end + end + + describe "delegators_count_sum/1" do + test "validators pools" do + insert(:staking_pool, is_active: true, is_validator: true, delegators_count: 10) + insert(:staking_pool, is_active: true, is_validator: false, delegators_count: 7) + insert(:staking_pool, is_active: true, is_validator: true, delegators_count: 5) + + assert Chain.delegators_count_sum(:validator) == 15 + end + + test "active staking pools" do + insert(:staking_pool, is_active: true, delegators_count: 10) + insert(:staking_pool, is_active: true, delegators_count: 7) + insert(:staking_pool, is_active: false, delegators_count: 5) + + assert Chain.delegators_count_sum(:active) == 17 + end + + test "inactive staking pools" do + insert(:staking_pool, is_active: true, delegators_count: 10) + insert(:staking_pool, is_active: true, delegators_count: 7) + insert(:staking_pool, is_active: false, delegators_count: 5) + insert(:staking_pool, is_active: false, delegators_count: 1) + + assert Chain.delegators_count_sum(:inactive) == 6 + end + end + + describe "total_staked_amount_sum/1" do + test "validators pools" do + insert(:staking_pool, is_active: true, is_validator: true, total_staked_amount: 10) + insert(:staking_pool, is_active: true, is_validator: false, total_staked_amount: 7) + insert(:staking_pool, is_active: true, is_validator: true, total_staked_amount: 5) + + assert Chain.total_staked_amount_sum(:validator) == Decimal.new("15") + end + + test "active staking pools" do + insert(:staking_pool, is_active: true, total_staked_amount: 10) + insert(:staking_pool, is_active: true, total_staked_amount: 7) + insert(:staking_pool, is_active: false, total_staked_amount: 5) + + assert Chain.total_staked_amount_sum(:active) == Decimal.new("17") + end + + test "inactive staking pools" do + insert(:staking_pool, is_active: true, total_staked_amount: 10) + insert(:staking_pool, is_active: true, total_staked_amount: 7) + insert(:staking_pool, is_active: false, total_staked_amount: 5) + insert(:staking_pool, is_active: false, total_staked_amount: 1) + + assert Chain.total_staked_amount_sum(:inactive) == Decimal.new("6") + end + end + + describe "extract_db_name/1" do + test "extracts correct db name" do + db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1" + assert Chain.extract_db_name(db_url) == "blockscout-dev-1" + end + + test "returns empty db name" do + db_url = "" + assert Chain.extract_db_name(db_url) == "" + end + + test "returns nil db name" do + db_url = nil + assert Chain.extract_db_name(db_url) == "" + end + end + + describe "extract_db_host/1" do + test "extracts correct db host" do + db_url = "postgresql://viktor:@localhost:5432/blockscout-dev-1" + assert Chain.extract_db_host(db_url) == "localhost" + end + + test "returns empty db name" do + db_url = "" + assert Chain.extract_db_host(db_url) == "" + end + + test "returns nil db name" do + db_url = nil + assert Chain.extract_db_host(db_url) == "" + end + end + describe "fetch_first_trace/2" do test "fetched first trace", %{ json_rpc_named_arguments: json_rpc_named_arguments @@ -4761,4 +5717,360 @@ defmodule Explorer.ChainTest do assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type" end end + + describe "proxy contracts features" do + @proxy_abi [ + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [%{"type" => "bool", "name" => ""}], + "name" => "upgradeTo", + "inputs" => [%{"type" => "address", "name" => "newImplementation"}], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "uint256", "name" => ""}], + "name" => "version", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "implementation", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "renounceOwnership", + "inputs" => [], + "constant" => false + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getOwner", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "address", "name" => ""}], + "name" => "getProxyStorage", + "inputs" => [], + "constant" => true + }, + %{ + "type" => "function", + "stateMutability" => "nonpayable", + "payable" => false, + "outputs" => [], + "name" => "transferOwnership", + "inputs" => [%{"type" => "address", "name" => "_newOwner"}], + "constant" => false + }, + %{ + "type" => "constructor", + "stateMutability" => "nonpayable", + "payable" => false, + "inputs" => [ + %{"type" => "address", "name" => "_proxyStorage"}, + %{"type" => "address", "name" => "_implementationAddress"} + ] + }, + %{"type" => "fallback", "stateMutability" => "nonpayable", "payable" => false}, + %{ + "type" => "event", + "name" => "Upgraded", + "inputs" => [ + %{"type" => "uint256", "name" => "version", "indexed" => false}, + %{"type" => "address", "name" => "implementation", "indexed" => true} + ], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipRenounced", + "inputs" => [%{"type" => "address", "name" => "previousOwner", "indexed" => true}], + "anonymous" => false + }, + %{ + "type" => "event", + "name" => "OwnershipTransferred", + "inputs" => [ + %{"type" => "address", "name" => "previousOwner", "indexed" => true}, + %{"type" => "address", "name" => "newOwner", "indexed" => true} + ], + "anonymous" => false + } + ] + + @implementation_abi [ + %{ + "constant" => false, + "inputs" => [%{"name" => "x", "type" => "uint256"}], + "name" => "set", + "outputs" => [], + "payable" => false, + "stateMutability" => "nonpayable", + "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "get", + "outputs" => [%{"name" => "", "type" => "uint256"}], + "payable" => false, + "stateMutability" => "view", + "type" => "function" + } + ] + + test "combine_proxy_implementation_abi/2 returns empty [] abi if proxy abi is null" do + proxy_contract_address = insert(:contract_address) + assert Chain.combine_proxy_implementation_abi(proxy_contract_address, nil) == [] + end + + test "combine_proxy_implementation_abi/2 returns [] abi for unverified proxy" do + proxy_contract_address = insert(:contract_address) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + + assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + end + + test "combine_proxy_implementation_abi/2 returns proxy abi if implementation is not verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi) + assert Chain.combine_proxy_implementation_abi(proxy_contract_address, @proxy_abi) == @proxy_abi + end + + test "combine_proxy_implementation_abi/2 returns proxy + implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi) + + implementation_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ]} + end + ) + + combined_abi = Chain.combine_proxy_implementation_abi(proxy_contract_address.hash, @proxy_abi) + + assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == false + assert Enum.any?(@proxy_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == false + assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 0) end) == true + assert Enum.any?(combined_abi, fn el -> el == Enum.at(@implementation_abi, 1) end) == true + end + + test "get_implementation_abi_from_proxy/2 returns empty [] abi if proxy abi is null" do + proxy_contract_address = insert(:contract_address) + assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, nil) == [] + end + + test "get_implementation_abi_from_proxy/2 returns [] abi for unverified proxy" do + proxy_contract_address = insert(:contract_address) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + + assert Chain.combine_proxy_implementation_abi(proxy_contract_address, []) == [] + end + + test "get_implementation_abi_from_proxy/2 returns [] if implementation is not verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi) + assert Chain.get_implementation_abi_from_proxy(proxy_contract_address, @proxy_abi) == [] + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi) + + implementation_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [ + %{ + id: id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ]} + end + ) + + implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, @proxy_abi) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: []) + + implementation_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000" <> implementation_contract_address_hash_string} + end + ) + + implementation_abi = Chain.get_implementation_abi_from_proxy(proxy_contract_address.hash, []) + + assert implementation_abi == @implementation_abi + end + + test "get_implementation_abi/1 returns empty [] abi if implmentation address is null" do + assert Chain.get_implementation_abi(nil) == [] + end + + test "get_implementation_abi/1 returns [] if implementation is not verified" do + implementation_contract_address = insert(:contract_address) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + assert Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) == [] + end + + test "get_implementation_abi/1 returns implementation abi if implementation is verified" do + proxy_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi) + + implementation_contract_address = insert(:contract_address) + insert(:smart_contract, address_hash: implementation_contract_address.hash, abi: @implementation_abi) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + implementation_abi = Chain.get_implementation_abi("0x" <> implementation_contract_address_hash_string) + + assert implementation_abi == @implementation_abi + end + + test "get_total_staked_and_ordered should return just nil in case of invalid input and some response otherwise" do + assert Chain.get_total_staked_and_ordered(nil) == nil + assert Chain.get_total_staked_and_ordered(%{}) == nil + assert Chain.get_total_staked_and_ordered("") == nil + assert Chain.get_total_staked_and_ordered([]) == nil + + assert Chain.get_total_staked_and_ordered("0x3f7c51ef174ee8a62e3fcfb0947aa90c97bd2784") == %{ + stake_amount: Decimal.new(0), + ordered_withdraw: Decimal.new(0) + } + end + end end diff --git a/apps/indexer/config/config.exs b/apps/indexer/config/config.exs index 426ea5cc1839..c3004b51e177 100644 --- a/apps/indexer/config/config.exs +++ b/apps/indexer/config/config.exs @@ -1,19 +1,77 @@ # This file is responsible for configuring your application -# and its dependencies with the aid of the Config module. -import Config +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +import Bitwise + +block_transformers = %{ + "clique" => Indexer.Transform.Blocks.Clique, + "base" => Indexer.Transform.Blocks.Base +} + +# Compile time environment variable access requires recompilation. +configured_transformer = System.get_env("BLOCK_TRANSFORMER") || "base" + +block_transformer = + case Map.get(block_transformers, configured_transformer) do + nil -> + raise """ + No such block transformer: #{configured_transformer}. + + Valid values are: + #{Enum.join(Map.keys(block_transformers), "\n")} + + Please update environment variable BLOCK_TRANSFORMER accordingly. + """ + + transformer -> + transformer + end config :indexer, - ecto_repos: [Explorer.Repo] + block_transformer: block_transformer, + ecto_repos: [Explorer.Repo], + metadata_updater_seconds_interval: + String.to_integer(System.get_env("TOKEN_METADATA_UPDATE_INTERVAL") || "#{1 * 24 * 60 * 60}"), + # bytes + memory_limit: 25 <<< 30, + first_block: System.get_env("FIRST_BLOCK") || "", + last_block: System.get_env("LAST_BLOCK") || "", + trace_first_block: System.get_env("TRACE_FIRST_BLOCK") || "", + trace_last_block: System.get_env("TRACE_LAST_BLOCK") || "" -# config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true +config :indexer, Indexer.Fetcher.PendingTransaction.Supervisor, + disabled?: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "besu" + +token_balance_on_demand_fetcher_threshold = + if System.get_env("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES") do + case Integer.parse(System.get_env("TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD_MINUTES")) do + {integer, ""} -> integer + _ -> 60 + end + else + 60 + end + +config :indexer, Indexer.Fetcher.TokenBalanceOnDemand, threshold: token_balance_on_demand_fetcher_threshold + +config :indexer, Indexer.Fetcher.ReplacedTransaction.Supervisor, disabled?: true + +if System.get_env("POS_STAKING_CONTRACT") do + config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: true +else + config :indexer, Indexer.Fetcher.BlockReward.Supervisor, disabled?: false +end + +config :indexer, Indexer.Fetcher.InternalTransaction.Supervisor, disabled?: false + +config :indexer, Indexer.Supervisor, enabled: System.get_env("DISABLE_INDEXER") != "true" config :indexer, Indexer.Tracer, service: :indexer, adapter: SpandexDatadog.Adapter, trace_key: :blockscout -config :indexer, Indexer.Block.Catchup.MissingRangesCollector, future_check_interval: :timer.minutes(1) - config :logger, :indexer, # keep synced with `config/config.exs` format: "$dateT$time $metadata[$level] $message\n", @@ -24,4 +82,4 @@ config :logger, :indexer, # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{config_env()}.exs" +import_config "#{Mix.env()}.exs" diff --git a/apps/indexer/config/dev.exs b/apps/indexer/config/dev.exs index 28f928baa1ae..fe8144353030 100644 --- a/apps/indexer/config/dev.exs +++ b/apps/indexer/config/dev.exs @@ -1,4 +1,4 @@ -import Config +use Mix.Config config :indexer, Indexer.Tracer, env: "dev", disabled?: true @@ -31,12 +31,16 @@ config :logger, :empty_blocks_to_refetch, path: Path.absname("logs/dev/indexer/empty_blocks_to_refetch.log"), metadata_filter: [fetcher: :empty_blocks_to_refetch] -config :logger, :block_import_timings, - level: :debug, - path: Path.absname("logs/dev/indexer/block_import_timings.log"), - metadata_filter: [fetcher: :block_import_timings] - -config :logger, :withdrawal, - level: :debug, - path: Path.absname("logs/dev/indexer/withdrawal.log"), - metadata_filter: [fetcher: :withdrawal] +variant = + if is_nil(System.get_env("ETHEREUM_JSONRPC_VARIANT")) do + "parity" + else + System.get_env("ETHEREUM_JSONRPC_VARIANT") + |> String.split(".") + |> List.last() + |> String.downcase() + end + +# Import variant specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "dev/#{variant}.exs" diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs index ef77231c83e5..4a2f31053222 100644 --- a/apps/indexer/config/dev/erigon.exs +++ b/apps/indexer/config/dev/erigon.exs @@ -1,14 +1,7 @@ -import Config - -~w(config config_helper.exs) -|> Path.join() -|> Code.eval_file() - -hackney_opts = ConfigHelper.hackney_options() -timeout = ConfigHelper.timeout(10) +use Mix.Config config :indexer, - block_interval: ConfigHelper.parse_time_env_var("INDEXER_CATCHUP_BLOCK_INTERVAL", "5s"), + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", @@ -19,19 +12,32 @@ config :indexer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", - fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), - fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", - trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", - trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" + trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], - http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Erigon ], + # Example configuration to override json_rpc_named_arguments for just the realtime block fetcher + # realtime_overrides: [ + # json_rpc_named_arguments: [ + # transport: EthereumJSONRPC.HTTP, + # transport_options: [ + # http: EthereumJSONRPC.HTTP.HTTPoison, + # url: System.get_env("ETHEREUM_JSONRPC_REALTIME_HTTP_URL") || "http://localhost:8545", + # method_to_url: [ + # eth_getBalance: System.get_env("ETHEREUM_JSONRPC_REALTIME_TRACE_URL") || "http://localhost:8545", + # trace_block: System.get_env("ETHEREUM_JSONRPC_REALTIME_TRACE_URL") || "http://localhost:8545", + # trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_REALTIME_TRACE_URL") || "http://localhost:8545" + # ], + # http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + # ], + # variant: EthereumJSONRPC.Erigon + # ] + # ], subscribe_named_arguments: [ transport: System.get_env("ETHEREUM_JSONRPC_WS_URL") && System.get_env("ETHEREUM_JSONRPC_WS_URL") !== "" && diff --git a/apps/indexer/config/prod.exs b/apps/indexer/config/prod.exs index 10a73b31c516..467429b99fe2 100644 --- a/apps/indexer/config/prod.exs +++ b/apps/indexer/config/prod.exs @@ -3,7 +3,7 @@ import Config config :indexer, Indexer.Tracer, env: "production", disabled?: true config :logger, :indexer, - level: :info, + level: :debug, path: Path.absname("logs/prod/indexer.log"), rotate: %{max_bytes: 52_428_800, keep: 5} diff --git a/apps/indexer/config/prod/erigon.exs b/apps/indexer/config/prod/erigon.exs index 84a5c6250390..052088d5ff36 100644 --- a/apps/indexer/config/prod/erigon.exs +++ b/apps/indexer/config/prod/erigon.exs @@ -1,14 +1,7 @@ -import Config - -~w(config config_helper.exs) -|> Path.join() -|> Code.eval_file() - -hackney_opts = ConfigHelper.hackney_options() -timeout = ConfigHelper.timeout(10) +use Mix.Config config :indexer, - block_interval: ConfigHelper.parse_time_env_var("INDEXER_CATCHUP_BLOCK_INTERVAL", "5s"), + block_interval: :timer.seconds(5), json_rpc_named_arguments: [ transport: if(System.get_env("ETHEREUM_JSONRPC_TRANSPORT", "http") == "http", @@ -18,16 +11,12 @@ config :indexer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), - fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), - fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), - trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], - http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Erigon ], diff --git a/apps/indexer/config/test/erigon.exs b/apps/indexer/config/test/erigon.exs index 5f15cc7a8531..a2f9acd7fbf6 100644 --- a/apps/indexer/config/test/erigon.exs +++ b/apps/indexer/config/test/erigon.exs @@ -1,4 +1,4 @@ -import Config +use Mix.Config config :indexer, json_rpc_named_arguments: [ diff --git a/apps/indexer/lib/indexer/block/catchup/fetcher.ex b/apps/indexer/lib/indexer/block/catchup/fetcher.ex index 3b5fcbbc31fa..8aebf59f3425 100644 --- a/apps/indexer/lib/indexer/block/catchup/fetcher.ex +++ b/apps/indexer/lib/indexer/block/catchup/fetcher.ex @@ -23,104 +23,116 @@ defmodule Indexer.Block.Catchup.Fetcher do alias Ecto.Changeset alias Explorer.Chain - alias Explorer.Utility.MissingRangesManipulator alias Indexer.{Block, Tracer} - alias Indexer.Block.Catchup.{Sequence, TaskSupervisor} + alias Indexer.Block.Catchup.Sequence alias Indexer.Memory.Shrinkable - alias Indexer.Prometheus @behaviour Block.Fetcher - @shutdown_after :timer.minutes(5) + # These are all the *default* values for options. + # DO NOT use them directly in the code. Get options from `state`. + + @blocks_batch_size 4 + @blocks_concurrency 10 @sequence_name :block_catchup_sequencer - defstruct block_fetcher: nil, + defstruct blocks_batch_size: @blocks_batch_size, + blocks_concurrency: @blocks_concurrency, + block_fetcher: nil, memory_monitor: nil + @doc false + def default_blocks_batch_size, do: @blocks_batch_size + @doc """ Required named arguments * `:json_rpc_named_arguments` - `t:EthereumJSONRPC.json_rpc_named_arguments/0` passed to `EthereumJSONRPC.json_rpc/2`. + + The follow options can be overridden: + + * `:blocks_batch_size` - The number of blocks to request in one call to the JSONRPC. Defaults to + `#{@blocks_batch_size}`. Block requests also include the transactions for those blocks. *These transactions + are not paginated.* + * `:blocks_concurrency` - The number of concurrent requests of `:blocks_batch_size` to allow against the JSONRPC. + Defaults to #{@blocks_concurrency}. So, up to `blocks_concurrency * block_batch_size` (defaults to + `#{@blocks_concurrency * @blocks_batch_size}`) blocks can be requested from the JSONRPC at once over all + connections. Up to `block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to + `#{@blocks_concurrency * Block.Fetcher.default_receipts_batch_size() * Block.Fetcher.default_receipts_batch_size()}` + ) receipts can be requested from the JSONRPC at once over all connections. + """ - def task(state) do + def task( + %__MODULE__{ + blocks_batch_size: blocks_batch_size, + block_fetcher: %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments} + } = state + ) do Logger.metadata(fetcher: :block_catchup) - case MissingRangesManipulator.get_latest_batch() do - [] -> - %{ - first_block_number: nil, - last_block_number: nil, - missing_block_count: 0, - shrunk: false - } - - latest_missing_ranges -> - missing_ranges = filter_consensus_blocks(latest_missing_ranges) - - first.._ = List.first(missing_ranges) - _..last = List.last(missing_ranges) - - Logger.metadata(first_block_number: first, last_block_number: last) - - missing_block_count = - missing_ranges - |> Stream.map(&Enum.count/1) - |> Enum.sum() - - step = step(first, last, blocks_batch_size()) - sequence_opts = put_memory_monitor([ranges: missing_ranges, step: step], state) - gen_server_opts = [name: @sequence_name] - {:ok, sequence} = Sequence.start_link(sequence_opts, gen_server_opts) - Sequence.cap(sequence) - - stream_fetch_and_import(state, sequence) - - shrunk = Shrinkable.shrunk?(sequence) - - %{ - first_block_number: first, - last_block_number: last, - missing_block_count: missing_block_count, - shrunk: shrunk - } - end - end + with {:ok, latest_block_number} <- fetch_last_block(json_rpc_named_arguments) do + case latest_block_number do + # let realtime indexer get the genesis block + 0 -> + %{first_block_number: 0, missing_block_count: 0, last_block_number: 0, shrunk: false} - defp filter_consensus_blocks(ranges) do - filtered_ranges = - ranges - |> Enum.map(&Chain.missing_block_number_ranges(&1)) - |> List.flatten() + _ -> + # realtime indexer gets the current latest block + first = latest_block_number - 1 + last = last_block() - consensus_blocks = ranges_to_numbers(ranges) -- ranges_to_numbers(filtered_ranges) + Logger.metadata(first_block_number: first, last_block_number: last) - consensus_blocks - |> numbers_to_ranges() - |> MissingRangesManipulator.clear_batch() + missing_ranges = Chain.missing_block_number_ranges(first..last) - filtered_ranges - end + range_count = Enum.count(missing_ranges) - @doc """ - The number of blocks to request in one call to the JSONRPC. Defaults to - 10. Block requests also include the transactions for those blocks. *These transactions - are not paginated. - """ - def blocks_batch_size do - Application.get_env(:indexer, __MODULE__)[:batch_size] + missing_block_count = + missing_ranges + |> Stream.map(&Enum.count/1) + |> Enum.sum() + + Logger.debug(fn -> "Missed blocks in ranges." end, + missing_block_range_count: range_count, + missing_block_count: missing_block_count + ) + + shrunk = + case missing_block_count do + 0 -> + false + + _ -> + step = step(first, last, blocks_batch_size) + sequence_opts = put_memory_monitor([ranges: missing_ranges, step: step], state) + gen_server_opts = [name: @sequence_name] + {:ok, sequence} = Sequence.start_link(sequence_opts, gen_server_opts) + Sequence.cap(sequence) + + stream_fetch_and_import(state, sequence) + + Shrinkable.shrunk?(sequence) + end + + %{ + first_block_number: first, + last_block_number: last, + missing_block_count: missing_block_count, + shrunk: shrunk + } + end + end end - @doc """ - The number of concurrent requests of `blocks_batch_size` to allow against the JSONRPC. - Defaults to 10. So, up to `blocks_concurrency * block_batch_size` (defaults to - `10 * 10`) blocks can be requested from the JSONRPC at once over all - connections. Up to `block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to - `#{10 * Block.Fetcher.default_receipts_batch_size() * Block.Fetcher.default_receipts_concurrency()}` - ) receipts can be requested from the JSONRPC at once over all connections. - """ - def blocks_concurrency do - Application.get_env(:indexer, __MODULE__)[:concurrency] + defp fetch_last_block(json_rpc_named_arguments) do + case latest_block() do + nil -> + EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) + + number -> + {:ok, number} + end end defp step(first, last, blocks_batch_size) do @@ -165,15 +177,14 @@ defmodule Indexer.Block.Catchup.Fetcher do async_import_token_instances(imported) end - defp stream_fetch_and_import(state, sequence) + defp stream_fetch_and_import(%__MODULE__{blocks_concurrency: blocks_concurrency} = state, sequence) when is_pid(sequence) do - ranges = Sequence.build_stream(sequence) - - TaskSupervisor - |> Task.Supervisor.async_stream(ranges, &fetch_and_import_range_from_sequence(state, &1, sequence), - max_concurrency: blocks_concurrency(), - timeout: :infinity, - shutdown: @shutdown_after + sequence + |> Sequence.build_stream() + |> Task.async_stream( + &fetch_and_import_range_from_sequence(state, &1, sequence), + max_concurrency: blocks_concurrency, + timeout: :infinity ) |> Stream.run() end @@ -190,22 +201,15 @@ defmodule Indexer.Block.Catchup.Fetcher do sequence ) do Logger.metadata(fetcher: :block_catchup, first_block_number: first, last_block_number: last) - Process.flag(:trap_exit, true) - - {fetch_duration, result} = :timer.tc(fn -> fetch_and_import_range(block_fetcher, range) end) - Prometheus.Instrumenter.block_full_process(fetch_duration, __MODULE__) - - case result do + case fetch_and_import_range(block_fetcher, range) do {:ok, %{inserted: inserted, errors: errors}} -> errors = cap_seq(sequence, errors) retry(sequence, errors) - clear_missing_ranges(range, errors) {:ok, inserted: inserted} {:error, {:import = step, [%Changeset{} | _] = changesets}} = error -> - Prometheus.Instrumenter.import_errors() Logger.error(fn -> ["failed to validate: ", inspect(changesets), ". Retrying."] end, step: step) push_back(sequence, range) @@ -213,7 +217,6 @@ defmodule Indexer.Block.Catchup.Fetcher do error {:error, {:import = step, reason}} = error -> - Prometheus.Instrumenter.import_errors() Logger.error(fn -> [inspect(reason), ". Retrying."] end, step: step) push_back(sequence, range) @@ -247,6 +250,9 @@ defmodule Indexer.Block.Catchup.Fetcher do rescue exception -> Logger.error(fn -> [Exception.format(:error, exception, __STACKTRACE__), ?\n, ?\n, "Retrying."] end) + + push_back(sequence, range) + {:error, exception} end @@ -283,14 +289,6 @@ defmodule Indexer.Block.Catchup.Fetcher do |> Enum.map(&push_back(sequence, &1)) end - defp clear_missing_ranges(initial_range, errors) do - success_numbers = Enum.to_list(initial_range) -- Enum.map(errors, &block_error_to_number/1) - - success_numbers - |> numbers_to_ranges() - |> MissingRangesManipulator.clear_batch() - end - defp block_errors_to_block_number_ranges(block_errors) when is_list(block_errors) do block_errors |> Enum.map(&block_error_to_number/1) @@ -303,29 +301,23 @@ defmodule Indexer.Block.Catchup.Fetcher do defp numbers_to_ranges(numbers) when is_list(numbers) do numbers - |> Enum.sort(&>=/2) + |> Enum.sort() |> Enum.chunk_while( nil, fn number, nil -> {:cont, number..number} - number, first..last when number == last - 1 -> + number, first..last when number == last + 1 -> {:cont, first..number} number, range -> {:cont, range, number..number} end, - fn range -> {:cont, range, nil} end + fn range -> {:cont, range} end ) end - defp ranges_to_numbers(ranges) do - ranges - |> Enum.map(&Enum.to_list/1) - |> List.flatten() - end - defp put_memory_monitor(sequence_options, %__MODULE__{memory_monitor: nil}) when is_list(sequence_options), do: sequence_options @@ -341,21 +333,43 @@ defmodule Indexer.Block.Catchup.Fetcher do def push_front(block_numbers) do if Process.whereis(@sequence_name) do Enum.reduce_while(block_numbers, :ok, fn block_number, :ok -> - sequence_push_front(block_number) + if is_integer(block_number) do + case Sequence.push_front(@sequence_name, block_number..block_number) do + :ok -> {:cont, :ok} + {:error, _} = error -> {:halt, error} + end + else + Logger.warn(fn -> ["Received a non-integer block number: ", inspect(block_number)] end) + end end) else {:error, :queue_unavailable} end end - defp sequence_push_front(block_number) do - if is_integer(block_number) do - case Sequence.push_front(@sequence_name, block_number..block_number) do - :ok -> {:cont, :ok} - {:error, _} = error -> {:halt, error} - end - else - Logger.warn(fn -> ["Received a non-integer block number: ", inspect(block_number)] end) + defp last_block do + string_value = Application.get_env(:indexer, :first_block) + + case Integer.parse(string_value) do + {integer, ""} -> + integer + + _ -> + min_missing_block_number = + "min_missing_block_number" + |> Chain.get_last_fetched_counter() + |> Decimal.to_integer() + + min_missing_block_number + end + end + + defp latest_block do + string_value = Application.get_env(:indexer, :last_block) + + case Integer.parse(string_value) do + {integer, ""} -> integer + _ -> nil end end end diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 862d5994a0ff..09df2f8ec66a 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -11,43 +11,34 @@ defmodule Indexer.Block.Fetcher do alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, Import, Transaction, Wei} + alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} alias Explorer.Chain.Block.Reward - alias Explorer.Chain.Cache.Blocks, as: BlocksCache - alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions, Uncles} + alias Explorer.Chain.Cache.{Accounts, BlockNumber, Uncles} alias Indexer.Block.Fetcher.Receipts - alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup - alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime - alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime alias Indexer.Fetcher.{ BlockReward, + CoinBalance, ContractCode, InternalTransaction, ReplacedTransaction, Token, TokenBalance, + TokenInstance, UncleBlock } - alias Indexer.{Prometheus, TokenBalances, Tracer} + alias Indexer.Tracer alias Indexer.Transform.{ AddressCoinBalances, + AddressCoinBalancesDaily, Addresses, AddressTokenBalances, MintTransfers, - OptimismWithdrawals, - TokenInstances, - TokenTransfers, - TransactionActions + TokenTransfers } - alias Indexer.Transform.PolygonEdge.{DepositExecutes, Withdrawals} - - alias Indexer.Transform.Arbitrum.Messaging, as: ArbitrumMessaging - alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge - alias Indexer.Transform.Blocks, as: TransformBlocks @type address_hash_to_fetched_balance_block_number :: %{String.t() => Block.block_number()} @@ -118,7 +109,7 @@ defmodule Indexer.Block.Fetcher do @spec fetch_and_import_range(t, Range.t()) :: {:ok, %{inserted: %{}, errors: [EthereumJSONRPC.Transport.error()]}} | {:error, - {step :: atom(), reason :: [Ecto.Changeset.t()] | term()} + {step :: atom(), reason :: [%Ecto.Changeset{}] | term()} | {step :: atom(), failed_value :: term(), changes_so_far :: term()}} def fetch_and_import_range( %__MODULE__{ @@ -129,128 +120,72 @@ defmodule Indexer.Block.Fetcher do _.._ = range ) when callback_module != nil do - {fetch_time, fetched_blocks} = - :timer.tc(fn -> EthereumJSONRPC.fetch_blocks_by_range(range, json_rpc_named_arguments) end) - with {:blocks, {:ok, %Blocks{ blocks_params: blocks_params, transactions_params: transactions_params_without_receipts, - withdrawals_params: withdrawals_params, block_second_degree_relations_params: block_second_degree_relations_params, errors: blocks_errors - }}} <- {:blocks, fetched_blocks}, + }}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(range, json_rpc_named_arguments)}, blocks = TransformBlocks.transform_blocks(blocks_params), {:receipts, {:ok, receipt_params}} <- {:receipts, Receipts.fetch(state, transactions_params_without_receipts)}, %{logs: logs, receipts: receipts} = receipt_params, transactions_with_receipts = Receipts.put(transactions_params_without_receipts, receipts), %{token_transfers: token_transfers, tokens: tokens} = TokenTransfers.parse(logs), - %{transaction_actions: transaction_actions} = TransactionActions.parse(logs), %{mint_transfers: mint_transfers} = MintTransfers.parse(logs), - optimism_withdrawals = - if(callback_module == Indexer.Block.Realtime.Fetcher, do: OptimismWithdrawals.parse(logs), else: []), - polygon_edge_withdrawals = - if(callback_module == Indexer.Block.Realtime.Fetcher, do: Withdrawals.parse(logs), else: []), - polygon_edge_deposit_executes = - if(callback_module == Indexer.Block.Realtime.Fetcher, - do: DepositExecutes.parse(logs), - else: [] - ), - shibarium_bridge_operations = - if(callback_module == Indexer.Block.Realtime.Fetcher, - do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), - else: [] - ), - arbitrum_xlevel_messages = - if(callback_module == Indexer.Block.Realtime.Fetcher, - do: ArbitrumMessaging.parse(transactions_with_receipts, logs), - else: [] - ), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = - fetch_beneficiaries(blocks, transactions_with_receipts, json_rpc_named_arguments), + fetch_beneficiaries(blocks, json_rpc_named_arguments), addresses = Addresses.extract_addresses(%{ block_reward_contract_beneficiaries: MapSet.to_list(beneficiary_params_set), blocks: blocks, logs: logs, mint_transfers: mint_transfers, - shibarium_bridge_operations: shibarium_bridge_operations, token_transfers: token_transfers, - transactions: transactions_with_receipts, - transaction_actions: transaction_actions, - withdrawals: withdrawals_params + transactions: transactions_with_receipts }), coin_balances_params_set = %{ beneficiary_params: MapSet.to_list(beneficiary_params_set), blocks_params: blocks, logs_params: logs, - transactions_params: transactions_with_receipts, - withdrawals: withdrawals_params + transactions_params: transactions_with_receipts } |> AddressCoinBalances.params_set(), - beneficiaries_with_gas_payment = - beneficiaries_with_gas_payment(blocks, beneficiary_params_set, transactions_with_receipts), + coin_balances_params_daily_set = + %{ + coin_balances_params: coin_balances_params_set, + blocks: blocks + } + |> AddressCoinBalancesDaily.params_set(), + beneficiaries_with_gas_payment <- + beneficiary_params_set + |> add_gas_payments(transactions_with_receipts, blocks) + |> BlockReward.reduce_uncle_rewards(), address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers}), - transaction_actions = - Enum.map(transaction_actions, fn action -> Map.put(action, :data, Map.delete(action.data, :block_number)) end), - token_instances = TokenInstances.params_set(%{token_transfers_params: token_transfers}), - basic_import_options = %{ - addresses: %{params: addresses}, - address_coin_balances: %{params: coin_balances_params_set}, - address_token_balances: %{params: address_token_balances}, - address_current_token_balances: %{ - params: address_token_balances |> MapSet.to_list() |> TokenBalances.to_address_current_token_balances() - }, - blocks: %{params: blocks}, - block_second_degree_relations: %{params: block_second_degree_relations_params}, - block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment}, - logs: %{params: logs}, - optimism_withdrawals: %{params: optimism_withdrawals}, - token_transfers: %{params: token_transfers}, - tokens: %{params: tokens}, - transactions: %{params: transactions_with_receipts}, - withdrawals: %{params: withdrawals_params}, - token_instances: %{params: token_instances} - }, - import_options = - (case Application.get_env(:explorer, :chain_type) do - "polygon_edge" -> - basic_import_options - |> Map.put_new(:polygon_edge_withdrawals, %{params: polygon_edge_withdrawals}) - |> Map.put_new(:polygon_edge_deposit_executes, %{params: polygon_edge_deposit_executes}) - - "shibarium" -> - basic_import_options - |> Map.put_new(:shibarium_bridge_operations, %{params: shibarium_bridge_operations}) - - "arbitrum" -> - basic_import_options - |> Map.put_new(:arbitrum_messages, %{params: arbitrum_xlevel_messages}) - - _ -> - basic_import_options - end), {:ok, inserted} <- __MODULE__.import( state, - import_options - ), - {:tx_actions, {:ok, inserted_tx_actions}} <- - {:tx_actions, - Chain.import(%{ - transaction_actions: %{params: transaction_actions}, - timeout: :infinity - })} do - inserted = Map.merge(inserted, inserted_tx_actions) - Prometheus.Instrumenter.block_batch_fetch(fetch_time, callback_module) + %{ + addresses: %{params: addresses}, + address_coin_balances: %{params: coin_balances_params_set}, + address_coin_balances_daily: %{params: coin_balances_params_daily_set}, + address_token_balances: %{params: address_token_balances}, + blocks: %{params: blocks}, + block_second_degree_relations: %{params: block_second_degree_relations_params}, + block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment}, + logs: %{params: logs}, + token_transfers: %{params: token_transfers}, + tokens: %{on_conflict: :nothing, params: tokens}, + transactions: %{params: transactions_with_receipts} + } + ) do + Logger.info(["### fetch_and_import_range FINALIZED ", inspect(range), " ###"]) result = {:ok, %{inserted: inserted, errors: blocks_errors}} update_block_cache(inserted[:blocks]) - update_transactions_cache(inserted[:transactions]) update_addresses_cache(inserted[:addresses]) update_uncles_cache(inserted[:block_second_degree_relations]) - update_withdrawals_cache(inserted[:withdrawals]) result else {step, {:error, reason}} -> {:error, {step, reason}} @@ -265,30 +200,16 @@ defmodule Indexer.Block.Fetcher do BlockNumber.update_all(max_block.number) BlockNumber.update_all(min_block.number) - BlocksCache.update(blocks) end defp update_block_cache(_), do: :ok - defp update_transactions_cache(transactions) do - Transactions.update(transactions) - end - defp update_addresses_cache(addresses), do: Accounts.drop(addresses) defp update_uncles_cache(updated_relations) do Uncles.update_from_second_degree_relations(updated_relations) end - defp update_withdrawals_cache([_ | _] = withdrawals) do - %{index: index} = List.last(withdrawals) - Chain.upsert_count_withdrawals(index) - end - - defp update_withdrawals_cache(_) do - :ok - end - def import( %__MODULE__{broadcast: broadcast, callback_module: callback_module} = state, options @@ -306,19 +227,11 @@ defmodule Indexer.Block.Fetcher do } ) - {import_time, result} = :timer.tc(fn -> callback_module.import(state, options_with_broadcast) end) - - no_blocks_to_import = length(options_with_broadcast.blocks.params) - - if no_blocks_to_import != 0 do - Prometheus.Instrumenter.block_import(import_time / no_blocks_to_import, callback_module) - end - - result + callback_module.import(state, options_with_broadcast) end def async_import_token_instances(%{token_transfers: token_transfers}) do - TokenInstanceRealtime.async_fetch(token_transfers) + TokenInstance.async_fetch(token_transfers) end def async_import_token_instances(_), do: :ok @@ -339,17 +252,11 @@ defmodule Indexer.Block.Fetcher do block_number = Map.fetch!(address_hash_to_block_number, to_string(address_hash)) %{address_hash: address_hash, block_number: block_number} end) - |> CoinBalanceCatchup.async_fetch_balances() + |> CoinBalance.async_fetch_balances() end def async_import_coin_balances(_, _), do: :ok - def async_import_realtime_coin_balances(%{address_coin_balances: balances}) do - CoinBalanceRealtime.async_fetch_balances(balances) - end - - def async_import_realtime_coin_balances(_), do: :ok - def async_import_created_contract_codes(%{transactions: transactions}) do transactions |> Enum.flat_map(fn @@ -423,48 +330,7 @@ defmodule Indexer.Block.Fetcher do quantity_to_integer(block_quantity) end - defp fetch_beneficiaries(blocks, all_transactions, json_rpc_named_arguments) do - case Application.get_env(:indexer, :fetch_rewards_way) do - "manual" -> fetch_beneficiaries_manual(blocks, all_transactions) - _ -> fetch_beneficiaries_by_trace_block(blocks, json_rpc_named_arguments) - end - end - - def fetch_beneficiaries_manual(blocks, all_transactions) when is_list(blocks) do - block_transactions_map = Enum.group_by(all_transactions, & &1.block_number) - - blocks - |> Enum.map(fn block -> fetch_beneficiaries_manual(block, block_transactions_map[block.number] || []) end) - |> Enum.reduce(%FetchedBeneficiaries{}, fn params_set, %{params_set: acc_params_set} = acc -> - %FetchedBeneficiaries{acc | params_set: MapSet.union(acc_params_set, params_set)} - end) - end - - def fetch_beneficiaries_manual(block, transactions) do - block - |> Block.block_reward_by_parts(transactions) - |> reward_parts_to_beneficiaries() - end - - defp reward_parts_to_beneficiaries(reward_parts) do - reward = - reward_parts.static_reward - |> Wei.sum(reward_parts.transaction_fees) - |> Wei.sub(reward_parts.burnt_fees) - |> Wei.sum(reward_parts.uncle_reward) - - MapSet.new([ - %{ - address_hash: reward_parts.miner_hash, - block_hash: reward_parts.block_hash, - block_number: reward_parts.block_number, - reward: reward, - address_type: :validator - } - ]) - end - - defp fetch_beneficiaries_by_trace_block(blocks, json_rpc_named_arguments) do + defp fetch_beneficiaries(blocks, json_rpc_named_arguments) do hash_string_by_number = Enum.into(blocks, %{}, fn %{number: number, hash: hash_string} when is_integer(number) and is_binary(hash_string) -> @@ -528,18 +394,6 @@ defmodule Indexer.Block.Fetcher do |> Enum.into(MapSet.new()) end - defp beneficiaries_with_gas_payment(blocks, beneficiary_params_set, transactions_with_receipts) do - case Application.get_env(:indexer, :fetch_rewards_way) do - "manual" -> - beneficiary_params_set - - _ -> - beneficiary_params_set - |> add_gas_payments(transactions_with_receipts, blocks) - |> BlockReward.reduce_uncle_rewards() - end - end - defp add_gas_payments(beneficiaries, transactions, blocks) do transactions_by_block_number = Enum.group_by(transactions, & &1.block_number) @@ -615,7 +469,6 @@ defmodule Indexer.Block.Fetcher do hash: hash } = address_params ) do - {{String.downcase(hash), fetched_coin_balance_block_number}, - Map.delete(address_params, :fetched_coin_balance_block_number)} + {{hash, fetched_coin_balance_block_number}, Map.delete(address_params, :fetched_coin_balance_block_number)} end end diff --git a/apps/indexer/lib/indexer/block/fetcher/receipts.ex b/apps/indexer/lib/indexer/block/fetcher/receipts.ex index c5b1b21f9e59..40d3d89f8ee4 100644 --- a/apps/indexer/lib/indexer/block/fetcher/receipts.ex +++ b/apps/indexer/lib/indexer/block/fetcher/receipts.ex @@ -30,8 +30,12 @@ defmodule Indexer.Block.Fetcher.Receipts do {:halt, {:error, reason}} end) |> case do - {:ok, receipt_params} -> {:ok, set_block_number_to_logs(receipt_params, transaction_params)} - other -> other + {:ok, receipt_params} -> + Logger.info(["### Receipts fetched ###"]) + {:ok, set_block_number_to_logs(receipt_params, transaction_params)} + + other -> + other end end diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index d5d1af69436a..24a6ee768688 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -5,26 +5,32 @@ defmodule Indexer.Fetcher.InternalTransaction do See `async_fetch/1` for details on configuring limits. """ - use Indexer.Fetcher, restart: :permanent + use Indexer.Fetcher use Spandex.Decorators require Logger import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2] - alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain alias Explorer.Chain.Block - alias Explorer.Chain.Cache.{Accounts, Blocks} - alias Explorer.Chain.Import.Runner.Blocks, as: BlocksRunner + alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.InternalTransaction.Supervisor, as: InternalTransactionSupervisor alias Indexer.Transform.Addresses @behaviour BufferedTask - @default_max_batch_size 10 - @default_max_concurrency 4 + @max_batch_size 10 + @max_concurrency 4 + @defaults [ + flush_interval: :timer.seconds(3), + max_concurrency: @max_concurrency, + max_batch_size: @max_batch_size, + poll: true, + task_supervisor: Indexer.Fetcher.InternalTransaction.TaskSupervisor, + metadata: [fetcher: :internal_transaction] + ] @doc """ Asynchronously fetches internal transactions. @@ -34,10 +40,10 @@ defmodule Indexer.Fetcher.InternalTransaction do Internal transactions are an expensive upstream operation. The number of results to fetch is configured by `@max_batch_size` and represents the number of transaction hashes to request internal transactions in a single JSONRPC - request. Defaults to `#{@default_max_batch_size}`. + request. Defaults to `#{@max_batch_size}`. The `@max_concurrency` attribute configures the number of concurrent requests - of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@default_max_concurrency}`. + of `@max_batch_size` to allow against the JSONRPC. Defaults to `#{@max_concurrency}`. *Note*: The internal transactions for individual transactions cannot be paginated, so the total number of internal transactions that could be produced is unknown. @@ -62,7 +68,7 @@ defmodule Indexer.Fetcher.InternalTransaction do end merged_init_opts = - defaults() + @defaults |> Keyword.merge(mergeable_init_options) |> Keyword.put(:state, state) @@ -72,12 +78,9 @@ defmodule Indexer.Fetcher.InternalTransaction do @impl BufferedTask def init(initial, reducer, _json_rpc_named_arguments) do {:ok, final} = - Chain.stream_blocks_with_unfetched_internal_transactions( - initial, - fn block_number, acc -> - reducer.(block_number, acc) - end - ) + Chain.stream_blocks_with_unfetched_internal_transactions(initial, fn block_number, acc -> + reducer.(block_number, acc) + end) final end @@ -94,99 +97,50 @@ defmodule Indexer.Fetcher.InternalTransaction do tracer: Tracer ) def run(block_numbers, json_rpc_named_arguments) do - unique_numbers = - block_numbers - |> Enum.uniq() - |> Chain.filter_consensus_block_numbers() - - filtered_unique_numbers = - unique_numbers - |> RangesHelper.filter_traceable_block_numbers() - |> drop_genesis(json_rpc_named_arguments) + unique_numbers = Enum.uniq(block_numbers) - filtered_unique_numbers_count = Enum.count(filtered_unique_numbers) - Logger.metadata(count: filtered_unique_numbers_count) + unique_numbers_count = Enum.count(unique_numbers) + Logger.metadata(count: unique_numbers_count) Logger.debug("fetching internal transactions for blocks") json_rpc_named_arguments |> Keyword.fetch!(:variant) - |> fetch_internal_transactions(filtered_unique_numbers, json_rpc_named_arguments) |> case do - {:ok, internal_transactions_params} -> - safe_import_internal_transaction(internal_transactions_params, filtered_unique_numbers) + EthereumJSONRPC.Parity -> + EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments) - {:error, reason} -> - Logger.error( - fn -> - ["failed to fetch internal transactions for blocks: ", Exception.format(:error, reason)] - end, - error_count: filtered_unique_numbers_count - ) + EthereumJSONRPC.Erigon -> + EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments) - handle_not_found_transaction(reason) + EthereumJSONRPC.Besu -> + EthereumJSONRPC.fetch_block_internal_transactions(unique_numbers, json_rpc_named_arguments) - # re-queue the de-duped entries - {:retry, filtered_unique_numbers} + _ -> + try do + fetch_block_internal_transactions_by_transactions(unique_numbers, json_rpc_named_arguments) + rescue + error -> + {:error, error} + end + end + |> case do + {:ok, internal_transactions_params} -> + import_internal_transaction(internal_transactions_params, unique_numbers) - {:error, reason, stacktrace} -> - Logger.error( - fn -> - ["failed to fetch internal transactions for blocks: ", Exception.format(:error, reason, stacktrace)] - end, - error_count: filtered_unique_numbers_count + {:error, reason} -> + Logger.error(fn -> ["failed to fetch internal transactions for blocks: ", inspect(reason)] end, + error_count: unique_numbers_count ) - handle_not_found_transaction(reason) - # re-queue the de-duped entries - {:retry, filtered_unique_numbers} + {:retry, unique_numbers} :ignore -> :ok end end - defp fetch_internal_transactions(variant, block_numbers, json_rpc_named_arguments) do - if variant in block_traceable_variants() do - EthereumJSONRPC.fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) - else - try do - fetch_block_internal_transactions_by_transactions(block_numbers, json_rpc_named_arguments) - rescue - error -> - {:error, error, __STACKTRACE__} - end - end - end - - @default_block_traceable_variants [ - EthereumJSONRPC.Nethermind, - EthereumJSONRPC.Erigon, - EthereumJSONRPC.Besu, - EthereumJSONRPC.RSK - ] - defp block_traceable_variants do - if Application.get_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth)[:block_traceable?] do - [EthereumJSONRPC.Geth | @default_block_traceable_variants] - else - @default_block_traceable_variants - end - end - - defp drop_genesis(block_numbers, json_rpc_named_arguments) do - first_block = Application.get_env(:indexer, :trace_first_block) - - if first_block in block_numbers do - case EthereumJSONRPC.fetch_blocks_by_numbers([first_block], json_rpc_named_arguments) do - {:ok, %{transactions_params: [_ | _]}} -> block_numbers - _ -> block_numbers -- [first_block] - end - else - block_numbers - end - end - def import_first_trace(internal_transactions_params) do imports = Chain.import(%{ @@ -223,7 +177,7 @@ defmodule Indexer.Fetcher.InternalTransaction do EthereumJSONRPC.fetch_internal_transactions(transactions, json_rpc_named_arguments) catch :exit, error -> - {:error, error, __STACKTRACE__} + {:error, error} end end |> case do @@ -236,14 +190,6 @@ defmodule Indexer.Fetcher.InternalTransaction do end) end - defp safe_import_internal_transaction(internal_transactions_params, block_numbers) do - import_internal_transaction(internal_transactions_params, block_numbers) - rescue - Postgrex.Error -> - handle_foreign_key_violation(internal_transactions_params, block_numbers) - {:retry, block_numbers} - end - defp import_internal_transaction(internal_transactions_params, unique_numbers) do internal_transactions_params_without_failed_creations = remove_failed_creations(internal_transactions_params) @@ -254,7 +200,7 @@ defmodule Indexer.Fetcher.InternalTransaction do address_hash_to_block_number = Enum.into(addresses_params, %{}, fn %{fetched_coin_balance_block_number: block_number, hash: hash} -> - {String.downcase(hash), block_number} + {hash, block_number} end) empty_block_numbers = @@ -275,8 +221,7 @@ defmodule Indexer.Fetcher.InternalTransaction do case imports do {:ok, imported} -> - Accounts.drop(imported[:addresses]) - Blocks.drop_nonconsensus(imported[:remove_consensus_of_missing_transactions_blocks]) + Accounts.drop(imported[:addreses]) async_import_coin_balances(imported, %{ address_hash_to_fetched_balance_block_number: address_hash_to_block_number @@ -294,8 +239,6 @@ defmodule Indexer.Fetcher.InternalTransaction do error_count: Enum.count(unique_numbers) ) - handle_unique_key_violation(reason, unique_numbers) - # re-queue the de-duped entries {:retry, unique_numbers} end @@ -322,71 +265,10 @@ defmodule Indexer.Fetcher.InternalTransaction do |> Map.delete(:created_contract_code) |> Map.delete(:gas_used) |> Map.delete(:output) - |> Map.put(:error, internal_transaction_param[:error] || failed_parent[:error]) + |> Map.put(:error, failed_parent[:error]) else internal_transaction_param end end) end - - defp handle_unique_key_violation(%{exception: %{postgres: %{code: :unique_violation}}}, block_numbers) do - BlocksRunner.invalidate_consensus_blocks(block_numbers) - - Logger.error(fn -> - [ - "unique_violation on internal transactions import, block numbers: ", - inspect(block_numbers) - ] - end) - end - - defp handle_unique_key_violation(_reason, _block_numbers), do: :ok - - defp handle_foreign_key_violation(internal_transactions_params, block_numbers) do - BlocksRunner.invalidate_consensus_blocks(block_numbers) - - transaction_hashes = - internal_transactions_params - |> Enum.map(&to_string(&1.transaction_hash)) - |> Enum.uniq() - - Logger.error(fn -> - [ - "foreign_key_violation on internal transactions import, foreign transactions hashes: ", - Enum.join(transaction_hashes, ", ") - ] - end) - end - - defp handle_not_found_transaction(errors) when is_list(errors) do - Enum.each(errors, &handle_not_found_transaction/1) - end - - defp handle_not_found_transaction(error) do - case error do - %{data: data, message: "historical backend error" <> _} -> invalidate_block_from_error(data) - %{data: data, message: "genesis is not traceable"} -> invalidate_block_from_error(data) - %{data: data, message: "transaction not found"} -> invalidate_block_from_error(data) - _ -> :ok - end - end - - defp invalidate_block_from_error(%{"blockNumber" => block_number}), - do: BlocksRunner.invalidate_consensus_blocks([block_number]) - - defp invalidate_block_from_error(%{block_number: block_number}), - do: BlocksRunner.invalidate_consensus_blocks([block_number]) - - defp invalidate_block_from_error(_error_data), do: :ok - - def defaults do - [ - poll: false, - flush_interval: :timer.seconds(3), - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, - task_supervisor: Indexer.Fetcher.InternalTransaction.TaskSupervisor, - metadata: [fetcher: :internal_transaction] - ] - end end diff --git a/apps/indexer/lib/indexer/fetcher/token.ex b/apps/indexer/lib/indexer/fetcher/token.ex index 9d4b0dd253f3..b96e5ab29f83 100644 --- a/apps/indexer/lib/indexer/fetcher/token.ex +++ b/apps/indexer/lib/indexer/fetcher/token.ex @@ -3,7 +3,7 @@ defmodule Indexer.Fetcher.Token do Fetches information about a token. """ - use Indexer.Fetcher, restart: :permanent + use Indexer.Fetcher use Spandex.Decorators alias Explorer.Chain @@ -14,7 +14,12 @@ defmodule Indexer.Fetcher.Token do @behaviour BufferedTask - @default_max_concurrency 10 + @defaults [ + flush_interval: 300, + max_batch_size: 1, + max_concurrency: 4, + task_supervisor: Indexer.Fetcher.Token.TaskSupervisor + ] @doc false def child_spec([init_options, gen_server_options]) do @@ -27,7 +32,7 @@ defmodule Indexer.Fetcher.Token do end merged_init_opts = - defaults() + @defaults |> Keyword.merge(mergeable_init_options) |> Keyword.put(:state, state) @@ -37,13 +42,9 @@ defmodule Indexer.Fetcher.Token do @impl BufferedTask def init(initial_acc, reducer, _) do {:ok, acc} = - Chain.stream_uncataloged_token_contract_address_hashes( - initial_acc, - fn address, acc -> - reducer.(address, acc) - end, - true - ) + Chain.stream_uncataloged_token_contract_address_hashes(initial_acc, fn address, acc -> + reducer.(address, acc) + end) acc end @@ -73,16 +74,7 @@ defmodule Indexer.Fetcher.Token do |> MetadataRetriever.get_functions_of() |> Map.put(:cataloged, true) - {:ok, _} = Chain.update_token(token, token_params) + {:ok, _} = Chain.update_token(%{token | updated_at: DateTime.utc_now()}, token_params) :ok end - - defp defaults do - [ - flush_interval: 300, - max_batch_size: 1, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - task_supervisor: Indexer.Fetcher.Token.TaskSupervisor - ] - end end diff --git a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs index dc3d820e4b79..051d2daaa240 100644 --- a/apps/indexer/test/indexer/block/catchup/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/catchup/fetcher_test.exs @@ -8,13 +8,9 @@ defmodule Indexer.Block.Catchup.FetcherTest do alias Explorer.Chain alias Explorer.Chain.Block.Reward alias Explorer.Chain.Hash - alias Explorer.Utility.MissingRangesManipulator - alias Explorer.Utility.MissingBlockRange alias Indexer.Block alias Indexer.Block.Catchup.Fetcher - alias Indexer.Block.Catchup.MissingRangesCollector - alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup - alias Indexer.Fetcher.{BlockReward, InternalTransaction, Token, TokenBalance, UncleBlock} + alias Indexer.Fetcher.{BlockReward, CoinBalance, InternalTransaction, Token, TokenBalance, UncleBlock} @moduletag capture_log: true @@ -31,28 +27,17 @@ defmodule Indexer.Block.Catchup.FetcherTest do transport: EthereumJSONRPC.Mox, transport_options: [], # Which one does not matter, so pick one - variant: EthereumJSONRPC.Nethermind + variant: EthereumJSONRPC.Parity ] } end describe "import/1" do - setup do - configuration = Application.get_env(:indexer, :last_block) - Application.put_env(:indexer, :last_block, 0) - - on_exit(fn -> - Application.put_env(:indexer, :last_block, configuration) - end) - end - test "fetches uncles asynchronously", %{json_rpc_named_arguments: json_rpc_named_arguments} do - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - MissingRangesCollector.start_link([]) - MissingRangesManipulator.start_link([]) parent = self() @@ -138,23 +123,13 @@ defmodule Indexer.Block.Catchup.FetcherTest do end describe "task/1" do - setup do - initial_env = Application.get_env(:indexer, :block_ranges) - on_exit(fn -> Application.put_env(:indexer, :block_ranges, initial_env) end) - end - test "ignores fetched beneficiaries with different hash for same number", %{ json_rpc_named_arguments: json_rpc_named_arguments } do - Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) - Application.put_env(:indexer, :block_ranges, "0..1") - start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - MissingRangesCollector.start_link([]) - MissingRangesManipulator.start_link([]) latest_block_number = 2 latest_block_quantity = integer_to_quantity(latest_block_number) @@ -287,17 +262,16 @@ defmodule Indexer.Block.Catchup.FetcherTest do assert count(Chain.Block) == 0 - Process.sleep(50) - assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ + blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments } }) - Process.sleep(3000) + Process.sleep(1000) assert count(Chain.Block) == 1 assert count(Reward) == 0 @@ -306,15 +280,10 @@ defmodule Indexer.Block.Catchup.FetcherTest do test "async fetches beneficiaries when individual responses error out", %{ json_rpc_named_arguments: json_rpc_named_arguments } do - Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) - Application.put_env(:indexer, :block_ranges, "0..1") - start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - MissingRangesCollector.start_link([]) - MissingRangesManipulator.start_link([]) latest_block_number = 2 latest_block_quantity = integer_to_quantity(latest_block_number) @@ -442,36 +411,30 @@ defmodule Indexer.Block.Catchup.FetcherTest do Process.register(pid, BlockReward) - Process.sleep(50) - assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ + blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments } }) - Process.sleep(3000) + Process.sleep(1000) assert count(Chain.Block) == 1 assert count(Reward) == 0 - assert_receive {:block_numbers, [^block_number]}, 5_000 + assert_receive {:block_numbers, [block_number]}, 5_000 end test "async fetches beneficiaries when entire call errors out", %{ json_rpc_named_arguments: json_rpc_named_arguments } do - Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 1, concurrency: 10) - Application.put_env(:indexer, :block_ranges, "0..1") - start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) TokenBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) - MissingRangesCollector.start_link([]) - MissingRangesManipulator.start_link([]) latest_block_number = 2 latest_block_quantity = integer_to_quantity(latest_block_number) @@ -553,7 +516,7 @@ defmodule Indexer.Block.Catchup.FetcherTest do jsonrpc: "2.0", result: %{ "hash" => to_string(block_hash_0), - "number" => "0x0", + "number" => block_quantity, "difficulty" => "0x0", "gasLimit" => "0x0", "gasUsed" => "0x0", @@ -592,78 +555,20 @@ defmodule Indexer.Block.Catchup.FetcherTest do Process.register(pid, BlockReward) - Process.sleep(50) - assert %{first_block_number: ^block_number, last_block_number: 0, missing_block_count: 2, shrunk: false} = Fetcher.task(%Fetcher{ + blocks_batch_size: 1, block_fetcher: %Block.Fetcher{ callback_module: Fetcher, json_rpc_named_arguments: json_rpc_named_arguments } }) - Process.sleep(3000) + Process.sleep(1000) assert count(Chain.Block) == 1 assert count(Reward) == 0 - assert_receive {:block_numbers, [^block_number]}, 5_000 - end - - test "failed blocks handles correctly", %{json_rpc_named_arguments: json_rpc_named_arguments} do - Application.put_env(:indexer, Indexer.Block.Catchup.Fetcher, batch_size: 2, concurrency: 10) - Application.put_env(:indexer, :block_ranges, "0..1") - start_supervised!({Task.Supervisor, name: Indexer.Block.Catchup.TaskSupervisor}) - MissingRangesCollector.start_link([]) - MissingRangesManipulator.start_link([]) - - EthereumJSONRPC.Mox - |> expect(:json_rpc, 2, fn - [ - %{ - id: id_1, - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: ["0x1", true] - }, - %{ - id: id_2, - jsonrpc: "2.0", - method: "eth_getBlockByNumber", - params: ["0x0", true] - } - ], - _options -> - {:ok, - [ - %{ - id: id_1, - jsonrpc: "2.0", - error: %{message: "error"} - }, - %{ - id: id_2, - jsonrpc: "2.0", - error: %{message: "error"} - } - ]} - - [], _options -> - {:ok, []} - end) - - Process.sleep(50) - - assert %{first_block_number: 1, last_block_number: 0, missing_block_count: 2, shrunk: false} = - Fetcher.task(%Fetcher{ - block_fetcher: %Block.Fetcher{ - callback_module: Fetcher, - json_rpc_named_arguments: json_rpc_named_arguments - } - }) - - Process.sleep(1000) - - assert %{from_number: 1, to_number: 0} = Repo.one(MissingBlockRange) + assert_receive {:block_numbers, [_block_number]}, 5_000 end end diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index ca9ae2a099de..2f3436badc2c 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -5,14 +5,15 @@ defmodule Indexer.Block.FetcherTest do import Mox import EthereumJSONRPC, only: [integer_to_quantity: 1] + import EthereumJSONRPC.Case alias Explorer.Chain alias Explorer.Chain.{Address, Log, Transaction, Wei} alias Indexer.Block.Fetcher alias Indexer.BufferedTask - alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.{ + CoinBalance, ContractCode, InternalTransaction, ReplacedTransaction, @@ -49,7 +50,7 @@ defmodule Indexer.Block.FetcherTest do describe "import_range/2" do setup %{json_rpc_named_arguments: json_rpc_named_arguments} do - CoinBalanceCatchup.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) + CoinBalance.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) ContractCode.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) InternalTransaction.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) Token.Supervisor.Case.start_supervised!(json_rpc_named_arguments: json_rpc_named_arguments) @@ -82,7 +83,7 @@ defmodule Indexer.Block.FetcherTest do # res = eth_block_number_fake_response(block_quantity) # case Keyword.fetch!(json_rpc_named_arguments, :variant) do - # EthereumJSONRPC.Nethermind -> + # EthereumJSONRPC.Parity -> # EthereumJSONRPC.Mox # |> expect(:json_rpc, fn [%{id: id, method: "eth_getBlockByNumber", params: [^block_quantity, true]}], # _options -> @@ -219,7 +220,7 @@ defmodule Indexer.Block.FetcherTest do # } # } - # EthereumJSONRPC.Nethermind -> + # EthereumJSONRPC.Parity -> # %{ # address_hash: %Explorer.Chain.Hash{ # byte_count: 20, @@ -273,7 +274,7 @@ defmodule Indexer.Block.FetcherTest do if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Nethermind -> + EthereumJSONRPC.Parity -> block_quantity = integer_to_quantity(block_number) from_address_hash = "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" to_address_hash = "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" @@ -375,7 +376,8 @@ defmodule Indexer.Block.FetcherTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0" + "transactionLogIndex" => "0x0", + "type" => "mined" } ], "logsBloom" => @@ -581,7 +583,7 @@ defmodule Indexer.Block.FetcherTest do }} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) wait_for_tasks(InternalTransaction) - wait_for_tasks(CoinBalanceCatchup) + wait_for_tasks(CoinBalance) assert Repo.aggregate(Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 5 @@ -613,7 +615,7 @@ defmodule Indexer.Block.FetcherTest do assert fifth_address.fetched_coin_balance == %Wei{value: Decimal.new(930_417_572_224_879_702_000)} assert fifth_address.fetched_coin_balance_block_number == block_number - EthereumJSONRPC.Nethermind -> + EthereumJSONRPC.Parity -> assert {:ok, %{ inserted: %{ @@ -675,7 +677,7 @@ defmodule Indexer.Block.FetcherTest do }} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) wait_for_tasks(InternalTransaction) - wait_for_tasks(CoinBalanceCatchup) + wait_for_tasks(CoinBalance) assert Repo.aggregate(Chain.Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 2 @@ -743,7 +745,7 @@ defmodule Indexer.Block.FetcherTest do %{id: id, method: "trace_block"} -> block_quantity = integer_to_quantity(block_number) - _res = eth_block_number_fake_response(block_quantity) + res = eth_block_number_fake_response(block_quantity) %{ id: id, @@ -790,9 +792,11 @@ defmodule Indexer.Block.FetcherTest do end) end - assert {:ok, %{errors: [], inserted: %{block_rewards: _block_rewards}}} = + assert {:ok, %{errors: [], inserted: _}} = Fetcher.fetch_and_import_range(block_fetcher, block_number..block_number) + Process.sleep(1000) + assert Repo.one!(select(Chain.Block.Reward, fragment("COUNT(*)"))) == 2 end end diff --git a/docker/Dockerfile b/docker/Dockerfile index d75d5fce9a7b..5ed3f13b7b8e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,28 +1,30 @@ -FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 AS builder +FROM bitwalker/alpine-elixir-phoenix:1.12 -WORKDIR /app +RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file -ENV MIX_ENV="prod" - -RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 file gcompat +ENV GLIBC_REPO=https://github.com/sgerrand/alpine-pkg-glibc +ENV GLIBC_VERSION=2.30-r0 RUN set -ex && \ - apk --update add libstdc++ curl ca-certificates gcompat - -ARG CACHE_EXCHANGE_RATES_PERIOD -ARG API_V1_READ_METHODS_DISABLED -ARG DISABLE_WEBAPP -ARG API_V1_WRITE_METHODS_DISABLED -ARG CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED -ARG ADMIN_PANEL_ENABLED -ARG CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL -ARG SESSION_COOKIE_DOMAIN -ARG MIXPANEL_TOKEN -ARG MIXPANEL_URL -ARG AMPLITUDE_API_KEY -ARG AMPLITUDE_URL -ARG CHAIN_TYPE -ENV CHAIN_TYPE=${CHAIN_TYPE} + apk --update add libstdc++ curl ca-certificates && \ + for pkg in glibc-${GLIBC_VERSION} glibc-bin-${GLIBC_VERSION}; \ + do curl -sSL ${GLIBC_REPO}/releases/download/${GLIBC_VERSION}/${pkg}.apk -o /tmp/${pkg}.apk; done && \ + apk add --allow-untrusted /tmp/*.apk && \ + rm -v /tmp/*.apk && \ + /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib + +# Get Rust +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +ENV PATH="$HOME/.cargo/bin:${PATH}" +ENV RUSTFLAGS="-C target-feature=-crt-static" + +EXPOSE 4000 + +ENV MIX_ENV="prod" +ENV SOCKET_ROOT="/eth/mainnet" +ENV NETWORK_PATH="/eth/mainnet" +ENV CHAIN_ID="1" # Cache elixir deps ADD mix.exs mix.lock ./ @@ -31,53 +33,26 @@ ADD apps/explorer/mix.exs ./apps/explorer/ ADD apps/ethereum_jsonrpc/mix.exs ./apps/ethereum_jsonrpc/ ADD apps/indexer/mix.exs ./apps/indexer/ -ENV MIX_HOME=/opt/mix -RUN mix local.hex --force RUN HEX_HTTP_TIMEOUT=3600 mix do deps.get, local.rebar --force, deps.compile -ADD apps ./apps -ADD config ./config -ADD rel ./rel -ADD *.exs ./ +ADD . . -RUN apk add --update nodejs npm +ARG COIN +RUN if [ "$COIN" != "" ]; then sed -i s/"POA"/"${COIN}"/g apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po; fi # Run forderground build and phoenix digest -RUN mix compile && npm install npm@latest +RUN mix compile + +RUN npm install npm@latest # Add blockscout npm deps RUN cd apps/block_scout_web/assets/ && \ npm install && \ npm run deploy && \ - cd /app/apps/explorer/ && \ - npm install && \ - apk update && \ - apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 + cd - +RUN cd apps/explorer/ && \ + npm install && \ + apk update && apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python3 -RUN apk add --update git make - -RUN mix phx.digest - -RUN mkdir -p /opt/release \ - && mix release blockscout \ - && mv _build/${MIX_ENV}/rel/blockscout /opt/release - -############################################################## -FROM hexpm/elixir:1.14.5-erlang-24.2.2-alpine-3.18.2 - -ARG RELEASE_VERSION -ENV RELEASE_VERSION=${RELEASE_VERSION} -ARG CHAIN_TYPE -ENV CHAIN_TYPE=${CHAIN_TYPE} -ARG BLOCKSCOUT_VERSION -ENV BLOCKSCOUT_VERSION=${BLOCKSCOUT_VERSION} - -RUN apk --no-cache --update add jq curl - -WORKDIR /app - -COPY --from=builder /opt/release/blockscout . -COPY --from=builder /app/apps/explorer/node_modules ./node_modules -COPY --from=builder /app/config/config_helper.exs ./config/config_helper.exs -COPY --from=builder /app/config/config_helper.exs /app/releases/${RELEASE_VERSION}/config_helper.exs +RUN mix phx.digest \ No newline at end of file diff --git a/docker/Makefile b/docker/Makefile index 63bcd32de63b..eabf3c4b1fb9 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,150 +1,487 @@ -DOCKER_REPO := blockscout -BACKEND_APP_NAME := blockscout -FRONTEND_APP_NAME := frontend -BACKEND_CONTAINER_IMAGE := $(DOCKER_REPO)/$(BACKEND_APP_NAME) -BACKEND_CONTAINER_NAME := backend -FRONTEND_CONTAINER_NAME := frontend -VISUALIZER_CONTAINER_NAME := visualizer -SIG_PROVIDER_CONTAINER_NAME := sig-provider -STATS_CONTAINER_NAME := stats -STATS_DB_CONTAINER_NAME := stats-postgres -PROXY_CONTAINER_NAME := proxy -PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '6.0.0' -TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") -STABLE_TAG := $(RELEASE_VERSION) +SYSTEM = $(shell uname -s) +HOST = host.docker.internal +DOCKER_IMAGE = blockscout_prod_mainnet +BS_CONTAINER_NAME = blockscout_mainnet +THIS_FILE = $(lastword $(MAKEFILE_LIST)) -start: - @echo "==> Starting blockscout db" - @docker-compose -f ../docker-compose/services/db.yml up -d - @echo "==> Starting blockscout backend" - @docker-compose -f ../docker-compose/services/backend.yml up -d - @echo "==> Starting stats microservice" - @docker-compose -f ../docker-compose/services/stats.yml up -d - @echo "==> Starting visualizer microservice" - @docker-compose -f ../docker-compose/services/visualizer.yml up -d - @echo "==> Starting sig-provider microservice" - @docker-compose -f ../docker-compose/services/sig-provider.yml up -d - @echo "==> Starting blockscout frontend" - @docker-compose -f ../docker-compose/services/frontend.yml up -d - @echo "==> Starting Nginx proxy" - @docker-compose -f ../docker-compose/services/nginx.yml up -d +ifeq ($(SYSTEM), Linux) + HOST=localhost +endif -BS_BACKEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${BACKEND_CONTAINER_NAME}$ | grep ${BACKEND_CONTAINER_NAME}) -BS_FRONTEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${FRONTEND_CONTAINER_NAME}$ | grep ${FRONTEND_CONTAINER_NAME}) -BS_STATS_STARTED := $(shell docker ps --no-trunc --filter name=^/${STATS_CONTAINER_NAME}$ | grep ${STATS_CONTAINER_NAME}) -BS_STATS_DB_STARTED := $(shell docker ps --no-trunc --filter name=^/${STATS_DB_CONTAINER_NAME}$ | grep ${STATS_DB_CONTAINER_NAME}) -BS_VISUALIZER_STARTED := $(shell docker ps --no-trunc --filter name=^/${VISUALIZER_CONTAINER_NAME}$ | grep ${VISUALIZER_CONTAINER_NAME}) -BS_SIG_PROVIDER_STARTED := $(shell docker ps --no-trunc --filter name=^/${SIG_PROVIDER_CONTAINER_NAME}$ | grep ${SIG_PROVIDER_CONTAINER_NAME}) -BS_PROXY_STARTED := $(shell docker ps --no-trunc --filter name=^/${PROXY_CONTAINER_NAME}$ | grep ${PROXY_CONTAINER_NAME}) -stop: -ifdef BS_FRONTEND_STARTED - @echo "==> Stopping Blockscout frontend container." - @docker stop $(FRONTEND_CONTAINER_NAME) && docker rm -f $(FRONTEND_CONTAINER_NAME) - @echo "==> Blockscout frontend container stopped." -else - @echo "==> Blockscout frontend container already stopped before." +BLOCKSCOUT_CONTAINER_PARAMS = -e 'MIX_ENV=prod' \ + +ifeq ($(SYSTEM), Linux) + BLOCKSCOUT_CONTAINER_PARAMS += --network=host endif -ifdef BS_BACKEND_STARTED - @echo "==> Stopping Blockscout backend container." - @docker stop $(BACKEND_CONTAINER_NAME) && docker rm -f $(BACKEND_CONTAINER_NAME) - @echo "==> Blockscout backend container stopped." -else - @echo "==> Blockscout backend container already stopped before." +ifdef NETWORK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK=$(NETWORK)' endif -ifdef BS_STATS_DB_STARTED - @echo "==> Stopping Blockscout stats db container." - @docker stop $(STATS_DB_CONTAINER_NAME) && docker rm -f $(STATS_DB_CONTAINER_NAME) - @echo "==> Blockscout stats db container stopped." -else - @echo "==> Blockscout stats db container already stopped before." +ifdef SUBNETWORK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUBNETWORK=$(SUBNETWORK)' endif -ifdef BS_STATS_STARTED - @echo "==> Stopping Blockscout stats container." - @docker stop $(STATS_CONTAINER_NAME) && docker rm -f $(STATS_CONTAINER_NAME) - @echo "==> Blockscout stats container stopped." -else - @echo "==> Blockscout stats container already stopped before." +ifdef LOGO + BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO=$(LOGO)' endif -ifdef BS_VISUALIZER_STARTED - @echo "==> Stopping Blockscout visualizer container." - @docker stop $(VISUALIZER_CONTAINER_NAME) && docker rm -f $(VISUALIZER_CONTAINER_NAME) - @echo "==> Blockscout visualizer container stopped." -else - @echo "==> Blockscout visualizer container already stopped before." +ifdef LOGO_FOOTER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'LOGO_FOOTER=$(LOGO_FOOTER)' endif -ifdef BS_SIG_PROVIDER_STARTED - @echo "==> Stopping Blockscout sig-provider container." - @docker stop $(SIG_PROVIDER_CONTAINER_NAME) && docker rm -f $(SIG_PROVIDER_CONTAINER_NAME) - @echo "==> Blockscout sig-provider container stopped." -else - @echo "==> Blockscout sig-provider container already stopped before." +ifdef ETHEREUM_JSONRPC_VARIANT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_VARIANT=$(ETHEREUM_JSONRPC_VARIANT)' endif -ifdef BS_PROXY_STARTED - @echo "==> Stopping Nginx proxy container." - @docker stop $(PROXY_CONTAINER_NAME) && docker rm -f $(PROXY_CONTAINER_NAME) - @echo "==> Nginx proxy container stopped." -else - @echo "==> Nginx proxy container already stopped before." +ifdef ETHEREUM_JSONRPC_HTTP_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_HTTP_URL=$(ETHEREUM_JSONRPC_HTTP_URL)' endif -ifdef PG_STARTED - @echo "==> Stopping Postgres container." - @docker stop $(PG_CONTAINER_NAME) - @echo "==> Postgres container stopped." -else - @echo "==> Postgres container already stopped before." +ifdef ETHEREUM_JSONRPC_TRACE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRACE_URL=$(ETHEREUM_JSONRPC_TRACE_URL)' +endif +ifdef ETHEREUM_JSONRPC_WS_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_WS_URL=$(ETHEREUM_JSONRPC_WS_URL)' +endif +ifdef ETHEREUM_JSONRPC_TRANSPORT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ETHEREUM_JSONRPC_TRANSPORT=$(ETHEREUM_JSONRPC_TRANSPORT)' +endif +ifdef IPC_PATH + BLOCKSCOUT_CONTAINER_PARAMS += -e 'IPC_PATH=$(IPC_PATH)' +endif +ifdef NETWORK_PATH + BLOCKSCOUT_CONTAINER_PARAMS += -e 'NETWORK_PATH=$(NETWORK_PATH)' +endif +ifdef SECRET_KEY_BASE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SECRET_KEY_BASE=$(SECRET_KEY_BASE)' +endif +ifdef CHECK_ORIGIN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECK_ORIGIN=$(CHECK_ORIGIN)' +endif +ifdef PORT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'PORT=$(PORT)' +endif +ifdef COIN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN=$(COIN)' +endif +ifdef METADATA_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'METADATA_CONTRACT=$(METADATA_CONTRACT)' +endif +ifdef POS_STAKING_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'POS_STAKING_CONTRACT=$(POS_STAKING_CONTRACT)' +endif +ifdef VALIDATORS_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'VALIDATORS_CONTRACT=$(VALIDATORS_CONTRACT)' +endif +ifdef KEYS_MANAGER_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'KEYS_MANAGER_CONTRACT=$(KEYS_MANAGER_CONTRACT)' +endif +ifdef SUPPLY_MODULE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUPPLY_MODULE=$(SUPPLY_MODULE)' +endif +ifdef SOURCE_MODULE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCE_MODULE=$(SOURCE_MODULE)' +endif +ifdef DATABASE_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATABASE_URL=$(DATABASE_URL)' +endif +ifdef POOL_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'POOL_SIZE=$(POOL_SIZE)' +endif +ifdef ECTO_USE_SSL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ECTO_USE_SSL=$(ECTO_USE_SSL)' +endif +ifdef DATADOG_HOST + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATADOG_HOST=$(DATADOG_HOST)' +endif +ifdef DATADOG_PORT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DATADOG_PORT=$(DATADOG_PORT)' +endif +ifdef SPANDEX_BATCH_SIZE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SPANDEX_BATCH_SIZE=$(SPANDEX_BATCH_SIZE)' +endif +ifdef SPANDEX_SYNC_THRESHOLD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SPANDEX_SYNC_THRESHOLD=$(SPANDEX_SYNC_THRESHOLD)' +endif +ifdef HEART_BEAT_TIMEOUT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEART_BEAT_TIMEOUT=$(HEART_BEAT_TIMEOUT)' +endif +ifdef HEART_COMMAND + BLOCKSCOUT_CONTAINER_PARAMS += -e 'HEART_COMMAND=$(HEART_COMMAND)' +endif +ifdef BLOCKSCOUT_VERSION + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_VERSION=$(BLOCKSCOUT_VERSION)' +endif +ifdef RELEASE_LINK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RELEASE_LINK=$(RELEASE_LINK)' +endif +ifdef ELIXIR_VERSION + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ELIXIR_VERSION=$(ELIXIR_VERSION)' +endif +ifdef BLOCK_TRANSFORMER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCK_TRANSFORMER=$(BLOCK_TRANSFORMER)' +endif +ifdef GRAPHIQL_TRANSACTION + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GRAPHIQL_TRANSACTION=$(GRAPHIQL_TRANSACTION)' +endif +ifdef FIRST_BLOCK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FIRST_BLOCK=$(FIRST_BLOCK)' +endif +ifdef LAST_BLOCK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'LAST_BLOCK=$(LAST_BLOCK)' +endif +ifdef TRACE_FIRST_BLOCK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TRACE_FIRST_BLOCK=$(TRACE_FIRST_BLOCK)' +endif +ifdef TRACE_LAST_BLOCK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TRACE_LAST_BLOCK=$(TRACE_LAST_BLOCK)' +endif +ifdef TXS_COUNT_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_COUNT_CACHE_PERIOD=$(TXS_COUNT_CACHE_PERIOD)' +endif +ifdef ADDRESS_WITH_BALANCES_UPDATE_INTERVAL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_WITH_BALANCES_UPDATE_INTERVAL=$(ADDRESS_WITH_BALANCES_UPDATE_INTERVAL)' +endif +ifdef LINK_TO_OTHER_EXPLORERS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'LINK_TO_OTHER_EXPLORERS=$(LINK_TO_OTHER_EXPLORERS)' +endif +ifdef SUPPORTED_CHAINS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SUPPORTED_CHAINS=$(SUPPORTED_CHAINS)' +endif +ifdef BLOCK_COUNT_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCK_COUNT_CACHE_PERIOD=$(BLOCK_COUNT_CACHE_PERIOD)' +endif +ifdef ADDRESS_SUM_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_SUM_CACHE_PERIOD=$(ADDRESS_SUM_CACHE_PERIOD)' +endif +ifdef ADDRESS_COUNT_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_COUNT_CACHE_PERIOD=$(ADDRESS_COUNT_CACHE_PERIOD)' +endif +ifdef ALLOWED_EVM_VERSIONS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ALLOWED_EVM_VERSIONS=$(ALLOWED_EVM_VERSIONS)' +endif +ifdef UNCLES_IN_AVERAGE_BLOCK_TIME + BLOCKSCOUT_CONTAINER_PARAMS += -e 'UNCLES_IN_AVERAGE_BLOCK_TIME=$(UNCLES_IN_AVERAGE_BLOCK_TIME)' +endif +ifdef AVERAGE_BLOCK_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'AVERAGE_BLOCK_CACHE_PERIOD=$(AVERAGE_BLOCK_CACHE_PERIOD)' +endif +ifdef MARKET_HISTORY_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MARKET_HISTORY_CACHE_PERIOD=$(MARKET_HISTORY_CACHE_PERIOD)' +endif +ifdef DISABLE_WEBAPP + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_WEBAPP=$(DISABLE_WEBAPP)' +endif +ifdef DISABLE_READ_API + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_READ_API=$(DISABLE_READ_API)' +endif +ifdef DISABLE_WRITE_API + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_WRITE_API=$(DISABLE_WRITE_API)' +endif +ifdef DISABLE_INDEXER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_INDEXER=$(DISABLE_INDEXER)' +endif +ifdef WEBAPP_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'WEBAPP_URL=$(WEBAPP_URL)' +endif +ifdef API_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_URL=$(API_URL)' +endif +ifdef CHAIN_SPEC_PATH + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHAIN_SPEC_PATH=$(CHAIN_SPEC_PATH)' +endif +ifdef EMISSION_FORMAT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'EMISSION_FORMAT=$(EMISSION_FORMAT)' +endif +ifdef REWARDS_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'REWARDS_CONTRACT=$(REWARDS_CONTRACT)' +endif +ifdef SHOW_ADDRESS_MARKETCAP_PERCENTAGE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_ADDRESS_MARKETCAP_PERCENTAGE=$(SHOW_ADDRESS_MARKETCAP_PERCENTAGE)' +endif +ifdef BLOCKSCOUT_HOST + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_HOST=$(BLOCKSCOUT_HOST)' +endif +ifdef BLOCKSCOUT_PROTOCOL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BLOCKSCOUT_PROTOCOL=$(BLOCKSCOUT_PROTOCOL)' +endif +ifdef DECOMPILED_SMART_CONTRACT_TOKEN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DECOMPILED_SMART_CONTRACT_TOKEN=$(DECOMPILED_SMART_CONTRACT_TOKEN)' +endif +ifdef SOCKET_ROOT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOCKET_ROOT=$(SOCKET_ROOT)' +endif +ifdef API_PATH + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_PATH=$(API_PATH)' +endif +ifdef CHECKSUM_ADDRESS_HASHES + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_ADDRESS_HASHES=$(CHECKSUM_ADDRESS_HASHES)' +endif +ifdef CHECKSUM_FUNCTION + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHECKSUM_FUNCTION=$(CHECKSUM_FUNCTION)' +endif +ifdef COINGECKO_COIN_ID + BLOCKSCOUT_CONTAINER_PARAMS += -e 'COINGECKO_COIN_ID=$(COINGECKO_COIN_ID)' +endif +ifdef DISABLE_EXCHANGE_RATES + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_EXCHANGE_RATES=$(DISABLE_EXCHANGE_RATES)' +endif +ifdef SHOW_PRICE_CHART + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_PRICE_CHART=$(SHOW_PRICE_CHART)' +endif +ifdef SHOW_TXS_CHART + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TXS_CHART=$(SHOW_TXS_CHART)' +endif +ifdef HISTORY_FETCH_INTERVAL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'HISTORY_FETCH_INTERVAL=$(HISTORY_FETCH_INTERVAL)' +endif +ifdef TXS_HISTORIAN_INIT_LAG + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_HISTORIAN_INIT_LAG=$(TXS_HISTORIAN_INIT_LAG)' +endif +ifdef TXS_STATS_DAYS_TO_COMPILE_AT_INIT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TXS_STATS_DAYS_TO_COMPILE_AT_INIT=$(TXS_STATS_DAYS_TO_COMPILE_AT_INIT)' +endif +ifdef APPS_MENU + BLOCKSCOUT_CONTAINER_PARAMS += -e 'APPS_MENU=$(APPS_MENU)' +endif +ifdef EXTERNAL_APPS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'EXTERNAL_APPS=$(EXTERNAL_APPS)' +endif +ifdef GAS_PRICE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE=$(GAS_PRICE)' +endif +ifdef TOKEN_METADATA_UPDATE_INTERVAL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_METADATA_UPDATE_INTERVAL=$(TOKEN_METADATA_UPDATE_INTERVAL)' +endif +ifdef WOBSERVER_ENABLED + BLOCKSCOUT_CONTAINER_PARAMS += -e 'WOBSERVER_ENABLED=$(WOBSERVER_ENABLED)' +endif +ifdef OMNI_BRIDGE_MEDIATOR + BLOCKSCOUT_CONTAINER_PARAMS += -e 'OMNI_BRIDGE_MEDIATOR=$(OMNI_BRIDGE_MEDIATOR)' +endif +ifdef AMB_BRIDGE_MEDIATORS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'AMB_BRIDGE_MEDIATORS=$(AMB_BRIDGE_MEDIATORS)' +endif +ifdef FOREIGN_JSON_RPC + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FOREIGN_JSON_RPC=$(FOREIGN_JSON_RPC)' +endif +ifdef BRIDGE_MARKET_CAP_UPDATE_INTERVAL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'BRIDGE_MARKET_CAP_UPDATE_INTERVAL=$(BRIDGE_MARKET_CAP_UPDATE_INTERVAL)' +endif +ifdef RESTRICTED_LIST + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RESTRICTED_LIST=$(RESTRICTED_LIST)' +endif +ifdef RESTRICTED_LIST_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RESTRICTED_LIST_KEY=$(RESTRICTED_LIST_KEY)' +endif +ifdef ADDRESS_TRANSACTIONS_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_TRANSACTIONS_CACHE_PERIOD=$(ADDRESS_TRANSACTIONS_CACHE_PERIOD)' +endif +ifdef DISABLE_BRIDGE_MARKET_CAP_UPDATER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_BRIDGE_MARKET_CAP_UPDATER=$(DISABLE_BRIDGE_MARKET_CAP_UPDATER)' endif -run: start - -docker-login: ## login to DockerHub with credentials found in env - docker login -u ${DOCKER_USERNAME} -p ${DOCKER_PASSWORD} - -# Docker release - build, tag and push the container -pre-release: build publish ## Make a release by building and publishing the `{version}` ans `latest` tagged containers to hub -release: build publish-stable ## Make a release by building and publishing the `{version}` ans `latest` tagged containers to hub - -# Docker publish -publish: docker-login publish-latest publish-version ## publish the `{version}` ans `latest` tagged containers to hub -publish-stable: docker-login publish-latest publish-stable-version ## publish the `{version}` ans `latest` tagged containers to hub - -publish-latest: tag-latest ## publish the `latest` tagged container to hub - @echo 'publish latest to $(DOCKER_REPO)' - docker push $(BACKEND_CONTAINER_IMAGE):latest +ifdef ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_CACHE_PERIOD=$(ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_CACHE_PERIOD)' +endif +ifdef TOTAL_GAS_USAGE_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOTAL_GAS_USAGE_CACHE_PERIOD=$(TOTAL_GAS_USAGE_CACHE_PERIOD)' +endif +ifdef DISABLE_KNOWN_TOKENS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_KNOWN_TOKENS=$(DISABLE_KNOWN_TOKENS)' +endif +ifdef DISABLE_LP_TOKENS_IN_MARKET_CAP + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISABLE_LP_TOKENS_IN_MARKET_CAP=$(DISABLE_LP_TOKENS_IN_MARKET_CAP)' +endif +ifdef CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN=$(CUSTOM_CONTRACT_ADDRESSES_TEST_TOKEN)' +endif +ifdef ENABLE_FAUCET + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ENABLE_FAUCET=$(ENABLE_FAUCET)' +endif +ifdef FAUCET_ADDRESS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_ADDRESS=$(FAUCET_ADDRESS)' +endif +ifdef FAUCET_GAS_PRICE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_GAS_PRICE=$(FAUCET_GAS_PRICE)' +endif +ifdef FAUCET_VALUE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_VALUE=$(FAUCET_VALUE)' +endif +ifdef FAUCET_GAS_LIMIT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_GAS_LIMIT=$(FAUCET_GAS_LIMIT)' +endif +ifdef FAUCET_JSONRPC_HTTP_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_JSONRPC_HTTP_URL=$(FAUCET_JSONRPC_HTTP_URL)' +endif +ifdef FAUCET_COIN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_COIN=$(FAUCET_COIN)' +endif +ifdef FAUCET_H_CAPTCHA_CLIENT_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_H_CAPTCHA_CLIENT_KEY=$(FAUCET_H_CAPTCHA_CLIENT_KEY)' +endif +ifdef FAUCET_H_CAPTCHA_SECRET_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_H_CAPTCHA_SECRET_KEY=$(FAUCET_H_CAPTCHA_SECRET_KEY)' +endif +ifdef FAUCET_ADDRESS_PK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_ADDRESS_PK=$(FAUCET_ADDRESS_PK)' +endif +ifdef FAUCET_DONATE_VALUE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_DONATE_VALUE=$(FAUCET_DONATE_VALUE)' +endif +ifdef FAUCET_PHONE_NUMBER_SALT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'FAUCET_PHONE_NUMBER_SALT=$(FAUCET_PHONE_NUMBER_SALT)' +endif +ifdef TWILIO_ACCOUNT_SID + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TWILIO_ACCOUNT_SID=$(TWILIO_ACCOUNT_SID)' +endif +ifdef TWILIO_AUTH_TOKEN + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TWILIO_AUTH_TOKEN=$(TWILIO_AUTH_TOKEN)' +endif +ifdef TWILIO_FROM + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TWILIO_FROM=$(TWILIO_FROM)' +endif +ifdef TWILIO_PROHIBITED_CARRIERS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TWILIO_PROHIBITED_CARRIERS=$(TWILIO_PROHIBITED_CARRIERS)' +endif +ifdef GAS_TRACKER_ENABLED + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_TRACKER_ENABLED=$(GAS_TRACKER_ENABLED)' +endif +ifdef GAS_TRACKER_ENABLED_IN_MENU + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_TRACKER_ENABLED_IN_MENU=$(GAS_TRACKER_ENABLED_IN_MENU)' +endif +ifdef GAS_TRACKER_ACCESS_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_TRACKER_ACCESS_KEY=$(GAS_TRACKER_ACCESS_KEY)' +endif +ifdef GAS_PRICE_ORACLE_NUM_OF_BLOCKS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_NUM_OF_BLOCKS=$(GAS_PRICE_ORACLE_NUM_OF_BLOCKS)' +endif +ifdef GAS_PRICE_ORACLE_SAFELOW_PERCENTILE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_SAFELOW_PERCENTILE=$(GAS_PRICE_ORACLE_SAFELOW_PERCENTILE)' +endif +ifdef GAS_PRICE_ORACLE_AVERAGE_PERCENTILE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_AVERAGE_PERCENTILE=$(GAS_PRICE_ORACLE_AVERAGE_PERCENTILE)' +endif +ifdef GAS_PRICE_ORACLE_FAST_PERCENTILE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_FAST_PERCENTILE=$(GAS_PRICE_ORACLE_FAST_PERCENTILE)' +endif +ifdef GAS_PRICE_ORACLE_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'GAS_PRICE_ORACLE_CACHE_PERIOD=$(GAS_PRICE_ORACLE_CACHE_PERIOD)' +endif +ifdef HIDE_BLOCK_MINER + BLOCKSCOUT_CONTAINER_PARAMS += -e 'HIDE_BLOCK_MINER=$(HIDE_BLOCK_MINER)' +endif +ifdef COIN_BALANCE_HISTORY_DAYS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_BALANCE_HISTORY_DAYS=$(COIN_BALANCE_HISTORY_DAYS)' +endif +ifdef POS_STAKING_CONTRACT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'POS_STAKING_CONTRACT=$(POS_STAKING_CONTRACT)' +endif +ifdef ENABLE_POS_STAKING_IN_MENU + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ENABLE_POS_STAKING_IN_MENU=$(ENABLE_POS_STAKING_IN_MENU)' +endif +ifdef TOKEN_EXCHANGE_RATE_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TOKEN_EXCHANGE_RATE_CACHE_PERIOD=$(TOKEN_EXCHANGE_RATE_CACHE_PERIOD)' +endif +ifdef ADDRESS_TOKENS_USD_SUM_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_TOKENS_USD_SUM_CACHE_PERIOD=$(ADDRESS_TOKENS_USD_SUM_CACHE_PERIOD)' +endif +ifdef SHOW_MAINTENANCE_ALERT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_MAINTENANCE_ALERT=$(SHOW_MAINTENANCE_ALERT)' +endif +ifdef MAINTENANCE_ALERT_MESSAGE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MAINTENANCE_ALERT_MESSAGE=$(MAINTENANCE_ALERT_MESSAGE)' +endif +ifdef SHOW_STAKING_WARNING + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_STAKING_WARNING=$(SHOW_STAKING_WARNING)' +endif +ifdef STAKING_WARNING_MESSAGE + BLOCKSCOUT_CONTAINER_PARAMS += -e 'STAKING_WARNING_MESSAGE=$(STAKING_WARNING_MESSAGE)' +endif +ifdef ENABLE_SOURCIFY_INTEGRATION + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ENABLE_SOURCIFY_INTEGRATION=$(ENABLE_SOURCIFY_INTEGRATION)' +endif +ifdef SOURCIFY_SERVER_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCIFY_SERVER_URL=$(SOURCIFY_SERVER_URL)' +endif +ifdef SOURCIFY_REPO_URL + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SOURCIFY_REPO_URL=$(SOURCIFY_REPO_URL)' +endif +ifdef CHAIN_ID + BLOCKSCOUT_CONTAINER_PARAMS += -e 'CHAIN_ID=$(CHAIN_ID)' +endif +ifdef JSON_RPC + BLOCKSCOUT_CONTAINER_PARAMS += -e 'JSON_RPC=$(JSON_RPC)' +endif +ifdef MAX_SIZE_UNLESS_HIDE_ARRAY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MAX_SIZE_UNLESS_HIDE_ARRAY=$(MAX_SIZE_UNLESS_HIDE_ARRAY)' +endif +ifdef DISPLAY_TOKEN_ICONS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'DISPLAY_TOKEN_ICONS=$(DISPLAY_TOKEN_ICONS)' +endif +ifdef SHOW_TENDERLY_LINK + BLOCKSCOUT_CONTAINER_PARAMS += -e 'SHOW_TENDERLY_LINK=$(SHOW_TENDERLY_LINK)' +endif +ifdef TENDERLY_CHAIN_PATH + BLOCKSCOUT_CONTAINER_PARAMS += -e 'TENDERLY_CHAIN_PATH=$(TENDERLY_CHAIN_PATH)' +endif +ifdef MAX_STRING_LENGTH_WITHOUT_TRIMMING + BLOCKSCOUT_CONTAINER_PARAMS += -e 'MAX_STRING_LENGTH_WITHOUT_TRIMMING=$(MAX_STRING_LENGTH_WITHOUT_TRIMMING)' +endif +ifdef RE_CAPTCHA_SECRET_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_SECRET_KEY=$(RE_CAPTCHA_SECRET_KEY)' +endif +ifdef RE_CAPTCHA_CLIENT_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_CLIENT_KEY=$(RE_CAPTCHA_CLIENT_KEY)' +endif +ifdef ADDRESS_TOKEN_TRANSFERS_COUNTER_CACHE_PERIOD + BLOCKSCOUT_CONTAINER_PARAMS += -e 'ADDRESS_TOKEN_TRANSFERS_COUNTER_CACHE_PERIOD=$(ADDRESS_TOKEN_TRANSFERS_COUNTER_CACHE_PERIOD)' +endif +ifdef API_RATE_LIMIT + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT=$(API_RATE_LIMIT)' +endif +ifdef API_RATE_LIMIT_BY_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_KEY=$(API_RATE_LIMIT_BY_KEY)' +endif +ifdef API_RATE_LIMIT_BY_IP + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_BY_IP=$(API_RATE_LIMIT_BY_IP)' +endif +ifdef API_RATE_LIMIT_STATIC_API_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_STATIC_API_KEY=$(API_RATE_LIMIT_STATIC_API_KEY)' +endif +ifdef API_RATE_LIMIT_WHITELISTED_IPS + BLOCKSCOUT_CONTAINER_PARAMS += -e 'API_RATE_LIMIT_WHITELISTED_IPS=$(API_RATE_LIMIT_WHITELISTED_IPS)' +endif +ifdef COIN_NAME + BLOCKSCOUT_CONTAINER_PARAMS += -e 'COIN_NAME=$(COIN_NAME)' +endif -publish-version: tag-version ## publish the `{version}` tagged container to hub - @echo 'publish $(TAG) to $(DOCKER_REPO)' - docker push $(BACKEND_CONTAINER_IMAGE):$(TAG) +HAS_BLOCKSCOUT_IMAGE := $(shell docker images | grep -sw ${DOCKER_IMAGE}) +build: + @echo "==> Checking for blockscout image $(DOCKER_IMAGE)" +ifdef HAS_BLOCKSCOUT_IMAGE + @echo "==> Image exist. Using $(DOCKER_IMAGE)" +else + @echo "==> No image found trying to build one..." + @docker build --build-arg COIN="$(COIN)" -f ./Dockerfile -t $(DOCKER_IMAGE) ../ +endif -publish-stable-version: tag-stable-version ## publish the `{version}` tagged container to hub - @echo 'publish $(STABLE_TAG) to $(DOCKER_REPO)' - docker push $(BACKEND_CONTAINER_IMAGE):$(STABLE_TAG) +migrate_only: + @echo "==> Running migrations" + @docker run --rm \ + $(BLOCKSCOUT_CONTAINER_PARAMS) \ + $(DOCKER_IMAGE) /bin/sh -c "echo $$MIX_ENV && mix do ecto.create, ecto.migrate" -# Docker tagging -tag: tag-latest tag-version ## Generate container tags for the `{version}` ans `latest` tags -tag-stable: tag-latest tag-stable-version ## Generate container tags for the `{version}` ans `latest` tags +migrate: + @$(MAKE) -f $(THIS_FILE) migrate_only -tag-latest: ## Generate container `latest` tag - @echo 'create latest tag' - docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):latest -tag-version: ## Generate container `{version}` tag - @echo 'create tag $(TAG)' - docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):$(TAG) +start: + @$(MAKE) -f $(THIS_FILE) build + @$(MAKE) -f $(THIS_FILE) migrate + @echo "==> Starting blockscout" + @docker run --rm --name $(BS_CONTAINER_NAME) \ + $(BLOCKSCOUT_CONTAINER_PARAMS) \ + -p 4000:4000 \ + $(DOCKER_IMAGE) /bin/sh -c "mix do ecto.create, ecto.migrate; mix phx.server" -tag-stable-version: ## Generate container `{version}` tag - @echo 'create tag $(STABLE_TAG)' - docker tag $(BACKEND_CONTAINER_IMAGE) $(BACKEND_CONTAINER_IMAGE):$(STABLE_TAG) +run: start .PHONY: build \ + migrate \ start \ - stop \ - run \ - docker-login \ - release \ - publish \ - publish-latest \ - publish-version \ - tag \ - tag-latest \ - tag-version + run \ No newline at end of file diff --git a/mix.exs b/mix.exs index 06f570b70e79..218bf7fdf1a1 100644 --- a/mix.exs +++ b/mix.exs @@ -5,18 +5,18 @@ defmodule BlockScout.Mixfile do def project do [ - # app: :block_scout, - # aliases: aliases(config_env()), - version: "6.0.0", + app: :block_scout, + aliases: aliases(Mix.env()), + version: "2.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), - elixir: "~> 1.13", + elixir: "~> 1.12", preferred_cli_env: [ credo: :test, dialyzer: :test ], - # start_permanent: config_env() == :prod, + start_permanent: Mix.env() == :prod, releases: [ blockscout: [ applications: [ @@ -24,9 +24,7 @@ defmodule BlockScout.Mixfile do ethereum_jsonrpc: :permanent, explorer: :permanent, indexer: :permanent - ], - steps: [:assemble, ©_prod_runtime_config/1], - validate_compile_env: false + ] ] ] ] @@ -34,22 +32,6 @@ defmodule BlockScout.Mixfile do ## Private Functions - defp copy_prod_runtime_config(%Mix.Release{path: path} = release) do - File.mkdir_p!(Path.join([path, "config", "runtime"])) - File.cp!(Path.join(["config", "runtime", "prod.exs"]), Path.join([path, "config", "runtime", "prod.exs"])) - File.mkdir_p!(Path.join([path, "apps", "explorer", "config", "prod"])) - - File.cp_r!( - Path.join(["apps", "explorer", "config", "prod"]), - Path.join([path, "apps", "explorer", "config", "prod"]) - ) - - File.mkdir_p!(Path.join([path, "apps", "indexer", "config", "prod"])) - File.cp_r!(Path.join(["apps", "indexer", "config", "prod"]), Path.join([path, "apps", "indexer", "config", "prod"])) - - release - end - defp dialyzer() do [ plt_add_deps: :transitive, @@ -60,23 +42,23 @@ defmodule BlockScout.Mixfile do ] end - # defp aliases(env) do - # [ - # # to match behavior of `mix test` in `apps/indexer`, which needs to not start applications for `indexer` to - # # prevent its supervision tree from starting, which is undesirable in test - # test: "test --no-start" - # ] ++ env_aliases(env) - # end + defp aliases(env) do + [ + # to match behavior of `mix test` in `apps/indexer`, which needs to not start applications for `indexer` to + # prevent its supervision tree from starting, which is undesirable in test + test: "test --no-start" + ] ++ env_aliases(env) + end - # defp env_aliases(:dev) do - # [] - # end + defp env_aliases(:dev) do + [] + end - # defp env_aliases(_env) do - # [ - # compile: "compile --warnings-as-errors" - # ] - # end + defp env_aliases(_env) do + [ + compile: "compile --warnings-as-errors" + ] + end # Dependencies can be Hex packages: # @@ -92,11 +74,11 @@ defmodule BlockScout.Mixfile do # and cannot be accessed from applications inside the apps folder defp deps do [ - {:prometheus_ex, git: "https://github.com/lanodan/prometheus.ex", branch: "fix/elixir-1.14", override: true}, + {:con_cache, "~> 1.0"}, {:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true}, - {:tesla, "~> 1.8.0"}, + {:tesla, "~> 1.3.3"}, # Documentation - {:ex_doc, "~> 0.31.0", only: :dev, runtime: false}, + {:ex_doc, "~> 0.25.2", only: :dev, runtime: false}, {:number, "~> 1.0.3"} ] end