merge master

pull/2576/head
saneery 5 years ago
commit 041a8093a2
  1. 2
      .dialyzer-ignore
  2. 11
      CHANGELOG.md
  3. 2
      apps/block_scout_web/.sobelow-conf
  4. 14
      apps/block_scout_web/assets/css/_images-preload.scss
  5. 1
      apps/block_scout_web/assets/css/app.scss
  6. 31
      apps/block_scout_web/assets/css/components/_network-selector.scss
  7. 2
      apps/block_scout_web/assets/css/non-critical.scss
  8. 1
      apps/block_scout_web/assets/css/theme/_dai_variables-non-critical.scss
  9. 1
      apps/block_scout_web/assets/css/theme/_ether1_variables-non-critical.scss
  10. 1
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables-non-critical.scss
  11. 1
      apps/block_scout_web/assets/css/theme/_ethereum_variables-non-critical.scss
  12. 1
      apps/block_scout_web/assets/css/theme/_goerli_variables-non-critical.scss
  13. 1
      apps/block_scout_web/assets/css/theme/_kovan_variables-non-critical.scss
  14. 1
      apps/block_scout_web/assets/css/theme/_lukso_variables-non-critical.scss
  15. 1
      apps/block_scout_web/assets/css/theme/_neutral_variables-non-critical.scss
  16. 1
      apps/block_scout_web/assets/css/theme/_poa_variables-non-critical.scss
  17. 1
      apps/block_scout_web/assets/css/theme/_rinkeby_variables-non-critical.scss
  18. 1
      apps/block_scout_web/assets/css/theme/_ropsten_variables-non-critical.scss
  19. 1
      apps/block_scout_web/assets/css/theme/_rsk_variables-non-critical.scss
  20. 1
      apps/block_scout_web/assets/css/theme/_sokol_variables-non-critical.scss
  21. 22
      apps/block_scout_web/assets/css/theme/_variables-non-critical.scss
  22. 11
      apps/block_scout_web/assets/js/lib/async_listing_load.js
  23. 10
      apps/block_scout_web/assets/js/lib/coin_balance_history_chart.js
  24. 31
      apps/block_scout_web/assets/js/lib/token_transfers_toggle.js
  25. 2
      apps/block_scout_web/config/config.exs
  26. 3
      apps/block_scout_web/config/test.exs
  27. 1
      apps/block_scout_web/lib/block_scout_web/application.ex
  28. 11
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  29. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_network_selector_item.html.eex
  30. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  31. 3
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  32. 3
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  33. 250
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  34. 4
      apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
  35. 12
      apps/block_scout_web/test/block_scout_web/features/address_contract_verification_test.exs
  36. 4
      apps/block_scout_web/test/block_scout_web/features/pages/contract_verify_page.ex
  37. 16
      apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
  38. 4
      apps/block_scout_web/test/support/conn_case.ex
  39. 4
      apps/block_scout_web/test/support/feature_case.ex
  40. 2
      apps/block_scout_web/test/test_helper.exs
  41. 2
      apps/explorer/config/config.exs
  42. 6
      apps/explorer/lib/explorer/application.ex
  43. 25
      apps/explorer/lib/explorer/chain.ex
  44. 22
      apps/explorer/lib/explorer/chain/address/coin_balance.ex
  45. 5
      apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
  46. 8
      apps/explorer/lib/explorer/chain/block/emission_reward.ex
  47. 2
      apps/explorer/lib/explorer/chain/block/range.ex
  48. 90
      apps/explorer/lib/explorer/chain/cache/blocks.ex
  49. 153
      apps/explorer/lib/explorer/chain/cache/transactions.ex
  50. 326
      apps/explorer/lib/explorer/chain/ordered_cache.ex
  51. 2
      apps/explorer/lib/explorer/chain/token_transfer.ex
  52. 108
      apps/explorer/lib/explorer/chain_spec/genesis_data.ex
  53. 107
      apps/explorer/lib/explorer/chain_spec/parity/importer.ex
  54. 3
      apps/explorer/lib/explorer/etherscan.ex
  55. 32
      apps/explorer/test/explorer/chain/address/coin_balance_test.exs
  56. 37
      apps/explorer/test/explorer/chain/cache/blocks_test.exs
  57. 108
      apps/explorer/test/explorer/chain_spec/parity/importer_test.exs
  58. 4
      apps/explorer/test/explorer/chain_test.exs
  59. 6
      apps/explorer/test/explorer/etherscan_test.exs
  60. 4
      apps/explorer/test/explorer/market/market_history_cache_test.exs
  61. 8
      apps/explorer/test/explorer/market/market_test.exs
  62. 8
      apps/explorer/test/support/data_case.ex
  63. 1533
      apps/explorer/test/support/fixture/chain_spec/foundation.json
  64. 2
      apps/indexer/lib/indexer/block/fetcher.ex
  65. 1
      docs/env-variables.md
  66. 105
      mix.lock

@ -11,3 +11,5 @@ apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System'
apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
lib/block_scout_web/router.ex:1
lib/phoenix/router.ex:324

@ -1,10 +1,21 @@
## Current
### Features
- [#2561](https://github.com/poanetwork/blockscout/pull/2561) - Add token's type to the response of tokenlist method
- [#2499](https://github.com/poanetwork/blockscout/pull/2499) - import emission reward ranges
- [#2497](https://github.com/poanetwork/blockscout/pull/2497) - Add generic Ordered Cache behaviour and implementation
### Fixes
- [#2572](https://github.com/poanetwork/blockscout/pull/2572) - Ease non-critical css
- [#2570](https://github.com/poanetwork/blockscout/pull/2570) - Network icons preload
- [#2569](https://github.com/poanetwork/blockscout/pull/2569) - do not fetch emission rewards for transactions csv exporter
- [#2568](https://github.com/poanetwork/blockscout/pull/2568) - filter pending token transfers
- [#2564](https://github.com/poanetwork/blockscout/pull/2564) - fix first page button for uncles and reorgs
- [#2563](https://github.com/poanetwork/blockscout/pull/2563) - Fix view less transfers button
- [#2538](https://github.com/poanetwork/blockscout/pull/2538) - fetch the last not empty coin balance records
### Chore
- [#2566](https://github.com/poanetwork/blockscout/pull/2566) - upgrade absinthe phoenix
## 2.0.3-beta

@ -5,5 +5,5 @@
router: "lib/block_scout_web/router.ex",
exit: "low",
format: "compact",
ignore: ["Config.Headers"]
ignore: ["Config.Headers", "Config.CSWH"]
]

@ -0,0 +1,14 @@
body:after {
position:absolute; width:0; height:0; overflow:hidden; z-index:-1;
content:
url(/images/network-selector-icons/callisto-mainnet.png)
url(/images/network-selector-icons/ethereum-classic.png)
url(/images/network-selector-icons/goerli-testnet.png)
url(/images/network-selector-icons/kovan-testnet.png)
url(/images/network-selector-icons/poa-core.png)
url(/images/network-selector-icons/poa-sokol.png)
url(/images/network-selector-icons/rinkeby-testnet.png)
url(/images/network-selector-icons/rsk-mainnet.png)
url(/images/network-selector-icons/ropsten-testnet.png)
url(/images/network-selector-icons/xdai-chain.png)
};

@ -60,6 +60,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
// Custom SCSS
@import "layout";
@import "typography";
@import "images-preload";
@import "code";
@import "helpers";
@import "elements";

@ -243,6 +243,37 @@ $network-selector-item-icon-dimensions: 30px !default;
height: $network-selector-item-icon-dimensions;
margin: 0 15px 0 0;
width: $network-selector-item-icon-dimensions;
&-callisto-mainnet {
background-image: url(/images/network-selector-icons/callisto-mainnet.png)
}
&-ethereum-classic {
background-image: url(/images/network-selector-icons/ethereum-classic.png)
}
&-goerli-testnet {
background-image: url(/images/network-selector-icons/goerli-testnet.png)
}
&-kovan-testnet {
background-image: url(/images/network-selector-icons/kovan-testnet.png)
}
&-poa-core {
background-image: url(/images/network-selector-icons/poa-core.png)
}
&-poa-sokol {
background-image: url(/images/network-selector-icons/poa-sokol.png)
}
&-rinkeby-testnet {
background-image: url(/images/network-selector-icons/rinkeby-testnet.png)
}
&-rsk-mainnet {
background-image: url(/images/network-selector-icons/rsk-mainnet.png)
}
&-ropsten-testnet {
background-image: url(/images/network-selector-icons/ropsten-testnet.png)
}
&-xdai-chain {
background-image: url(/images/network-selector-icons/xdai-chain.png)
}
}
.network-selector-item-title {

@ -2,7 +2,7 @@
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/mixins";
@import "theme/variables";
@import "theme/variables-non-critical";
@import "node_modules/bootstrap/scss/modal";
@import "node_modules/bootstrap/scss/tooltip";

@ -0,0 +1 @@
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: #4b021e; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $tertiary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $tertiary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $primary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $primary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $primary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $secondary; // button border and font color && hover bg color

@ -0,0 +1 @@
$btn-line-color: $sub-accent-color; // button border and font color && hover bg color

@ -0,0 +1,22 @@
@import "theme/base_variables";
@import "neutral_variables-non-critical";
// @import "dai_variables-non-critical";
// @import "ethereum_classic_variables-non-critical";
// @import "ethereum_variables-non-critical";
// @import "ether1_variables-non-critical";
// @import "expanse_variables-non-critical";
// @import "gochain_variables-non-critical";
// @import "goerli_variables-non-critical";
// @import "kovan_variables-non-critical";
// @import "lukso_variables-non-critical";
// @import "musicoin_variables-non-critical";
// @import "pirl_variables-non-critical";
// @import "poa_variables-non-critical";
// @import "posdao_variables-non-critical";
// @import "rinkeby_variables-non-critical";
// @import "ropsten_variables-non-critical";
// @import "social_variables-non-critical";
// @import "sokol_variables-non-critical";
// @import "tobalaba_variables-non-critical";
// @import "tomochain_variables-non-critical";
// @import "rsk_variables-non-critical";

@ -205,7 +205,16 @@ export const elements = {
}
$el.show()
$el.attr('disabled', false)
$el.attr('href', window.location.href.split('?')[0])
const urlParams = new URLSearchParams(window.location.search)
const blockParam = urlParams.get('block_type')
const firstPageHref = window.location.href.split('?')[0]
if (blockParam !== null) {
$el.attr('href', firstPageHref + '?block_type=' + blockParam)
} else {
$el.attr('href', firstPageHref)
}
}
},
'[data-async-listing] [data-page-number]': {

@ -18,6 +18,14 @@ export function createCoinBalanceHistoryChart (el) {
y: balance.value
}))
var stepSize = 3
if (data.length > 1) {
var diff = Math.abs(new Date(data[data.length - 1].date) - new Date(data[data.length - 2].date))
var periodInDays = diff / (1000 * 60 * 60 * 24)
stepSize = periodInDays
}
return new Chart(el, {
type: 'line',
data: {
@ -36,7 +44,7 @@ export function createCoinBalanceHistoryChart (el) {
type: 'time',
time: {
unit: 'day',
stepSize: 3
stepSize: stepSize
}
}],
yAxes: [{

@ -1,20 +1,17 @@
import $ from 'jquery'
const tokenTransferToggle = (element) => {
const $element = $(element)
const $tokenTransferClose = $element.find("[data-selector='token-transfer-close']")
const $tokenTransferOpen = $element.find("[data-selector='token-transfer-open']")
$(document.body).on('click', '[data-selector="token-transfer-open"]', event => {
const $tokenTransferOpen = event.target
const $parent = $tokenTransferOpen.parentElement
const $tokenTransferClose = $parent.querySelector("[data-selector='token-transfer-close']")
$tokenTransferOpen.classList.add('d-none')
$tokenTransferClose.classList.remove('d-none')
})
$element.on('show.bs.collapse', () => {
$tokenTransferOpen.addClass('d-none')
$tokenTransferClose.removeClass('d-none')
})
$element.on('hide.bs.collapse', () => {
$tokenTransferClose.addClass('d-none')
$tokenTransferOpen.removeClass('d-none')
})
}
// Initialize the script scoped for each card.
$("[data-selector='token-transfers-toggle']").each((_index, element) => tokenTransferToggle(element))
$(document.body).on('click', '[data-selector="token-transfer-close"]', event => {
const $tokenTransferClose = event.target
const $parent = $tokenTransferClose.parentElement
const $tokenTransferOpen = $parent.querySelector("[data-selector='token-transfer-open']")
$tokenTransferClose.classList.add('d-none')
$tokenTransferOpen.classList.remove('d-none')
})

@ -42,7 +42,7 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
path: System.get_env("NETWORK_PATH") || "/"
],
render_errors: [view: BlockScoutWeb.ErrorView, accepts: ~w(html json)],
pubsub: [name: BlockScoutWeb.PubSub, adapter: Phoenix.PubSub.PG2]
pubsub: [name: BlockScoutWeb.PubSub]
config :block_scout_web, BlockScoutWeb.Tracer,
service: :block_scout_web,

@ -7,7 +7,8 @@ config :block_scout_web, :sql_sandbox, true
config :block_scout_web, BlockScoutWeb.Endpoint,
http: [port: 4002],
secret_key_base: "27Swe6KtEtmN37WyEYRjKWyxYULNtrxlkCEKur4qoV+Lwtk8lafsR16ifz1XBBYj",
server: true
server: true,
pubsub: [name: BlockScoutWeb.PubSub]
config :block_scout_web, BlockScoutWeb.Tracer, disabled?: false

@ -18,6 +18,7 @@ defmodule BlockScoutWeb.Application do
# Define workers and child supervisors to be supervised
children = [
# Start the endpoint when the application starts
{Phoenix.PubSub.PG2, name: BlockScoutWeb.PubSub, fastlane: Phoenix.Channel.Server},
supervisor(Endpoint, []),
supervisor(Absinthe.Subscription, [Endpoint]),
{RealtimeEventHandler, name: RealtimeEventHandler},

@ -163,7 +163,16 @@ defmodule BlockScoutWeb.Etherscan do
"contractAddress" => "0x0000000000000000000000000000000000000000",
"name" => "Example Token",
"decimals" => "18",
"symbol" => "ET"
"symbol" => "ET",
"type" => "ERC-20"
},
%{
"balance" => "1",
"contractAddress" => "0x0000000000000000000000000000000000000001",
"name" => "Example ERC-721 Token",
"decimals" => "18",
"symbol" => "ET7",
"type" => "ERC-721"
}
]
}

@ -5,7 +5,7 @@
<span class="radio-icon"></span>
</span>
<span class="network-selector-item-content">
<span class='network-selector-item-icon network-selector-item-icon-<%= String.downcase(String.replace(@title, " ", "-")) %>' style="background-image: url('/images/network-selector-icons/<%= String.downcase(String.replace(@title, " ", "-")) %>.png');"></span>
<span class='network-selector-item-icon network-selector-item-icon-<%= String.downcase(String.replace(@title, " ", "-")) %>'></span>
<span class="network-selector-item-title">
<%= @title %>
</span>

@ -37,7 +37,7 @@
<span class="mr-2 mr-md-0 order-1">
<%= link(
gettext("Block #%{number}", number: @transfer.block_number),
to: block_path(BlockScoutWeb.Endpoint, :show, @transfer.transaction.block_number)
to: block_path(BlockScoutWeb.Endpoint, :show, @transfer.block_number)
) %>
</span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= @transfer.transaction.block.timestamp %>"></span>

@ -168,7 +168,8 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
"contractAddress" => to_string(token.contract_address_hash),
"name" => token.name,
"decimals" => to_string(token.decimals),
"symbol" => token.symbol
"symbol" => token.symbol,
"type" => token.type
}
end

@ -2079,7 +2079,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do
"contractAddress" => to_string(token_balance.token_contract_address_hash),
"name" => token_balance.token.name,
"decimals" => to_string(token_balance.token.decimals),
"symbol" => token_balance.token.symbol
"symbol" => token_balance.token.symbol,
"type" => token_balance.token.type
}
]

@ -11,9 +11,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
schema = resolve_schema()
assert ExJsonSchema.Validator.valid?(schema, response)
@ -31,9 +31,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
schema = resolve_schema()
assert ExJsonSchema.Validator.valid?(schema, response)
@ -50,17 +50,18 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"txhash" => "0x40eb908387324f2b575b4879cd9d7188f69c8fc9d87c901b9e2daaea4b442170"
}
schema = resolve_schema(%{
"type" => "object",
"properties" => %{
"status" => %{"type" => "string"}
}
})
schema =
resolve_schema(%{
"type" => "object",
"properties" => %{
"status" => %{"type" => "string"}
}
})
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert ExJsonSchema.Validator.valid?(schema, response)
assert response["result"] == %{"status" => ""}
@ -83,9 +84,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == %{"status" => "1"}
assert response["status"] == "1"
@ -107,9 +108,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == %{"status" => "0"}
assert response["status"] == "1"
@ -126,9 +127,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == %{"status" => ""}
assert response["status"] == "1"
@ -144,9 +145,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
schema = resolve_schema()
assert ExJsonSchema.Validator.valid?(schema, response)
@ -164,9 +165,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
schema = resolve_schema()
assert ExJsonSchema.Validator.valid?(schema, response)
@ -188,18 +189,19 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"errDescription" => ""
}
schema = resolve_schema(%{
"type" => "object",
"properties" => %{
"isError" => %{"type" => "string"},
"errDescription" => %{"type" => "string"}
}
})
schema =
resolve_schema(%{
"type" => "object",
"properties" => %{
"isError" => %{"type" => "string"},
"errDescription" => %{"type" => "string"}
}
})
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert ExJsonSchema.Validator.valid?(schema, response)
assert response["result"] == expected_result
@ -227,9 +229,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
@ -304,9 +306,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
@ -328,9 +330,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["result"] == expected_result
assert response["status"] == "1"
@ -346,9 +348,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
schema = resolve_schema()
assert ExJsonSchema.Validator.valid?(schema, response)
@ -366,9 +368,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "Invalid txhash format"
assert response["status"] == "0"
@ -384,9 +386,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
}
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert response["message"] =~ "Transaction not found"
assert response["status"] == "0"
@ -394,7 +396,6 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
refute response["result"]
end
@tag :wip
test "paginates logs", %{conn: conn} do
block = insert(:block, hash: "0x30d522bcf2d8e0cabc286e6e40623c475c3bc05d0ec484ea239c103b1ac0ded9", number: 99)
@ -420,29 +421,30 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"txhash" => "#{transaction.hash}"
}
schema = resolve_schema(%{
"type" => "object",
"properties" => %{
"next_page_params" => %{
"type" => ["object", "null"],
"properties" => %{
"action" => %{"type" => "string"},
"index" => %{"type" => "number"},
"module" => %{"type" => "string"},
"txhash" => %{"type" => "string"}
schema =
resolve_schema(%{
"type" => "object",
"properties" => %{
"next_page_params" => %{
"type" => ["object", "null"],
"properties" => %{
"action" => %{"type" => "string"},
"index" => %{"type" => "number"},
"module" => %{"type" => "string"},
"txhash" => %{"type" => "string"}
}
},
"logs" => %{
"type" => "array",
"items" => %{"type" => "object"}
}
},
"logs" => %{
"type" => "array",
"items" => %{"type" => "object"}
}
}
})
})
assert response1 =
conn
|> get("/api", params1)
|> json_response(200)
conn
|> get("/api", params1)
|> json_response(200)
assert ExJsonSchema.Validator.valid?(schema, response1)
assert response1["status"] == "1"
@ -458,9 +460,9 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
params2 = response1["result"]["next_page_params"]
assert response2 =
conn
|> get("/api", params2)
|> json_response(200)
conn
|> get("/api", params2)
|> json_response(200)
assert ExJsonSchema.Validator.valid?(schema, response2)
assert response2["status"] == "1"
@ -469,7 +471,6 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
assert response1["result"]["logs"] != response2["result"]["logs"]
end
@tag :wip
test "with a txhash with ok status", %{conn: conn} do
block = insert(:block)
@ -518,54 +519,55 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do
"next_page_params" => nil
}
schema = resolve_schema(%{
"type" => "object",
"properties" => %{
"hash" => %{"type" => "string"},
"timeStamp" => %{"type" => "string"},
"blockNumber" => %{"type" => "string"},
"confirmations" => %{"type" => "string"},
"success" => %{"type" => "boolean"},
"from" => %{"type" => "string"},
"to" => %{"type" => "string"},
"value" => %{"type" => "string"},
"input" => %{"type" => "string"},
"gasLimit" => %{"type" => "string"},
"gasUsed" => %{"type" => "string"},
"gasPrice" => %{"type" => "string"},
"logs" => %{
"type" => "array",
"items" => %{
"type" => "object",
schema =
resolve_schema(%{
"type" => "object",
"properties" => %{
"hash" => %{"type" => "string"},
"timeStamp" => %{"type" => "string"},
"blockNumber" => %{"type" => "string"},
"confirmations" => %{"type" => "string"},
"success" => %{"type" => "boolean"},
"from" => %{"type" => "string"},
"to" => %{"type" => "string"},
"value" => %{"type" => "string"},
"input" => %{"type" => "string"},
"gasLimit" => %{"type" => "string"},
"gasUsed" => %{"type" => "string"},
"gasPrice" => %{"type" => "string"},
"logs" => %{
"type" => "array",
"items" => %{
"type" => "object",
"properties" => %{
"address" => %{"type" => "string"},
"data" => %{"type" => "string"},
"topics" => %{
"type" => "array",
"items" => %{"type" => ["string", "null"]}
},
"index" => %{"type" => "string"}
}
}
},
"next_page_params" => %{
"type" => ["object", "null"],
"properties" => %{
"address" => %{"type" => "string"},
"data" => %{"type" => "string"},
"topics" => %{
"type" => "array",
"items" => %{"type" => ["string", "null"]}
},
"index" => %{"type" => "string"}
"action" => %{"type" => "string"},
"index" => %{"type" => "number"},
"module" => %{"type" => "string"},
"txhash" => %{"type" => "string"}
}
}
},
"next_page_params" => %{
"type" => ["object", "null"],
"properties" => %{
"action" => %{"type" => "string"},
"index" => %{"type" => "number"},
"module" => %{"type" => "string"},
"txhash" => %{"type" => "string"}
}
},
}
})
}
})
assert response =
conn
|> get("/api", params)
|> json_response(200)
conn
|> get("/api", params)
|> json_response(200)
assert :ok = ExJsonSchema.Validator.validate(schema, response)
assert ExJsonSchema.Validator.valid?(schema, response)
assert response["result"] == expected_result
assert response["status"] == "1"
assert response["message"] == "OK"

@ -4,8 +4,8 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do
alias Explorer.{Chain, PagingOptions}
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
:ok
end

@ -49,17 +49,19 @@ defmodule BlockScoutWeb.AddressContractVerificationTest do
test "with invalid data shows error messages", %{session: session, bypass: bypass} do
Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, solc_bin_versions()) end)
address = insert(:address)
session
|> ContractVerifyPage.visit_page("0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461")
|> ContractVerifyPage.visit_page(address)
|> ContractVerifyPage.fill_form(%{
contract_name: "",
version: nil,
optimization: nil,
contract_name: "name",
version: "default",
optimization: "true",
source_code: "",
evm_version: "byzantium"
})
|> ContractVerifyPage.verify_and_publish()
|> assert_has(ContractVerifyPage.validation_error())
|> ContractVerifyPage.has_message?("There was an error validating your contract, please try again.")
end
defp solc_bin_versions do

@ -48,6 +48,10 @@ defmodule BlockScoutWeb.ContractVerifyPage do
css("[data-test='contract-source-code-error']")
end
def has_message?(session, message) do
String.contains?(page_source(session), message)
end
def verify_and_publish(session) do
click(session, button("Verify & publish"))
end

@ -30,6 +30,14 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
end
describe "api_url/1" do
setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end)
:ok
end
test "adds slash before path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/chain/dog"]
@ -48,6 +56,14 @@ defmodule BlockScoutWeb.ApiDocsViewTest do
end
describe "eth_rpc_api_url/1" do
setup do
original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end)
:ok
end
test "adds slash before path" do
Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/chain/dog"]

@ -40,8 +40,8 @@ defmodule BlockScoutWeb.ConnCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
{:ok, conn: Phoenix.ConnTest.build_conn()}
end

@ -27,8 +27,8 @@ defmodule BlockScoutWeb.FeatureCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata)

@ -18,4 +18,6 @@ Mox.defmock(Explorer.ExchangeRates.Source.TestSource, for: Explorer.ExchangeRate
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual)
Absinthe.Test.prime(BlockScoutWeb.Schema)
Mox.defmock(EthereumJSONRPC.Mox, for: EthereumJSONRPC.Transport)

@ -27,6 +27,8 @@ config :explorer, Explorer.Counters.AverageBlockTime,
enabled: true,
period: average_block_period
config :explorer, Explorer.ChainSpec.GenesisData, enabled: true, chain_spec_path: System.get_env("CHAIN_SPEC_PATH")
config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,

@ -37,17 +37,18 @@ defmodule Explorer.Application do
Explorer.Repo,
Supervisor.Spec.worker(SpandexDatadog.ApiServer, [datadog_opts()]),
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),
Explorer.SmartContract.SolcDownloader,
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents},
{Admin.Recovery, [[], [name: Admin.Recovery]]},
{TransactionCount, [[], []]},
{BlockCount, []},
con_cache_child_spec(Blocks.cache_name()),
Blocks,
con_cache_child_spec(NetVersion.cache_name()),
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)),
con_cache_child_spec(Transactions.cache_name())
Transactions
]
children = base_children ++ configurable_children()
@ -64,6 +65,7 @@ defmodule Explorer.Application do
defp configurable_children do
[
configure(Explorer.ExchangeRates),
configure(Explorer.ChainSpec.GenesisData),
configure(Explorer.KnownTokens),
configure(Explorer.Market.History.Cataloger),
configure(Explorer.Counters.AddressesWithBalanceCounter),

@ -248,7 +248,7 @@ defmodule Explorer.Chain do
end
end
defp address_to_transactions_without_rewards(address_hash, paging_options, options) do
def address_to_transactions_without_rewards(address_hash, paging_options, options) do
direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
@ -1258,14 +1258,16 @@ defmodule Explorer.Chain do
block_type = Keyword.get(options, :block_type, "Block")
if block_type == "Block" && !paging_options.key do
if Blocks.enough_elements?(paging_options.page_size) do
Blocks.blocks(paging_options.page_size)
else
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
case Blocks.take_enough(paging_options.page_size) do
nil ->
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
Blocks.update(elements)
Blocks.rewrite_cache(elements)
elements
elements
blocks ->
blocks
end
else
fetch_blocks(block_type, paging_options, necessity_by_association)
@ -2975,8 +2977,13 @@ defmodule Explorer.Chain 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()
|> Repo.one()
address_hash
|> CoinBalance.balances_by_day()
|> CoinBalance.balances_by_day(latest_block_timestamp)
|> Repo.all()
|> normalize_balances_by_day()
end
@ -2992,7 +2999,7 @@ defmodule Explorer.Chain do
today = Date.to_string(NaiveDateTime.utc_now())
if Enum.count(result) > 0 && !Enum.any?(result, fn map -> map[:date] == today end) do
[%{date: today, value: List.last(result)[:value]} | result]
List.flatten([result | [%{date: today, value: List.last(result)[:value]}]])
else
result
end

@ -75,7 +75,7 @@ defmodule Explorer.Chain.Address.CoinBalance do
from(
cb in CoinBalance,
where: cb.address_hash == ^address_hash,
where: cb.value > ^0,
where: not is_nil(cb.value),
inner_join: b in Block,
on: cb.block_number == b.number,
order_by: [desc: :block_number],
@ -89,16 +89,32 @@ defmodule Explorer.Chain.Address.CoinBalance do
Builds an `Ecto.Query` to fetch a series of balances by day for the given account. Each element in the series
corresponds to the maximum balance in that day. Only the last 90 days of data are used.
"""
def balances_by_day(address_hash) do
def balances_by_day(address_hash, block_timestamp \\ nil) do
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
|> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'"))
|> limit_time_interval(block_timestamp)
|> group_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> order_by([cb, b], fragment("date_trunc('day', ?)", b.timestamp))
|> select([cb, b], %{date: type(fragment("date_trunc('day', ?)", b.timestamp), :date), value: max(cb.value)})
end
def limit_time_interval(query, nil) do
query |> where([cb, b], b.timestamp >= fragment("date_trunc('day', now()) - interval '90 days'"))
end
def limit_time_interval(query, %{timestamp: timestamp}) do
query |> where([cb, b], b.timestamp >= fragment("(? AT TIME ZONE ?) - interval '90 days'", ^timestamp, ^"Etc/UTC"))
end
def last_coin_balance_timestamp(address_hash) do
CoinBalance
|> join(:inner, [cb], b in Block, on: cb.block_number == b.number)
|> where([cb], cb.address_hash == ^address_hash)
|> last(:block_number)
|> select([cb, b], %{timestamp: b.timestamp})
end
def changeset(%__MODULE__{} = balance, params) do
balance
|> cast(params, @allowed_fields)

@ -42,9 +42,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
end
defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do
options = Keyword.merge(@necessity_by_association, paging_options: paging_options)
transactions = Chain.address_to_transactions_with_rewards(address_hash, options)
transactions =
Chain.address_to_transactions_without_rewards(address_hash, paging_options, @necessity_by_association)
new_acc = transactions ++ acc

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Block.EmissionReward do
Represents the static reward given to the miner of a block in a range of block numbers.
"""
use Ecto.Schema
use Explorer.Schema
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Wei
@ -24,4 +24,10 @@ defmodule Explorer.Chain.Block.EmissionReward do
field(:block_range, Range)
field(:reward, Wei)
end
def changeset(%__MODULE__{} = emission_reward, attrs) do
emission_reward
|> cast(attrs, [:block_range, :reward])
|> validate_required([:block_range, :reward])
end
end

@ -136,10 +136,12 @@ defmodule Explorer.Chain.Block.Range do
def load(_), do: :error
defp parse_upper(%PGRange{upper: :unbound}), do: :infinity
defp parse_upper(%PGRange{upper: nil}), do: :infinity
defp parse_upper(%PGRange{upper: upper, upper_inclusive: true}), do: upper
defp parse_upper(%PGRange{upper: upper, upper_inclusive: false}), do: upper - 1
defp parse_upper(%PGRange{lower: :unbound}), do: :negative_infinity
defp parse_lower(%PGRange{lower: nil}), do: :negative_infinity
defp parse_lower(%PGRange{lower: lower, lower_inclusive: true}), do: lower
defp parse_lower(%PGRange{lower: lower, lower_inclusive: false}), do: lower + 1

@ -3,87 +3,19 @@ defmodule Explorer.Chain.Cache.Blocks do
Caches the last imported blocks
"""
alias Explorer.Repo
alias Explorer.Chain.Block
@block_numbers_key "block_numbers"
@cache_name :blocks
@number_of_elements 60
use Explorer.Chain.OrderedCache,
name: :blocks,
max_size: 60,
ids_list_key: "block_numbers",
preload: :transactions,
preload: [miner: :names],
preload: :rewards
def update(block) do
numbers = block_numbers()
@type element :: Block.t()
max_number = if numbers == [], do: -1, else: Enum.max(numbers)
min_number = if numbers == [], do: -1, else: Enum.min(numbers)
@type id :: non_neg_integer()
in_range? = block.number > min_number && Enum.all?(numbers, fn number -> number != block.number end)
not_too_far_away? = block.number > max_number - @number_of_elements - 1
if (block.number > max_number || Enum.count(numbers) == 1 || in_range?) && not_too_far_away? do
if Enum.count(numbers) >= @number_of_elements do
remove_block(numbers)
put_block(block, List.delete(numbers, Enum.min(numbers)))
else
put_block(block, numbers)
end
end
end
def rewrite_cache(elements) do
numbers = block_numbers()
ConCache.delete(@cache_name, @block_numbers_key)
numbers
|> Enum.each(fn number ->
ConCache.delete(@cache_name, number)
end)
elements
|> Enum.reduce([], fn element, acc ->
put_block(element, acc)
[element.number | acc]
end)
end
def enough_elements?(number) do
ConCache.size(@cache_name) > number
end
def update_blocks(blocks) do
Enum.each(blocks, fn block ->
update(block)
end)
end
def blocks(number \\ nil) do
numbers = block_numbers()
number = if is_nil(number), do: Enum.count(numbers), else: number
numbers
|> Enum.sort()
|> Enum.reverse()
|> Enum.slice(0, number)
|> Enum.map(fn number ->
ConCache.get(@cache_name, number)
end)
end
def cache_name, do: @cache_name
def block_numbers do
ConCache.get(@cache_name, @block_numbers_key) || []
end
defp remove_block(numbers) do
min_number = Enum.min(numbers)
ConCache.delete(@cache_name, min_number)
end
defp put_block(block, numbers) do
block_with_preloads = Repo.preload(block, [:transactions, [miner: :names], :rewards])
ConCache.put(@cache_name, block.number, block_with_preloads)
ConCache.put(@cache_name, @block_numbers_key, [block.number | numbers])
end
def element_to_id(%Block{number: number}), do: number
end

@ -4,140 +4,25 @@ defmodule Explorer.Chain.Cache.Transactions do
"""
alias Explorer.Chain.Transaction
alias Explorer.Repo
@transactions_ids_key "transactions_ids"
@cache_name :transactions
@max_size 51
@preloads [
:block,
created_contract_address: :names,
from_address: :names,
to_address: :names,
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
]
@spec cache_name :: atom()
def cache_name, do: @cache_name
@doc """
Fetches a transaction from its id ({block_number, index}), returns nil if not found
"""
@spec get({non_neg_integer(), non_neg_integer()}) :: Transaction.t() | nil
def get(id), do: ConCache.get(@cache_name, id)
@doc """
Return the current number of transactions stored
"""
@spec size :: non_neg_integer()
def size, do: Enum.count(transactions_ids())
@doc """
Checks if there are enough transactions stored
"""
@spec enough?(non_neg_integer()) :: boolean()
def enough?(amount) do
amount <= size()
end
@doc """
Checks if the number of transactions stored is already the max allowed
"""
@spec full? :: boolean()
def full? do
@max_size <= size()
end
@doc "Returns the list ids of the transactions currently stored"
@spec transactions_ids :: [{non_neg_integer(), non_neg_integer()}]
def transactions_ids do
ConCache.get(@cache_name, @transactions_ids_key) || []
end
@doc "Returns all the stored transactions"
@spec all :: [Transaction.t()]
def all, do: Enum.map(transactions_ids(), &get(&1))
@doc "Returns the `n` most recent transactions stored"
@spec take(integer()) :: [Transaction.t()]
def take(amount) do
transactions_ids()
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
@doc """
Returns the `n` most recent transactions, unless there are not as many stored,
in which case returns `nil`
"""
@spec take_enough(integer()) :: [Transaction.t()] | nil
def take_enough(amount) do
if enough?(amount), do: take(amount)
end
@doc """
Adds a transaction (or a list of transactions).
If the cache is already full, the transaction will be only stored if it can take
the place of a less recent one.
NOTE: each transaction is inserted atomically
"""
@spec update([Transaction.t()] | Transaction.t() | nil) :: :ok
def update(transactions) when is_nil(transactions), do: :ok
def update(transactions) when is_list(transactions) do
Enum.map(transactions, &update(&1))
end
def update(transaction) do
ConCache.isolated(@cache_name, @transactions_ids_key, fn ->
transaction_id = {transaction.block_number, transaction.index}
ids = transactions_ids()
if full?() do
{init, [min]} = Enum.split(ids, -1)
cond do
transaction_id < min ->
:ok
transaction_id > min ->
insert_transaction(transaction_id, transaction, init)
ConCache.delete(@cache_name, min)
transaction_id == min ->
put_transaction(transaction_id, transaction)
end
else
insert_transaction(transaction_id, transaction, ids)
end
end)
end
defp insert_transaction(transaction_id, transaction, ids) do
put_transaction(transaction_id, transaction)
ConCache.put(@cache_name, @transactions_ids_key, insert_sorted(transaction_id, ids))
end
defp put_transaction(transaction_id, transaction) do
full_transaction = Repo.preload(transaction, @preloads)
ConCache.put(@cache_name, transaction_id, full_transaction)
end
defp insert_sorted(id, ids) do
case ids do
[] ->
[id]
[head | tail] ->
cond do
head > id -> [head | insert_sorted(id, tail)]
head < id -> [id | ids]
head == id -> ids
end
end
use Explorer.Chain.OrderedCache,
name: :transactions,
max_size: 51,
preloads: [
:block,
created_contract_address: :names,
from_address: :names,
to_address: :names,
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
]
@type element :: Transaction.t()
@type id :: {non_neg_integer(), non_neg_integer()}
def element_to_id(%Transaction{block_number: block_number, index: index}) do
{block_number, index}
end
end

@ -0,0 +1,326 @@
defmodule Explorer.Chain.OrderedCache do
@moduledoc """
Behaviour for a cache of ordered elements.
A macro based on `ConCache` is provided as well, at its minimum it can be used as;
```
use Explorer.Chain.OrderedCache, name
```
where `name is an `t:atom/0` identifying the cache.
All default values can be modified by overriding their respective function or
by setting an option. For example (showing all of them):
```
use Explorer.Chain.OrderedCache,
name: :name, # need to be set
max_size: 51, # defaults to 100
ids_list_key: :ids_key, # defaults to `name`
preloads: [] # defaults to []
```
Note: `preloads` can also be set singularly with the option `preload`, e.g.:
```
use Explorer.Chain.OrderedCache,
name: :cache
preload: :block
preload: :address
preload: [transaction: :hash]
```
Additionally all of the options accepted by `ConCache.start_link/1` can be
provided as well. By default only `ttl_check_interval:` is set (to `false`).
It's also possible, and advised, to override the implementation of the `c:prevails?/2`
and `c:element_to_id/1` callbacks.
For typechecking purposes it's also recommended to override the `t:element/0`
and `t:id/0` type definitions.
"""
@type element :: struct()
@type id :: term()
@doc """
An atom that identifies this cache
"""
@callback cache_name :: atom()
@doc """
The key used to store the (ordered) list of elements.
Because this list is stored in the cache itself, one needs to make sure it is
cannot be equal to any element id.
"""
@callback ids_list_key :: term()
@doc """
The size that this cache cannot exceed.
"""
@callback max_size :: non_neg_integer()
@doc """
Fields of the stored elements that need to be preloaded.
For entities that are not stored in `Explorer.Repo` this should be empty.
"""
@callback preloads :: [term()]
@doc """
The function that orders the elements and decides the ones that are stored.
`prevails?(id_a, id_b)` should return `true` if (in case there is no space for both)
the element with `id_a` should be stored instead of the element with `id_b`,
`false` otherwise.
"""
@callback prevails?(id, id) :: boolean()
@doc """
The function that obtains an unique `t:id/0` from an `t:element/0`
"""
@callback element_to_id(element()) :: id()
@doc "Returns the list ids of the elements currently stored"
@callback ids_list :: [id]
@doc """
Fetches a element from its id, returns nil if not found
"""
@callback get(id) :: element | nil
@doc """
Return the current number of elements stored
"""
@callback size() :: non_neg_integer()
@doc """
Checks if there are enough elements stored
"""
@callback enough?(non_neg_integer()) :: boolean()
@doc """
Checks if the number of elements stored is already the max allowed
"""
@callback full? :: boolean()
@doc "Returns all the stored elements"
@callback all :: [element]
@doc "Returns the `n` most prevailing elements stored, based on `c:prevails?/2`"
@callback take(integer()) :: [element]
@doc """
Returns the `n` most prevailing elements, based on `c:prevails?/2`, unless there
are not as many stored, in which case it returns `nil`
"""
@callback take_enough(integer()) :: [element] | nil
@doc """
Adds an element, or a list of elements, to the cache.
When the cache is full, only the most prevailing elements will be stored, based
on `c:prevails?/2`.
NOTE: every update is isolated from another one.
"""
@callback update([element] | element | nil) :: :ok
defmacro __using__(name) when is_atom(name), do: do_using(name, [])
defmacro __using__(opts) when is_list(opts) do
# name is necessary
name = Keyword.fetch!(opts, :name)
do_using(name, opts)
end
# credo:disable-for-next-line /Complexity/
defp do_using(name, opts) when is_atom(name) and is_list(opts) do
ids_list_key = Keyword.get(opts, :ids_list_key, name)
max_size = Keyword.get(opts, :max_size, 100)
preloads = Keyword.get(opts, :preloads) || Keyword.get_values(opts, :preload)
concache_params =
opts
|> Keyword.drop([:ids_list_key, :max_size, :preloads, :preload])
|> Keyword.put_new(:ttl_check_interval, false)
# credo:disable-for-next-line Credo.Check.Refactor.LongQuoteBlocks
quote do
alias Explorer.Chain.OrderedCache
@behaviour OrderedCache
### Automatically set functions
@impl OrderedCache
def cache_name, do: unquote(name)
@impl OrderedCache
def ids_list_key, do: unquote(ids_list_key)
@impl OrderedCache
def max_size, do: unquote(max_size)
@impl OrderedCache
def preloads, do: unquote(preloads)
### Settable functions
@impl OrderedCache
def prevails?(id_a, id_b), do: id_a > id_b
@impl OrderedCache
def element_to_id(element), do: element
### Straightforward fetching functions
@impl OrderedCache
def ids_list, do: ConCache.get(cache_name(), ids_list_key()) || []
@impl OrderedCache
def get(id), do: ConCache.get(cache_name(), id)
@impl OrderedCache
def size, do: ids_list() |> Enum.count()
@impl OrderedCache
def enough?(amount), do: amount <= size()
@impl OrderedCache
def full?, do: max_size() <= size()
@impl OrderedCache
def all, do: Enum.map(ids_list(), &get(&1))
@impl OrderedCache
def take(amount) do
ids_list()
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
@impl OrderedCache
def take_enough(amount) do
# behaves just like `if enough?(amount), do: take(amount)` but fetching
# the list only once
ids = ids_list()
if amount <= Enum.count(ids) do
ids
|> Enum.take(amount)
|> Enum.map(&get(&1))
end
end
### Updating function
@impl OrderedCache
def update(elements) when is_nil(elements), do: :ok
def update(elements) when is_list(elements) do
ConCache.update(cache_name(), ids_list_key(), fn ids ->
updated_list =
elements
|> Enum.map(&{element_to_id(&1), &1})
|> Enum.sort(&prevails?(&1, &2))
|> merge_and_update(ids || [], max_size())
{:ok, updated_list}
end)
end
def update(element), do: update([element])
defp merge_and_update(_candidates, existing, 0) do
# if there is no more space in the list remove the remaining existing
# elements and return an empty list
remove(existing)
[]
end
defp merge_and_update([], existing, size) do
# if there are no more candidates to be inserted keep as many of the
# exsisting elements and remove the rest
{remaining, to_remove} = Enum.split(existing, size)
remove(to_remove)
remaining
end
defp merge_and_update(candidates, [], size) do
# if there are still candidates and no more existing value insert as many
# candidates as possible and ignore the rest
candidates
|> Enum.take(size)
|> Enum.map(fn {element_id, element} ->
put_element(element_id, element)
element_id
end)
end
defp merge_and_update(candidates, existing, size) do
[{candidate_id, candidate} | to_check] = candidates
[head | tail] = existing
cond do
head == candidate_id ->
# if a candidate has the id of and existing element, update its value
put_element(candidate_id, candidate)
[head | merge_and_update(to_check, tail, size - 1)]
prevails?(head, candidate_id) ->
# keep the prevaling existing value and compare all candidates against the rest
[head | merge_and_update(candidates, tail, size - 1)]
true ->
# insert new prevailing candidate and compare the remaining ones with the rest
put_element(candidate_id, candidate)
[candidate_id | merge_and_update(to_check, existing, size - 1)]
end
end
defp remove(key) do
# Always performs async removal so it can wait 1/10 of a second and
# others have the time to get elements that were in the cache's list.
# Different updates cannot interfere with the removed element because
# if this was scheduled for removal it means it is too old, so following
# updates cannot insert it in the future.
Task.start(fn ->
Process.sleep(100)
if is_list(key) do
Enum.map(key, &ConCache.delete(cache_name(), &1))
else
ConCache.delete(cache_name(), key)
end
end)
end
defp put_element(element_id, element) do
full_element =
if Enum.empty?(preloads()) do
element
else
Explorer.Repo.preload(element, preloads())
end
# dirty puts are a little faster than puts with locks.
# this is not a problem because this is the only function modifying rows
# and it only gets called inside `update`, which works isolated
ConCache.dirty_put(cache_name(), element_id, full_element)
end
### Supervisor's child specification
@doc """
The child specification for a Supervisor. Note that all the `params`
provided to this function will override the ones set by using the macro
"""
def child_spec(params) do
params = Keyword.merge(unquote(concache_params), params)
Supervisor.child_spec({ConCache, params}, id: child_id())
end
def child_id, do: {ConCache, cache_name()}
defoverridable cache_name: 0,
ids_list_key: 0,
max_size: 0,
preloads: 0,
prevails?: 2,
element_to_id: 1
end
end
end

@ -129,7 +129,7 @@ defmodule Explorer.Chain.TokenTransfer do
query =
from(
tt in TokenTransfer,
where: tt.token_contract_address_hash == ^token_address_hash,
where: tt.token_contract_address_hash == ^token_address_hash and not is_nil(tt.block_number),
preload: [{:transaction, :block}, :token, :from_address, :to_address],
order_by: [desc: tt.block_number, desc: tt.log_index]
)

@ -0,0 +1,108 @@
defmodule Explorer.ChainSpec.GenesisData do
@moduledoc """
Fetches genesis data.
"""
use GenServer
require Logger
alias Explorer.ChainSpec.Parity.Importer
alias HTTPoison.Response
@interval :timer.minutes(2)
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl GenServer
def init(_) do
:timer.send_interval(@interval, :import)
Process.send_after(self(), :import, @interval)
{:ok, %{}}
end
# Callback for errored fetch
@impl GenServer
def handle_info({_ref, {:error, reason}}, state) do
Logger.warn(fn -> "Failed to fetch genesis data '#{reason}'." end)
fetch_genesis_data()
{:noreply, state}
end
@impl GenServer
def handle_info(:import, state) do
Logger.debug(fn -> "Importing genesis data" end)
fetch_genesis_data()
{:noreply, state}
end
# Callback that a monitored process has shutdown
@impl GenServer
def handle_info({:DOWN, _, :process, _, _}, state) do
{:noreply, state}
end
# Callback for successful fetch
@impl GenServer
def handle_info({_ref, _}, state) do
{:noreply, state}
end
def fetch_genesis_data do
path = Application.get_env(:explorer, __MODULE__)[:chain_spec_path]
if path do
Task.Supervisor.async_nolink(Explorer.GenesisDataTaskSupervisor, fn ->
case fetch_spec(path) do
{:ok, chain_spec} ->
Importer.import_emission_rewards(chain_spec)
{:ok, _} = Importer.import_genesis_coin_balances(chain_spec)
{:error, reason} ->
Logger.warn(fn -> "Failed to fetch genesis data. #{inspect(reason)}" end)
end
end)
else
Logger.warn(fn -> "Failed to fetch genesis data. Chain spec path is not set." end)
end
end
defp fetch_spec(path) do
if valid_url?(path) do
fetch_from_url(path)
else
fetch_from_file(path)
end
end
# sobelow_skip ["Traversal"]
defp fetch_from_file(path) do
with {:ok, data} <- File.read(path),
{:ok, json} <- Jason.decode(data) do
{:ok, json}
end
end
defp fetch_from_url(url) do
case HTTPoison.get(url) do
{:ok, %Response{body: body, status_code: 200}} ->
{:ok, Jason.decode!(body)}
reason ->
{:error, reason}
end
end
defp valid_url?(string) do
uri = URI.parse(string)
uri.scheme != nil && uri.host =~ "."
end
end

@ -0,0 +1,107 @@
defmodule Explorer.ChainSpec.Parity.Importer do
@moduledoc """
Imports data from parity chain spec.
"""
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.Hash.Address, as: AddressHash
alias Explorer.Chain.Wei
@max_block_number :infinity
def import_emission_rewards(chain_spec) do
rewards = emission_rewards(chain_spec)
{_, nil} = Repo.delete_all(EmissionReward)
{_, nil} = Repo.insert_all(EmissionReward, rewards)
end
def import_genesis_coin_balances(chain_spec) do
balance_params =
chain_spec
|> genesis_coin_balances()
|> Stream.map(fn balance_map ->
Map.put(balance_map, :block_number, 0)
end)
|> Enum.to_list()
address_params =
balance_params
|> Stream.map(fn %{address_hash: hash} ->
%{hash: hash}
end)
|> Enum.to_list()
params = %{address_coin_balances: %{params: balance_params}, addresses: %{params: address_params}}
Chain.import(params)
end
def genesis_coin_balances(chain_spec) do
accounts = chain_spec["accounts"]
parse_accounts(accounts)
end
def emission_rewards(chain_spec) do
rewards = chain_spec["engine"]["Ethash"]["params"]["blockReward"]
rewards
|> parse_hex_numbers()
|> format_ranges()
end
defp parse_accounts(accounts) do
accounts
|> Stream.filter(fn {_address, map} ->
!is_nil(map["balance"])
end)
|> Stream.map(fn {address, %{"balance" => value}} ->
{:ok, address_hash} = AddressHash.cast(address)
balance = parse_hex_number(value)
%{address_hash: address_hash, value: balance}
end)
|> Enum.to_list()
end
defp format_ranges(block_number_reward_pairs) do
block_number_reward_pairs
|> Enum.chunk_every(2, 1)
|> Enum.map(fn values ->
create_range(values)
end)
end
defp create_range([{block_number1, reward}, {block_number2, _}]) do
block_number1 = if block_number1 != 0, do: block_number1 + 1, else: 0
%{
block_range: %Range{from: block_number1, to: block_number2},
reward: reward
}
end
defp create_range([{block_number, reward}]) do
%{
block_range: %Range{from: block_number + 1, to: @max_block_number},
reward: reward
}
end
defp parse_hex_numbers(rewards) do
Enum.map(rewards, fn {hex_block_number, hex_reward} ->
block_number = parse_hex_number(hex_block_number)
{:ok, reward} = hex_reward |> parse_hex_number() |> Wei.cast()
{block_number, reward}
end)
end
defp parse_hex_number("0x" <> hex_number) do
{number, ""} = Integer.parse(hex_number, 16)
number
end
end

@ -249,7 +249,8 @@ defmodule Explorer.Etherscan do
contract_address_hash: tb.token_contract_address_hash,
name: t.name,
decimals: t.decimals,
symbol: t.symbol
symbol: t.symbol,
type: t.type
}
)

@ -2,8 +2,8 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do
use Explorer.DataCase
alias Ecto.Changeset
alias Explorer.Chain.{Block, Wei}
alias Explorer.Chain.Address.CoinBalance
alias Explorer.Chain.{Block, Wei}
alias Explorer.PagingOptions
describe "changeset/2" do
@ -225,7 +225,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do
assert(length(result) == 1)
value = List.first(result) |> Map.get(:value)
value = result |> List.first() |> Map.get(:value)
assert(value == Wei.from(Decimal.new(3000), :wei))
end
@ -247,7 +247,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do
assert(length(result) == 1)
value = List.first(result) |> Map.get(:value)
value = result |> List.first() |> Map.get(:value)
assert(value == Wei.from(Decimal.new(3000), :wei))
end
@ -269,9 +269,33 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do
assert(length(result) == 1)
value = List.first(result) |> Map.get(:value)
value = result |> List.first() |> Map.get(:value)
assert(value == Wei.from(Decimal.new(3000), :wei))
end
test "fetches old records" do
address = insert(:address)
noon = Timex.now() |> Timex.beginning_of_day() |> Timex.set(hour: 12)
old_block = insert(:block, timestamp: Timex.shift(noon, days: -700))
insert(:fetched_balance, address_hash: address.hash, value: 2000, block_number: old_block.number)
latest_block_timestamp =
address.hash
|> CoinBalance.last_coin_balance_timestamp()
|> Repo.one()
result =
address.hash
|> CoinBalance.balances_by_day(latest_block_timestamp)
|> Repo.all()
assert(length(result) == 1)
value = result |> List.first() |> Map.get(:value)
assert(value == Wei.from(Decimal.new(2000), :wei))
end
end
end

@ -16,7 +16,7 @@ defmodule Explorer.Chain.Cache.BlocksTest do
Blocks.update(block)
assert Blocks.blocks() == [block]
assert Blocks.all() == [block]
end
test "adds a new elements removing the oldest one" do
@ -30,22 +30,16 @@ defmodule Explorer.Chain.Cache.BlocksTest do
block.number
end)
assert Blocks.size() == 60
new_block = insert(:block, number: 70)
Blocks.update(new_block)
new_blocks = blocks |> List.replace_at(0, new_block.number) |> Enum.sort() |> Enum.reverse()
assert Enum.map(Blocks.blocks(), & &1.number) == new_blocks
end
test "does not add too old blocks" do
block = insert(:block, number: 100_000) |> Repo.preload([:transactions, [miner: :names], :rewards])
old_block = insert(:block, number: 1_000)
assert Blocks.full?()
Blocks.update(block)
Blocks.update(old_block)
assert Blocks.blocks() == [block]
assert Enum.map(Blocks.all(), & &1.number) == new_blocks
end
test "adds missing element" do
@ -55,30 +49,13 @@ defmodule Explorer.Chain.Cache.BlocksTest do
Blocks.update(block1)
Blocks.update(block2)
assert Enum.count(Blocks.blocks()) == 2
assert Blocks.size() == 2
block3 = insert(:block, number: 6)
Blocks.update(block3)
assert Enum.map(Blocks.blocks(), & &1.number) == [10, 6, 4]
end
end
describe "rewrite_cache/1" do
test "updates cache" do
block = insert(:block)
Blocks.update(block)
block1 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
block2 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
new_blocks = [block1, block2]
Blocks.rewrite_cache(new_blocks)
assert Blocks.blocks() == [block2, block1]
assert Enum.map(Blocks.all(), & &1.number) == [10, 6, 4]
end
end
end

@ -0,0 +1,108 @@
defmodule Explorer.ChainSpec.Parity.ImporterTest do
use Explorer.DataCase
alias Explorer.Chain.Address.CoinBalance
alias Explorer.Chain.Block.{EmissionReward, Range}
alias Explorer.Chain.{Address, Hash, Wei}
alias Explorer.ChainSpec.Parity.Importer
alias Explorer.Repo
@chain_spec "#{File.cwd!()}/test/support/fixture/chain_spec/foundation.json"
|> File.read!()
|> Jason.decode!()
describe "emission_rewards/1" do
test "fetches and formats reward ranges" do
assert Importer.emission_rewards(@chain_spec) == [
%{
block_range: %Range{from: 0, to: 4_370_000},
reward: %Wei{value: Decimal.new(5_000_000_000_000_000_000)}
},
%{
block_range: %Range{from: 4_370_001, to: 7_280_000},
reward: %Wei{value: Decimal.new(3_000_000_000_000_000_000)}
},
%{
block_range: %Range{from: 7_280_001, to: :infinity},
reward: %Wei{value: Decimal.new(2_000_000_000_000_000_000)}
}
]
end
end
describe "import_emission_rewards/1" do
test "inserts emission rewards from chain spec" do
assert {3, nil} = Importer.import_emission_rewards(@chain_spec)
end
test "rewrites all recored" do
old_block_rewards = %{
"0x0" => "0x1bc16d674ec80000",
"0x42ae50" => "0x29a2241af62c0000",
"0x6f1580" => "0x4563918244f40000"
}
chain_spec = %{
@chain_spec
| "engine" => %{
@chain_spec["engine"]
| "Ethash" => %{
@chain_spec["engine"]["Ethash"]
| "params" => %{@chain_spec["engine"]["Ethash"]["params"] | "blockReward" => old_block_rewards}
}
}
}
assert {3, nil} = Importer.import_emission_rewards(chain_spec)
[first, second, third] = Repo.all(EmissionReward)
assert first.reward == %Wei{value: Decimal.new(2_000_000_000_000_000_000)}
assert first.block_range == %Range{from: 0, to: 4_370_000}
assert second.reward == %Wei{value: Decimal.new(3_000_000_000_000_000_000)}
assert second.block_range == %Range{from: 4_370_001, to: 7_280_000}
assert third.reward == %Wei{value: Decimal.new(5_000_000_000_000_000_000)}
assert third.block_range == %Range{from: 7_280_001, to: :infinity}
assert {3, nil} = Importer.import_emission_rewards(@chain_spec)
[new_first, new_second, new_third] = Repo.all(EmissionReward)
assert new_first.reward == %Wei{value: Decimal.new(5_000_000_000_000_000_000)}
assert new_first.block_range == %Range{from: 0, to: 4_370_000}
assert new_second.reward == %Wei{value: Decimal.new(3_000_000_000_000_000_000)}
assert new_second.block_range == %Range{from: 4_370_001, to: 7_280_000}
assert new_third.reward == %Wei{value: Decimal.new(2_000_000_000_000_000_000)}
assert new_third.block_range == %Range{from: 7_280_001, to: :infinity}
end
end
describe "genesis_coin_balances/1" do
test "parses coin balance" do
coin_balances = Importer.genesis_coin_balances(@chain_spec)
assert Enum.count(coin_balances) == 403
assert %{
address_hash: %Hash{
byte_count: 20,
bytes: <<121, 174, 179, 69, 102, 185, 116, 195, 90, 88, 129, 222, 192, 32, 146, 125, 167, 223, 93, 37>>
},
value: 2_000_000_000_000_000_000_000
} ==
List.first(coin_balances)
end
end
describe "import_genesis_coin_balances/1" do
test "imports coin balances" do
{:ok, %{address_coin_balances: address_coin_balances}} = Importer.import_genesis_coin_balances(@chain_spec)
assert Enum.count(address_coin_balances) == 403
assert CoinBalance |> Repo.all() |> Enum.count() == 403
assert Address |> Repo.all() |> Enum.count() == 403
end
end
end

@ -3896,8 +3896,8 @@ defmodule Explorer.ChainTest do
balances = Chain.address_to_balances_by_day(address.hash)
assert balances == [
%{date: today |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")},
%{date: yesterday |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")}
%{date: yesterday |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")},
%{date: today |> NaiveDateTime.to_date() |> Date.to_string(), value: Decimal.new("1E-15")}
]
end
end

@ -1421,7 +1421,8 @@ defmodule Explorer.EtherscanTest do
contract_address_hash: token_balance.token_contract_address_hash,
name: token_balance.token.name,
decimals: token_balance.token.decimals,
symbol: token_balance.token.symbol
symbol: token_balance.token.symbol,
type: token_balance.token.type
}
]
@ -1468,7 +1469,8 @@ defmodule Explorer.EtherscanTest do
contract_address_hash: token_balance.token_contract_address_hash,
name: token_balance.token.name,
decimals: token_balance.token.decimals,
symbol: token_balance.token.symbol
symbol: token_balance.token.symbol,
type: token_balance.token.type
}
]

@ -9,8 +9,8 @@ defmodule Explorer.Market.MarketHistoryCacheTest do
Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
end)
:ok

@ -6,12 +6,12 @@ defmodule Explorer.MarketTest do
alias Explorer.Repo
setup do
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
on_exit(fn ->
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
end)
:ok

@ -40,10 +40,10 @@ defmodule Explorer.DataCase do
end
Explorer.Chain.Cache.BlockNumber.setup()
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
:ok
end

@ -189,7 +189,7 @@ defmodule Indexer.Block.Fetcher do
BlockNumber.update(max_block.number)
BlockNumber.update(min_block.number)
BlocksCache.update_blocks(blocks)
BlocksCache.update(blocks)
end
defp update_transactions_cache(transactions) do

@ -53,6 +53,7 @@ $ export NETWORK=POA
| `ADDRESS_WITH_BALANCES` <br /> `_UPDATE_INTERVAL`| | Interval in seconds to restart the task, which calculates addresses with balances. | 30 * 60 | v1.3.9+ |
| `LINK_TO_OTHER_EXPLORERS` | | true/false. If true, links to other explorers are added in the footer | (empty) | v1.3.0+ |
| `COINMARKETCAP_PAGES` | | the number of pages on coinmarketcap to list in order to find token's price | 10 | v1.3.10+ |
| `CHAIN_SPEC_PATH` | | Chain specification path (absolute file system path or url) to import block emission reward ranges and genesis account balances from | (empty) | master |
| `SUPPORTED_CHAINS` | | Array of supported chains that displays in the footer and in the chains dropdown. This var was introduced in this PR [#1900](https://github.com/poanetwork/blockscout/pull/1900) and looks like an array of JSON objects. | (empty) | v2.0.0+ |
| `BLOCK_COUNT_CACHE_PERIOD ` | | time to live of cache in seconds. This var was introduced in [#1876](https://github.com/poanetwork/blockscout/pull/1876) | 600 | v2.0.0+ |
| `ALLOWED_EVM_VERSIONS ` | | the comma-separated list of allowed EVM versions for contracts verification. This var was introduced in [#1964](https://github.com/poanetwork/blockscout/pull/1964) | "homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg" | v2.0.0+ |

@ -1,117 +1,112 @@
%{
"abi": {:hex, :abi, "0.1.12", "87ae04cb09e2308db7b3c350584dc3934de0e308f6a056ba82be5756b081a1ca", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"abnf2": {:hex, :abnf2, "0.1.2", "6f8792b8ac3288dba5fc889c2bceae9fe78f74e1a7b36bea9726ffaa9d7bef95", [:mix], []},
"absinthe": {:hex, :absinthe, "1.4.14", "fef224a6aac63d6eaafbc0cb96040a8abcd572275b9b4db69d46329acdcae7c7", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"absinthe_phoenix": {:git, "https://github.com/ayrat555/absinthe_phoenix.git", "0f5127844a9e4e1c5fecb1fcee225894a2af6336", [branch: "master"]},
"absinthe": {:hex, :absinthe, "1.4.16", "0933e4d9f12652b12115d5709c0293a1bf78a22578032e9ad0dad4efee6b9eb1", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"absinthe_phoenix": {:git, "https://github.com/ayrat555/absinthe_phoenix.git", "4dbb73a25a0935a4d292e63876698e18534d835f", [branch: "master"]},
"absinthe_plug": {:git, "https://github.com/ayrat555/absinthe_plug.git", "cbe1c170e11e60b3b0146b925a1ce6ec562840ce", [branch: "ab-enable-default-query"]},
"absinthe_relay": {:hex, :absinthe_relay, "1.4.6", "ec0e2288994b388556247cf9601245abec785cdf206d6e844f2992d29de21624", [:mix], [{:absinthe, "~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"accept": {:hex, :accept, "0.3.3", "548ebb6fb2e8b0d170e75bb6123aea6ceecb0189bb1231eeadf52eac08384a97", [:rebar3], [], "hexpm"},
"artificery": {:hex, :artificery, "0.2.6", "f602909757263f7897130cbd006b0e40514a541b148d366ad65b89236b93497a", [:mix], [], "hexpm"},
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "1.1.1", "6b5560e47a02196ce5f0ab3f1d8265db79a23868c137e973b27afef928ed8006", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"},
"benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, optional: false]}, {:csv, "~> 2.0", [hex: :csv, optional: false]}]},
"binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], []},
"benchee_csv": {:hex, :benchee_csv, "0.8.0", "0ca094677d6e2b2f601b7ee7864b754789ef9d24d079432e5e3d6f4fb83a4d80", [:mix], [{:benchee, "~> 0.12", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm"},
"binary": {:hex, :binary, "0.0.5", "20d816f7274ea34f1b673b4cff2fdb9ebec9391a7a68c349070d515c66b1b2cf", [:mix], [], "hexpm"},
"briefly": {:git, "https://github.com/CargoSense/briefly.git", "2526e9674a4e6996137e066a1295ea60962712b8", []},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"bypass": {:hex, :bypass, "1.0.0", "b78b3dcb832a71aca5259c1a704b2e14b55fd4e1327ff942598b4e7d1a7ad83d", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"cldr_utils": {:hex, :cldr_utils, "2.3.0", "e7e8b5ad7494a929c1b620cc489c3aa3f6e7e5299584c1a934bbdb56d1a53c70", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], []},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"comeonin": {:hex, :comeonin, "4.1.2", "3eb5620fd8e35508991664b4c2b04dd41e52f1620b36957be837c1d7784b7592", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, repo: "hexpm", optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, repo: "hexpm", optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, repo: "hexpm", optional: true]}], "hexpm"},
"con_cache": {:hex, :con_cache, "0.13.1", "047e097ab2a8c6876e12d0c29e29a86d487b592df97b98e3e2abedad574e215d", [:mix], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "1.1.2", "02b6422f3e659eb74b05aca3c20c1d8da0119a05ee82577a82e6c2938bf29f81", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"csv": {:hex, :csv, "2.1.1", "a4c1a7c30d2151b6e4976cb2f52c0a1d49ec965afb737ed84a684bc4284d1627", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, optional: false]}]},
"csv": {:hex, :csv, "2.3.1", "9ce11eff5a74a07baf3787b2b19dd798724d29a9c3a492a41df39f6af686da0e", [:mix], [{:parallel_stream, "~> 1.0.4", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm"},
"dataloader": {:hex, :dataloader, "1.0.6", "fb724d6d3fb6acb87d27e3b32dea3a307936ad2d245faf9cf5221d1323d6a4ba", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"decorator": {:hex, :decorator, "1.3.0", "6203dbd6e4e519a21a079e2a74e50fddaf03e80be22711b92eb4cd410173abea", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], []},
"distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.3.1", "73812f447f7a42358d3ba79283cfa3075a7580a3a2ed457616d6517ac3738cb9", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.5", "0db71c8290b5bc81cb0101a2a507a76dca659513984d683119ee722828b424f6", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.1.7", "fa21d06ef56cdc2fdaa62574e8c3ba34a2751d44ea34c30bc65f0728421043e5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.1.6", "1e80e30d16138a729c717f73dcb938590bcdb3a4502f3012414d0cbb261045d8", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0 or ~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
"ex_abi": {:hex, :ex_abi, "0.1.18", "19db9bffdd201edbdff97d7dd5849291218b17beda045c1b76bff5248964f37d", [:mix], [{:exth_crypto, "~> 0.1.4", [hex: :exth_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"ex_cldr": {:hex, :ex_cldr, "2.7.2", "d79a1af6ed12630a15175d2b88d4381b22db5d6f835c5da8de132f0cf712b233", [:mix], [{:cldr_utils, "~> 2.1", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:plug, "~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.3.0", "bffae489416b8b05d4683403263f5d62aae17de70c24ff915a533541fea514de", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_cldr_lists": {:hex, :ex_cldr_lists, "2.2.0", "b99f8752d098fc6ba5f083bbd0b25d0d01e36b0042155cf6abd5f205306ba849", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.6.4", "5b1ac8451f889576bb29dee70412de1170974298727ab944aa4d17e91bdd3472", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.3", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_cldr_units": {:hex, :ex_cldr_units, "2.5.1", "0e65067a22a7c5146266c313d6333c2700868c32aa6d536f47c6c0d84aac3ac1", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.2", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_json_schema": {:hex, :ex_json_schema, "0.6.1", "b57c0588385b8262b80f19d33d9b9b71fcd60d247691abf2635b57a03ec0ad44", [:mix], [], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rlp": {:hex, :ex_rlp, "0.5.2", "7f4ce7bd55e543c054ce6d49629b01e9833c3462e3d547952be89865f39f2c58", [:mix], [], "hexpm"},
"ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm"},
"exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], []},
"exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"},
"excoveralls": {:git, "https://github.com/KronicDeth/excoveralls.git", "0a859b68851eeba9b43eba59fbc8f9098299cfe1", [branch: "circle-workflows"]},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"exth_crypto": {:hex, :exth_crypto, "0.1.6", "8e636a9bcb75d8e32451be96e547a495121ed2178d078db294edb0f81f7cf2e8", [:mix], [{:binary, "~> 0.0.4", [hex: :binary, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600_orig, repo: "hexpm", optional: false]}, {:libsecp256k1, "~> 0.1.9", [hex: :libsecp256k1, repo: "hexpm", optional: false]}], "hexpm"},
"exvcr": {:hex, :exvcr, "0.10.3", "1ae3b97560430acfa88ebc737c85b2b7a9dbacd8a2b26789a19718b51ae3522c", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
"exvcr": {:hex, :exvcr, "0.10.1", "cb266e5cc0d4fef12572ce6673d13f97aa3c302911010d64d51cee0690f566d1", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm"},
"floki": {:hex, :floki, "0.20.4", "be42ac911fece24b4c72f3b5846774b6e61b83fe685c2fc9d62093277fb3bc86", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"flow": {:hex, :flow, "0.14.3", "0d92991fe53035894d24aa8dec10dcfccf0ae00c4ed436ace3efa9813a646902", [:mix], [{:gen_stage, "~> 0.14.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.1", "9d46723fda072d4f4bb31a102560013f7960f5d80ea44dcb96fd6304ed61e7a4", [:mix], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.2", "6a2a578a510c5bfca8a45e6b27552f613b41cf584b58210f017088d3d17d0b14", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], []},
"httpoison": {:hex, :httpoison, "1.5.0", "71ae9f304bdf7f00e9cd1823f275c955bdfc68282bc5eb5c85c3a9ade865d68e", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], []},
"junit_formatter": {:hex, :junit_formatter, "3.0.0", "13950d944dbd295da7d8cc4798b8faee808a8bb9b637c88069954eac078ac9da", [:mix], [], "hexpm"},
"keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], []},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"junit_formatter": {:hex, :junit_formatter, "3.0.1", "4ed76a50886717a6d683a978cec775abdcb88d9d51cfddd3d8fbf8e6af4625da", [:mix], [], "hexpm"},
"keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm"},
"libsecp256k1": {:hex, :libsecp256k1, "0.1.10", "d27495e2b9851c7765129b76c53b60f5e275bd6ff68292c50536bf6b8d091a4d", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm"},
"logger_file_backend": {:hex, :logger_file_backend, "0.0.10", "876f9f84ae110781207c54321ffbb62bebe02946fe3c13f0d7c5f5d8ad4fa910", [:mix], [], "hexpm"},
"makeup": {:hex, :makeup, "0.8.0", "9cf32aea71c7fe0a4b2e9246c2c4978f9070257e5c9ce6d4a28ec450a839b55f", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"math": {:hex, :math, "0.3.0", "e14e7291115201cb155a3567e66d196bf5088a6f55b030d598107d7ae934a11c", [:mix], []},
"meck": {:hex, :meck, "0.8.12", "1f7b1a9f5d12c511848fec26bbefd09a21e1432eadb8982d9a8aceb9891a3cf2", [:rebar3], [], "hexpm"},
"logger_file_backend": {:hex, :logger_file_backend, "0.0.11", "3bbc5f31d3669e8d09d7a9443e86056fae7fc18e45c6f748c33b8c79a7e147a1", [:mix], [], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"math": {:hex, :math, "0.3.0", "e14e7291115201cb155a3567e66d196bf5088a6f55b030d598107d7ae934a11c", [:mix], [], "hexpm"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"},
"memento": {:hex, :memento, "0.3.1", "b2909390820550d8b90b68ec96f9e15ff8a45a28b6f97fa4a62ef50e87c2f9d9", [:mix], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm"},
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"},
"mock": {:hex, :mock, "0.3.2", "e98e998fd76c191c7e1a9557c8617912c53df3d4a6132f561eb762b699ef59fa", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mox": {:hex, :mox, "0.4.0", "7f120840f7d626184a3d65de36189ca6f37d432e5d63acd80045198e4c5f7e6e", [:mix], [], "hexpm"},
"msgpax": {:hex, :msgpax, "2.2.2", "559a07806bbe5fdd31e4597455be030bd96356f7a621ca9eed832747c83bfb67", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"mock": {:hex, :mock, "0.3.3", "42a433794b1291a9cf1525c6d26b38e039e0d3a360732b5e467bfc77ef26c914", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
"msgpax": {:hex, :msgpax, "2.2.4", "7b3790ef684089076b63c0f08c2f4b079c6311daeb006b69e4ed2bf67518291e", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm"},
"nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm"},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], []},
"parallel_stream": {:hex, :parallel_stream, "1.0.6", "b967be2b23f0f6787fab7ed681b4c45a215a81481fb62b01a5b750fa8f30f76c", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.4", "4af0603d8d41ca638e70f74d6defff331e4db106dd85f75f125579ca27bd8b64", [:mix], [{:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
"phoenix_form_awesomplete": {:hex, :phoenix_form_awesomplete, "0.1.5", "d09aade160b584e3428e1e095645482396f17bddda4f566f1118f12d2598d11c", [:mix], [{:phoenix_html, "~> 2.10", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.1", "274a4b07c4adbdd7785d45a8b0bb57634d0b4f45b18d2c508b26c0344bd59b8f", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.15.0", "dd5349161019caeea93efa42f9b22f9d79995c3a86bdffb796427b4c9863b0f0", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus": {:hex, :prometheus, "4.2.0", "06c58bfdfe28d3168b926da614cb9a6d39593deebde648a5480e32dfa3c370e9", [:mix, :rebar3], [], "hexpm"},
"prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.3", "5d722263bb1f7a9b1d02554de42e61ea672b4e3c07c3f74e23ce35ab5e111cfa", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"qrcode": {:hex, :qrcode, "0.1.4", "544dc67ba42eed5ebce3d2a691d053387937740561d251f83f0a067917fae2dc", [:mix], [], "hexpm"},
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.3", "657386e8f142fc817347d95c1f3a05ab08710f7df9e7f86db6facaed107ed929", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"qrcode": {:hex, :qrcode, "0.1.5", "551271830515c150f34568345b060c625deb0e6691db2a01b0a6de3aafc93886", [:mix], [], "hexpm"},
"que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"set_locale": {:git, "https://github.com/minifast/set_locale.git", "da9ae029642bc0fbd9212c2aaf86c0adca70c084", [branch: "master"]},
"sobelow": {:hex, :sobelow, "0.7.4", "228cc6185b448b63ecc88429b43e864e8dd570e8e09f2d04b3aa71894db1bdbb", [:mix], [], "hexpm"},
"sobelow": {:hex, :sobelow, "0.8.0", "a3ec73e546dfde19f14818e5000c418e3f305d9edb070e79dd391de0ae1cd1ea", [:mix], [], "hexpm"},
"spandex": {:git, "https://github.com/spandex-project/spandex.git", "92992b4aaf3d92e031c2417ff2e6c9e94d27fe36", [branch: "allow-setting-trace-key"]},
"spandex_datadog": {:hex, :spandex_datadog, "0.4.0", "75113a73e843123074886a2e31994af07d6e0632749a8d97e9ca6157b120c287", [:mix], [{:msgpax, "~> 2.2.1", [hex: :msgpax, repo: "hexpm", optional: false]}, {:spandex, "~> 2.3", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"},
"spandex_ecto": {:hex, :spandex_ecto, "0.4.0", "deaeaddc11a35f1c551206c53d09bb93a0da5808dbef751430e465c8c7de01d3", [:mix], [{:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"},
"spandex_phoenix": {:hex, :spandex_phoenix, "0.3.1", "9cb9a4a9f2161f171d9df9afa1289a0d037abbbeecabae674f959b57f106f201", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"},
"spandex_phoenix": {:hex, :spandex_phoenix, "0.3.2", "e81889d80852a895cf62ce2e25181b15766d21e8647962e0a4458414b935feb3", [:mix], [{:plug, "~> 1.3", [hex: :plug, repo: "hexpm", optional: false]}, {:spandex, "~> 2.2", [hex: :spandex, repo: "hexpm", optional: false]}], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},

Loading…
Cancel
Save