diff --git a/apps/arweave/include/ar_mining.hrl b/apps/arweave/include/ar_mining.hrl index 6b9bd7ccef..00d73842b2 100644 --- a/apps/arweave/include/ar_mining.hrl +++ b/apps/arweave/include/ar_mining.hrl @@ -9,7 +9,8 @@ cache_ref = not_set, %% not serialized chunk1 = not_set, %% not serialized chunk2 = not_set, %% not serialized - cm_diff = not_set, %% serialized. set to the difficulty used by the H1 miner + diff_pair = not_set, %% serialized. set to the difficulty associated with this candidate. + %% diff_pair can be set by CM peers or a pool server. cm_h1_list = [], %% serialized. list of {h1, nonce} pairs cm_lead_peer = not_set, %% not serialized. if set, this candidate came from another peer h0 = not_set, %% serialized @@ -27,31 +28,38 @@ preimage = not_set, %% serialized. this can be either the h1 or h2 preimage seed = not_set, %% serialized session_key = not_set, %% serialized + solution_peer = not_set, %% serialized. if set, the winning hash came from another peer start_interval_number = not_set, %% serialized step_number = not_set, %% serialized - label = <<"not_set">> %% not atom, for prevent atom table pollution DoS + label = <<"not_set">> %% not atom, in order to prevent atom table pollution DoS }). -record(mining_solution, { - last_step_checkpoints = [], - merkle_rebase_threshold = 0, + last_step_checkpoints = [], + mining_address = << 0:256 >>, next_seed = << 0:(8 * 48) >>, next_vdf_difficulty = 0, nonce = 0, nonce_limiter_output = << 0:256 >>, partition_number = 0, + partition_upper_bound = 0, poa1 = #poa{}, poa2 = #poa{}, preimage = << 0:256 >>, recall_byte1 = 0, recall_byte2 = undefined, + seed = << 0:(8 * 48) >>, solution_hash = << 0:256 >>, + solution_peer = not_set, %% serialized. if set, the solution hash came from another peer start_interval_number = 0, step_number = 0, - steps = [], - seed = << 0:(8 * 48) >>, - mining_address = << 0:256 >>, - partition_upper_bound = 0 + steps = [] +}). + +%% @doc Solution validation response. +-record(solution_response, { + indep_hash = <<>>, + status = <<>> }). -endif. diff --git a/apps/arweave/include/ar_pool.hrl b/apps/arweave/include/ar_pool.hrl index 6e9ad00e17..350287a8de 100644 --- a/apps/arweave/include/ar_pool.hrl +++ b/apps/arweave/include/ar_pool.hrl @@ -34,12 +34,6 @@ partition_upper_bound = 0 }). -%% @doc Partial solution validation response. --record(partial_solution_response, { - indep_hash = <<>>, - status = <<>> -}). - %% @doc A set of coordinated mining jobs provided by the pool. %% %% Miners fetch and submit pool CM jobs via the same POST /pool_cm_jobs endpoint. diff --git a/apps/arweave/src/ar_chain_stats.erl b/apps/arweave/src/ar_chain_stats.erl index ef212fafe0..d05b590419 100644 --- a/apps/arweave/src/ar_chain_stats.erl +++ b/apps/arweave/src/ar_chain_stats.erl @@ -3,6 +3,7 @@ -behaviour(gen_server). -include_lib("arweave/include/ar.hrl"). +-include_lib("arweave/include/ar_config.hrl"). -include_lib("arweave/include/ar_chain_stats.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -38,7 +39,9 @@ get_forks(StartTime) -> init([]) -> %% Trap exit to avoid corrupting any open files on quit.. process_flag(trap_exit, true), - ok = ar_kv:open(filename:join(?ROCKS_DB_DIR, "forks_db"), forks_db), + {ok, Config} = application:get_env(arweave, config), + RocksDBDir = filename:join(Config#config.data_dir, ?ROCKS_DB_DIR), + ok = ar_kv:open(filename:join(RocksDBDir, "forks_db"), forks_db), {ok, #{}}. handle_call({get_forks, StartTime}, _From, State) -> diff --git a/apps/arweave/src/ar_coordination.erl b/apps/arweave/src/ar_coordination.erl index de5e3dd15c..b56a42f195 100644 --- a/apps/arweave/src/ar_coordination.erl +++ b/apps/arweave/src/ar_coordination.erl @@ -61,7 +61,7 @@ computed_h1(Candidate, DiffPair) -> ShareableCandidate = Candidate#mining_candidate{ chunk1 = not_set, chunk2 = not_set, - cm_diff = DiffPair, + diff_pair = DiffPair, cm_lead_peer = not_set, h1 = not_set, h2 = not_set, @@ -243,8 +243,20 @@ handle_cast({compute_h2_for_peer, Candidate}, State) -> {noreply, State}; handle_cast({computed_h2_for_peer, Candidate}, State) -> - #mining_candidate{ cm_lead_peer = Peer } = Candidate, - send_h2(Peer, Candidate), + #mining_candidate{ + h0 = H0, nonce = Nonce, partition_number = Partition1, + partition_upper_bound = PartitionUpperBound, cm_lead_peer = Peer + } = Candidate, + + {_RecallByte1, RecallByte2} = ar_mining_server:get_recall_bytes(H0, Partition1, + Nonce, PartitionUpperBound), + PoA = ar_mining_server:load_poa(RecallByte2, Candidate), + case PoA of + not_found -> + ar_mining_router:reject_solution(Candidate, failed_to_read_chunk_proofs, []); + _ -> + send_h2(Peer, Candidate#mining_candidate{ poa2 = PoA }) + end, {noreply, State}; handle_cast(refetch_peer_partitions, State) -> @@ -385,24 +397,14 @@ send_h1(Candidate, State) -> spawn(fun() -> ar_http_iface_client:cm_h1_send(Peer, Candidate2) end), - case Peer of - {pool, _} -> - ar_mining_stats:h1_sent_to_peer(pool, length(H1List)); - _ -> - ar_mining_stats:h1_sent_to_peer(Peer, length(H1List)) - end + ar_mining_stats:h1_sent_to_peer(Peer, length(H1List)) end. send_h2(Peer, Candidate) -> spawn(fun() -> ar_http_iface_client:cm_h2_send(Peer, Candidate) end), - case Peer of - {pool, _} -> - ar_mining_stats:h2_sent_to_peer(pool); - _ -> - ar_mining_stats:h2_sent_to_peer(Peer) - end. + ar_mining_stats:h2_sent_to_peer(Peer). add_mining_peer({Peer, StorageModules}, State) -> Partitions = lists:map( diff --git a/apps/arweave/src/ar_events_sup.erl b/apps/arweave/src/ar_events_sup.erl index bc734ff8ff..cb91ff1912 100644 --- a/apps/arweave/src/ar_events_sup.erl +++ b/apps/arweave/src/ar_events_sup.erl @@ -55,8 +55,6 @@ init([]) -> ?CHILD(ar_events, chunk_storage, worker), %% Events: add_range, remove_range, global_remove_range, cut, global_cut. ?CHILD(ar_events, sync_record, worker), - %% Events: rejected, stale, partial, accepted. - ?CHILD(ar_events, solution, worker), %% Used for the testing purposes. ?CHILD(ar_events, testing, worker) ]}}. diff --git a/apps/arweave/src/ar_http_iface_client.erl b/apps/arweave/src/ar_http_iface_client.erl index 665c01a6ca..d9f14aa79e 100644 --- a/apps/arweave/src/ar_http_iface_client.erl +++ b/apps/arweave/src/ar_http_iface_client.erl @@ -15,7 +15,7 @@ get_block_time_history/3, push_nonce_limiter_update/3, get_vdf_update/1, get_vdf_session/1, get_previous_vdf_session/1, get_cm_partition_table/1, cm_h1_send/2, cm_h2_send/2, - cm_publish_send/2, get_jobs/2, post_partial_solution/2, + get_jobs/2, post_partial_solution/2, get_pool_cm_jobs/2, post_pool_cm_jobs/2, post_cm_partition_table_to_pool/2]). -include_lib("arweave/include/ar.hrl"). @@ -591,17 +591,10 @@ cm_h1_send(Peer, Candidate) -> cm_h2_send(Peer, Candidate) -> JSON = ar_serialize:jsonify(ar_serialize:candidate_to_json_struct(Candidate)), Req = build_cm_or_pool_request(post, Peer, "/coordinated_mining/h2", JSON), - handle_cm_noop_response(ar_http:req(Req)). - -cm_publish_send(Peer, Solution) -> - ?LOG_DEBUG([{event, cm_publish_send}, {peer, ar_util:format_peer(Peer)}, - {solution, ar_util:encode(Solution#mining_solution.solution_hash)}, - {step_number, Solution#mining_solution.step_number}, - {start_interval_number, Solution#mining_solution.start_interval_number}, - {seed, ar_util:encode(Solution#mining_solution.seed)}]), - JSON = ar_serialize:jsonify(ar_serialize:solution_to_json_struct(Solution)), - Req = build_cm_or_pool_request(post, Peer, "/coordinated_mining/publish", JSON), - handle_cm_noop_response(ar_http:req(Req)). + handle_solution_response(ar_http:req(Req#{ + timeout => 20 * 1000, + connect_timeout => 5 * 1000 + })). %% @doc Fetch the jobs from the pool or coordinated mining exit peer. get_jobs(Peer, PrevOutput) -> @@ -619,7 +612,7 @@ post_partial_solution(Peer, Solution) -> ar_serialize:jsonify(ar_serialize:solution_to_json_struct(Solution)) end, Req = build_cm_or_pool_request(post, Peer, "/partial_solution", Payload), - handle_post_partial_solution_response(ar_http:req(Req#{ + handle_solution_response(ar_http:req(Req#{ timeout => 20 * 1000, connect_timeout => 5 * 1000 })). @@ -704,14 +697,14 @@ handle_post_pool_cm_jobs_response({ok, {{<<"200">>, _}, _, _, _, _}}) -> handle_post_pool_cm_jobs_response(Reply) -> {error, Reply}. -handle_post_partial_solution_response({ok, {{<<"200">>, _}, _, Body, _, _}}) -> +handle_solution_response({ok, {{<<"200">>, _}, _, Body, _, _}}) -> case catch jiffy:decode(Body, [return_maps]) of {'EXIT', _} -> {error, invalid_json}; Response -> {ok, Response} end; -handle_post_partial_solution_response(Reply) -> +handle_solution_response(Reply) -> {error, Reply}. handle_get_jobs_response({ok, {{<<"200">>, _}, _, Body, _, _}}) -> diff --git a/apps/arweave/src/ar_http_iface_cm_pool.erl b/apps/arweave/src/ar_http_iface_cm_pool.erl new file mode 100644 index 0000000000..8016538f81 --- /dev/null +++ b/apps/arweave/src/ar_http_iface_cm_pool.erl @@ -0,0 +1,113 @@ +-module(ar_http_iface_cm_pool). + +-export([handle_post_solution/2, handle_get_jobs/3]). + +-include_lib("arweave/include/ar_config.hrl"). + +%%%=================================================================== +%%% Public interface. +%%%=================================================================== + +handle_post_solution(Req, Pid) -> + case ar_node:is_joined() of + false -> + not_joined(Req); + true -> + APISecret = case {ar_pool:is_server(), ar_coordination:is_exit_peer()} of + {true, _} -> + pool; + {_, true} -> + cm; + _ -> + error + end, + case validate_request(APISecret, Req) of + true -> + handle_post_solution2(Req, Pid); + FailureResponse -> + FailureResponse + end + end. + +handle_get_jobs(EncodedPrevOutput, Req, Pid) -> + case ar_node:is_joined() of + false -> + not_joined(Req); + true -> + case ar_util:safe_decode(EncodedPrevOutput) of + {ok, PrevOutput} -> + handle_get_jobs2(PrevOutput, Req); + {error, invalid} -> + {400, #{}, jiffy:encode(#{ error => invalid_prev_output }), Req} + end + end; + + +%%%=================================================================== +%%% Internal functions. +%%%=================================================================== + +handle_post_solution2(Req, Pid) -> + Peer = ar_http_util:arweave_peer(Req), + case read_complete_body(Req, Pid) of + {ok, Body, Req2} -> + case catch ar_serialize:json_map_to_solution( + jiffy:decode(Body, [return_maps])) of + {'EXIT', _} -> + {400, #{}, jiffy:encode(#{ error => invalid_json }), Req2}; + Solution -> + ar_mining_router:received_solution(Solution, + [{peer, ar_util:format_peer(Peer)}]), + Response = ar_mining_router:route_solution(Solution), + JSON = ar_serialize:solution_response_to_json_struct(Response), + {200, #{}, ar_serialize:jsonify(JSON), Req2} + end; + {error, body_size_too_large} -> + {413, #{}, <<"Payload too large">>, Req}; + {error, timeout} -> + {500, #{}, <<"Handler timeout">>, Req} + end. + +handle_get_jobs2(PrevOutput, Req) -> + {ok, Config} = application:get_env(arweave, config), + CMExitNode = ar_coordination:is_exit_peer() andalso ar_pool:is_client(), + case {Config#config.is_pool_server, CMExitNode} of + {false, false} -> + {501, #{}, jiffy:encode(#{ error => configuration }), Req}; + {true, _} -> + case check_internal_api_secret(Req) of + {reject, {Status, Headers, Body}} -> + {Status, Headers, Body, Req}; + pass -> + Jobs = ar_pool:generate_jobs(PrevOutput), + JSON = ar_serialize:jsonify(ar_serialize:jobs_to_json_struct(Jobs)), + {200, #{}, JSON, Req} + end; + {_, true} -> + case check_cm_api_secret(Req) of + {reject, {Status, Headers, Body}} -> + {Status, Headers, Body, Req}; + pass -> + Jobs = ar_pool:get_cached_jobs(PrevOutput), + JSON = ar_serialize:jsonify(ar_serialize:jobs_to_json_struct(Jobs)), + {200, #{}, JSON, Req} + end + end. + +validate_request(APISecret, Req) -> + SecretCheck = case APISecret of + pool -> + check_internal_api_secret(Req); + cm -> + check_cm_api_secret(Req); + _ -> + {501, #{}, jiffy:encode(#{ error => configuration }), Req} + end, + case SecretCheck of + pass -> + true; + {reject, {Status, Headers, Body}} -> + {Status, Headers, Body, Req}; + _ -> + SecretCheck + end. \ No newline at end of file diff --git a/apps/arweave/src/ar_http_iface_middleware.erl b/apps/arweave/src/ar_http_iface_middleware.erl index 4e3692e5c9..aa021ff42c 100644 --- a/apps/arweave/src/ar_http_iface_middleware.erl +++ b/apps/arweave/src/ar_http_iface_middleware.erl @@ -475,82 +475,6 @@ handle(<<"POST">>, [<<"block2">>], Req, Pid) -> erlang:put(post_block2, true), post_block(request, {Req, Pid, binary}, erlang:timestamp()); -%% Accept a (partial) solution from a pool or a CM node and validate it. -%% -%% If the node is a CM exit node and a pool client, send the given solution to -%% the pool and return an empty JSON object. -%% -%% If the node is a pool server, return a JSON object: -%% { -%% "indep_hash": "", -%% "status": "" -%% }, -%% where the status is one of "accepted", "accepted_block", "rejected_bad_poa", -%% "rejected_wrong_hash", "rejected_bad_vdf", "rejected_mining_address_banned", -%% "stale", "rejected_vdf_not_found", "rejected_missing_key_file". -%% If the solution is partial, "indep_hash" string is empty. -handle(<<"POST">>, [<<"partial_solution">>], Req, Pid) -> - case ar_node:is_joined() of - true -> - handle_post_partial_solution(Req, Pid); - false -> - not_joined(Req) - end; - -%% Return the information about up to ?GET_JOBS_COUNT latest VDF steps and a difficulty. -%% -%% If the given VDF output is present in the latest 10 VDF steps, return only the steps -%% strictly above the given output. If the given output is our latest output, -%% wait for up to ?GET_JOBS_TIMEOUT_S and return an empty list if no new steps are -%% computed by the time. Also, only return the steps strictly above the latest block. -%% -%% If we are a pool server, return the current network difficulty along with the VDF -%% information. -%% -%% If we are a CM exit node and a pool client, return the partial difficulty provided -%% by the pool. -%% -%% Return a JSON object: -%% { -%% "jobs": -%% [ -%% {"nonce_limiter_output": "...", "step_number": "...", "partition_upper_bound": "..."}, -%% ... -%% ], -%% "partial_diff": "...", -%% "next_seed": "...", -%% "interval_number": "...", -%% "next_vdf_difficulty": "..." -%% } -handle(<<"GET">>, [<<"jobs">>, EncodedPrevOutput], Req, _Pid) -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - case ar_util:safe_decode(EncodedPrevOutput) of - {ok, PrevOutput} -> - handle_get_jobs(PrevOutput, Req); - {error, invalid} -> - {400, #{}, jiffy:encode(#{ error => invalid_prev_output }), Req} - end - end; - -handle(<<"GET">>, [<<"jobs">>], Req, _Pid) -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - handle_get_jobs(<<>>, Req) - end; - -handle(<<"POST">>, [<<"pool_cm_jobs">>], Req, Pid) -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - handle_post_pool_cm_jobs(Req, Pid) - end; - %% Generate a wallet and receive a secret key identifying it. %% Requires internal_api_secret startup option to be set. %% WARNING: only use it if you really really know what you are doing. @@ -1377,111 +1301,145 @@ handle(<<"GET">>, [<<"vdf4">>, <<"previous_session">>], Req, _Pid) -> handle_get_vdf(Req, get_previous_session, 4) end; +%%%=================================================================== +%%% CM and Pool endpoints. +%%%=================================================================== + +%% Accept a (partial) solution from a pool or a CM node and validate it. +%% +%% If the node is a CM exit node and a pool client, send the given solution to +%% the pool and return an empty JSON object. +%% +%% If the node is a pool server, return a JSON object: +%% { +%% "indep_hash": "", +%% "status": "" +%% }, +%% where the status is one of "accepted", "accepted_block", "rejected_bad_poa", +%% "rejected_wrong_hash", "rejected_bad_vdf", "rejected_mining_address_banned", +%% "stale", "rejected_vdf_not_found", "rejected_missing_key_file". +%% If the solution is partial, "indep_hash" string is empty. +handle(<<"POST">>, [<<"partial_solution">>], Req, Pid) -> + ar_http_iface_cm_pool:handle_post_solution(Req, Pid); + +%% Deprecated. Use POST /partial_solution instead. +handle(<<"POST">>, [<<"coordinated_mining">>, <<"publish">>], Req, Pid) -> + ar_http_iface_cm_pool:handle_post_solution(Req, Pid); + +%% Return the information about up to ?GET_JOBS_COUNT latest VDF steps and a difficulty. +%% +%% If the given VDF output is present in the latest 10 VDF steps, return only the steps +%% strictly above the given output. If the given output is our latest output, +%% wait for up to ?GET_JOBS_TIMEOUT_S and return an empty list if no new steps are +%% computed by the time. Also, only return the steps strictly above the latest block. +%% +%% If we are a pool server, return the current network difficulty along with the VDF +%% information. +%% +%% If we are a CM exit node and a pool client, return the partial difficulty provided +%% by the pool. +%% +%% Return a JSON object: +%% { +%% "jobs": +%% [ +%% {"nonce_limiter_output": "...", "step_number": "...", "partition_upper_bound": "..."}, +%% ... +%% ], +%% "partial_diff": "...", +%% "next_seed": "...", +%% "interval_number": "...", +%% "next_vdf_difficulty": "..." +%% } +handle(<<"GET">>, [<<"jobs">>, EncodedPrevOutput], Req, Pid) -> + ar_http_iface_cm_pool:handle_get_jobs(EncodedPrevOutput, Req, Pid); + +handle(<<"GET">>, [<<"jobs">>], Req, Pid) -> + ar_http_iface_cm_pool:handle_get_jobs(<<>>, Req, Pid); + +handle(<<"POST">>, [<<"pool_cm_jobs">>], Req, Pid) -> + case ar_node:is_joined() of + false -> + not_joined(Req); + true -> + handle_post_pool_cm_jobs(Req, Pid) + end; + handle(<<"GET">>, [<<"coordinated_mining">>, <<"partition_table">>], Req, _Pid) -> - case check_cm_api_secret(Req) of - pass -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - Partitions = - case {ar_pool:is_client(), ar_coordination:is_exit_peer()} of - {true, true} -> - %% When we work with a pool, the exit node shares - %% the information about external partitions with - %% every internal miner. - ar_coordination:get_self_plus_external_partitions_list(); - _ -> - %% CM miners ask each other about their local - %% partitions. A CM exit node is not an exception - it - %% does NOT aggregate peer partitions in this case. - ar_coordination:get_unique_partitions_list() - end, - JSON = ar_serialize:jsonify(Partitions), - {200, #{}, JSON, Req} - end; - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req} + case validate_cm_request(Req) of + true -> + Partitions = + case {ar_pool:is_client(), ar_coordination:is_exit_peer()} of + {true, true} -> + %% When we work with a pool, the exit node shares + %% the information about external partitions with + %% every internal miner. + ar_coordination:get_self_plus_external_partitions_list(); + _ -> + %% CM miners ask each other about their local + %% partitions. A CM exit node is not an exception - it + %% does NOT aggregate peer partitions in this case. + ar_coordination:get_unique_partitions_list() + end, + JSON = ar_serialize:jsonify(Partitions), + {200, #{}, JSON, Req}; + FailureResponse -> + FailureResponse end; + % If somebody want to make GUI, monitoring tool handle(<<"GET">>, [<<"coordinated_mining">>, <<"state">>], Req, _Pid) -> - case check_cm_api_secret(Req) of - pass -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - {ok, {LastPeerResponse}} = ar_coordination:get_public_state(), - Peers = maps:fold(fun(Peer, Value, Acc) -> - {AliveStatus, PartitionList} = Value, - Table = lists:map( - fun (ListValue) -> - {Bucket, BucketSize, Addr} = ListValue, - {[ - {bucket, Bucket}, - {bucketsize, BucketSize}, - {addr, ar_util:encode(Addr)} - ]} - end, - PartitionList - ), - Val = {[ - {peer, list_to_binary(ar_util:format_peer(Peer))}, - {alive, AliveStatus}, - {partition_table, Table} - ]}, - [Val | Acc] - end, - [], - LastPeerResponse - ), - {200, #{}, ar_serialize:jsonify(Peers), Req} - end; - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req} + case validate_cm_request(Req) of + true -> + {ok, {LastPeerResponse}} = ar_coordination:get_public_state(), + Peers = maps:fold(fun(Peer, Value, Acc) -> + {AliveStatus, PartitionList} = Value, + Table = lists:map( + fun (ListValue) -> + {Bucket, BucketSize, Addr} = ListValue, + {[ + {bucket, Bucket}, + {bucketsize, BucketSize}, + {addr, ar_util:encode(Addr)} + ]} + end, + PartitionList + ), + Val = {[ + {peer, list_to_binary(ar_util:format_peer(Peer))}, + {alive, AliveStatus}, + {partition_table, Table} + ]}, + [Val | Acc] + end, + [], + LastPeerResponse + ), + {200, #{}, ar_serialize:jsonify(Peers), Req}; + FailureResponse -> + FailureResponse end; %% POST request to /coordinated_mining/h1. handle(<<"POST">>, [<<"coordinated_mining">>, <<"h1">>], Req, Pid) -> - case check_cm_api_secret(Req) of - pass -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - handle_mining_h1(Req, Pid) - end; - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req} + case validate_cm_request(Req) of + true -> + handle_mining_h1(Req, Pid); + FailureResponse -> + FailureResponse end; %% POST request to /coordinated_mining/h2. handle(<<"POST">>, [<<"coordinated_mining">>, <<"h2">>], Req, Pid) -> - case check_cm_api_secret(Req) of - pass -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - handle_mining_h2(Req, Pid) - end; - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req} + case validate_cm_request(Req) of + true -> + handle_mining_h2(Req, Pid); + FailureResponse -> + FailureResponse end; -handle(<<"POST">>, [<<"coordinated_mining">>, <<"publish">>], Req, Pid) -> - case check_cm_api_secret(Req) of - pass -> - case ar_node:is_joined() of - false -> - not_joined(Req); - true -> - handle_mining_cm_publish(Req, Pid) - end; - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req} - end; + %% Catch case for requests made to unknown endpoints. %% Returns error code 400 - Request type not found. @@ -2528,116 +2486,6 @@ check_block_receive_timestamp(H) -> end end. -handle_post_partial_solution(Req, Pid) -> - {ok, Config} = application:get_env(arweave, config), - CMExitNode = ar_coordination:is_exit_peer() andalso ar_pool:is_client(), - case {Config#config.is_pool_server, CMExitNode} of - {false, false} -> - {501, #{}, jiffy:encode(#{ error => configuration }), Req}; - {true, _} -> - case check_internal_api_secret(Req) of - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req}; - pass -> - handle_post_partial_solution_pool_server(Req, Pid) - end; - {_, true} -> - case check_cm_api_secret(Req) of - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req}; - pass -> - handle_post_partial_solution_cm_exit_peer_pool_client(Req, Pid) - end - end. - -handle_post_partial_solution_pool_server(Req, Pid) -> - case read_complete_body(Req, Pid) of - {ok, Body, Req2} -> - case catch ar_serialize:json_map_to_solution( - jiffy:decode(Body, [return_maps])) of - {'EXIT', _} -> - {400, #{}, jiffy:encode(#{ error => invalid_json }), Req2}; - Solution -> - Response = ar_pool:process_partial_solution(Solution), - JSON = ar_serialize:partial_solution_response_to_json_struct(Response), - {200, #{}, ar_serialize:jsonify(JSON), Req2} - end; - {error, body_size_too_large} -> - {413, #{}, <<"Payload too large">>, Req}; - {error, timeout} -> - {500, #{}, <<"Handler timeout">>, Req} - end. - -handle_post_partial_solution_cm_exit_peer_pool_client(Req, Pid) -> - case read_complete_body(Req, Pid) of - {ok, Body, Req2} -> - ar_pool:post_partial_solution(Body), - {200, #{}, jiffy:encode(#{}), Req2}; - {error, body_size_too_large} -> - {413, #{}, <<"Payload too large">>, Req}; - {error, timeout} -> - {500, #{}, <<"Handler timeout">>, Req} - end. - -handle_get_jobs(PrevOutput, Req) -> - {ok, Config} = application:get_env(arweave, config), - CMExitNode = ar_coordination:is_exit_peer() andalso ar_pool:is_client(), - case {Config#config.is_pool_server, CMExitNode} of - {false, false} -> - {501, #{}, jiffy:encode(#{ error => configuration }), Req}; - {true, _} -> - case check_internal_api_secret(Req) of - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req}; - pass -> - handle_get_jobs_pool_server(PrevOutput, Req) - end; - {_, true} -> - case check_cm_api_secret(Req) of - {reject, {Status, Headers, Body}} -> - {Status, Headers, Body, Req}; - pass -> - handle_get_jobs_cm_exit_peer_pool_client(PrevOutput, Req) - end - end. - -handle_get_jobs_pool_server(PrevOutput, Req) -> - Props = - ets:select( - node_state, - [{{'$1', '$2'}, - [{'or', - {'==', '$1', diff_pair}, - {'==', '$1', nonce_limiter_info}}], ['$_']}] - ), - DiffPair = proplists:get_value(diff_pair, Props), - Info = proplists:get_value(nonce_limiter_info, Props), - Result = ar_util:do_until( - fun() -> - S = ar_nonce_limiter:get_step_triplets(Info, PrevOutput, ?GET_JOBS_COUNT), - case S of - [] -> - false; - _ -> - {ok, S} - end - end, - 200, - (?GET_JOBS_TIMEOUT_S) * 1000 - ), - Steps = case Result of {ok, S} -> S; _ -> [] end, - {NextSeed, IntervalNumber, NextVDFDiff} = ar_nonce_limiter:session_key(Info), - JobList = [#job{ output = O, global_step_number = SN, - partition_upper_bound = U } || {O, SN, U} <- Steps], - Jobs = #jobs{ jobs = JobList, seed = Info#nonce_limiter_info.seed, - next_seed = NextSeed, interval_number = IntervalNumber, - next_vdf_difficulty = NextVDFDiff, partial_diff = DiffPair }, - {200, #{}, ar_serialize:jsonify(ar_serialize:jobs_to_json_struct(Jobs)), Req}. - -handle_get_jobs_cm_exit_peer_pool_client(PrevOutput, Req) -> - {200, #{}, ar_serialize:jsonify( - ar_serialize:jobs_to_json_struct(ar_pool:get_jobs(PrevOutput))), Req}. - %% Only for cm miners that are NOT exit peers. handle_post_pool_cm_jobs(Req, Pid) -> PoolCMMiner = (not ar_coordination:is_exit_peer()) andalso ar_pool:is_client(), @@ -3205,6 +3053,19 @@ read_body_chunk(Req, Pid, Size, Timeout) -> {error, timeout} end. +validate_cm_request(Req) -> + case check_cm_api_secret(Req) of + pass -> + case ar_node:is_joined() of + false -> + not_joined(Req); + true -> + true + end; + {reject, {Status, Headers, Body}} -> + {Status, Headers, Body, Req} + end. + handle_mining_h1(Req, Pid) -> Peer = ar_http_util:arweave_peer(Req), case read_complete_body(Req, Pid) of @@ -3260,8 +3121,12 @@ handle_mining_h2(Req, Pid) -> Payload) end), {200, #{}, <<>>, Req2}; _ -> - ar_mining_server:prepare_and_post_solution(Candidate), ar_mining_stats:h2_received_from_peer(Peer), + ar_mining_router:received_solution(Candidate, [ + {peer, ar_util:format_peer(Peer)} + ]), + %% xxx: prepare_solution, need response + ar_mining_router:prepare_solution(Candidate), {200, #{}, <<>>, Req} end end; @@ -3271,29 +3136,3 @@ handle_mining_h2(Req, Pid) -> {error, body_size_too_large} -> {413, #{}, <<"Payload too large">>, Req} end. - -handle_mining_cm_publish(Req, Pid) -> - Peer = ar_http_util:arweave_peer(Req), - case read_complete_body(Req, Pid) of - {ok, Body, Req2} -> - case ar_serialize:json_decode(Body, [return_maps]) of - {ok, JSON} -> - case catch ar_serialize:json_map_to_solution(JSON) of - {'EXIT', _} -> - {400, #{}, jiffy:encode(#{ error => invalid_json }), Req2}; - Solution -> - ar:console("Block candidate ~p from ~p ~n", [ - ar_util:encode(Solution#mining_solution.solution_hash), - ar_util:format_peer(Peer)]), - ?LOG_INFO("Block candidate ~p from ~p ~n", [ - ar_util:encode(Solution#mining_solution.solution_hash), - ar_util:format_peer(Peer)]), - ar_mining_server:post_solution(Solution), - {200, #{}, <<>>, Req} - end; - {error, _} -> - {400, #{}, jiffy:encode(#{ error => invalid_json }), Req2} - end; - {error, body_size_too_large} -> - {413, #{}, <<"Payload too large">>, Req} - end. diff --git a/apps/arweave/src/ar_mining_router.erl b/apps/arweave/src/ar_mining_router.erl new file mode 100644 index 0000000000..2dba5f6c04 --- /dev/null +++ b/apps/arweave/src/ar_mining_router.erl @@ -0,0 +1,252 @@ +-module(ar_mining_router). + +-behaviour(gen_server). + +-export([start_link/0, + prepare_solution/1, route_solution/1, + found_solution/2, received_solution/2, + reject_solution/3, reject_solution/4, + accept_solution/1, accept_solution/2, + accept_block_solution/2, accept_block_solution/3, + route_h1/2, route_h2/1]). + +-export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). + +-include_lib("arweave/include/ar.hrl"). +-include_lib("arweave/include/ar_config.hrl"). +-include_lib("arweave/include/ar_mining.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +-record(state, { + request_pid_by_ref = maps:new() +}). + +%%%=================================================================== +%%% Public interface. +%%%=================================================================== + +%% @doc Start the gen_server. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +prepare_solution(#mining_candidate{} = Candidate) -> + %% A pool client does not validate VDF before sharing a solution. + {ok, Config} = application:get_env(arweave, config), + ar_mining_server:prepare_solution(Candidate, Config#config.is_pool_client). + +route_solution(#mining_solution{} = Solution) -> + {ok, Config} = application:get_env(arweave, config), + %% XXX: handle timeout + gen_server:call(?MODULE, {route_solution, Config, Solution}). + +found_solution(#mining_candidate{} = Candidate, ExtraLogs) -> + #mining_candidate{ + mining_address = MiningAddress, + nonce_limiter_output = NonceLimiterOutput, + seed = Seed, next_seed = NextSeed, + start_interval_number = StartIntervalNumber, step_number = StepNumber } = Candidate, + + {Hash, PartitionNumber} = select_hash(Candidate), + ?LOG_INFO([ + {event, solution_lifecycle}, + {status, found}, + {hash, ar_util:safe_encode(Hash)}, + {mining_address, ar_util:safe_encode(MiningAddress)}, + {partition, PartitionNumber}, + {seed, Seed}, + {next_seed, NextSeed}, + {start_interval_number, StartIntervalNumber}, + {step_number, StepNumber}, + {nonce_limiter_output, ar_util:safe_encode(NonceLimiterOutput)}] ++ + ExtraLogs), + ar_mining_stats:solution(found). + +received_solution(#mining_candidate{} = Candidate, ExtraLogs) -> + #mining_candidate{ + mining_address = MiningAddress, + nonce_limiter_output = NonceLimiterOutput, + seed = Seed, next_seed = NextSeed, + start_interval_number = StartIntervalNumber, step_number = StepNumber } = Candidate, + + {Hash, PartitionNumber} = select_hash(Candidate), + received_solution(Hash, MiningAddress, PartitionNumber, Seed, NextSeed, + StartIntervalNumber, StepNumber, NonceLimiterOutput, ExtraLogs); + +received_solution(#mining_solution{} = Solution, ExtraLogs) -> + #mining_solution{ + mining_address = MiningAddress, + nonce_limiter_output = NonceLimiterOutput, partition_number = PartitionNumber, + solution_hash = H, seed = Seed, next_seed = NextSeed, + start_interval_number = StartIntervalNumber, step_number = StepNumber } = Solution, + received_solution(H, MiningAddress, PartitionNumber, Seed, NextSeed, StartIntervalNumber, + StepNumber, NonceLimiterOutput, ExtraLogs). + +reject_solution(#mining_candidate{} = Candidate, Reason, ExtraLogs) -> + #mining_candidate{ + mining_address = MiningAddress, + nonce_limiter_output = NonceLimiterOutput, + seed = Seed, next_seed = NextSeed, + start_interval_number = StartIntervalNumber, step_number = StepNumber } = Candidate, + + {Hash, PartitionNumber} = select_hash(Candidate), + reject_solution(Reason, Hash, MiningAddress, PartitionNumber, Seed, NextSeed, + StartIntervalNumber, StepNumber, NonceLimiterOutput, ExtraLogs); + +reject_solution(#mining_solution{} = Solution, Reason, ExtraLogs) -> + #mining_solution{ + mining_address = MiningAddress, + nonce_limiter_output = NonceLimiterOutput, partition_number = PartitionNumber, + solution_hash = H, seed = Seed, next_seed = NextSeed, + start_interval_number = StartIntervalNumber, step_number = StepNumber } = Solution, + reject_solution(Reason, H, MiningAddress, PartitionNumber, + Seed, NextSeed, StartIntervalNumber, StepNumber, NonceLimiterOutput, ExtraLogs). + +reject_solution(#mining_solution{} = Solution, Reason, ExtraLogs, Ref) -> + reject_solution(Solution, Reason, ExtraLogs), + Status = iolist_to_binary([<<"rejected_">>, atom_to_binary(Reason)]), + gen_server:cast(?MODULE, {solution_response, Status, <<>>, Ref}). + +accept_solution(#mining_solution{} = Solution) -> + #mining_solution{ mining_address = MiningAddress, solution_hash = H } = Solution, + ?LOG_WARNING([ + {event, solution_lifecycle}, + {status, accepted}, + {hash, ar_util:safe_encode(H)}, + {mining_address, ar_util:safe_encode(MiningAddress)}]), + ar_mining_stats:solution(accepted). + +accept_solution(#mining_solution{} = Solution, Ref) -> + accept_solution(Solution), + gen_server:cast(?MODULE, {solution_response, <<"accepted">>, <<>>, Ref}). + +accept_block_solution(#mining_solution{} = Solution, BlockH) -> + #mining_solution{ mining_address = MiningAddress, solution_hash = H } = Solution, + ?LOG_WARNING([ + {event, solution_lifecycle}, + {status, accepted}, + {hash, ar_util:safe_encode(H)}, + {mining_address, ar_util:safe_encode(MiningAddress)}, + {block_hash, ar_util:safe_encode(BlockH)}]), + ar_mining_stats:solution(accepted). + +accept_block_solution(#mining_solution{} = Solution, BlockH, Ref) -> + accept_block_solution(Solution, BlockH), + gen_server:cast(?MODULE, {solution_response, <<"accepted_block">>, BlockH, Ref}). + +route_h1(#mining_candidate{} = Candidate, DiffPair) -> + {ok, Config} = application:get_env(arweave, config), + case Config#config.coordinated_mining of + false -> + ok; + true -> + ar_coordination:computed_h1(Candidate, DiffPair) + end. + +route_h2(#mining_candidate{ cm_lead_peer = not_set } = Candidate) -> + prepare_solution(Candidate); +route_h2(#mining_candidate{} = Candidate) -> + ar_coordination:computed_h2_for_peer(Candidate). + +%%%=================================================================== +%%% Generic server callbacks. +%%%=================================================================== + +init([]) -> + {ok, #state{}}. + +handle_call({route_solution, Config, Solution}, From, State) -> + #state{ request_pid_by_ref = Map } = State, + Ref = make_ref(), + case route_solution(Config, Solution, Ref) of + noreply -> + {noreply, State#state{ request_pid_by_ref = maps:put(Ref, From, Map) }}; + Reply -> + {reply, Reply, State} + end; + +handle_call(Request, _From, State) -> + ?LOG_WARNING([{event, unhandled_call}, {module, ?MODULE}, {request, Request}]), + {reply, ok, State}. + +handle_cast({solution_response, Status, BlockH, {pool, Ref}}, State) -> + #state{ request_pid_by_ref = Map } = State, + PID = maps:get(Ref, Map), + gen_server:reply(PID, + #solution_response{ indep_hash = BlockH, status = Status }), + {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; + +handle_cast(Cast, State) -> + ?LOG_WARNING([{event, unhandled_cast}, {module, ?MODULE}, {cast, Cast}]), + {noreply, State}. + +handle_info(Message, State) -> + ?LOG_WARNING([{event, unhandled_info}, {module, ?MODULE}, {message, Message}]), + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +%%%=================================================================== +%%% Private functions. +%%%=================================================================== + +received_solution(Hash, MiningAddress, PartitionNumber, Seed, NextSeed, + StartIntervalNumber, StepNumber, NonceLimiterOutput, ExtraLogs) -> + ?LOG_INFO([ + {event, solution_lifecycle}, + {status, received}, + {hash, ar_util:safe_encode(Hash)}, + {mining_address, ar_util:safe_encode(MiningAddress)}, + {partition, PartitionNumber}, + {seed, Seed}, + {next_seed, NextSeed}, + {start_interval_number, StartIntervalNumber}, + {step_number, StepNumber}, + {nonce_limiter_output, ar_util:safe_encode(NonceLimiterOutput)}] ++ + ExtraLogs), + ar_mining_stats:solution(received). + +reject_solution(Reason, H, MiningAddress, PartitionNumber, + Seed, NextSeed, StartIntervalNumber, StepNumber, NonceLimiterOutput, ExtraLogs) -> + ar:console("WARNING: solution was rejected. Check logs for more details~n"), + ?LOG_WARNING([ + {event, solution_lifecycle}, + {status, rejected}, + {reason, Reason}, + {hash, ar_util:safe_encode(H)}, + {mining_address, ar_util:safe_encode(MiningAddress)}, + {partition, PartitionNumber}, + {seed, Seed}, + {next_seed, NextSeed}, + {start_interval_number, StartIntervalNumber}, + {step_number, StepNumber}, + {nonce_limiter_output, ar_util:safe_encode(NonceLimiterOutput)}] ++ + ExtraLogs), + ar_mining_stats:solution(rejected). + +route_solution(#config{ is_pool_server = true }, Solution, Ref) -> + ar_pool:process_partial_solution(Solution, Ref); +route_solution(#config{ cm_exit_peer = not_set, is_pool_client = true }, Solution, Ref) -> + %% When posting a partial solution the pool client will skip many of the validation steps + %% that are normally performed before sharing a solution. + ar_pool:post_partial_solution(Solution); +route_solution(#config{ cm_exit_peer = not_set, is_pool_client = false }, Solution, Ref) -> + ar_mining_server:validate_solution(Solution); +route_solution(#config{ cm_exit_peer = ExitPeer }, Solution, Ref) -> + case ar_http_iface_client:post_partial_solution(ExitPeer, Solution) of + {ok, _} -> + ok; + {error, Reason} -> + ?LOG_WARNING([{event, solution_rejected}, + {reason, failed_to_reach_exit_node}, + {message, io_lib:format("~p", [Reason])}]), + ar:console("We found a solution but failed to reach the exit node, " + "error: ~p.", [io_lib:format("~p", [Reason])]), + ar_mining_stats:solution(rejected) + end. + +select_hash(#mining_candidate{ h2 = not_set } = Candidate) -> + {Candidate#mining_candidate.h1, Candidate#mining_candidate.partition_number}; +select_hash(#mining_candidate{} = Candidate) -> + {Candidate#mining_candidate.h2, Candidate#mining_candidate.partition_number2}. diff --git a/apps/arweave/src/ar_mining_server.erl b/apps/arweave/src/ar_mining_server.erl index c0265557c1..347662664f 100644 --- a/apps/arweave/src/ar_mining_server.erl +++ b/apps/arweave/src/ar_mining_server.erl @@ -4,9 +4,9 @@ -behaviour(gen_server). -export([start_link/0, start_mining/1, set_difficulty/1, set_merkle_rebase_threshold/1, - compute_h2_for_peer/1, prepare_and_post_solution/1, post_solution/1, read_poa/3, - get_recall_bytes/4, active_sessions/0, encode_sessions/1, add_pool_job/6, - is_one_chunk_solution/1]). + prepare_solution/2, validate_solution/1, + compute_h2_for_peer/1, load_poa/2, get_recall_bytes/4, active_sessions/0, + encode_sessions/1, add_pool_job/6, is_one_chunk_solution/1]). -export([pause/0]). -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). @@ -28,8 +28,7 @@ chunk_cache_limit = 0, gc_frequency_ms = undefined, gc_process_ref = undefined, - merkle_rebase_threshold = infinity, - is_pool_client = false + merkle_rebase_threshold = infinity }). -define(FETCH_POA_FROM_PEERS_TIMEOUT_MS, 10000). @@ -66,12 +65,6 @@ add_pool_job(SessionKey, StepNumber, Output, PartitionUpperBound, Seed, PartialD Args = {SessionKey, StepNumber, Output, PartitionUpperBound, Seed, PartialDiff}, gen_server:cast(?MODULE, {add_pool_job, Args}). -prepare_and_post_solution(Candidate) -> - gen_server:cast(?MODULE, {prepare_and_post_solution, Candidate}). - -post_solution(Solution) -> - gen_server:cast(?MODULE, {post_solution, Solution}). - active_sessions() -> gen_server:call(?MODULE, active_sessions). @@ -83,6 +76,12 @@ encode_sessions(Sessions) -> is_one_chunk_solution(Solution) -> Solution#mining_solution.recall_byte2 == undefined. +prepare_solution(Candidate, SkipVDF) -> + gen_server:cast(?MODULE, {prepare_solution, Candidate, SkipVDF}). + +validate_solution(Solution) -> + gen_server:cast(?MODULE, {validate_solution, Solution}). + %%%=================================================================== %%% Generic server callbacks. %%%=================================================================== @@ -102,8 +101,7 @@ init([]) -> ), {ok, #state{ - workers = Workers, - is_pool_client = ar_pool:is_client() + workers = Workers }}. handle_call(active_sessions, _From, State) -> @@ -165,14 +163,6 @@ handle_cast({compute_h2_for_peer, Candidate}, State) -> end, {noreply, State}; -handle_cast({prepare_and_post_solution, Candidate}, State) -> - prepare_and_post_solution(Candidate, State), - {noreply, State}; - -handle_cast({post_solution, Solution}, State) -> - post_solution(Solution, State), - {noreply, State}; - handle_cast({manual_garbage_collect, Ref}, #state{ gc_process_ref = Ref } = State) -> %% Reading recall ranges from disk causes a large amount of binary data to be allocated and %% references to that data is spread among all the different mining processes. Because of this @@ -200,6 +190,54 @@ handle_cast({manual_garbage_collect, _}, State) -> %% Does not originate from the running instance of the server; happens in tests. {noreply, State}; +handle_cast({prepare_solution, Candidate, SkipVDF}, State) -> + #mining_candidate{ + mining_address = MiningAddress, next_seed = NextSeed, + next_vdf_difficulty = NextVDFDifficulty, nonce = Nonce, + nonce_limiter_output = NonceLimiterOutput, partition_number = PartitionNumber, + partition_upper_bound = PartitionUpperBound, poa2 = PoA2, preimage = Preimage, + seed = Seed, start_interval_number = StartIntervalNumber, step_number = StepNumber + } = Candidate, + + Solution = #mining_solution{ + mining_address = MiningAddress, + next_seed = NextSeed, + next_vdf_difficulty = NextVDFDifficulty, + nonce = Nonce, + nonce_limiter_output = NonceLimiterOutput, + partition_number = PartitionNumber, + partition_upper_bound = PartitionUpperBound, + poa2 = PoA2, + preimage = Preimage, + seed = Seed, + start_interval_number = StartIntervalNumber, + step_number = StepNumber + }, + + Solution2 = case SkipVDF of + true -> + prepare_solution_proofs(Candidate, Solution); + false -> + prepare_solution_last_step_checkpoints(Candidate, Solution) + end, + case Solution2 of + error -> ok; + _ -> ar_mining_router:route_solution(Solution2) + end, + {noreply, State}; + +handle_cast({validate_solution, Solution}, State) -> + #state{ diff_pair = DiffPair } = State, + case validate_solution(Solution, DiffPair) of + error -> + ar_mining_router:reject_solution(Solution, failed_to_validate_solution, []); + {false, Reason} -> + ar_mining_router:reject_solution(Solution, Reason, []); + {true, PoACache, PoA2Cache} -> + ar_events:send(miner, {found_solution, miner, Solution, PoACache, PoA2Cache}) + end, + {noreply, State}; + handle_cast(Cast, State) -> ?LOG_WARNING([{event, unhandled_cast}, {module, ?MODULE}, {cast, Cast}]), {noreply, State}. @@ -449,44 +487,7 @@ get_recall_bytes(H0, PartitionNumber, Nonce, PartitionUpperBound) -> RelativeOffset = Nonce * (?DATA_CHUNK_SIZE), {RecallRange1Start + RelativeOffset, RecallRange2Start + RelativeOffset}. -prepare_and_post_solution(Candidate, State) -> - Solution = prepare_solution(Candidate, State), - post_solution(Solution, State). - -prepare_solution(Candidate, State) -> - #state{ merkle_rebase_threshold = RebaseThreshold, is_pool_client = IsPoolClient } = State, - #mining_candidate{ - mining_address = MiningAddress, next_seed = NextSeed, - next_vdf_difficulty = NextVDFDifficulty, nonce = Nonce, - nonce_limiter_output = NonceLimiterOutput, partition_number = PartitionNumber, - partition_upper_bound = PartitionUpperBound, poa2 = PoA2, preimage = Preimage, - seed = Seed, start_interval_number = StartIntervalNumber, step_number = StepNumber - } = Candidate, - - Solution = #mining_solution{ - mining_address = MiningAddress, - merkle_rebase_threshold = RebaseThreshold, - next_seed = NextSeed, - next_vdf_difficulty = NextVDFDifficulty, - nonce = Nonce, - nonce_limiter_output = NonceLimiterOutput, - partition_number = PartitionNumber, - partition_upper_bound = PartitionUpperBound, - poa2 = PoA2, - preimage = Preimage, - seed = Seed, - start_interval_number = StartIntervalNumber, - step_number = StepNumber - }, - %% A pool client does not validate VDF before sharing a solution. - case IsPoolClient of - true -> - prepare_solution(proofs, Candidate, Solution); - false -> - prepare_solution(last_step_checkpoints, Candidate, Solution) - end. - -prepare_solution(last_step_checkpoints, Candidate, Solution) -> +prepare_solution_last_step_checkpoints(Candidate, Solution) -> #mining_candidate{ next_seed = NextSeed, next_vdf_difficulty = NextVDFDifficulty, start_interval_number = StartIntervalNumber, step_number = StepNumber } = Candidate, @@ -501,10 +502,10 @@ prepare_solution(last_step_checkpoints, Candidate, Solution) -> _ -> LastStepCheckpoints end, - prepare_solution(steps, Candidate, Solution#mining_solution{ - last_step_checkpoints = LastStepCheckpoints2 }); + prepare_solution_steps(Candidate, Solution#mining_solution{ + last_step_checkpoints = LastStepCheckpoints2 }). -prepare_solution(steps, Candidate, Solution) -> +prepare_solution_steps(Candidate, Solution) -> #mining_candidate{ step_number = StepNumber } = Candidate, [{_, TipNonceLimiterInfo}] = ets:lookup(node_state, nonce_limiter_info), #nonce_limiter_info{ global_step_number = PrevStepNumber, next_seed = PrevNextSeed, @@ -515,29 +516,24 @@ prepare_solution(steps, Candidate, Solution) -> PrevStepNumber, StepNumber, PrevNextSeed, PrevNextVDFDifficulty), case Steps of not_found -> - ?LOG_WARNING([{event, found_solution_but_failed_to_find_checkpoints}, - {start_step_number, PrevStepNumber}, - {next_step_number, StepNumber}, - {next_seed, ar_util:safe_encode(PrevNextSeed)}, - {next_vdf_difficulty, PrevNextVDFDifficulty}]), - ar:console("WARNING: found a solution but failed to find checkpoints, " - "start step number: ~B, end step number: ~B, next_seed: ~s.", - [PrevStepNumber, StepNumber, PrevNextSeed]), + ar_mining_router:reject_solution(Solution, failed_to_find_checkpoints, + [{prev_next_seed, ar_util:safe_encode(PrevNextSeed)}, + {prev_next_vdf_difficulty, PrevNextVDFDifficulty}, + {prev_step_number, PrevStepNumber}]), error; _ -> - prepare_solution(proofs, Candidate, + prepare_solution_proofs(Candidate, Solution#mining_solution{ steps = Steps }) end; false -> - ?LOG_WARNING([{event, found_solution_but_stale_step_number}, - {start_step_number, PrevStepNumber}, - {next_step_number, StepNumber}, - {next_seed, ar_util:safe_encode(PrevNextSeed)}, - {next_vdf_difficulty, PrevNextVDFDifficulty}]), + ar_mining_router:reject_solution(Solution, stale_step_number, + [{prev_next_seed, ar_util:safe_encode(PrevNextSeed)}, + {prev_next_vdf_difficulty, PrevNextVDFDifficulty}, + {prev_step_number, PrevStepNumber}]), error - end; + end. -prepare_solution(proofs, Candidate, Solution) -> +prepare_solution_proofs(Candidate, Solution) -> #mining_candidate{ h0 = H0, h1 = H1, h2 = H2, nonce = Nonce, partition_number = PartitionNumber, partition_upper_bound = PartitionUpperBound } = Candidate, @@ -546,166 +542,39 @@ prepare_solution(proofs, Candidate, Solution) -> PartitionUpperBound), case { H1, H2 } of {not_set, not_set} -> - ?LOG_WARNING([{event, found_solution_but_h1_h2_not_set}]), + ar_mining_router:reject_solution(Solution, h1_h2_not_set, []), error; {H1, not_set} -> - prepare_solution(poa1, Candidate, Solution#mining_solution{ + prepare_solution_poa1(Candidate, Solution#mining_solution{ solution_hash = H1, recall_byte1 = RecallByte1, poa1 = may_be_empty_poa(PoA1), poa2 = #poa{} }); {_, H2} -> - prepare_solution(poa2, Candidate, Solution#mining_solution{ + prepare_solution_poa2(Candidate, Solution#mining_solution{ solution_hash = H2, recall_byte1 = RecallByte1, recall_byte2 = RecallByte2, poa1 = may_be_empty_poa(PoA1), poa2 = may_be_empty_poa(PoA2) }) - end; + end. -prepare_solution(poa1, Candidate, +prepare_solution_poa1(Candidate, #mining_solution{ poa1 = #poa{ chunk = <<>> } } = Solution) -> - #mining_solution{ - mining_address = MiningAddress, partition_number = PartitionNumber, - recall_byte1 = RecallByte1 } = Solution, - #mining_candidate{ - chunk1 = Chunk1, h0 = H0, nonce = Nonce, - partition_upper_bound = PartitionUpperBound } = Candidate, - case read_poa(RecallByte1, Chunk1, MiningAddress) of - {ok, PoA1} -> - Solution#mining_solution{ poa1 = PoA1 }; - _ -> - Modules = ar_storage_module:get_all(RecallByte1 + 1), - ModuleIDs = [ar_storage_module:id(Module) || Module <- Modules], - ?LOG_ERROR([{event, failed_to_find_poa_proofs_locally}, - {tags, [solution_proofs]}, - {recall_byte, RecallByte1}, - {modules_covering_recall_byte, ModuleIDs}]), - ar:console("WARNING: we have mined a block but did not find the PoA1 proofs " - "locally - searching the peers...~n"), - case fetch_poa_from_peers(RecallByte1) of - not_found -> - {RecallRange1Start, _RecallRange2Start} = ar_block:get_recall_range(H0, - PartitionNumber, PartitionUpperBound), - ?LOG_WARNING([{event, mined_block_but_failed_to_read_chunk_proofs}, - {recall_byte1, RecallByte1}, - {recall_range_start1, RecallRange1Start}, - {nonce, Nonce}, - {partition, PartitionNumber}, - {mining_address, ar_util:safe_encode(MiningAddress)}]), - ar:console("WARNING: we have mined a block but failed to find " - "the PoA1 proofs required for publishing it. " - "Check logs for more details~n"), - error; - PoA1 -> - Solution#mining_solution{ poa1 = PoA1#poa{ chunk = Chunk1 } } - end - end; -prepare_solution(poa2, Candidate, + #mining_solution{ recall_byte1 = RecallByte1 } = Solution, + case load_poa(RecallByte1, Candidate) of + not_found -> + ar_mining_router:reject_solution(Solution, failed_to_read_chunk_proofs, []), + error; + PoA -> Solution#mining_solution{ poa1 = PoA } + end. +prepare_solution_poa2(Candidate, #mining_solution{ poa2 = #poa{ chunk = <<>> } } = Solution) -> - #mining_solution{ mining_address = MiningAddress, partition_number = PartitionNumber, - recall_byte2 = RecallByte2 } = Solution, - #mining_candidate{ - chunk2 = Chunk2, h0 = H0, nonce = Nonce, - partition_upper_bound = PartitionUpperBound } = Candidate, - case read_poa(RecallByte2, Chunk2, MiningAddress) of - {ok, PoA2} -> - prepare_solution(poa1, Candidate, Solution#mining_solution{ poa2 = PoA2 }); - _ -> - Modules = ar_storage_module:get_all(RecallByte2 + 1), - ModuleIDs = [ar_storage_module:id(Module) || Module <- Modules], - ?LOG_ERROR([{event, failed_to_find_poa2_proofs_locally}, - {tags, [solution_proofs]}, - {recall_byte2, RecallByte2}, - {modules_covering_recall_byte2, ModuleIDs}]), - ar:console("WARNING: we have mined a block but did not find the PoA2 proofs " - "locally - searching the peers...~n"), - case fetch_poa_from_peers(RecallByte2) of - not_found -> - {_RecallRange1Start, RecallRange2Start} = ar_block:get_recall_range(H0, - PartitionNumber, PartitionUpperBound), - ?LOG_ERROR([{event, mined_block_but_failed_to_read_chunk_proofs}, - {tags, [solution_proofs]}, - {recall_byte2, RecallByte2}, - {recall_range_start2, RecallRange2Start}, - {nonce, Nonce}, - {partition, PartitionNumber}, - {mining_address, ar_util:safe_encode(MiningAddress)}]), - ar:console("WARNING: we have mined a block but failed to find " - "the PoA2 proofs required for publishing it. " - "Check logs for more details~n"), - error; - PoA2 -> - prepare_solution(poa1, Candidate, - Solution#mining_solution{ poa2 = PoA2#poa{ chunk = Chunk2 } }) - end + #mining_solution{ recall_byte2 = RecallByte2 } = Solution, + case load_poa(RecallByte2, Candidate) of + not_found -> + ar_mining_router:reject_solution(Solution, failed_to_read_chunk_proofs, []), + error; + PoA -> Solution#mining_solution{ poa2 = PoA } end; -prepare_solution(poa2, Candidate, +prepare_solution_poa2(Candidate, #mining_solution{ poa1 = #poa{ chunk = <<>> } } = Solution) -> - prepare_solution(poa1, Candidate, Solution); -prepare_solution(_, _Candidate, Solution) -> - Solution. - -post_solution(error, _State) -> - ?LOG_WARNING([{event, found_solution_but_could_not_build_a_block}]), - error; -post_solution(Solution, State) -> - {ok, Config} = application:get_env(arweave, config), - post_solution(Config#config.cm_exit_peer, Solution, State). - -post_solution(not_set, Solution, #state{ is_pool_client = true }) -> - %% When posting a partial solution the pool client will skip many of the validation steps - %% that are normally performed before sharing a solution. - ar_pool:post_partial_solution(Solution); -post_solution(not_set, Solution, State) -> - #state{ diff_pair = DiffPair } = State, - #mining_solution{ - mining_address = MiningAddress, nonce_limiter_output = NonceLimiterOutput, - partition_number = PartitionNumber, recall_byte1 = RecallByte1, - recall_byte2 = RecallByte2, - solution_hash = H, step_number = StepNumber } = Solution, - case validate_solution(Solution, DiffPair) of - error -> - ?LOG_WARNING([{event, failed_to_validate_solution}, - {partition, PartitionNumber}, - {step_number, StepNumber}, - {mining_address, ar_util:safe_encode(MiningAddress)}, - {recall_byte1, RecallByte1}, - {recall_byte2, RecallByte2}, - {solution_h, ar_util:safe_encode(H)}, - {nonce_limiter_output, ar_util:safe_encode(NonceLimiterOutput)}]), - ar:console("WARNING: we failed to validate our solution. Check logs for more " - "details~n"); - {false, Reason} -> - ?LOG_WARNING([{event, found_invalid_solution}, - {reason, Reason}, - {partition, PartitionNumber}, - {step_number, StepNumber}, - {mining_address, ar_util:safe_encode(MiningAddress)}, - {recall_byte1, RecallByte1}, - {recall_byte2, RecallByte2}, - {solution_h, ar_util:safe_encode(H)}, - {nonce_limiter_output, ar_util:safe_encode(NonceLimiterOutput)}]), - ar:console("WARNING: the solution we found is invalid. Check logs for more " - "details~n"); - {true, PoACache, PoA2Cache} -> - ar_events:send(miner, {found_solution, miner, Solution, PoACache, PoA2Cache}) - end; -post_solution(ExitPeer, Solution, #state{ is_pool_client = true }) -> - case ar_http_iface_client:post_partial_solution(ExitPeer, Solution) of - {ok, _} -> - ok; - {error, Reason} -> - ?LOG_WARNING([{event, found_partial_solution_but_failed_to_reach_exit_node}, - {reason, io_lib:format("~p", [Reason])}]), - ar:console("We found a partial solution but failed to reach the exit node, " - "error: ~p.", [io_lib:format("~p", [Reason])]) - end; -post_solution(ExitPeer, Solution, _State) -> - case ar_http_iface_client:cm_publish_send(ExitPeer, Solution) of - {ok, _} -> - ok; - {error, Reason} -> - ?LOG_WARNING([{event, found_solution_but_failed_to_reach_exit_node}, - {reason, io_lib:format("~p", [Reason])}]), - ar:console("We found a solution but failed to reach the exit node, " - "error: ~p.", [io_lib:format("~p", [Reason])]) - end. + prepare_solution_poa1(Candidate, Solution). may_be_empty_poa(not_set) -> #poa{}; @@ -782,7 +651,7 @@ handle_computed_output(SessionKey, StepNumber, Output, PartitionUpperBound, step_number = StepNumber, nonce_limiter_output = Output, partition_upper_bound = PartitionUpperBound, - cm_diff = PartialDiff + diff_pair = PartialDiff }, distribute_output(Candidate, State3), ?LOG_DEBUG([{event, mining_debug_processing_vdf_output}, @@ -792,6 +661,34 @@ handle_computed_output(SessionKey, StepNumber, Output, PartitionUpperBound, end, {noreply, State3}. +load_poa(RecallByte, Candidate) -> + #mining_candidate{ chunk1 = Chunk1, chunk2 = Chunk2, + h1 = H1, h2 = H2, mining_address = MiningAddress } = Candidate, + Chunk = case {H1, H2} of + {H1, not_set} -> Chunk1; + {_, H2} -> Chunk2 + end, + + case read_poa(RecallByte, Chunk, MiningAddress) of + {ok, PoA} -> + PoA; + _ -> + Modules = ar_storage_module:get_all(RecallByte + 1), + ModuleIDs = [ar_storage_module:id(Module) || Module <- Modules], + ?LOG_ERROR([{event, failed_to_find_poa_proofs_locally}, + {tags, [solution_proofs]}, + {recall_byte, RecallByte}, + {modules_covering_recall_byte, ModuleIDs}]), + ar:console("WARNING: we have found a solution but did not " + "find the PoA proofs locally - searching the peers...~n"), + case fetch_poa_from_peers(RecallByte) of + not_found -> + not_found; + PoA -> + PoA#poa{ chunk = Chunk } + end + end. + read_poa(RecallByte, Chunk, MiningAddress) -> PoAReply = read_poa(RecallByte, MiningAddress), case {Chunk, PoAReply} of diff --git a/apps/arweave/src/ar_mining_stats.erl b/apps/arweave/src/ar_mining_stats.erl index c31c2289b5..ca2cb41e6e 100644 --- a/apps/arweave/src/ar_mining_stats.erl +++ b/apps/arweave/src/ar_mining_stats.erl @@ -4,7 +4,7 @@ -export([start_link/0, start_performance_reports/0, pause_performance_reports/1, mining_paused/0, set_total_data_size/1, set_storage_module_data_size/6, vdf_computed/0, raw_read_rate/2, chunk_read/1, h1_computed/1, h2_computed/1, - h1_solution/0, h2_solution/0, block_found/0, + solution/1, block_found/0, h1_sent_to_peer/2, h1_received_from_peer/2, h2_sent_to_peer/1, h2_received_from_peer/1]). -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). @@ -22,8 +22,8 @@ -record(report, { now, vdf_speed = undefined, - h1_solution = 0, - h2_solution = 0, + solutions = #{}, + blocks = #{}, confirmed_block = 0, total_data_size = 0, optimal_overall_read_mibps = 0.0, @@ -121,6 +121,8 @@ h2_computed(PartitionNumber) -> increment_count({partition, PartitionNumber, h2, total}), increment_count({partition, PartitionNumber, h2, current}). +h1_sent_to_peer({pool, _}, H1Count) -> + h1_sent_to_peer(pool, H1Count); h1_sent_to_peer(Peer, H1Count) -> increment_count({peer, Peer, h1_to_peer, total}, H1Count), increment_count({peer, Peer, h1_to_peer, current}, H1Count). @@ -129,17 +131,16 @@ h1_received_from_peer(Peer, H1Count) -> increment_count({peer, Peer, h1_from_peer, total}, H1Count), increment_count({peer, Peer, h1_from_peer, current}, H1Count). +h2_sent_to_peer({pool, _}) -> + h2_sent_to_peer(pool); h2_sent_to_peer(Peer) -> increment_count({peer, Peer, h2_to_peer, total}). h2_received_from_peer(Peer) -> increment_count({peer, Peer, h2_from_peer, total}). -h1_solution() -> - increment_count(h1_solution). - -h2_solution() -> - increment_count(h2_solution). +solution(Status) -> + increment_count({solution, Status}). block_found() -> increment_count(confirmed_block). @@ -365,6 +366,12 @@ optimal_partition_hash_hps(PoA1Multiplier, VDFSpeed, PartitionDataSize, TotalDat H2Optimal = BasePartitionHashes * min(1.0, (TotalDataSize / WeaveSize)), H1Optimal + H2Optimal. +get_solutions() -> + #{ + found => get_count({solution, found}), + rejected => get_count({solution, rejected}) + }. + generate_report() -> {ok, Config} = application:get_env(arweave, config), Height = ar_node:get_height(), @@ -387,8 +394,7 @@ generate_report(Height, Partitions, Peers, WeaveSize, Now) -> Report = #report{ now = Now, vdf_speed = VDFSpeed, - h1_solution = get_count(h1_solution), - h2_solution = get_count(h2_solution), + solutions = get_solutions(), confirmed_block = get_count(confirmed_block), total_data_size = TotalDataSize, total_h2_to_peer = get_overall_total(peer, h2_to_peer, total), @@ -509,7 +515,7 @@ log_report(ReportString) -> log_report_lines([]) -> ok; log_report_lines([Line | Lines]) -> - ?LOG_INFO(Line), + ?LOG_ERROR(Line), log_report_lines(Lines). set_metrics(Report) -> @@ -590,12 +596,9 @@ format_report(Report, WeaveSize) -> "================================================= Mining Performance Report =================================================\n" "\n" "VDF Speed: ~s\n" - "H1 Solutions: ~B\n" - "H2 Solutions: ~B\n" "Confirmed Blocks: ~B\n" "\n", - [format_vdf_speed(Report#report.vdf_speed), Report#report.h1_solution, - Report#report.h2_solution, Report#report.confirmed_block] + [format_vdf_speed(Report#report.vdf_speed), Report#report.confirmed_block] ), PartitionTable = format_partition_report(Report, WeaveSize), PeerTable = format_peer_report(Report), @@ -603,16 +606,36 @@ format_report(Report, WeaveSize) -> io_lib:format("\n~s~s~s", [Preamble, PartitionTable, PeerTable]). format_partition_report(Report, WeaveSize) -> - Header = + BlocksHeader = + "Solution and block stats:\n" + "+--------------+-----------------+------------------+-----------------+----------------+------------------+\n" + "| Sol'ns Found | Sol'ns Rejected | Blocks Published | Blocks Orphaned | Blocks Rebased | Blocks Confirmed |\n" + "+--------------+-----------------+------------------+-----------------+----------------+------------------+\n", + BlocksRow = format_blocks_row(Report), + BlocksFooter = + "+--------------+-----------------+-----------------+-----------------+-----------------+------------------+\n\n", + MiningHeader = "Local mining stats:\n" "+-----------+-----------+----------+---------------+---------------+---------------+------------+------------+--------------+\n" "| Partition | Data Size | % of Max | Read (Cur) | Read (Avg) | Read (Ideal) | Hash (Cur) | Hash (Avg) | Hash (Ideal) |\n" "+-----------+-----------+----------+---------------+---------------+---------------+------------+------------+--------------+\n", TotalRow = format_partition_total_row(Report, WeaveSize), PartitionRows = format_partition_rows(Report#report.partitions), - Footer = + MiningFooter = "+-----------+-----------+----------+---------------+---------------+---------------+------------+------------+--------------+\n", - io_lib:format("~s~s~s~s", [Header, TotalRow, PartitionRows, Footer]). + io_lib:format("~s~s~s~s~s~s~s", + [BlocksHeader, BlocksRow, BlocksFooter, MiningHeader, TotalRow, PartitionRows, MiningFooter]). + + +format_blocks_row(Report) -> + Found = maps:get(found, Report#report.solutions, 0), + Rejected = maps:get(rejected, Report#report.solutions, 0), + Published = maps:get(published, Report#report.blocks, 0), + Orphaned = maps:get(orphaned, Report#report.blocks, 0), + Rebased = maps:get(rebased, Report#report.blocks, 0), + Confirmed = maps:get(confirmed, Report#report.blocks, 0), + io_lib:format("| ~12B | ~15B | ~16B | ~15B | ~14B | ~16B |\n", + [Found, Rejected, Published, Orphaned, Rebased, Confirmed]). format_partition_total_row(Report, WeaveSize) -> #report{ @@ -1245,9 +1268,10 @@ test_report(PoA1Multiplier) -> ar_mining_stats:vdf_computed(), ar_mining_stats:vdf_computed(), ar_mining_stats:vdf_computed(), - ar_mining_stats:h1_solution(), - ar_mining_stats:h2_solution(), - ar_mining_stats:h2_solution(), + ar_mining_stats:solution(found), + ar_mining_stats:solution(found), + ar_mining_stats:solution(found), + ar_mining_stats:solution(rejected), ar_mining_stats:block_found(), ar_mining_stats:chunk_read(1), ar_mining_stats:chunk_read(1), @@ -1303,8 +1327,10 @@ test_report(PoA1Multiplier) -> ?assertEqual(#report{ now = Now+1000, vdf_speed = 1.0 / 3.0, - h1_solution = 1, - h2_solution = 2, + solutions = #{ + found => 3, + rejected => 1 + }, confirmed_block = 1, total_data_size = floor(0.6 * ?PARTITION_SIZE), optimal_overall_read_mibps = 190.7998163223283, diff --git a/apps/arweave/src/ar_mining_sup.erl b/apps/arweave/src/ar_mining_sup.erl index 516a607920..3b2ed3fa6e 100644 --- a/apps/arweave/src/ar_mining_sup.erl +++ b/apps/arweave/src/ar_mining_sup.erl @@ -32,6 +32,7 @@ init([]) -> ar_mining_io:get_partitions(infinity) ), Children = MiningWorkers ++ [ + ?CHILD(ar_mining_router, worker), ?CHILD(ar_mining_server, worker), ?CHILD(ar_mining_hash, worker), ?CHILD(ar_mining_io, worker), diff --git a/apps/arweave/src/ar_mining_worker.erl b/apps/arweave/src/ar_mining_worker.erl index 2a1f6b5c07..7370edce3b 100644 --- a/apps/arweave/src/ar_mining_worker.erl +++ b/apps/arweave/src/ar_mining_worker.erl @@ -8,7 +8,6 @@ -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). -include_lib("arweave/include/ar.hrl"). --include_lib("arweave/include/ar_config.hrl"). -include_lib("arweave/include/ar_consensus.hrl"). -include_lib("arweave/include/ar_mining.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -23,8 +22,7 @@ chunk_cache_size = #{}, chunk_cache_limit = 0, vdf_queue_limit = 0, - latest_vdf_step_number = 0, - is_pool_client = false + latest_vdf_step_number = 0 }). -define(TASK_CHECK_FREQUENCY_MS, 200). @@ -117,8 +115,7 @@ init(Partition) -> gen_server:cast(self(), handle_task), gen_server:cast(self(), check_worker_status), prometheus_gauge:set(mining_server_chunk_cache_size, [Partition], 0), - {ok, #state{ name = Name, partition_number = Partition, - is_pool_client = ar_pool:is_client() }}. + {ok, #state{ name = Name, partition_number = Partition }}. handle_call(Request, _From, State) -> ?LOG_WARNING([{event, unhandled_call}, {module, ?MODULE}, {request, Request}]), @@ -279,7 +276,7 @@ handle_task({chunk2, Candidate}, State) -> not_set -> ok; _ -> - ?LOG_ERROR([{event, cm_chunk2_cached_before_chunk1}, + ?LOG_INFO([{event, cm_chunk2_cached_before_chunk1}, {worker, State#state.name}, {partition_number, Candidate#mining_candidate.partition_number}, {partition_number2, Candidate#mining_candidate.partition_number2}, @@ -355,121 +352,49 @@ handle_task({computed_h0, Candidate}, State) -> handle_task({computed_h1, Candidate}, State) -> #mining_candidate{ h1 = H1, chunk1 = Chunk1, session_key = SessionKey } = Candidate, - case h1_passes_diff_checks(H1, Candidate, State) of - true -> - ?LOG_INFO([{event, found_h1_solution}, {worker, State#state.name}, - {h1, ar_util:encode(H1)}, {difficulty, get_difficulty(State, Candidate)}]), - ar_mining_stats:h1_solution(), - %% Decrement 1 for chunk1: - %% Since we found a solution we won't need chunk2 (and it will be evicted if - %% necessary below) - State2 = remove_chunk_from_cache(Candidate, State), - ar_mining_server:prepare_and_post_solution(Candidate), + DiffPair = get_difficulty(State, Candidate), + DiffCheck = ar_node_utils:h1_passes_diff_check(H1, DiffPair), + case DiffCheck of + false -> ok; + _ -> + ar_mining_router:found_solution(Candidate, + [{worker, State#state.name}, {difficulty, DiffPair}]), + %% xxx: prepare_solution, non-blocking, no-response + ar_mining_router:prepare_solution(Candidate) + end, + + case cycle_chunk_cache(Candidate, {chunk1, Chunk1, H1}, State) of + {cached, State2} -> + %% Chunk2 hasn't been read yet, so we cache Chunk1 and wait for + %% Chunk2 to be read. {noreply, State2}; - Result -> - case Result of - partial -> - ar_mining_server:prepare_and_post_solution(Candidate); - _ -> - ok - end, - {ok, Config} = application:get_env(arweave, config), - case cycle_chunk_cache(Candidate, {chunk1, Chunk1, H1}, State) of - {cached, State2} -> - %% Chunk2 hasn't been read yet, so we cache Chunk1 and wait for - %% Chunk2 to be read. - {noreply, State2}; - {do_not_cache, State2} -> - %% This node does not store Chunk2. If we're part of a coordinated - %% mining set, we can try one of our peers, otherwise we're done. - case Config#config.coordinated_mining of - false -> - ok; - true -> - DiffPair = - case get_partial_difficulty(State, Candidate) of - not_set -> - get_difficulty(State, Candidate); - PartialDiffPair -> - PartialDiffPair - end, - ar_coordination:computed_h1(Candidate, DiffPair) - end, - %% Decrement 1 for chunk1: - %% do_not_cache indicates chunk2 was not and will not be read or cached - {noreply, update_chunk_cache_size(-1, SessionKey, State2)}; - {{chunk2, Chunk2}, State2} -> - %% Chunk2 has already been read, so we can compute H2 now. - ar_mining_hash:compute_h2( - self(), Candidate#mining_candidate{ chunk2 = Chunk2 }), - %% Decrement 2 for chunk1 and chunk2: - %% 1. chunk2 was previously read and cached - %% 2. chunk1 that was just read and used to compute H1 - {noreply, update_chunk_cache_size(-2, SessionKey, State2)} - end + {do_not_cache, State2} -> + %% This node does not store Chunk2. If we're part of a coordinated + %% mining set, we can try one of our peers, otherwise we're done. + ar_mining_router:route_h1(Candidate, DiffPair), + %% Decrement 1 for chunk1: + %% do_not_cache indicates chunk2 was not and will not be read or cached + {noreply, update_chunk_cache_size(-1, SessionKey, State2)}; + {{chunk2, Chunk2}, State2} -> + %% Chunk2 has already been read, so we can compute H2 now. + ar_mining_hash:compute_h2( + self(), Candidate#mining_candidate{ chunk2 = Chunk2 }), + %% Decrement 2 for chunk1 and chunk2: + %% 1. chunk2 was previously read and cached + %% 2. chunk1 that was just read and used to compute H1 + {noreply, update_chunk_cache_size(-2, SessionKey, State2)} end; handle_task({computed_h2, Candidate}, State) -> - #mining_candidate{ - chunk2 = Chunk2, h0 = H0, h2 = H2, mining_address = MiningAddress, - nonce = Nonce, partition_number = Partition1, - partition_upper_bound = PartitionUpperBound, cm_lead_peer = Peer - } = Candidate, - PassesDiffChecks = h2_passes_diff_checks(H2, Candidate, State), - case PassesDiffChecks of - false -> - ok; - true -> - ?LOG_INFO([{event, found_h2_solution}, - {worker, State#state.name}, - {h2, ar_util:encode(H2)}, - {difficulty, get_difficulty(State, Candidate)}, - {partial_difficulty, get_partial_difficulty(State, Candidate)}]), - ar_mining_stats:h2_solution(); - partial -> - ?LOG_INFO([{event, found_h2_partial_solution}, - {worker, State#state.name}, - {h2, ar_util:encode(H2)}, - {partial_difficulty, get_partial_difficulty(State, Candidate)}]) - end, - case {PassesDiffChecks, Peer} of - {false, _} -> - ok; - {_, not_set} -> - ar_mining_server:prepare_and_post_solution(Candidate); + #mining_candidate{ h2 = H2 } = Candidate, + DiffPair = get_difficulty(State, Candidate), + DiffCheck = ar_node_utils:h2_passes_diff_check(H2, DiffPair), + case DiffCheck of + false -> ok; _ -> - {_RecallByte1, RecallByte2} = ar_mining_server:get_recall_bytes(H0, Partition1, - Nonce, PartitionUpperBound), - LocalPoA2 = ar_mining_server:read_poa(RecallByte2, Chunk2, MiningAddress), - PoA2 = - case LocalPoA2 of - {ok, LocalPoA3} -> - LocalPoA3; - _ -> - ar:console("WARNING: we have found an H2 solution but did not find " - "the PoA2 proofs locally - searching the peers...~n"), - case ar_mining_server:fetch_poa_from_peers(RecallByte2) of - not_found -> - ?LOG_WARNING([{event, - mined_block_but_failed_to_read_second_chunk_proof}, - {worker, State#state.name}, - {recall_byte2, RecallByte2}, - {mining_address, ar_util:safe_encode(MiningAddress)}]), - ar:console("WARNING: we found an H2 solution but failed to find " - "the proof for the second chunk. See logs for more " - "details.~n"), - not_found; - PeerPoA2 -> - PeerPoA2 - end - end, - case PoA2 of - not_found -> - ok; - _ -> - ar_coordination:computed_h2_for_peer( - Candidate#mining_candidate{ poa2 = PoA2 }) - end + ar_mining_router:found_solution(Candidate, + [{worker, State#state.name}, {difficulty, DiffPair}]), + ar_mining_router:route_h2(Candidate) end, {noreply, State}; @@ -516,32 +441,6 @@ handle_task({compute_h2_for_peer, Candidate}, State) -> %%% Private functions. %%%=================================================================== -h1_passes_diff_checks(H1, Candidate, State) -> - passes_diff_checks(H1, true, Candidate, State). - -h2_passes_diff_checks(H2, Candidate, State) -> - passes_diff_checks(H2, false, Candidate, State). - -passes_diff_checks(SolutionHash, IsPoA1, Candidate, State) -> - DiffPair = get_difficulty(State, Candidate), - case ar_node_utils:passes_diff_check(SolutionHash, IsPoA1, DiffPair) of - true -> - true; - false -> - case get_partial_difficulty(State, Candidate) of - not_set -> - false; - PartialDiffPair -> - case ar_node_utils:passes_diff_check(SolutionHash, IsPoA1, - PartialDiffPair) of - true -> - partial; - false -> - false - end - end - end. - maybe_warn_about_lag(Q, Name) -> case gb_sets:is_empty(Q) of true -> @@ -763,14 +662,9 @@ cache_h1_list( State2 = cache_chunk({chunk1, H1}, Candidate#mining_candidate{ nonce = Nonce }, State), cache_h1_list(Candidate, H1List, State2). -get_difficulty(State, #mining_candidate{ cm_diff = not_set }) -> +get_difficulty(State, #mining_candidate{ diff_pair = not_set }) -> State#state.diff_pair; -get_difficulty(_State, #mining_candidate{ cm_diff = DiffPair }) -> - DiffPair. - -get_partial_difficulty(#state{ is_pool_client = false }, _Candidate) -> - not_set; -get_partial_difficulty(_State, #mining_candidate{ cm_diff = DiffPair }) -> +get_difficulty(_State, #mining_candidate{ diff_pair = DiffPair }) -> DiffPair. nonce_max() -> @@ -782,7 +676,3 @@ generate_cache_ref(Candidate) -> partition_upper_bound = PartitionUpperBound } = Candidate, CacheRef = {Partition1, Partition2, PartitionUpperBound, make_ref()}, Candidate#mining_candidate{ cache_ref = CacheRef }. - -%%%=================================================================== -%%% Public Test interface. -%%%=================================================================== diff --git a/apps/arweave/src/ar_node_utils.erl b/apps/arweave/src/ar_node_utils.erl index e9cc0b56e1..0f855d2f5e 100644 --- a/apps/arweave/src/ar_node_utils.erl +++ b/apps/arweave/src/ar_node_utils.erl @@ -3,8 +3,8 @@ -export([apply_tx/3, apply_txs/3, update_accounts/3, validate/6, h1_passes_diff_check/2, h2_passes_diff_check/2, solution_passes_diff_check/2, - block_passes_diff_check/1, block_passes_diff_check/2, passes_diff_check/3, - update_account/6, is_account_banned/2]). + candidate_passes_diff_check/2, block_passes_diff_check/1, block_passes_diff_check/2, + passes_diff_check/3, update_account/6, is_account_banned/2]). -include_lib("arweave/include/ar.hrl"). -include_lib("arweave/include/ar_pricing.hrl"). @@ -84,6 +84,12 @@ h1_passes_diff_check(H1, DiffPair) -> h2_passes_diff_check(H2, DiffPair) -> passes_diff_check(H2, false, DiffPair). +candidate_passes_diff_check( + #mining_candidate{ h2 = not_set } = Candidate, DiffPair) -> + passes_diff_check(Candidate#mining_candidate.h1, true, DiffPair); +candidate_passes_diff_check(Candidate, DiffPair) -> + passes_diff_check(Candidate#mining_candidate.h2, false, DiffPair). + solution_passes_diff_check(Solution, DiffPair) -> SolutionHash = Solution#mining_solution.solution_hash, IsPoA1 = ar_mining_server:is_one_chunk_solution(Solution), diff --git a/apps/arweave/src/ar_node_worker.erl b/apps/arweave/src/ar_node_worker.erl index c587e734ed..ccb7accba3 100644 --- a/apps/arweave/src/ar_node_worker.erl +++ b/apps/arweave/src/ar_node_worker.erl @@ -18,8 +18,6 @@ -include_lib("arweave/include/ar_consensus.hrl"). -include_lib("arweave/include/ar_config.hrl"). -include_lib("arweave/include/ar_pricing.hrl"). --include_lib("arweave/include/ar_data_sync.hrl"). --include_lib("arweave/include/ar_vdf.hrl"). -include_lib("arweave/include/ar_mining.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -1796,14 +1794,13 @@ handle_found_solution(Args, PrevB, State) -> PassesTimelineCheck = case IsBanned of true -> - ar_events:send(solution, {rejected, #{ reason => mining_address_banned, - source => Source }}), + ar_mining_router:reject_solution(Solution, address_banned, [], Source), {false, address_banned}; false -> case ar_nonce_limiter:is_ahead_on_the_timeline(NonceLimiterInfo, PrevNonceLimiterInfo) of false -> - ar_events:send(solution, {stale, #{ source => Source }}), + ar_mining_router:reject_solution(Solution, stale, [], Source), {false, timeline}; true -> true @@ -1823,7 +1820,7 @@ handle_found_solution(Args, PrevB, State) -> case {IntervalNumber, NonceLimiterNextSeed, NonceLimiterNextVDFDifficulty} == {PrevIntervalNumber, PrevNextSeed, PrevNextVDFDifficulty} of false -> - ar_events:send(solution, {stale, #{ source => Source }}), + ar_mining_router:reject_solution(Solution, stale, [], Source), {false, seed_data}; true -> true @@ -1843,7 +1840,7 @@ handle_found_solution(Args, PrevB, State) -> true -> case ar_node_utils:solution_passes_diff_check(Solution, DiffPair) of false -> - ar_events:send(solution, {partial, #{ source => Source }}), + ar_mining_router:accept_solution(Solution, Source), {false, diff}; true -> true @@ -1866,8 +1863,8 @@ handle_found_solution(Args, PrevB, State) -> true -> case RewardKey of not_found -> - ar_events:send(solution, - {rejected, #{ reason => missing_key_file, source => Source }}), + ar_mining_router:reject_solution( + Solution, missing_key_file, [], Source), {false, wallet_not_found}; _ -> true @@ -1890,8 +1887,7 @@ handle_found_solution(Args, PrevB, State) -> HaveSteps = case CorrectRebaseThreshold of {false, Reason5} -> - ?LOG_WARNING([{event, ignore_mining_solution}, - {reason, Reason5}, {solution, ar_util:encode(SolutionH)}]), + ar_mining_router:reject_solution(Solution, Reason5, []), false; true -> ar_nonce_limiter:get_steps(PrevStepNumber, StepNumber, PrevNextSeed, @@ -1911,11 +1907,10 @@ handle_found_solution(Args, PrevB, State) -> false -> {noreply, State}; not_found -> - ar_events:send(solution, - {rejected, #{ reason => vdf_not_found, source => Source }}), - ?LOG_WARNING([{event, did_not_find_steps_for_mined_block}, - {seed, ar_util:encode(PrevNextSeed)}, {prev_step_number, PrevStepNumber}, - {step_number, StepNumber}]), + ar_mining_router:reject_solution(Solution, vdf_not_found, [ + {prev_next_seed, ar_util:encode(PrevNextSeed)}, + {prev_step_number, PrevStepNumber} + ], Source), {noreply, State}; [NonceLimiterOutput | _] = Steps -> {Seed, NextSeed, PartitionUpperBound, NextPartitionUpperBound, VDFDifficulty} @@ -2026,17 +2021,14 @@ handle_found_solution(Args, PrevB, State) -> _ -> 2 end}]), ar_block_cache:add(block_cache, B), - ar_events:send(solution, {accepted, #{ indep_hash => H, source => Source }}), + ar_mining_router:accept_block_solution(Solution, H, Source), apply_block(update_solution_cache(H, Args, State)); _Steps -> - ar_events:send(solution, - {rejected, #{ reason => bad_vdf, source => Source }}), - ?LOG_ERROR([{event, bad_steps}, + ar_mining_router:reject_solution(Solution, bad_vdf, [ {prev_block, ar_util:encode(PrevH)}, - {step_number, StepNumber}, - {prev_step_number, PrevStepNumber}, {prev_next_seed, ar_util:encode(PrevNextSeed)}, - {output, ar_util:encode(NonceLimiterOutput)}]), + {prev_step_number, PrevStepNumber} + ], Source), {noreply, State} end. diff --git a/apps/arweave/src/ar_pool.erl b/apps/arweave/src/ar_pool.erl index 9a35bde567..710776aef8 100644 --- a/apps/arweave/src/ar_pool.erl +++ b/apps/arweave/src/ar_pool.erl @@ -35,9 +35,9 @@ -behaviour(gen_server). --export([start_link/0, is_client/0, get_current_session_key_seed_pairs/0, get_jobs/1, - get_latest_job/0, cache_jobs/1, process_partial_solution/1, - post_partial_solution/1, pool_peer/0, process_cm_jobs/2]). +-export([start_link/0, is_client/0, is_server/0, get_current_session_key_seed_pairs/0, + generate_jobs/1, get_latest_job/0, cache_jobs/1, get_cached_jobs/1, + process_partial_solution/2, post_partial_solution/1, pool_peer/0, process_cm_jobs/2]). -export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2]). @@ -51,8 +51,7 @@ %% The most recent keys come first. session_keys = [], %% Key => [{Output, StepNumber, PartitionUpperBound, Seed, Diff}, ...] - jobs_by_session_key = maps:new(), - request_pid_by_ref = maps:new() + jobs_by_session_key = maps:new() }). %%%=================================================================== @@ -68,13 +67,49 @@ is_client() -> {ok, Config} = application:get_env(arweave, config), Config#config.is_pool_client == true. +is_server() -> + {ok, Config} = application:get_env(arweave, config), + Config#config.is_pool_server == true. + %% @doc Return a list of up to two most recently cached VDF session key, seed pairs. get_current_session_key_seed_pairs() -> gen_server:call(?MODULE, get_current_session_key_seed_pairs, infinity). +generate_jobs(PrevOutput) -> + Props = + ets:select( + node_state, + [{{'$1', '$2'}, + [{'or', + {'==', '$1', diff_pair}, + {'==', '$1', nonce_limiter_info}}], ['$_']}] + ), + DiffPair = proplists:get_value(diff_pair, Props), + Info = proplists:get_value(nonce_limiter_info, Props), + Result = ar_util:do_until( + fun() -> + S = ar_nonce_limiter:get_step_triplets(Info, PrevOutput, ?GET_JOBS_COUNT), + case S of + [] -> + false; + _ -> + {ok, S} + end + end, + 200, + (?GET_JOBS_TIMEOUT_S) * 1000 + ), + Steps = case Result of {ok, S} -> S; _ -> [] end, + {NextSeed, IntervalNumber, NextVDFDiff} = ar_nonce_limiter:session_key(Info), + JobList = [#job{ output = O, global_step_number = SN, + partition_upper_bound = U } || {O, SN, U} <- Steps], + #jobs{ jobs = JobList, seed = Info#nonce_limiter_info.seed, + next_seed = NextSeed, interval_number = IntervalNumber, + next_vdf_difficulty = NextVDFDiff, partial_diff = DiffPair }. + %% @doc Return a set of the most recent cached jobs. -get_jobs(PrevOutput) -> - gen_server:call(?MODULE, {get_jobs, PrevOutput}, infinity). +get_cached_jobs(PrevOutput) -> + gen_server:call(?MODULE, {get_cached_jobs, PrevOutput}, infinity). %% @doc Return the most recent cached #job{}. Return an empty record if the %% cache is empty. @@ -87,8 +122,15 @@ cache_jobs(Jobs) -> %% @doc Validate the given (partial) solution. If the solution is eligible for %% producing a block, produce and publish a block. -process_partial_solution(Solution) -> - gen_server:call(?MODULE, {process_partial_solution, Solution}, infinity). +process_partial_solution(Solution, Ref) -> + PoA1 = Solution#mining_solution.poa1, + PoA2 = Solution#mining_solution.poa2, + case ar_block:validate_proof_size(PoA1) andalso ar_block:validate_proof_size(PoA2) of + true -> + process_partial_solution_field_size(Solution, Ref); + false -> + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) + end. %% @doc Send the partial solution to the pool. post_partial_solution(Solution) -> @@ -126,10 +168,10 @@ handle_call(get_current_session_key_seed_pairs, _From, State) -> KeySeedPairs = [{Key, element(4, hd(maps:get(Key, JobsBySessionKey)))} || Key <- Keys], {reply, KeySeedPairs, State}; -handle_call({get_jobs, PrevOutput}, _From, State) -> +handle_call({get_cached_jobs, PrevOutput}, _From, State) -> SessionKeys = State#state.session_keys, JobCache = State#state.jobs_by_session_key, - {reply, get_jobs(PrevOutput, SessionKeys, JobCache), State}; + {reply, get_cached_jobs(PrevOutput, SessionKeys, JobCache), State}; handle_call(get_latest_job, _From, State) -> case State#state.session_keys of @@ -141,16 +183,6 @@ handle_call(get_latest_job, _From, State) -> partition_upper_bound = U }, State} end; -handle_call({process_partial_solution, Solution}, From, State) -> - #state{ request_pid_by_ref = Map } = State, - Ref = make_ref(), - case process_partial_solution(Solution, Ref) of - noreply -> - {noreply, State#state{ request_pid_by_ref = maps:put(Ref, From, Map) }}; - Reply -> - {reply, Reply, State} - end; - handle_call(Request, _From, State) -> ?LOG_WARNING([{event, unhandled_call}, {module, ?MODULE}, {request, Request}]), {reply, ok, State}. @@ -201,59 +233,6 @@ handle_cast(Cast, State) -> ?LOG_WARNING([{event, unhandled_cast}, {module, ?MODULE}, {cast, Cast}]), {noreply, State}. -handle_info({event, solution, - {rejected, #{ reason := mining_address_banned, source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, - #partial_solution_response{ status = <<"rejected_mining_address_banned">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, - {rejected, #{ reason := missing_key_file, source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, - #partial_solution_response{ status = <<"rejected_missing_key_file">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, - {rejected, #{ reason := vdf_not_found, source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, #partial_solution_response{ status = <<"rejected_vdf_not_found">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, - {rejected, #{ reason := bad_vdf, source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, #partial_solution_response{ status = <<"rejected_bad_vdf">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, {partial, #{ source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, #partial_solution_response{ status = <<"accepted">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, {stale, #{ source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, #partial_solution_response{ status = <<"stale">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, - {accepted, #{ indep_hash := H, source := {pool, Ref} }}}, State) -> - #state{ request_pid_by_ref = Map } = State, - PID = maps:get(Ref, Map), - gen_server:reply(PID, - #partial_solution_response{ indep_hash = H, status = <<"accepted_block">> }), - {noreply, State#state{ request_pid_by_ref = maps:remove(Ref, Map) }}; - -handle_info({event, solution, _Event}, State) -> - {noreply, State}; - handle_info(Message, State) -> ?LOG_WARNING([{event, unhandled_info}, {module, ?MODULE}, {message, Message}]), {noreply, State}. @@ -265,13 +244,13 @@ terminate(_Reason, _State) -> %%% Private functions. %%%=================================================================== -get_jobs(PrevOutput, SessionKeys, JobCache) -> +get_cached_jobs(PrevOutput, SessionKeys, JobCache) -> case SessionKeys of [] -> #jobs{}; [{NextSeed, Interval, NextVDFDifficulty} = SessionKey | _] -> Jobs = maps:get(SessionKey, JobCache), - {Seed, PartialDiff, Jobs2} = collect_jobs(Jobs, PrevOutput, ?GET_JOBS_COUNT), + {Seed, PartialDiff, Jobs2} = collect_cached_jobs(Jobs, PrevOutput, ?GET_JOBS_COUNT), Jobs3 = [#job{ output = O, global_step_number = SN, partition_upper_bound = U } || {O, SN, U} <- Jobs2], #jobs{ jobs = Jobs3, seed = Seed, partial_diff = PartialDiff, @@ -279,37 +258,27 @@ get_jobs(PrevOutput, SessionKeys, JobCache) -> interval_number = Interval, next_vdf_difficulty = NextVDFDifficulty } end. -collect_jobs([], _PrevO, _N) -> +collect_cached_jobs([], _PrevO, _N) -> {<<>>, {0, 0}, []}; -collect_jobs(_Jobs, _PrevO, 0) -> +collect_cached_jobs(_Jobs, _PrevO, 0) -> {<<>>, {0, 0}, []}; -collect_jobs([{O, _SN, _U, _S, _PartialDiff} | _Jobs], O, _N) -> +collect_cached_jobs([{O, _SN, _U, _S, _PartialDiff} | _Jobs], O, _N) -> {<<>>, {0, 0}, []}; -collect_jobs([{O, SN, U, S, PartialDiff} | Jobs], PrevO, N) -> - {S, PartialDiff, [{O, SN, U} | collect_jobs(Jobs, PrevO, N - 1, PartialDiff)]}. +collect_cached_jobs([{O, SN, U, S, PartialDiff} | Jobs], PrevO, N) -> + {S, PartialDiff, [{O, SN, U} | collect_cached_jobs(Jobs, PrevO, N - 1, PartialDiff)]}. -collect_jobs([], _PrevO, _N, _PartialDiff) -> +collect_cached_jobs([], _PrevO, _N, _PartialDiff) -> []; -collect_jobs(_Jobs, _PrevO, 0, _PartialDiff) -> +collect_cached_jobs(_Jobs, _PrevO, 0, _PartialDiff) -> []; -collect_jobs([{O, _SN, _U, _S, _PartialDiff} | _Jobs], O, _N, _PartialDiff2) -> +collect_cached_jobs([{O, _SN, _U, _S, _PartialDiff} | _Jobs], O, _N, _PartialDiff2) -> []; -collect_jobs([{O, SN, U, _S, PartialDiff} | Jobs], PrevO, N, PartialDiff) -> - [{O, SN, U} | collect_jobs(Jobs, PrevO, N - 1, PartialDiff)]; -collect_jobs(_Jobs, _PrevO, _N, _PartialDiff) -> +collect_cached_jobs([{O, SN, U, _S, PartialDiff} | Jobs], PrevO, N, PartialDiff) -> + [{O, SN, U} | collect_cached_jobs(Jobs, PrevO, N - 1, PartialDiff)]; +collect_cached_jobs(_Jobs, _PrevO, _N, _PartialDiff) -> %% PartialDiff mismatch. []. -process_partial_solution(Solution, Ref) -> - PoA1 = Solution#mining_solution.poa1, - PoA2 = Solution#mining_solution.poa2, - case ar_block:validate_proof_size(PoA1) andalso ar_block:validate_proof_size(PoA2) of - true -> - process_partial_solution_field_size(Solution, Ref); - false -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } - end. - process_partial_solution_field_size(Solution, Ref) -> #mining_solution{ nonce_limiter_output = Output, @@ -332,7 +301,7 @@ process_partial_solution_field_size(Solution, Ref) -> %% We are not strict about the first chunk here to simplify tests. process_partial_solution_poa2_size(Solution, Ref); _ -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) end. process_partial_solution_poa2_size(Solution, Ref) -> @@ -345,7 +314,7 @@ process_partial_solution_poa2_size(Solution, Ref) -> {<<>>, <<>>, <<>>} -> process_partial_solution_partition_number(Solution, Ref); _ -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) end; false -> process_partial_solution_partition_number(Solution, Ref) @@ -359,7 +328,7 @@ process_partial_solution_partition_number(Solution, Ref) -> false -> process_partial_solution_nonce(Solution, Ref); true -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) end. process_partial_solution_nonce(Solution, Ref) -> @@ -368,7 +337,7 @@ process_partial_solution_nonce(Solution, Ref) -> false -> process_partial_solution_quick_pow(Solution, Ref); true -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) end. process_partial_solution_quick_pow(Solution, Ref) -> @@ -386,7 +355,7 @@ process_partial_solution_quick_pow(Solution, Ref) -> process_partial_solution_pow(Solution, Ref, H0); _ -> %% Solution hash mismatch (pattern matching against solution_hash = SolutionH). - #partial_solution_response{ status = <<"rejected_wrong_hash">> } + ar_mining_router:reject_solution(Solution, wrong_hash, [], Ref) end. process_partial_solution_pow(Solution, Ref, H0) -> @@ -406,7 +375,7 @@ process_partial_solution_pow(Solution, Ref, H0) -> {H2, Preimage2} = ar_block:compute_h2(H1, Chunk2, H0), case H2 == SolutionH andalso Preimage2 == Preimage of false -> - #partial_solution_response{ status = <<"rejected_wrong_hash">> }; + ar_mining_router:reject_solution(Solution, wrong_hash, [], Ref); true -> process_partial_solution_partition_upper_bound(Solution, Ref, H0, H1) end @@ -421,7 +390,7 @@ process_partial_solution_partition_upper_bound(Solution, Ref, H0, H1) -> true -> process_partial_solution_poa(Solution, Ref, H0, H1); _ -> - #partial_solution_response{ status = <<"rejected_bad_poa">> } + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref) end. process_partial_solution_poa(Solution, Ref, H0, H1) -> @@ -446,9 +415,9 @@ process_partial_solution_poa(Solution, Ref, H0, H1) -> {spora_2_6, MiningAddress}, not_set}) of error -> ?LOG_ERROR([{event, pool_failed_to_validate_proof_of_access}]), - #partial_solution_response{ status = <<"rejected_bad_poa">> }; + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref); false -> - #partial_solution_response{ status = <<"rejected_bad_poa">> }; + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref); {true, ChunkID} when H1 == SolutionH -> PoACache = {{BlockStart1, RecallByte1, TXRoot1, BlockSize1, {spora_2_6, MiningAddress}}, ChunkID}, @@ -464,9 +433,9 @@ process_partial_solution_poa(Solution, Ref, H0, H1) -> PoA2, {spora_2_6, MiningAddress}, not_set}) of error -> ?LOG_ERROR([{event, pool_failed_to_validate_proof_of_access}]), - #partial_solution_response{ status = <<"rejected_bad_poa">> }; + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref); false -> - #partial_solution_response{ status = <<"rejected_bad_poa">> }; + ar_mining_router:reject_solution(Solution, bad_poa, [], Ref); {true, Chunk2ID} -> PoA2Cache = {{BlockStart2, RecallByte2, TXRoot2, BlockSize2, {spora_2_6, MiningAddress}}, Chunk2ID}, @@ -481,7 +450,7 @@ process_partial_solution_difficulty(Solution, Ref, PoACache, PoA2Cache) -> IsPoA1 = (RecallByte2 == undefined), case ar_node_utils:passes_diff_check(SolutionH, IsPoA1, ar_node:get_current_diff()) of false -> - #partial_solution_response{ status = <<"accepted">> }; + ar_mining_router:accept_solution(Solution, Ref); true -> process_partial_solution_vdf(Solution, Ref, PoACache, PoA2Cache) end. @@ -502,11 +471,11 @@ process_partial_solution_vdf(Solution, Ref, PoACache, PoA2Cache) -> MayBeUpperBound = ar_nonce_limiter:get_active_partition_upper_bound(StepNumber, SessionKey), case {MayBeLastStepCheckpoints, MayBeSeed, MayBeUpperBound} of {not_found, _, _} -> - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }; + ar_mining_router:reject_solution(Solution, vdf_not_found, [], Ref); {_, not_found, _} -> - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }; + ar_mining_router:reject_solution(Solution, vdf_not_found, [], Ref); {_, _, not_found} -> - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }; + ar_mining_router:reject_solution(Solution, vdf_not_found, [], Ref); {[Output | _] = LastStepCheckpoints, Seed, PartitionUpperBound} -> Solution2 = Solution#mining_solution{ @@ -520,7 +489,7 @@ process_partial_solution_vdf(Solution, Ref, PoACache, PoA2Cache) -> _ -> %% {Output, Seed, PartitionUpperBound} mismatch (pattern matching against %% the solution fields deconstructed above). - #partial_solution_response{ status = <<"rejected_bad_vdf">> } + ar_mining_router:reject_solution(Solution, bad_vdf, [], Ref) end. process_h1_to_h2_jobs([], _Peer, _Partitions) -> @@ -539,7 +508,8 @@ process_h1_read_jobs([], _Partitions) -> process_h1_read_jobs([Candidate | Jobs], Partitions) -> case we_have_partition_for_the_first_recall_byte(Candidate, Partitions) of true -> - ar_mining_server:prepare_and_post_solution(Candidate), + %% Solution received + ar_mining_router:prepare_solution(Candidate), ar_mining_stats:h2_received_from_peer(pool); false -> ok @@ -571,10 +541,10 @@ we_have_partition_for_the_second_recall_byte(Candidate, [_Partition | Partitions %%%=================================================================== get_jobs_test() -> - ?assertEqual(#jobs{}, get_jobs(<<>>, [], maps:new())), + ?assertEqual(#jobs{}, get_cached_jobs(<<>>, [], maps:new())), ?assertEqual(#jobs{ next_seed = ns, interval_number = in, next_vdf_difficulty = nvd }, - get_jobs(o, [{ns, in, nvd}], + get_cached_jobs(o, [{ns, in, nvd}], #{ {ns, in, nvd} => [{o, gsn, u, s, d}] })), ?assertEqual(#jobs{ jobs = [#job{ output = o, global_step_number = gsn, @@ -584,7 +554,7 @@ get_jobs_test() -> next_seed = ns, interval_number = in, next_vdf_difficulty = nvd }, - get_jobs(a, [{ns, in, nvd}], + get_cached_jobs(a, [{ns, in, nvd}], #{ {ns, in, nvd} => [{o, gsn, u, s, d}] })), %% d2 /= d (the difficulties are different) => only take the latest job. @@ -595,7 +565,7 @@ get_jobs_test() -> next_seed = ns, interval_number = in, next_vdf_difficulty = nvd }, - get_jobs(a, [{ns, in, nvd}, {ns2, in2, nvd2}], + get_cached_jobs(a, [{ns, in, nvd}, {ns2, in2, nvd2}], #{ {ns, in, nvd} => [{o, gsn, u, s, d}, {o2, gsn2, u2, s, d2}], %% Same difficulty, but a different VDF session => not picked. {ns2, in2, nvd2} => [{o3, gsn3, u3, s3, d}] })), @@ -609,7 +579,7 @@ get_jobs_test() -> next_seed = ns, interval_number = in, next_vdf_difficulty = nvd }, - get_jobs(a, [{ns, in, nvd}, {ns2, in2, nvd2}], + get_cached_jobs(a, [{ns, in, nvd}, {ns2, in2, nvd2}], #{ {ns, in, nvd} => [{o, gsn, u, s, d}, {o2, gsn2, u2, s, d}], {ns2, in2, nvd2} => [{o2, gsn2, u2, s2, d2}] })), @@ -621,7 +591,7 @@ get_jobs_test() -> next_seed = ns, interval_number = in, next_vdf_difficulty = nvd }, - get_jobs(o2, [{ns, in, nvd}, {ns2, in2, nvd2}], + get_cached_jobs(o2, [{ns, in, nvd}, {ns2, in2, nvd2}], #{ {ns, in, nvd} => [{o, gsn, u, s, d}, {o2, gsn2, u2, s, d}], {ns2, in2, nvd2} => [{o2, gsn2, u2, s2, d2}] })). @@ -662,66 +632,66 @@ test_process_partial_solution() -> TestCases = [ {"Bad proof size 1", #mining_solution{ poa1 = #poa{ tx_path = << 0:(2177 * 8) >> } }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad proof size 2", #mining_solution{ poa2 = #poa{ tx_path = << 0:(2177 * 8) >> } }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad proof size 3", #mining_solution{ poa1 = #poa{ data_path = << 0:(349505 * 8) >> } }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad proof size 4", #mining_solution{ poa2 = #poa{ data_path = << 0:(349505 * 8) >> } }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 1", #mining_solution{ next_seed = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 2", #mining_solution{ seed = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 3", #mining_solution{ preimage = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 4", #mining_solution{ mining_address = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 5", #mining_solution{ nonce_limiter_output = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 6", #mining_solution{ solution_hash = <<>> }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 7", #mining_solution{ poa1 = #poa{ chunk = << 0:((?DATA_CHUNK_SIZE + 1) * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad field size 8", #mining_solution{ poa2 = #poa{ chunk = << 0:((?DATA_CHUNK_SIZE + 1) * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad partition number", #mining_solution{ partition_number = 1 }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad nonce", #mining_solution{ nonce = 2 }, % We have 2 nonces per recall range in debug mode. - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad quick pow", #mining_solution{}, - #partial_solution_response{ status = <<"rejected_wrong_hash">> }}, + #solution_response{ status = <<"rejected_wrong_hash">> }}, {"Bad pow", #mining_solution{ nonce = 1, solution_hash = SolutionHQuick }, - #partial_solution_response{ status = <<"rejected_wrong_hash">> }}, + #solution_response{ status = <<"rejected_wrong_hash">> }}, {"Bad partition upper bound", #mining_solution{ nonce = 1, solution_hash = SolutionH, preimage = Preimage1 }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad poa 1", #mining_solution{ nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1 }, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad poa 2", #mining_solution{ nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Bad poa 3", #mining_solution{ nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, @@ -730,7 +700,7 @@ test_process_partial_solution() -> data_path = << 0:(349504 * 8) >> }, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Two-chunk bad poa 1", #mining_solution{ nonce = 1, solution_hash = SolutionH, @@ -740,7 +710,7 @@ test_process_partial_solution() -> data_path = << 0:(349504 * 8) >> }, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_wrong_hash">> }}, + #solution_response{ status = <<"rejected_wrong_hash">> }}, {"Two-chunk bad poa 2", #mining_solution{ nonce = 1, solution_hash = SolutionH, preimage = Preimage2, partition_upper_bound = 1, @@ -749,7 +719,7 @@ test_process_partial_solution() -> data_path = << 0:(349504 * 8) >> }, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_wrong_hash">> }}, + #solution_response{ status = <<"rejected_wrong_hash">> }}, {"Two-chunk bad poa 3", #mining_solution{ nonce = 1, solution_hash = H2, preimage = Preimage2, partition_upper_bound = 1, @@ -758,7 +728,7 @@ test_process_partial_solution() -> data_path = << 0:(349504 * 8) >> }, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_poa">> }}, + #solution_response{ status = <<"rejected_bad_poa">> }}, {"Accepted", #mining_solution{ nonce = 1, solution_hash = SolutionH, @@ -766,7 +736,7 @@ test_process_partial_solution() -> recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"accepted">> }}, + #solution_response{ status = <<"accepted">> }}, {"Accepted 2", #mining_solution{ nonce = 1, solution_hash = H2, preimage = Preimage2, partition_upper_bound = 1, @@ -775,7 +745,7 @@ test_process_partial_solution() -> data_path = << 0:(349504 * 8) >> }, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"accepted">> }} + #solution_response{ status = <<"accepted">> }} ], lists:foreach( fun({Title, Solution, ExpectedReply}) -> @@ -863,42 +833,42 @@ test_process_solution() -> recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }}, + #solution_response{ status = <<"rejected_vdf_not_found">> }}, {"VDF not found 2", #mining_solution{ next_seed = << 11:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }}, + #solution_response{ status = <<"rejected_vdf_not_found">> }}, {"VDF not found 3", #mining_solution{ next_seed = << 12:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_vdf_not_found">> }}, + #solution_response{ status = <<"rejected_vdf_not_found">> }}, {"Bad VDF 1", #mining_solution{ next_seed = << 1:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_vdf">> }}, + #solution_response{ status = <<"rejected_bad_vdf">> }}, {"Bad VDF 2", #mining_solution{ next_seed = << 2:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_vdf">> }}, + #solution_response{ status = <<"rejected_bad_vdf">> }}, {"Bad VDF 3", #mining_solution{ next_seed = << 3:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, recall_byte1 = RecallByte1, poa1 = #poa{ tx_path = << 0:(2176 * 8) >>, data_path = << 0:(349504 * 8) >> }}, - #partial_solution_response{ status = <<"rejected_bad_vdf">> }}, + #solution_response{ status = <<"rejected_bad_vdf">> }}, {"Accepted", #mining_solution{ next_seed = << 4:(48*8) >>, nonce = 1, solution_hash = SolutionH, preimage = Preimage1, partition_upper_bound = 1, diff --git a/apps/arweave/src/ar_serialize.erl b/apps/arweave/src/ar_serialize.erl index 879ebb280a..14fb39425e 100644 --- a/apps/arweave/src/ar_serialize.erl +++ b/apps/arweave/src/ar_serialize.erl @@ -29,7 +29,7 @@ candidate_to_json_struct/1, solution_to_json_struct/1, json_map_to_solution/1, json_map_to_candidate/1, jobs_to_json_struct/1, json_struct_to_jobs/1, - partial_solution_response_to_json_struct/1, + solution_response_to_json_struct/1, pool_cm_jobs_to_json_struct/1, json_map_to_pool_cm_jobs/1]). -include_lib("arweave/include/ar.hrl"). @@ -1756,7 +1756,7 @@ binary_to_signature_type(List) -> candidate_to_json_struct( #mining_candidate{ - cm_diff = DiffPair, + diff_pair = DiffPair, cm_h1_list = H1List, h0 = H0, h1 = H1, @@ -1778,7 +1778,8 @@ candidate_to_json_struct( label = Label }) -> JSON = [ - {cm_diff, diff_pair_to_json_list(DiffPair)}, + %% cm_diff is the legacy name, kept for backwards compatibiltiy + {cm_diff, diff_pair_to_json_list(DiffPair)}, {cm_h1_list, h1_list_to_json_struct(H1List)}, {mining_address, ar_util:encode(MiningAddress)}, {h0, ar_util:encode(H0)}, @@ -1840,7 +1841,7 @@ json_map_to_candidate(JSON) -> Label = maps:get(<<"label">>, JSON, <<"not_set">>), #mining_candidate{ - cm_diff = DiffPair, + diff_pair = DiffPair, cm_h1_list = H1List, h0 = H0, h1 = H1, @@ -2031,25 +2032,19 @@ json_struct_to_job(Struct) -> #job{ output = Output, global_step_number = StepNumber, partition_upper_bound = PartitionUpperBound }. -partial_solution_response_to_json_struct(Response) -> - #partial_solution_response{ indep_hash = H, status = S } = Response, +solution_response_to_json_struct(Response) -> + #solution_response{ indep_hash = H, status = S } = Response, {[{<<"indep_hash">>, ar_util:encode(H)}, {<<"status">>, S}]}. pool_cm_jobs_to_json_struct(Jobs) -> #pool_cm_jobs{ h1_to_h2_jobs = H1ToH2Jobs, h1_read_jobs = H1ReadJobs, partitions = Partitions } = Jobs, {[ - {h1_to_h2_jobs, [pool_cm_h1_to_h2_job_to_json_struct(Job) || Job <- H1ToH2Jobs]}, - {h1_read_jobs, [pool_cm_h1_read_job_to_json_struct(Job) || Job <- H1ReadJobs]}, + {h1_to_h2_jobs, [candidate_to_json_struct(Job) || Job <- H1ToH2Jobs]}, + {h1_read_jobs, [candidate_to_json_struct(Job) || Job <- H1ReadJobs]}, {partitions, Partitions} ]}. -pool_cm_h1_to_h2_job_to_json_struct(Job) -> - candidate_to_json_struct(Job). - -pool_cm_h1_read_job_to_json_struct(Job) -> - candidate_to_json_struct(Job). - json_map_to_pool_cm_jobs(Map) -> H1ToH2Jobs = [json_map_to_candidate(Job) || Job <- maps:get(<<"h1_to_h2_jobs">>, Map, [])], diff --git a/apps/arweave/test/ar_coordinated_mining_tests.erl b/apps/arweave/test/ar_coordinated_mining_tests.erl index 387fd57c0c..9b8649477d 100644 --- a/apps/arweave/test/ar_coordinated_mining_tests.erl +++ b/apps/arweave/test/ar_coordinated_mining_tests.erl @@ -127,7 +127,7 @@ test_no_secret() -> ?assertMatch( {error, {ok, {{<<"421">>, _}, _, <<"CM API disabled or invalid CM API secret in request.">>, _, _}}}, - ar_http_iface_client:cm_publish_send(Peer, dummy_solution())). + ar_http_iface_client:post_partial_solution(Peer, dummy_solution())). test_bad_secret() -> [Node, _ExitNode, _ValidatorNode] = ar_test_node:start_coordinated(1), @@ -150,7 +150,7 @@ test_bad_secret() -> ?assertMatch( {error, {ok, {{<<"421">>, _}, _, <<"CM API disabled or invalid CM API secret in request.">>, _, _}}}, - ar_http_iface_client:cm_publish_send(Peer, dummy_solution())). + ar_http_iface_client:post_partial_solution(Peer, dummy_solution())). test_partition_table() -> [B0] = ar_weave:init([], ar_test_node:get_difficulty_for_invalid_hash(), 5 * ?PARTITION_SIZE), @@ -389,7 +389,7 @@ assert_empty_cache(_Node) -> dummy_candidate() -> #mining_candidate{ - cm_diff = {rand:uniform(1024), rand:uniform(1024)}, + diff_pair = {rand:uniform(1024), rand:uniform(1024)}, h0 = crypto:strong_rand_bytes(32), h1 = crypto:strong_rand_bytes(32), mining_address = crypto:strong_rand_bytes(32), @@ -408,7 +408,6 @@ dummy_candidate() -> dummy_solution() -> #mining_solution{ last_step_checkpoints = [], - merkle_rebase_threshold = rand:uniform(1024), mining_address = crypto:strong_rand_bytes(32), next_seed = crypto:strong_rand_bytes(32), next_vdf_difficulty = rand:uniform(1024), diff --git a/apps/arweave/test/ar_serialize_tests.erl b/apps/arweave/test/ar_serialize_tests.erl index ff98650d29..82608cd113 100644 --- a/apps/arweave/test/ar_serialize_tests.erl +++ b/apps/arweave/test/ar_serialize_tests.erl @@ -247,7 +247,7 @@ candidate_to_json_struct_test() -> end, DefaultCandidate = #mining_candidate{ - cm_diff = {rand:uniform(1024), rand:uniform(1024)}, + diff_pair = {rand:uniform(1024), rand:uniform(1024)}, cm_h1_list = [ {crypto:strong_rand_bytes(32), rand:uniform(100)}, {crypto:strong_rand_bytes(32), rand:uniform(100)}, @@ -389,16 +389,16 @@ partial_solution_to_json_struct_test() -> TestCases ). -partial_solution_response_to_json_struct_test() -> +solution_response_to_json_struct_test() -> TestCases = [ - {#partial_solution_response{}, <<>>, <<>>}, - {#partial_solution_response{ indep_hash = <<"H">>, status = <<"S">>}, + {#solution_response{}, <<>>, <<>>}, + {#solution_response{ indep_hash = <<"H">>, status = <<"S">>}, <<"H">>, <<"S">>} ], lists:foreach( fun({Case, ExpectedH, ExpectedStatus}) -> {Struct} = ar_serialize:dejsonify(ar_serialize:jsonify( - ar_serialize:partial_solution_response_to_json_struct(Case))), + ar_serialize:solution_response_to_json_struct(Case))), ?assertEqual(ExpectedH, ar_util:decode(proplists:get_value(<<"indep_hash">>, Struct))), ?assertEqual(ExpectedStatus, proplists:get_value(<<"status">>, Struct))