Unverified Commit 9f422b83 authored by Evan Vigil-McClanahan's avatar Evan Vigil-McClanahan Committed by GitHub
Browse files

Merge pull request #804 from helium/mra/jsonrpc

Initial JSONRPC interface
parents a4e8fe83 f5ab4b94
Showing with 1229 additions and 3 deletions
+1229 -3
......@@ -18,6 +18,7 @@
]},
{miner,
[
{jsonrpc_port, 4467},
{use_ebus, false},
{block_time, 3000},
{election_interval, 15},
......
......@@ -86,6 +86,8 @@
]},
{miner,
[
{jsonrpc_ip, {127,0,0,1}}, %% bind JSONRPC to localhost only
{jsonrpc_port, 4467},
{mode, gateway},
{use_ebus, true},
{batch_size, 2500},
......
......@@ -23,6 +23,7 @@
{miner,
[
{mode, validator},
{jsonrpc_port, 4467},
{use_ebus, false},
{block_time, 500},
{election_interval, 10},
......
......@@ -21,7 +21,10 @@
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {branch, "v2.8.0"}}},
{kvc, {git, "https://github.com/etrepum/kvc", {tag, "v1.7.0"}}},
{longfi, {git, "https://github.com/helium/longfi-erlang", {tag, "0.2.0"}}},
recon
recon,
{elli, "3.3.0"},
{jsonrpc2, {git, "https://github.com/zuiderkwast/jsonrpc2-erlang",
{branch, "master"}}}
]}.
{xref_checks, [
......
......@@ -45,6 +45,7 @@
{ref,"c089c1591a6a0bc9a393e1cefceadb0b19ee8c59"}},
0},
{<<"ecc_compact">>,{pkg,<<"ecc_compact">>,<<"1.0.5">>},3},
{<<"elli">>,{pkg,<<"elli">>,<<"3.3.0">>},0},
{<<"enacl">>,{pkg,<<"enacl">>,<<"1.1.1">>},3},
{<<"erasure">>,
{git,"https://github.com/helium/erlang-erasure.git",
......@@ -119,6 +120,10 @@
{ref,"e30b65d32711a4b7033fd4ac9b33b3c1c8be8bed"}},
2},
{<<"intercept">>,{pkg,<<"intercept">>,<<"1.0.0">>},3},
{<<"jsonrpc2">>,
{git,"https://github.com/zuiderkwast/jsonrpc2-erlang",
{ref,"be47c447aba6b94f52fcc46e544664dbbc6cede0"}},
0},
{<<"jsx">>,
{git,"https://github.com/talentdeficit/jsx.git",
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
......@@ -182,6 +187,7 @@
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
{<<"ctx">>, <<"78E0F16712E12D707A7F34277381B8E193D7C71EAA24D37330DC02477C09EDA5">>},
{<<"ecc_compact">>, <<"C9696FF16A1D721F2DC8CCD760440B8F45586522974C5C7BD88640822E08AACA">>},
{<<"elli">>, <<"089218762A7FF3D20AE81C8E911BD0F73EE4EE0ED85454226D1FC6B4FFF3B4F6">>},
{<<"enacl">>, <<"F65DC64D9BFF2D8A534CB77AEF14DA5E7A2FA148987D87856F79A4745C9C2627">>},
{<<"erl_base58">>, <<"37710854461D71DF338E73C65776302DB41C4BAB4674D2EC134ED7BCFC7B5552">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>},
......@@ -210,6 +216,7 @@
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
{<<"ctx">>, <<"52554D80F9D93B3F82DBA8E1776E8EA87B0D6218AE94BA0F73D61AD1579AED08">>},
{<<"ecc_compact">>, <<"3DB649D21AE7FEDF460B4D650B99813B761DD905EED6286420C2B2C7E169A356">>},
{<<"elli">>, <<"698B13B33D05661DB9FE7EFCBA41B84825A379CCE86E486CF6AFF9285BE0CCF8">>},
{<<"enacl">>, <<"60D329AC3976008F774E21ABA254671104976D61A792287615BB26816F09EA0F">>},
{<<"erl_base58">>, <<"41E8EC356C5C5558A45682F61F80725789AE9A11BD1CC7D5C73CDE1E3B546DD2">>},
{<<"getopt">>, <<"53E1AB83B9CEB65C9672D3E7A35B8092E9BDC9B3EE80721471A161C10C59959C">>},
......
-module(miner_jsonrpc_accounts).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"account_get">>, #{ <<"address">> := Address }) ->
Ledger = blockchain:ledger(blockchain_worker:blockchain()),
GetBalance = fun() ->
case blockchain_ledger_v1:find_entry(Address, Ledger) of
{ok, Entry} ->
#{ balance => blockchain_ledger_entry_v1:balance(Entry),
nonce => blockchain_ledger_entry_v1:nonce(Entry)
};
_ ->
#{ balance => 0,
nonce => 0
}
end
end,
GetSecurities = fun() ->
case blockchain_ledger_v1:find_security_entry(Address, Ledger) of
{ok, Entry} ->
#{ sec_balance => blockchain_ledger_security_entry_v1:balance(Entry),
sec_nonce => blockchain_ledger_security_entry_v1:nonce(Entry)
};
_ ->
#{ sec_balance => 0,
sec_nonce => 0
}
end
end,
GetDCs = fun() ->
case blockchain_ledger_v1:find_dc_entry(Address, Ledger) of
{ok, Entry} ->
#{ dc_balance => blockchain_ledger_data_credits_entry_v1:balance(Entry),
dc_nonce => blockchain_ledger_data_credits_entry_v1:nonce(Entry)
};
_ ->
#{ dc_balance => 0,
dc_nonce => 0
}
end
end,
lists:foldl(fun(Fun, Map) ->
maps:merge(Map, Fun())
end,
#{
address => ?BIN_TO_B58(Address)
},
[GetBalance, GetSecurities, GetDCs]);
handle_rpc(<<"account_get">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
-module(miner_jsonrpc_blocks).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
-export([handle_rpc/2]).
handle_rpc(<<"block_height">>, []) ->
{ok, Height} = blockchain:height(blockchain_worker:blockchain()),
#{ height => Height };
handle_rpc(<<"block_height">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"block_get">>, #{ <<"height">> := Height }) when is_integer(Height) ->
lookup_block(Height);
handle_rpc(<<"block_get">>, #{ <<"hash">> := Hash }) when is_binary(Hash) ->
try
BinHash = ?B64_TO_BIN(Hash),
lookup_block(BinHash)
catch
_:_ -> ?jsonrpc_error({invalid_params, Hash})
end;
handle_rpc(<<"block_get">>, []) ->
{ok, Height} = blockchain:height(blockchain_worker:blockchain()),
lookup_block(Height);
handle_rpc(<<"block_get">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
lookup_block(BlockId) ->
case blockchain:get_block(BlockId, blockchain_worker:blockchain()) of
{ok, Block} ->
blockchain_block:to_json(Block, []);
{error, not_found} ->
?jsonrpc_error({not_found, "Block not found: ~p", [BlockId]});
{error, _}=Error ->
?jsonrpc_error(Error)
end.
-module(miner_jsonrpc_dkg).
-include("miner_jsonrpc.hrl").
-include_lib("blockchain/include/blockchain_utils.hrl").
-include_lib("blockchain/include/blockchain_vars.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"dkg_status">>, []) ->
Status = try
miner_consensus_mgr:dkg_status() of
Stat -> Stat
catch _:_ ->
<<"not_running">>
end,
#{ status => Status };
handle_rpc(<<"dkg_queue">>, []) ->
#{ inbound := Inbound,
outbound := Outbound } = miner:relcast_queue(dkg_queue),
Workers = miner:relcast_info(dkg_queue),
Outbound1 = maps:map(fun(K, V) ->
#{
address := Raw,
connected := Connected,
ready := Ready,
in_flight := InFlight,
connects := Connects,
last_take := LastTake,
last_ack := LastAck
} = maps:get(K, Workers),
#{
address => ?TO_B58(Raw),
name => ?TO_ANIMAL_NAME(Raw),
count => length(V),
connected => Connected,
blocked => not Ready,
in_flight => InFlight,
connects => Connects,
last_take => LastTake,
last_ack => erlang:system_time(seconds) - LastAck
}
end, Outbound),
#{ inbound => length(Inbound),
outbound => Outbound1 };
handle_rpc(<<"dkg_running">>, []) ->
Chain = blockchain_worker:blockchain(),
Ledger = blockchain:ledger(Chain),
{ok, Curr} = blockchain_ledger_v1:current_height(Ledger),
{ok, ElectionInterval} = blockchain:config(?election_interval, Ledger),
{ok, RestartInterval} = blockchain:config(?election_restart_interval, Ledger),
{ok, N} = blockchain:config(?num_consensus_members, Ledger),
#{start_height := EpochStart} = blockchain_election:election_info(Ledger),
Height = EpochStart + ElectionInterval,
Diff = Curr - Height,
Delay = max(0, (Diff div RestartInterval) * RestartInterval),
case Curr >= Height of
false ->
#{ error => <<"not running">> };
_ ->
{ok, Block} = blockchain:get_block(Height+Delay, Chain),
Hash = blockchain_block:hash_block(Block),
ConsensusAddrs = blockchain_election:new_group(Ledger, Hash, N, Delay),
[ #{ name => ?TO_ANIMAL_NAME(A),
address => ?TO_B58(A) }
|| A <- ConsensusAddrs ]
end;
handle_rpc(<<"dkg_next">>, []) ->
Chain = blockchain_worker:blockchain(),
Ledger = blockchain:ledger(Chain),
{ok, Curr} = blockchain_ledger_v1:current_height(Ledger),
{ok, ElectionInterval} = blockchain:config(?election_interval, Ledger),
{ok, RestartInterval} = blockchain:config(?election_restart_interval, Ledger),
#{start_height := EpochStart} = blockchain_election:election_info(Ledger),
Height = EpochStart + ElectionInterval,
Diff = Curr - Height,
Delay = max(0, (Diff div RestartInterval) * RestartInterval),
case Curr >= Height of
false ->
#{ running => false,
next_election_height => Height,
blocks_to_election => Height - Curr };
_ ->
NextRestart = Height + Delay + RestartInterval,
#{ running => true,
next_restart_height => NextRestart,
blocks_to_restart => NextRestart - Curr }
end;
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
-module(miner_jsonrpc_hbbft).
-include("miner_jsonrpc.hrl").
-include_lib("blockchain/include/blockchain_vars.hrl").
-include_lib("blockchain/include/blockchain_utils.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"hbbft_status">>, []) ->
miner:hbbft_status();
handle_rpc(<<"hbbft_skip">>, []) ->
Result = miner:hbbft_skip(),
#{result => Result};
handle_rpc(<<"hbbft_queue">>, []) ->
#{
inbound := Inbound,
outbound := Outbound
} = miner:relcast_queue(consensus_group),
Workers = miner:relcast_info(consensus_group),
Outbound1 = maps:map(
fun(K, V) ->
#{
address := Raw,
connected := Connected,
ready := Ready,
in_flight := InFlight,
connects := Connects,
last_take := LastTake,
last_ack := LastAck
} = maps:get(K, Workers),
#{
address => ?TO_B58(Raw),
name => ?TO_ANIMAL_NAME(Raw),
count => length(V),
connected => Connected,
blocked => not Ready,
in_flight => InFlight,
connects => Connects,
last_take => LastTake,
last_ack => erlang:system_time(seconds) - LastAck
}
end,
Outbound
),
#{
inbound => length(Inbound),
outbound => Outbound1
};
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
-module(miner_jsonrpc_info).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"info_height">>, []) ->
Chain = blockchain_worker:blockchain(),
{ok, Height} = blockchain:height(Chain),
{ok, SyncHeight} = blockchain:sync_height(Chain),
{ok, HeadBlock} = blockchain:head_block(Chain),
{Epoch, _} = blockchain_block_v1:election_info(HeadBlock),
Output = #{
epoch => Epoch,
height => Height
},
case SyncHeight == Height of
true -> Output;
false -> Output#{sync_height => SyncHeight}
end;
handle_rpc(<<"info_in_consensus">>, []) ->
#{in_consensus => miner_consensus_mgr:in_consensus()};
handle_rpc(<<"info_name">>, []) ->
#{name => ?BIN_TO_ANIMAL(blockchain_swarm:pubkey_bin())};
handle_rpc(<<"info_block_age">>, []) ->
#{block_age => miner:block_age()};
handle_rpc(<<"info_p2p_status">>, []) ->
maps:from_list(miner:p2p_status());
handle_rpc(<<"info_region">>, []) ->
R =
case miner_lora:region() of
{ok, undefined} -> <<"undefined">>;
{ok, Region} -> atom_to_binary(Region, utf8)
end,
#{region => R};
%% TODO handle onboarding key data??
handle_rpc(<<"info_summary">>, []) ->
PubKey = blockchain_swarm:pubkey_bin(),
Chain = blockchain_worker:blockchain(),
%% get gateway info
MinerName = ?BIN_TO_ANIMAL(PubKey),
Macs = get_mac_addrs(),
BlockAge = miner:block_age(),
Uptime = get_uptime(),
FirmwareVersion = get_firmware_version(),
GWInfo = get_gateway_info(Chain, PubKey),
% get height data
{ok, Height} = blockchain:height(Chain),
{ok, SyncHeight} = blockchain:sync_height(Chain),
%% get epoch
{ok, HeadBlock} = blockchain:head_block(Chain),
{Epoch, _} = blockchain_block_v1:election_info(HeadBlock),
%% get peerbook count
Swarm = blockchain_swarm:swarm(),
Peerbook = libp2p_swarm:peerbook(Swarm),
PeerBookEntryCount = length(libp2p_peerbook:values(Peerbook)),
#{
name => MinerName,
mac_addresses => Macs,
block_age => BlockAge,
epoch => Epoch,
height => Height,
sync_height => SyncHeight,
uptime => Uptime,
peer_book_entry_count => PeerBookEntryCount,
firmware_version => FirmwareVersion,
gateway_details => GWInfo
};
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
%% internal
get_mac_addrs() ->
{ok, IFs} = inet:getifaddrs(),
Macs = format_macs_from_interfaces(IFs),
Macs.
get_firmware_version() ->
iolist_to_binary(os:cmd("cat /etc/lsb_release")).
get_uptime() ->
%% returns seconds of uptime
{UpTimeMS, _} = statistics(wall_clock),
(UpTimeMS div 1000) rem 1000000.
get_gateway_info(Chain, PubKey) ->
Ledger = blockchain:ledger(Chain),
case blockchain_ledger_v1:find_gateway_info(PubKey, Ledger) of
{ok, Gateway} ->
GWLoc = blockchain_ledger_gateway_v2:location(Gateway),
GWOwnAddr = libp2p_crypto:pubkey_bin_to_p2p(
blockchain_ledger_gateway_v2:owner_address(Gateway)
),
#{ <<"location">> => ?TO_VALUE(GWLoc),
<<"owner">> => ?TO_VALUE(GWOwnAddr) };
_ ->
undefined
end.
format_macs_from_interfaces(IFs) ->
lists:foldl(
fun({IFName, Prop}, Acc) ->
case proplists:get_value(hwaddr, Prop) of
undefined -> Acc;
HWAddr -> [ #{ ?TO_KEY(IFName) => ?TO_VALUE(format_hwaddr(HWAddr)) } | Acc]
end
end,
[],
IFs
).
format_hwaddr(HWAddr) ->
string:to_upper(blockchain_utils:bin_to_hex(list_to_binary(HWAddr))).
-module(miner_jsonrpc_ledger).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"ledger_balance">>, []) ->
%% get all
Entries = maps:filter(
fun(K, _V) ->
is_binary(K)
end,
blockchain_ledger_v1:entries(get_ledger())
),
[format_ledger_balance(A, E) || {A, E} <- maps:to_list(Entries)];
handle_rpc(<<"ledger_balance">>, #{<<"address">> := Address}) ->
%% get for address
try
BinAddr = ?B58_TO_BIN(Address),
case blockchain_ledger_v1:find_entry(BinAddr, get_ledger()) of
{error, not_found} -> #{Address => <<"not_found">>};
{ok, Entry} -> format_ledger_balance(BinAddr, Entry)
end
catch
_:_ -> ?jsonrpc_error({invalid_params, Address})
end;
handle_rpc(<<"ledger_balance">>, #{<<"htlc">> := true}) ->
%% get htlc
H = maps:filter(
fun(K, _V) -> is_binary(K) end,
blockchain_ledger_v1:htlcs(get_ledger())
),
maps:fold(
fun(Addr, Htlc, Acc) ->
[
#{
address => ?BIN_TO_B58(Addr),
payer => ?BIN_TO_B58(blockchain_ledger_htlc_v1:payer(Htlc)),
payee => ?BIN_TO_B58(blockchain_ledger_htlc_v1:payee(Htlc)),
hashlock => blockchain_utils:bin_to_hex(
blockchain_ledger_htlc_v1:hashlock(Htlc)
),
timelock => blockchain_ledger_htlc_v1:timelock(Htlc),
amount => blockchain_ledger_htlc_v1:amount(Htlc)
}
| Acc
]
end,
[],
H
);
handle_rpc(<<"ledger_balance">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"ledger_gateways">>, []) ->
handle_rpc(<<"ledger_gateways">>, #{<<"verbose">> => false});
handle_rpc(<<"ledger_gateways">>, #{<<"verbose">> := Verbose}) ->
L = get_ledger(),
{ok, Height} = blockchain_ledger_v1:current_height(L),
blockchain_ledger_v1:cf_fold(
active_gateways,
fun({Addr, BinGw}, Acc) ->
GW = blockchain_ledger_gateway_v2:deserialize(BinGw),
[format_ledger_gateway_entry(Addr, GW, Height, Verbose) | Acc]
end,
[],
L
);
handle_rpc(<<"ledger_gateways">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"ledger_validators">>, []) ->
handle_rpc(<<"ledger_validators">>, #{<<"verbose">> => false});
handle_rpc(<<"ledger_validators">>, #{<<"verbose">> := Verbose}) ->
Ledger = get_ledger(),
{ok, Height} = blockchain_ledger_v1:current_height(Ledger),
blockchain_ledger_v1:cf_fold(
validators,
fun({Addr, BinVal}, Acc) ->
Val = blockchain_ledger_validator_v1:deserialize(BinVal),
[format_ledger_validator(Addr, Val, Ledger, Height, Verbose) | Acc]
end,
[],
Ledger
);
handle_rpc(<<"ledger_validators">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"ledger_variables">>, []) ->
Vars = blockchain_ledger_v1:snapshot_vars(get_ledger()),
lists:foldl(fun({K, V}, Acc) ->
BinK = ?TO_KEY(K),
BinV = ?TO_VALUE(V),
Acc#{ BinK => BinV }
end, #{}, Vars);
handle_rpc(<<"ledger_variables">>, #{ <<"name">> := Name }) ->
try
NameAtom = binary_to_existing_atom(Name, utf8),
case blockchain_ledger_v1:config(NameAtom, get_ledger()) of
{ok, Var} ->
#{ Name => ?TO_VALUE(Var) };
{error, not_found} ->
#{ error => not_found}
end
catch
error:badarg ->
?jsonrpc_error({invalid_params, Name})
end;
handle_rpc(<<"ledger_variables">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
get_ledger() ->
case blockchain_worker:blockchain() of
undefined ->
blockchain_ledger_v1:new("data");
Chain ->
blockchain:ledger(Chain)
end.
format_ledger_balance(Addr, Entry) ->
#{
address => ?BIN_TO_B58(Addr),
nonce => blockchain_ledger_entry_v1:nonce(Entry),
balance => blockchain_ledger_entry_v1:balance(Entry)
}.
format_ledger_gateway_entry(Addr, GW, Height, Verbose) ->
GWAddr = ?BIN_TO_B58(Addr),
O = #{
name => iolist_to_binary(blockchain_utils:addr2name(GWAddr)),
address => GWAddr,
owner_address => ?BIN_TO_B58(blockchain_ledger_gateway_v2:owner_address(GW)),
location => ?MAYBE(blockchain_ledger_gateway_v2:location(GW)),
last_challenge => last_challenge(
Height,
blockchain_ledger_gateway_v2:last_poc_challenge(GW)
),
nonce => blockchain_ledger_gateway_v2:nonce(GW)
},
case Verbose of
false ->
O;
true ->
O#{
elevation => ?MAYBE(blockchain_ledger_gateway_v2:elevation(GW)),
gain => ?MAYBE(blockchain_ledger_gateway_v2:gain(GW)),
mode => ?MAYBE(blockchain_ledger_gateway_v2:mode(GW))
}
end.
last_challenge(_Height, undefined) -> <<"undefined">>;
last_challenge(Height, LC) -> Height - LC.
format_ledger_validator(Addr, Val, Ledger, Height, Verbose) ->
maps:from_list([
{name, blockchain_utils:addr2name(Addr)}
| blockchain_ledger_validator_v1:print(Val, Height, Verbose, Ledger)
]).
-module(miner_jsonrpc_peer).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"peer_session">>, []) ->
Swarm = blockchain_swarm:swarm(),
format_peer_sessions(Swarm);
handle_rpc(<<"peer_listen">>, []) ->
SwarmTID = blockchain_swarm:tid(),
Addrs = libp2p_swarm:listen_addrs(SwarmTID),
format_listen_addrs(SwarmTID, Addrs);
handle_rpc(<<"peer_addr">>, []) ->
#{
<<"peer_addr">> =>
?TO_VALUE(libp2p_crypto:pubkey_bin_to_p2p(blockchain_swarm:pubkey_bin()))
};
handle_rpc(<<"peer_connect">>, #{<<"addr">> := Addr}) when is_list(Addr) ->
TID = blockchain_swarm:tid(),
[connect_peer(TID, A) || A <- Addr];
handle_rpc(<<"peer_connect">>, #{<<"addr">> := Addr}) ->
TID = blockchain_swarm:tid(),
connect_peer(TID, Addr);
handle_rpc(<<"peer_connect">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"peer_ping">>, #{<<"addr">> := Addr}) when is_list(Addr) ->
TID = blockchain_swarm:tid(),
[ping_peer(TID, A) || A <- Addr];
handle_rpc(<<"peer_ping">>, #{<<"addr">> := Addr}) ->
TID = blockchain_swarm:tid(),
ping_peer(TID, Addr);
handle_rpc(<<"peer_ping">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"peer_book">>, #{<<"addr">> := <<"self">>}) ->
peer_book_response(self);
handle_rpc(<<"peer_book">>, #{<<"addr">> := <<"all">>}) ->
peer_book_response(all);
handle_rpc(<<"peer_book">>, #{<<"addr">> := Addr}) ->
peer_book_response(Addr);
handle_rpc(<<"peer_book">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"peer_gossip_peers">>, []) ->
[ ?TO_VALUE(A) || A <- blockchain_swarm:gossip_peers() ];
handle_rpc(<<"peer_gossip_peers">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"peer_refresh">>, #{ <<"addr">> := Addr}) ->
do_peer_refresh(Addr);
handle_rpc(<<"peer_refresh">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
%%
%% Internal
%%
connect_peer(TID, Addr) when is_binary(Addr) ->
connect_peer(TID, binary_to_list(Addr));
connect_peer(TID, Addr) when is_list(Addr) ->
case libp2p_swarm:connect(TID, Addr) of
{ok, _} ->
#{<<"connected">> => true};
{error, _} = Error ->
#{
<<"connected">> => false,
<<"error">> => ?TO_VALUE(Error)
}
end.
ping_peer(TID, Addr) when is_binary(Addr) ->
ping_peer(TID, binary_to_list(Addr));
ping_peer(TID, Addr) when is_list(Addr) ->
case libp2p_swarm:connect(TID, Addr) of
{ok, Session} ->
case libp2p_session:ping(Session) of
{ok, RTT} ->
Addr = ?TO_KEY(Addr),
#{Addr => RTT};
{error, _} = Error ->
Addr = ?TO_KEY(Addr),
#{
Addr => -1,
<<"error">> => ?TO_VALUE(Error)
}
end;
{error, _} = Err ->
#{<<"error">> => ?TO_VALUE(Err)}
end.
peer_book_response(Target) ->
TID = blockchain_swarm:tid(),
Peerbook = libp2p_swarm:peerbook(TID),
%% Always return a list here, even when there's just
%% a single entry
case Target of
all ->
[ format_peer(P) || P <- libp2p_peerbook:values(Peerbook) ];
self ->
{ok, Peer} = libp2p_peerbook:get(Peerbook, blockchain_swarm:pubkey_bin()),
[ lists:foldl(fun(M, Acc) -> maps:merge(Acc, M) end,
format_peer(Peer),
[format_listen_addrs(TID, libp2p_peer:listen_addrs(Peer)),
format_peer_sessions(TID)]
) ];
Addrs when is_list(Addrs) ->
[begin
{ok, P} = libp2p_peerbook:get(Peerbook, A),
lists:foldl(fun(M, Acc) -> maps:merge(Acc, M) end,
format_peer(P),
[format_listen_addrs(TID, libp2p_peer:listen_addrs(P)),
format_peer_connections(P)])
end || A <- Addrs ];
Addr ->
{ok, P} = libp2p_peerbook:get(Peerbook, Addr),
[ lists:foldl(fun(M, Acc) -> maps:merge(Acc, M) end,
format_peer(P),
[format_listen_addrs(TID, libp2p_peer:listen_addrs(P)),
format_peer_connections(P)]) ]
end.
format_peer(Peer) ->
ListenAddrs = libp2p_peer:listen_addrs(Peer),
ConnectedTo = libp2p_peer:connected_peers(Peer),
NatType = libp2p_peer:nat_type(Peer),
Timestamp = libp2p_peer:timestamp(Peer),
Bin = libp2p_peer:pubkey_bin(Peer),
M = #{
<<"address">> => libp2p_crypto:pubkey_bin_to_p2p(Bin),
<<"name">> => ?BIN_TO_ANIMAL(Bin),
<<"listen_addr_count">> => length(ListenAddrs),
<<"connection_count">> => length(ConnectedTo),
<<"nat">> => NatType,
<<"last_updated">> => (erlang:system_time(millisecond) - Timestamp) / 1000
},
maps:map(fun(_K, V) -> ?TO_VALUE(V) end, M).
format_peer_connections(Peer) ->
#{
<<"connections">> => [
?TO_VALUE(libp2p_crypto:pubkey_bin_to_p2p(P))
|| P <- libp2p_peer:connected_peers(Peer)
]
}.
format_listen_addrs(TID, Addrs) ->
libp2p_transport:sort_addrs(TID, Addrs),
#{<<"listen_addresses">> => [?TO_VALUE(A) || A <- Addrs]}.
format_peer_sessions(Swarm) ->
SessionInfos = libp2p_swarm:sessions(Swarm),
Rs = lists:filtermap(
fun({A, S}) ->
case multiaddr:protocols(A) of
[{"p2p", B58}] ->
{true, {A, libp2p_session:addr_info(libp2p_swarm:tid(Swarm), S), B58}};
_ ->
false
end
end,
SessionInfos
),
FormatEntry = fun({MA, {SockAddr, PeerAddr}, B58}) ->
M = #{
<<"local">> => SockAddr,
<<"remote">> => PeerAddr,
<<"p2p">> => MA,
<<"name">> => ?B58_TO_ANIMAL(B58)
},
maps:map(fun(_K, V) -> ?TO_VALUE(V) end, M)
end,
#{ <<"sessions">> => [FormatEntry(E) || E <- Rs] }.
do_peer_refresh(Addr) when is_list(Addr) ->
TID = blockchain_swarm:tid(),
Peerbook = libp2p_swarm:peerkbook(TID),
[ #{ A => do_refresh(Peerbook, A) } ||
A <- Addr ];
do_peer_refresh(Addr) ->
do_peer_refresh([Addr]).
do_refresh(Peerbook, A) ->
libp2p_peerbook:refresh(Peerbook, libp2p_crypto:p2p_to_pubkey_bin(A)).
-module(miner_jsonrpc_sc).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"sc_active">>, []) ->
case (catch blockchain_state_channels_server:active_sc_id()) of
{'EXIT', _} -> ?jsonrpc_error(timeout);
undefined -> #{<<"active">> => <<"none">>};
BinId -> #{<<"active">> => ?TO_VALUE(base64:encode(BinId))}
end;
handle_rpc(<<"sc_active">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(<<"sc_list">>, []) ->
case (catch blockchain_state_channels_server:state_channels()) of
{'EXIT', _} -> ?jsonrpc_error(timeout);
undefined -> #{<<"channels">> => <<"none">>};
SCs -> format_sc_list(SCs)
end;
handle_rpc(<<"sc_list">>, Params) ->
?jsonrpc_error({invalid_params, Params});
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
%%
%% Internal
%%
format_sc_list(SCs) ->
{ok, Height} = blockchain:height(blockchain_worker:blockchain()),
maps:fold(fun(_SCID, {SC, _Skew}, Acc) ->
#{ expire_at_block := ExpireAt } = Json
= blockchain_state_channel_v1:to_json(SC),
[ Json#{ <<"is_active">> => is_active(SC),
<<"expired">> => Height >= ExpireAt } | Acc ]
end, [], SCs).
is_active(SC) ->
ActiveSCID = blockchain_state_channels_server:active_sc_id(),
case blockchain_state_channel_v1:id(SC) of
ActiveSCID -> true;
_ -> false
end.
-module(miner_jsonrpc_snapshot).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
%% TODO: add an optional start block height parameter
handle_rpc(<<"snapshot_list">>, []) ->
Chain = blockchain_worker:blockchain(),
case blockchain:find_last_snapshots(Chain, 5) of
undefined -> #{ error => <<"no snapshots found">> };
Snapshots ->
[ #{ height => Height,
hash => ?BIN_TO_B64(Hash),
hash_hex => blockchain_utils:bin_to_hex(Hash),
have_snapshot => have_snapshot(Hash, Chain) } ||
{Height, _, Hash} <- Snapshots ]
end;
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
%%
%% Internal
%%
have_snapshot(Hash, Chain) ->
case blockchain:get_snapshot(Hash, Chain) of
{ok, _Snap} -> true;
_ -> false
end.
-module(miner_jsonrpc_txn).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
%% jsonrpc_handler
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
handle_rpc(<<"txn_queue">>, []) ->
case (catch blockchain_txn_mgr:txn_list()) of
{'EXIT', _} -> #{ error => <<"timeout">> };
[] -> [];
Txns ->
maps:fold(fun(T, D, Acc) ->
Type = blockchain_txn:type(T),
Hash = blockchain_txn:hash(T),
Accepts = proplists:get_value(acceptions, D, []),
Rejects = proplists:get_value(rejections, D, []),
AcceptHeight = proplists:get_value(recv_block_height, D, undefined),
[ #{ type => Type,
hash => ?BIN_TO_B64(Hash),
accepts => length(Accepts),
rejections => length(Rejects),
accepted_height => AcceptHeight } | Acc ]
end, [], Txns)
end;
handle_rpc(<<"txn_add_gateway">>, #{ <<"owner">> := OwnerB58 } = Params) ->
try
Payer = case maps:get(payer, Params, undefined) of
undefined -> undefined;
PayerB58 -> ?B58_TO_BIN(PayerB58)
end,
{ok, Bin} = blockchain:add_gateway_txn(?B58_TO_BIN(OwnerB58), Payer),
B64 = base64:encode(Bin),
#{ <<"result">> => B64 }
catch
T:E:St ->
lager:error("Couldn't do add gateway via JSONRPC because: ~p ~p: ~p",
[T, E, St]),
Error = io_lib:format("~p", [E]),
#{ <<"error">> => Error }
end;
handle_rpc(<<"txn_assert_location">>, #{ <<"owner">> := OwnerB58 } = Params) ->
try
Payer = case maps:get(payer, Params, undefined) of
undefined -> undefined;
PayerB58 -> ?B58_TO_BIN(PayerB58)
end,
H3String = case parse_location(Params) of
{error, _} = Err -> throw(Err);
{ok, S} -> S
end,
Nonce = maps:get(nonce, Params, 1),
{ok, Bin} = blockchain:assert_loc_txn(H3String, ?B58_TO_BIN(OwnerB58),
Payer, Nonce),
B64 = base64:encode(Bin),
#{ <<"result">> => B64 }
catch
T:E:St ->
lager:error("Couldn't complete assert location JSONRPC because ~p ~p: ~p",
[T, E, St]),
Error = io_lib:format("~p", [E]),
#{ <<"error">> => Error }
end;
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
parse_location(#{ <<"h3">> := H3 }) ->
try
%% h3 literally expects a string, not a binary string, so...
H3Str = binary_to_list(H3),
h3:from_string(H3Str),
{ok, H3Str}
catch
_:_ ->
{error, {invalid_location, H3}}
end;
parse_location(#{ <<"lat">> := LatIn,
<<"lon">> := LonIn }) ->
try
Lat = binary_to_float(LatIn),
Lon = binary_to_float(LonIn),
h3:to_string(h3:from_geo({Lat, Lon}, 12))
catch
_:_ ->
{error, {invalid_location, {LatIn, LonIn}}}
end;
parse_location(_Other) -> {error, no_valid_location_found}.
-module(miner_jsonrpc_txns).
-include("miner_jsonrpc.hrl").
-behavior(miner_jsonrpc_handler).
-define(MAX_LOOKBACK, 25). %% only search the previous 25 blocks
-export([handle_rpc/2]).
%%
%% jsonrpc_handler
%%
%% TODO: add an optional start block height parameter
handle_rpc(<<"transaction_get">>, #{ <<"hash">> := Hash }) ->
try
BinHash = ?B64_TO_BIN(Hash),
case get_transaction(BinHash) of
{ok, {Height, Txn}} ->
Json = blockchain_txn:to_json(Txn, []),
Json#{block => Height};
{error, not_found} ->
?jsonrpc_error({not_found, "No transaction: ~p", [Hash]})
end
catch
_:_ ->
?jsonrpc_error({invalid_params, Hash})
end;
handle_rpc(_, _) ->
?jsonrpc_error(method_not_found).
%%
%% Internal
%%
get_transaction(TxnHash) ->
Chain = blockchain_worker:blockchain(),
HeadBlock = blockchain:head_block(Chain),
HeadHeight = blockchain_block:height(HeadBlock),
case blockchain:fold_chain(fun(B, Acc) -> find_txn(B, TxnHash, HeadHeight, Acc) end,
{HeadHeight, undefined},
HeadBlock,
Chain) of
{_H, undefined} -> {error, not_found};
{H, Txn} -> {ok, {H, Txn}}
end.
find_txn(_Blk, _TxnHash, Start, {CH, undefined}) when Start - CH > ?MAX_LOOKBACK -> return;
find_txn(Blk, TxnHash, _Start, {_CH, undefined}) ->
NewHeight = blockchain_block:height(Blk),
Txns = blockchain_block:transactions(Blk),
case lists:filter(fun(T) -> blockchain_txn:hash(T) == TxnHash end, Txns) of
[] -> {NewHeight, undefined};
[Match] -> {NewHeight, Match}
end;
find_txn(_Blk, _TxnHash, _Start, _Acc) -> return.
......@@ -26,7 +26,9 @@
xmerl, %% nat_upnp has a missing dep
jsx,
kvc,
longfi
longfi,
elli,
jsonrpc2
]},
{included_applications, [blockchain, sibyl]},
{env,[]},
......
-define(jsonrpc_b58_to_bin(K, P), miner_jsonrpc_handler:jsonrpc_b58_to_bin((K), (P))).
-define(jsonrpc_b64_to_bin(K, P), miner_jsonrpc_handler:jsonrpc_b64_to_bin((K), (P))).
-define(jsonrpc_error(E), miner_jsonrpc_handler:jsonrpc_error((E))).
-define(BIN_TO_B58(B), list_to_binary(libp2p_crypto:bin_to_b58((B)))).
-define(B58_TO_BIN(B), libp2p_crypto:b58_to_bin(binary_to_list((B)))).
-define(BIN_TO_B64(B), base64url:encode((B))).
-define(B64_TO_BIN(B), base64url:decode((B))).
-define(TO_KEY(K), miner_jsonrpc_handler:to_key(K)).
-define(TO_VALUE(V), miner_jsonrpc_handler:to_value(V)).
-define(B58_TO_ANIMAL(V), iolist_to_binary(element(2, erl_angry_purple_tiger:animal_name(V)))).
-define(BIN_TO_ANIMAL(V),
iolist_to_binary(
element(2, erl_angry_purple_tiger:animal_name(?BIN_TO_B58(V)))
)
).
-define(MAYBE(X), miner_jsonrpc_handler:jsonrpc_maybe(X)).
-module(miner_jsonrpc_handler).
-callback handle_rpc(Method :: binary(), Params :: term()) -> jsx:json_term().
-export([handle/2, handle_event/3]).
-export([
jsonrpc_b58_to_bin/2,
jsonrpc_b64_to_bin/2,
jsonrpc_get_param/2,
jsonrpc_get_param/3,
jsonrpc_error/1,
to_key/1,
to_value/1,
jsonrpc_maybe/1
]).
-include("miner_jsonrpc.hrl").
-include_lib("elli/include/elli.hrl").
-behaviour(elli_handler).
handle(Req, _Args) ->
%% Delegate to our handler function
handle(Req#req.method, elli_request:path(Req), Req).
handle('POST', _, Req) ->
Json = elli_request:body(Req),
{reply, Reply} =
jsonrpc2:handle(Json, fun handle_rpc/2, fun decode_helper/1, fun encode_helper/1),
{ok, [], Reply};
handle(_, _, _Req) ->
{404, [], <<"Not Found">>}.
handle_rpc(Method, Params) ->
lager:info("Dispatching method ~p with params: ~p", [Method, Params]),
handle_rpc_(Method, format_params(Params)).
handle_rpc_(<<"block_", _/binary>> = Method, Params) ->
miner_jsonrpc_blocks:handle_rpc(Method, Params);
handle_rpc_(<<"transaction_", _/binary>> = Method, Params) ->
miner_jsonrpc_txns:handle_rpc(Method, Params);
handle_rpc_(<<"account_", _/binary>> = Method, Params) ->
miner_jsonrpc_accounts:handle_rpc(Method, Params);
handle_rpc_(<<"info_", _/binary>> = Method, Params) ->
miner_jsonrpc_info:handle_rpc(Method, Params);
handle_rpc_(<<"dkg_", _/binary>> = Method, Params) ->
miner_jsonrpc_dkg:handle_rpc(Method, Params);
handle_rpc_(<<"hbbft_", _/binary>> = Method, Params) ->
miner_jsonrpc_hbbft:handle_rpc(Method, Params);
handle_rpc_(<<"txn_", _/binary>> = Method, Params) ->
miner_jsonrpc_txn:handle_rpc(Method, Params);
handle_rpc_(<<"ledger_", _/binary>> = Method, Params) ->
miner_jsonrpc_ledger:handle_rpc(Method, Params);
handle_rpc_(<<"snapshot_", _/binary>> = Method, Params) ->
miner_jsonrpc_snapshot:handle_rpc(Method, Params);
handle_rpc_(<<"sc_", _/binary>> = Method, Params) ->
miner_jsonrpc_sc:handle_rpc(Method, Params);
handle_rpc_(<<"peer_", _/binary>> = Method, Params) ->
miner_jsonrpc_peer:handle_rpc(Method, Params);
handle_rpc_(_, _) ->
?jsonrpc_error(method_not_found).
%% @doc Handle request events, like request completed, exception
%% thrown, client timeout, etc. Must return `ok'.
handle_event(request_throw, [Req, Exception, Stack], _Config) ->
lager:error("exception: ~p~nstack: ~p~nrequest: ~p~n", [
Exception,
Stack,
elli_request:to_proplist(Req)
]),
ok;
handle_event(request_exit, [Req, Exit, Stack], _Config) ->
lager:error("exit: ~p~nstack: ~p~nrequest: ~p~n", [
Exit,
Stack,
elli_request:to_proplist(Req)
]),
ok;
handle_event(request_error, [Req, Error, Stack], _Config) ->
lager:error("error: ~p~nstack: ~p~nrequest: ~p~n", [
Error,
Stack,
elli_request:to_proplist(Req)
]),
ok;
handle_event(_, _, _) ->
ok.
jsonrpc_get_param(Key, PropList) ->
case proplists:get_value(Key, PropList, false) of
false -> ?jsonrpc_error(invalid_params);
V -> V
end.
jsonrpc_get_param(Key, PropList, Default) ->
proplists:get_value(Key, PropList, Default).
jsonrpc_b58_to_bin(Key, PropList) ->
B58 = jsonrpc_get_param(Key, PropList),
try
?B58_TO_BIN(B58)
catch
_:_ -> ?jsonrpc_error(invalid_params)
end.
jsonrpc_b64_to_bin(Key, PropList) ->
B64 = jsonrpc_get_param(Key, PropList),
try
?B64_TO_BIN(B64)
catch
_:_ -> ?jsonrpc_error(invalid_params)
end.
%%
%% Errors
%%
-define(throw_error(C, L), throw({jsonrpc2, C, iolist_to_binary((L))})).
-define(throw_error(C, F, A),
throw({jsonrpc2, C, iolist_to_binary(io_lib:format((F), (A)))})
).
-define(ERR_NOT_FOUND, -100).
-define(ERR_INVALID_PASSWORD, -110).
-define(ERR_ERROR, -150).
-spec jsonrpc_error(term()) -> no_return().
jsonrpc_error(method_not_found = E) ->
throw(E);
jsonrpc_error(invalid_params = E) ->
throw(E);
jsonrpc_error({invalid_params, _} = E) ->
throw(E);
jsonrpc_error({not_found, F, A}) ->
?throw_error(?ERR_NOT_FOUND, F, A);
jsonrpc_error({not_found, M}) ->
?throw_error(?ERR_NOT_FOUND, M);
jsonrpc_error(invalid_password) ->
?throw_error(?ERR_INVALID_PASSWORD, "Invalid password");
jsonrpc_error({error, F, A}) ->
?throw_error(?ERR_ERROR, F, A);
jsonrpc_error({error, E}) ->
jsonrpc_error({error, "~p", E}).
%%
%% Internal
%%
%% @doc We want params to be sent in as a map if there
%% _are_ parameters and as empty list (e.g., `[]') if
%% the parameter list is empty. We are using empty
%% _list_ instead of an empty map because Erlang
%% uses record style matching for maps so an
%% empty map in a function head does _NOT_ match
%% an empty map, it matches _any_ map. You have
%% to use the `map_size/1' function in a guard
%% to decide if a map is empty in a function head.
%% With an empty list, we can just literally
%% match that in the function head and it makes
%% the intention of the function clearer (in my
%% opinion anyway)
%%
%% So we will figure out the current shape of the
%% parameter argument (proplist, map, EEP18)
%% and convert as needed.
%%
%% We are doing this _here_ so that the conversion
%% is centralized and standardized and not copypasta'd
%% into every single jsonrpc module
format_params([]) -> [];
format_params({Params}) -> format_params(kvc:to_proplist(Params));
format_params(Params) when is_list(Params) ->
maps:from_list(Params);
format_params(Params) when is_map(Params) andalso map_size(Params) == 0 -> [];
format_params(Params) when is_map(Params) -> Params.
%% Note to future me - this function **MUST** return an EEP18 style term
%% otherwise ALL requests WILL be always rejected as "invalid request"
%%
%% That's an hour I'll never get back. And I already fscking figured it
%% out once before...
decode_helper(Bin) ->
%% returns proplists but uh, whatever, when in Rome...
{jsx:decode(Bin)}.
encode_helper(Json) ->
%% jsonrpc2 emits EEP18 which is god awful and ancient, but rather than
%% yak shaving that library to use maps as Crom intended, we will do
%% something awful:
%%
%% You see, EEP18 isn't supported by jsx and it seems stupid to pull in
%% a whole new JSON library to handle it, so we will use kvc's
%% `to_proplist/1' function to turn EEP18 -> a proplist format that jsx
%% _will_ encode
jsx:encode(kvc:to_proplist(Json)).
to_key(X) when is_atom(X) -> atom_to_binary(X, utf8);
to_key(X) when is_list(X) -> iolist_to_binary(X);
to_key(X) when is_binary(X) -> X.
%% don't want these atoms stringified
to_value(true) -> true;
to_value(false) -> false;
to_value(undefined) -> null;
%% lightly format floats, but pass through integers as-is
to_value(X) when is_float(X) -> float_to_binary(blockchain_utils:normalize_float(X), 2);
to_value(X) when is_integer(X) -> X;
%% make sure we have valid representations of other types which may show up in values
to_value(X) when is_list(X) -> iolist_to_binary(X);
to_value(X) when is_atom(X) -> atom_to_binary(X, utf8);
to_value(X) when is_binary(X) -> X;
to_value(X) when is_map(X) -> ensure_binary_map(X);
to_value(X) -> iolist_to_binary(io_lib:format("~p", [X])).
ensure_binary_map(M) ->
maps:fold(fun(K, V, Acc) ->
BinK = to_key(K),
BinV = to_value(V),
Acc#{BinK => BinV}
end, #{}, M).
jsonrpc_maybe(undefined) -> <<"undefined">>;
jsonrpc_maybe(X) -> X.
......@@ -47,6 +47,8 @@ init(_Args) ->
},
BaseDir = application:get_env(blockchain, base_dir, "data"),
JsonRpcPort = application:get_env(miner, jsonrpc_port, 4467),
JsonRpcIp = application:get_env(miner, jsonrpc_ip, {127,0,0,1}),
ok = libp2p_crypto:set_network(application:get_env(miner, network, mainnet)),
......@@ -73,6 +75,9 @@ init(_Args) ->
ChildSpecs =
[
?SUP(miner_critical_sup, [PublicKey, SigFun, ECDHFun, ECCWorker]),
?SUP(miner_restart_sup, [SigFun, ECDHFun])
?SUP(miner_restart_sup, [SigFun, ECDHFun]),
?WORKER(elli, [[{callback, miner_jsonrpc_handler},
{ip, JsonRpcIp},
{port, JsonRpcPort}]])
],
{ok, {SupFlags, ChildSpecs}}.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment