mirror of
https://github.com/Retropex/bitfeed.git
synced 2025-05-12 19:20:46 +02:00
Fix prevout inflation concurrency bug
- Replaces Hackney with Finch for http requests, to improve concurrent RPC handling under high load - Rips out obsolete elixometer instrumentation - Adds handling for failed prevout inflation - Fail inflation step fast
This commit is contained in:
parent
12b75889c2
commit
dc6286bd39
@ -30,7 +30,6 @@ export default class BitcoinBlock {
|
||||
this.avgFeerate = 0
|
||||
this.txns.forEach(txn => {
|
||||
if (!BitcoinTx.prototype.isCoinbase(txn)) {
|
||||
if (txn.fee <= 0) console.log(txn)
|
||||
const txFeerate = txn.fee / txn.vbytes
|
||||
this.maxFeerate = Math.max(this.maxFeerate, txFeerate)
|
||||
this.minFeerate = Math.min(this.minFeerate, txFeerate)
|
||||
|
@ -3,8 +3,9 @@ import config from '../config.js'
|
||||
import { mixColor, pink, bluegreen, orange, teal, green, purple } from '../utils/color.js'
|
||||
|
||||
export default class BitcoinTx {
|
||||
constructor ({ version, id, value, fee, vbytes, inputs, outputs, time, block }, vertexArray) {
|
||||
constructor ({ version, inflated, id, value, fee, vbytes, inputs, outputs, time, block }, vertexArray) {
|
||||
this.version = version
|
||||
this.is_inflated = !!inflated
|
||||
this.id = id
|
||||
this.vertexArray = vertexArray
|
||||
this.pixelPosition = { x: 0, y: 0, r: 0}
|
||||
@ -27,7 +28,7 @@ export default class BitcoinTx {
|
||||
|
||||
// is a coinbase transaction?
|
||||
this.coinbase = this.isCoinbase(this)
|
||||
if (this.coinbase) {
|
||||
if (this.coinbase || !this.is_inflated || (this.fee < 0)) {
|
||||
this.fee = null
|
||||
this.feerate = null
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ RUN mix do deps.compile
|
||||
|
||||
COPY lib ./lib
|
||||
COPY log ./log
|
||||
COPY config ./config
|
||||
|
||||
ENV MIX_ENV prod
|
||||
ENV RELEASE_NODE bitfeed
|
||||
|
@ -1,70 +0,0 @@
|
||||
# This file is responsible for configuring your application
|
||||
# and its dependencies with the aid of the Mix.Config module.
|
||||
use Mix.Config
|
||||
|
||||
# This configuration is loaded before any dependency and is restricted
|
||||
# to this project. If another project depends on this project, this
|
||||
# file won't be loaded nor affect the parent project. For this reason,
|
||||
# if you want to provide default values for your application for
|
||||
# third-party users, it should be done in your "mix.exs" file.
|
||||
|
||||
# You can configure your application as:
|
||||
#
|
||||
# config :dep_project, key: :value
|
||||
#
|
||||
# and access this configuration in your application as:
|
||||
#
|
||||
# Application.get_env(:dep_project, :key)
|
||||
#
|
||||
# You can also configure a third-party app:
|
||||
#
|
||||
# config :logger, level: :info
|
||||
#
|
||||
|
||||
# It is also possible to import configuration files, relative to this
|
||||
# directory. For example, you can emulate configuration per environment
|
||||
# by uncommenting the line below and defining dev.exs, test.exs and such.
|
||||
# Configuration from the imported file will override the ones defined
|
||||
# here (which is why it is important to import them last).
|
||||
#
|
||||
# import_config "#{Mix.env()}.exs"
|
||||
|
||||
polling_interval = 1_000
|
||||
histogram_stats = ~w(min max mean 95 90)a
|
||||
memory_stats = ~w(atom binary ets processes total)a
|
||||
|
||||
config(
|
||||
:exometer_core,
|
||||
predefined: [
|
||||
{
|
||||
~w(erlang memory)a,
|
||||
{:function, :erlang, :memory, [], :proplist, memory_stats},
|
||||
[]
|
||||
},
|
||||
{
|
||||
~w(erlang statistics)a,
|
||||
{:function, :erlang, :statistics, [:'$dp'], :value, [:run_queue]},
|
||||
[]
|
||||
}
|
||||
],
|
||||
report: [
|
||||
reporters: [{BitcoinStream.ExometerReportDash, []}],
|
||||
subscribers: [
|
||||
{
|
||||
BitcoinStream.ExometerReportDash,
|
||||
[:erlang, :memory], memory_stats, polling_interval, true
|
||||
},
|
||||
{
|
||||
BitcoinStream.ExometerReportDash,
|
||||
[:erlang, :statistics], :run_queue, polling_interval, true
|
||||
},
|
||||
]
|
||||
]
|
||||
)
|
||||
config(
|
||||
:elixometer,
|
||||
reporter: BitcoinStream.ExometerReportDash,
|
||||
env: Mix.env,
|
||||
metric_prefix: "bitfeed_dash",
|
||||
excluded_datapoints: [:ms_since_reset]
|
||||
)
|
@ -1,11 +1,11 @@
|
||||
Application.ensure_all_started(:hackney)
|
||||
|
||||
defmodule BitcoinStream.RPC do
|
||||
@moduledoc """
|
||||
GenServer for bitcoin rpc requests
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
alias Plug.BasicAuth, as: BasicAuth
|
||||
|
||||
def start_link(opts) do
|
||||
{port, opts} = Keyword.pop(opts, :port);
|
||||
{host, opts} = Keyword.pop(opts, :host);
|
||||
@ -85,14 +85,13 @@ defmodule BitcoinStream.RPC do
|
||||
defp make_request(host, port, creds, method, params) do
|
||||
with { user, pw } <- creds,
|
||||
{:ok, rpc_request} <- Jason.encode(%{method: method, params: params}),
|
||||
{:ok, code, _headers, body_ref} <- :hackney.request(:post, "http://#{host}:#{port}", [{"content-type", "application/json"}], rpc_request, [basic_auth: { user, pw }]),
|
||||
{:ok, body} <- :hackney.body(body_ref),
|
||||
{:ok, %Finch.Response{body: body, headers: _headers, status: status}} <- Finch.build(:post, "http://#{host}:#{port}", [{"content-type", "application/json"}, {"authorization", BasicAuth.encode_basic_auth(user, pw)}], rpc_request) |> Finch.request(FinchClient),
|
||||
{:ok, %{"result" => info}} <- Jason.decode(body) do
|
||||
{:ok, code, info}
|
||||
{:ok, status, info}
|
||||
else
|
||||
{:ok, code, _} ->
|
||||
IO.puts("RPC request #{method} failed with HTTP code #{code}")
|
||||
{:error, code}
|
||||
{:ok, status, _} ->
|
||||
IO.puts("RPC request #{method} failed with HTTP code #{status}")
|
||||
{:error, status}
|
||||
{:error, reason} ->
|
||||
IO.puts("RPC request #{method} failed");
|
||||
IO.inspect(reason)
|
||||
|
@ -1,72 +0,0 @@
|
||||
Application.ensure_all_started(BitcoinStream.Metrics.SocketHandler)
|
||||
|
||||
defmodule BitcoinStream.ExometerReportDash do
|
||||
@behaviour :exometer_report
|
||||
|
||||
# Initializes the exometer_report with passed params
|
||||
# Requires :channel and :app_name options
|
||||
@impl true
|
||||
def exometer_init(opts) do
|
||||
{:ok, opts}
|
||||
end
|
||||
|
||||
# Converts the data passed by Exometer and relays it to the channel
|
||||
@impl true
|
||||
def exometer_report(metric, data_point, extra, value, opts) do
|
||||
id = name(metric, data_point)
|
||||
|
||||
metric_payload = %{
|
||||
key: id,
|
||||
val: value,
|
||||
extra: extra,
|
||||
timestamp: :os.system_time(:milli_seconds)
|
||||
}
|
||||
|
||||
if Process.whereis(Registry.BitcoinStream) != nil do
|
||||
if (Registry.lookup(Registry.BitcoinStream, "metrics")) == [] do
|
||||
Registry.BitcoinStream
|
||||
|> Registry.register("metrics", {})
|
||||
end
|
||||
|
||||
Registry.dispatch(Registry.BitcoinStream, "metrics", fn(entries) ->
|
||||
for {pid, _} <- entries do
|
||||
# IO.puts("Forwarding to pid #{inspect pid}")
|
||||
case Jason.encode(%{type: "metric", metric: metric_payload}) do
|
||||
{:ok, payload} -> Process.send(pid, payload, []);
|
||||
{:error, reason} -> IO.puts("Error json encoding reporter metric: #{reason}");
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
{:ok, opts}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def exometer_subscribe(_, _, _, _, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_unsubscribe(_, _, _, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_call(_, _, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_cast(_, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_info(_, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_newentry(_, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_setopts(_, _, _, opts), do: {:ok, opts}
|
||||
|
||||
@impl true
|
||||
def exometer_terminate(_, _), do: nil
|
||||
|
||||
defp name(metric, data_point) do
|
||||
Enum.join(metric, "_") <> "_" <> "#{data_point}"
|
||||
end
|
||||
end
|
@ -370,7 +370,12 @@ defmodule BitcoinStream.Mempool do
|
||||
{:ok, txn } <- BitcoinTx.decode(rawtx),
|
||||
inflated_txn <- BitcoinTx.inflate(txn) do
|
||||
register(pid, txid, nil, false);
|
||||
insert(pid, txid, inflated_txn)
|
||||
if inflated_txn.inflated do
|
||||
insert(pid, txid, inflated_txn)
|
||||
else
|
||||
IO.puts("failed to inflate loaded mempool txn #{txid}")
|
||||
end
|
||||
|
||||
else
|
||||
_ -> IO.puts("sync_mempool_txn failed #{txid}")
|
||||
end
|
||||
|
@ -1,28 +0,0 @@
|
||||
defmodule BitcoinStream.Metrics.Probe do
|
||||
use GenServer
|
||||
use Elixometer
|
||||
|
||||
def child_spec() do
|
||||
%{
|
||||
id: BitcoinStream.Metrics.Probe,
|
||||
start: {BitcoinStream.Metrics.Probe, :start_link, []}
|
||||
}
|
||||
end
|
||||
|
||||
def start_link(_) do
|
||||
IO.puts("Starting custom metrics probe");
|
||||
GenServer.start_link(__MODULE__, %{})
|
||||
end
|
||||
|
||||
def init(arg) do
|
||||
IO.puts("Initialising custom metrics probe");
|
||||
:timer.send_interval(1000, :tick);
|
||||
{:ok, arg}
|
||||
end
|
||||
|
||||
def handle_info(:tick, state) do
|
||||
connections = Registry.count(Registry.BitcoinStream);
|
||||
update_gauge("sockets", connections);
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
@ -1,31 +0,0 @@
|
||||
defmodule BitcoinStream.Metrics.SocketHandler do
|
||||
@behaviour :cowboy_websocket
|
||||
|
||||
use Elixometer
|
||||
|
||||
def init(request, state) do
|
||||
{:cowboy_websocket, request, state}
|
||||
end
|
||||
|
||||
def websocket_init(state) do
|
||||
Registry.BitcoinStream
|
||||
|> Registry.register("metrics", {})
|
||||
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
@timed(key: "timed.function")
|
||||
def websocket_handle({:text, msg}, state) do
|
||||
IO.puts("Metric message received: #{msg} | #{inspect self()}");
|
||||
case msg do
|
||||
"hb" -> {:reply, {:text, msg}, state};
|
||||
"metric" -> {:reply, {:text, "{ \"type\": \"metric\", \"metric\": { \"key\": \"x\", \"val\": 3.14 }}"}, state}
|
||||
_ ->
|
||||
{:reply, {:text, "?"}, state}
|
||||
end
|
||||
end
|
||||
|
||||
def websocket_info(info, state) do
|
||||
{:reply, {:text, info}, state}
|
||||
end
|
||||
end
|
@ -66,8 +66,12 @@ defp summarise_txns(txns) do
|
||||
end
|
||||
end
|
||||
|
||||
defp summarise_txns([], summarised, total, fees, _do_inflate) do
|
||||
{Enum.reverse(summarised), total, fees}
|
||||
defp summarise_txns([], summarised, total, fees, do_inflate) do
|
||||
if do_inflate do
|
||||
{Enum.reverse(summarised), total, fees}
|
||||
else
|
||||
{Enum.reverse(summarised), total, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp summarise_txns([next | rest], summarised, total, fees, do_inflate) do
|
||||
@ -76,9 +80,13 @@ defp summarise_txns([next | rest], summarised, total, fees, do_inflate) do
|
||||
# if the mempool is still syncing, inflating txs will take too long, so skip it
|
||||
if do_inflate do
|
||||
inflated_txn = BitcoinTx.inflate(extended_txn)
|
||||
summarise_txns(rest, [inflated_txn | summarised], total + inflated_txn.value, fees + inflated_txn.fee, true)
|
||||
if (inflated_txn.inflated) do
|
||||
summarise_txns(rest, [inflated_txn | summarised], total + inflated_txn.value, fees + inflated_txn.fee, true)
|
||||
else
|
||||
summarise_txns(rest, [inflated_txn | summarised], total + inflated_txn.value, nil, false)
|
||||
end
|
||||
else
|
||||
summarise_txns(rest, [extended_txn | summarised], nil, nil, false)
|
||||
summarise_txns(rest, [extended_txn | summarised], total + extended_txn.value, nil, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -18,6 +18,7 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
@derive Jason.Encoder
|
||||
defstruct [
|
||||
:version,
|
||||
:inflated,
|
||||
:vbytes,
|
||||
:inputs,
|
||||
:outputs,
|
||||
@ -38,6 +39,7 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
|
||||
{:ok, %__MODULE__{
|
||||
version: raw_tx.version,
|
||||
inflated: false,
|
||||
vbytes: raw_tx.vbytes,
|
||||
inputs: raw_tx.inputs,
|
||||
outputs: raw_tx.outputs,
|
||||
@ -60,6 +62,7 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
|
||||
%__MODULE__{
|
||||
version: txn.version,
|
||||
inflated: false,
|
||||
vbytes: txn.vbytes,
|
||||
inputs: txn.inputs,
|
||||
outputs: txn.outputs,
|
||||
@ -72,19 +75,37 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
end
|
||||
|
||||
def inflate(txn) do
|
||||
{inputs, in_value } = inflate_inputs(txn.id, txn.inputs);
|
||||
%__MODULE__{
|
||||
version: txn.version,
|
||||
vbytes: txn.vbytes,
|
||||
inputs: inputs,
|
||||
outputs: txn.outputs,
|
||||
value: txn.value,
|
||||
fee: (in_value - txn.value),
|
||||
# witnesses: txn.witnesses,
|
||||
lock_time: txn.lock_time,
|
||||
id: txn.id,
|
||||
time: txn.time
|
||||
}
|
||||
case inflate_inputs(txn.id, txn.inputs) do
|
||||
{:ok, inputs, in_value} ->
|
||||
%__MODULE__{
|
||||
version: txn.version,
|
||||
inflated: true,
|
||||
vbytes: txn.vbytes,
|
||||
inputs: inputs,
|
||||
outputs: txn.outputs,
|
||||
value: txn.value,
|
||||
fee: (in_value - txn.value),
|
||||
# witnesses: txn.witnesses,
|
||||
lock_time: txn.lock_time,
|
||||
id: txn.id,
|
||||
time: txn.time
|
||||
}
|
||||
|
||||
{:failed, inputs, _in_value} ->
|
||||
%__MODULE__{
|
||||
version: txn.version,
|
||||
inflated: false,
|
||||
vbytes: txn.vbytes,
|
||||
inputs: inputs,
|
||||
outputs: txn.outputs,
|
||||
value: txn.value,
|
||||
fee: 0,
|
||||
# witnesses: txn.witnesses,
|
||||
lock_time: txn.lock_time,
|
||||
id: txn.id,
|
||||
time: txn.time
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
defp count_value([], total) do
|
||||
@ -136,18 +157,18 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
end
|
||||
|
||||
defp inflate_inputs([], inflated, total) do
|
||||
{inflated, total}
|
||||
{:ok, Enum.reverse(inflated), total}
|
||||
end
|
||||
defp inflate_inputs([next_input | rest], inflated, total) do
|
||||
case inflate_input(next_input) do
|
||||
{:ok, inflated_txn} ->
|
||||
inflate_inputs(rest, [inflated_txn | inflated], total + inflated_txn.value)
|
||||
_ ->
|
||||
inflate_inputs(rest, [inflated], total)
|
||||
{:failed, Enum.reverse(inflated) ++ [next_input | rest], 0}
|
||||
end
|
||||
end
|
||||
def inflate_inputs([], nil) do
|
||||
{ nil, 0 }
|
||||
{ :failed, nil, 0 }
|
||||
end
|
||||
def inflate_inputs(txid, inputs) do
|
||||
case :ets.lookup(:mempool_cache, txid) do
|
||||
@ -160,8 +181,8 @@ defmodule BitcoinStream.Protocol.Transaction do
|
||||
inflate_inputs(inputs, [], 0)
|
||||
|
||||
# cache hit, just return the cached values
|
||||
[{_, input_state, _}] ->
|
||||
input_state
|
||||
[{_, {inputs, total}, _}] ->
|
||||
{:ok, inputs, total}
|
||||
|
||||
other ->
|
||||
IO.puts("unexpected cached value: ")
|
||||
|
@ -7,6 +7,8 @@ defmodule BitcoinStream.Server do
|
||||
{ zmq_block_port, "" } = Integer.parse(System.get_env("BITCOIN_ZMQ_RAWBLOCK_PORT"));
|
||||
{ zmq_sequence_port, "" } = Integer.parse(System.get_env("BITCOIN_ZMQ_SEQUENCE_PORT"));
|
||||
{ rpc_port, "" } = Integer.parse(System.get_env("BITCOIN_RPC_PORT"));
|
||||
{ rpc_pools, "" } = Integer.parse(System.get_env("RPC_POOLS"));
|
||||
{ rpc_pool_size, "" } = Integer.parse(System.get_env("RPC_POOL_SIZE"));
|
||||
btc_host = System.get_env("BITCOIN_HOST");
|
||||
|
||||
children = [
|
||||
@ -14,9 +16,14 @@ defmodule BitcoinStream.Server do
|
||||
keys: :duplicate,
|
||||
name: Registry.BitcoinStream
|
||||
),
|
||||
{Finch,
|
||||
name: FinchClient,
|
||||
pools: %{
|
||||
:default => [size: rpc_pool_size, count: rpc_pools],
|
||||
"http://#{btc_host}:#{rpc_port}" => [size: rpc_pool_size, count: rpc_pools]
|
||||
}},
|
||||
{ BitcoinStream.RPC, [host: btc_host, port: rpc_port, name: :rpc] },
|
||||
{ BitcoinStream.BlockData, [name: :block_data] },
|
||||
BitcoinStream.Metrics.Probe,
|
||||
Plug.Cowboy.child_spec(
|
||||
scheme: :http,
|
||||
plug: BitcoinStream.Router,
|
||||
@ -51,7 +58,6 @@ defmodule BitcoinStream.Server do
|
||||
{:_,
|
||||
[
|
||||
{"/ws/txs", BitcoinStream.SocketHandler, []},
|
||||
{"/ws/status", BitcoinStream.Metrics.SocketHandler, []},
|
||||
{:_, Plug.Cowboy.Handler, {BitcoinStream.Router, []}}
|
||||
]
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
defmodule BitcoinStream.SocketHandler do
|
||||
@behaviour :cowboy_websocket
|
||||
|
||||
use Elixometer
|
||||
|
||||
alias BitcoinStream.Mempool, as: Mempool
|
||||
alias BitcoinStream.BlockData, as: BlockData
|
||||
|
||||
@ -43,7 +41,6 @@ defmodule BitcoinStream.SocketHandler do
|
||||
"{ \"type\": \"block_id\", \"block_id\": \"#{last_id}\"}"
|
||||
end
|
||||
|
||||
@timed(key: "timed.function")
|
||||
def websocket_handle({:text, msg}, state) do
|
||||
# IO.puts("message received: #{msg} | #{inspect self()}");
|
||||
case msg do
|
||||
|
@ -21,7 +21,7 @@ defmodule BitcoinStream.MixProject do
|
||||
def application do
|
||||
[
|
||||
mod: {BitcoinStream.Server, []},
|
||||
extra_applications: [:logger, :elixometer, :corsica]
|
||||
extra_applications: [:logger, :corsica]
|
||||
]
|
||||
end
|
||||
|
||||
@ -36,13 +36,12 @@ defmodule BitcoinStream.MixProject do
|
||||
# {:bitcoinex, "~> 0.1.0"},
|
||||
# {:bitcoinex, git: "git@github.com:mononaut/bitcoinex.git", tag: "master"},
|
||||
{:bitcoinex, path: "./bitcoinex", override: true},
|
||||
{:hackney, "~> 1.15"},
|
||||
{:cowboy, "~> 2.4"},
|
||||
{:plug, "~> 1.7"},
|
||||
{:finch, "~> 0.10"},
|
||||
{:cowboy, "~> 2.7"},
|
||||
{:plug, "~> 1.13"},
|
||||
{:corsica, "~> 1.0"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
{:jason, "~> 1.1"},
|
||||
{:elixometer, "~> 1.4"}
|
||||
{:jason, "~> 1.1"}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
@ -1,35 +1,41 @@
|
||||
%{
|
||||
"bear": {:hex, :bear, "0.8.7", "16264309ae5d005d03718a5c82641fcc259c9e8f09adeb6fd79ca4271168656f", [:rebar3], [], "hexpm", "534217dce6a719d59e54fb0eb7a367900dbfc5f85757e8c1f94269df383f6d9b"},
|
||||
"castore": {:hex, :castore, "0.1.15", "dbb300827d5a3ec48f396ca0b77ad47058578927e9ebe792abd99fcbc3324326", [:mix], [], "hexpm", "c69379b907673c7e6eb229f09a0a09b60bb27cfb9625bcb82ea4c04ba82a8442"},
|
||||
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
|
||||
"chumak": {:hex, :chumak, "1.4.0", "79eb44ba2da1e2a072c06bca1c79016c936423c6b8f826d6a7c2e22e046a3d40", [:rebar3], [], "hexpm", "a3a618a2cae0e3f8e844752e7f6f56c6231c5daef1a8de498a5973baa202cc5c"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
"corsica": {:hex, :corsica, "1.1.3", "5f1de40bc9285753aa03afbdd10c364dac79b2ddbf2ba9c5c9c47b397ec06f40", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8156b3a14a114a346262871333a931a1766b2597b56bf994fcfcb65443a348ad"},
|
||||
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
|
||||
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
|
||||
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||
"decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"},
|
||||
"elixometer": {:hex, :elixometer, "1.4.0", "c16f5ac3f369bb1747de059a95e46f86e097d9adb3de5666e997922089c396d1", [:mix], [{:exometer_core, "~> 1.5", [hex: :exometer_core, repo: "hexpm", optional: false]}, {:lager, ">= 3.2.1", [hex: :lager, repo: "hexpm", optional: false]}, {:pobox, "~> 1.2", [hex: :pobox, repo: "hexpm", optional: false]}], "hexpm", "9cd6c8fca17600e3958bbea65c1274ac5baffa03d919b652bdf1f33b5aec64f2"},
|
||||
"exometer_core": {:hex, :exometer_core, "1.5.7", "ab97e34a5d69ab14e6ae161db4cca5b5e655e635b842f830ee6ab2cbfcfdc30a", [:rebar3], [{:folsom, "0.8.7", [hex: :folsom, repo: "hexpm", optional: false]}, {:hut, "1.2.1", [hex: :hut, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:setup, "2.0.2", [hex: :setup, repo: "hexpm", optional: false]}], "hexpm", "6afbd8f6b1aaf7443d6a5a05bbbcd15285622353550f3077c87176e25be99c1e"},
|
||||
"finch": {:hex, :finch, "0.10.2", "9ad27d68270d879f73f26604bb2e573d40f29bf0e907064a9a337f90a16a0312", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dd8b11b282072cec2ef30852283949c248bd5d2820c88d8acc89402b81db7550"},
|
||||
"folsom": {:hex, :folsom, "0.8.7", "a885f0aeee4c84270954c88a55a5a473d6b2c7493e32ffdc5765412dd555a951", [:rebar3], [{:bear, "0.8.7", [hex: :bear, repo: "hexpm", optional: false]}], "hexpm", "f7b644fc002a75af00b8bfbd3cc5c2bd955e09a118d2982d9a6c04e5646ff367"},
|
||||
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
|
||||
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"},
|
||||
"hackney": {:hex, :hackney, "1.17.1", "08463f93d2cc1a03817bf28d8dae6021543f773bd436c9377047224856c4422c", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "d2cba9e3c8103ad0320623e9f1c33e8d378a15eaabe2ee8ae441898f3d35a18c"},
|
||||
"hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"},
|
||||
"hut": {:hex, :hut, "1.2.1", "08d46679523043424870723923971889e8a34d63b2f946a35b46cf921d1236e7", [:"erlang.mk", :rebar, :rebar3], [], "hexpm", "953fc447514baf9cc79fa147d66469243c94dfa1593779614e070c692d0bf0f3"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
|
||||
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
|
||||
"lager": {:hex, :lager, "3.9.1", "5885bc71308cd38f9d025c8ecde4e5cce1ce8565f80bfc6199865c845d6dbe95", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "3f59ba75a04a99e5f18bf91c89f46dce536f83c6cb415fe26e6e75a62bef37dc"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
|
||||
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mint": {:hex, :mint, "1.4.1", "49b3b6ea35a9a38836d2ad745251b01ca9ec062f7cb66f546bf22e6699137126", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "cd261766e61011a9079cccf8fa9d826e7a397c24fbedf0e11b49312bea629b58"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
|
||||
"plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
|
||||
"plug": {:hex, :plug, "1.13.3", "93b299039c21a8b82cc904d13812bce4ced45cf69153e8d35ca16ffb3e8c5d98", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98c8003e4faf7b74a9ac41bee99e328b08f069bf932747d4a7532e97ae837a17"},
|
||||
"plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
|
||||
"pobox": {:hex, :pobox, "1.2.0", "3127cb48f13d18efec7a9ea2622077f4f9c5f067cc1182af1977dacd7a74fdb8", [:rebar3], [], "hexpm", "25d6fcdbe4fedbbf4bcaa459fadee006e75bb3281d4e6c9b2dc0ee93c51920c4"},
|
||||
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"setup": {:hex, :setup, "2.0.2", "1203f4cda11306c2e34434244576ded0a7bbfb0908d9a572356c809bd0cdf085", [:rebar3], [], "hexpm", "7d6aaf5281d0b0c40980e128f9dc410dacd03799a8577201d4c8b43e7f97509a"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
|
||||
"timex": {:hex, :timex, "3.7.5", "3eca56e23bfa4e0848f0b0a29a92fa20af251a975116c6d504966e8a90516dfd", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "a15608dca680f2ef663d71c95842c67f0af08a0f3b1d00e17bbd22872e2874e4"},
|
||||
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||
|
Loading…
Reference in New Issue
Block a user