diff --git a/apps/arweave/include/ar_mining.hrl b/apps/arweave/include/ar_mining.hrl index 6b9bd7cce..70b627c6a 100644 --- a/apps/arweave/include/ar_mining.hrl +++ b/apps/arweave/include/ar_mining.hrl @@ -29,29 +29,28 @@ session_key = not_set, %% serialized 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 >>, start_interval_number = 0, step_number = 0, - steps = [], - seed = << 0:(8 * 48) >>, - mining_address = << 0:256 >>, - partition_upper_bound = 0 + steps = [] }). -endif. diff --git a/apps/arweave/src/ar_chain_stats.erl b/apps/arweave/src/ar_chain_stats.erl index ef212fafe..d05b59041 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_http_iface_middleware.erl b/apps/arweave/src/ar_http_iface_middleware.erl index 4e3692e5c..ea3c838cb 100644 --- a/apps/arweave/src/ar_http_iface_middleware.erl +++ b/apps/arweave/src/ar_http_iface_middleware.erl @@ -3260,8 +3260,8 @@ 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:prepare_solution(Candidate), {200, #{}, <<>>, Req} end end; @@ -3288,7 +3288,7 @@ handle_mining_cm_publish(Req, Pid) -> ?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), + ar_mining_router:post_solution(Solution), {200, #{}, <<>>, Req} end; {error, _} -> diff --git a/apps/arweave/src/ar_mining_router.erl b/apps/arweave/src/ar_mining_router.erl new file mode 100644 index 000000000..ab0172870 --- /dev/null +++ b/apps/arweave/src/ar_mining_router.erl @@ -0,0 +1,88 @@ +-module(ar_mining_router). + +-behaviour(gen_server). + +-export([start_link/0, prepare_solution/1, post_solution/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, { +}). + +%%%=================================================================== +%%% Public interface. +%%%=================================================================== + +%% @doc Start the gen_server. +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +prepare_solution(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). + +post_solution(Solution) -> + {ok, Config} = application:get_env(arweave, config), + post_solution(Config#config.cm_exit_peer, Config#config.is_pool_client, Solution). + +%%%=================================================================== +%%% Generic server callbacks. +%%%=================================================================== + +init([]) -> + {ok, #state{}}. + +handle_call(Request, _From, State) -> + ?LOG_WARNING([{event, unhandled_call}, {module, ?MODULE}, {request, Request}]), + {reply, ok, State}. + +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. +%%%=================================================================== + +post_solution(not_set, true, Solution) -> + %% 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, _IsPoolClient, Solution) -> + ar_mining_server:validate_solution(Solution); +post_solution(ExitPeer, true, Solution) -> + 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, _IsPoolClient, Solution) -> + case ar_http_iface_client:cm_publish_send(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. diff --git a/apps/arweave/src/ar_mining_server.erl b/apps/arweave/src/ar_mining_server.erl index c0265557c..efe1ec9c8 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, read_poa/3, 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,81 @@ 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:post_solution(Solution2) + end, + {noreply, State}; + +handle_cast({validate_solution, 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, solution_rejected}, + {reason, 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"), + ar_mining_stats:solution(rejected); + {false, Reason} -> + ?LOG_WARNING([{event, solution_rejected}, + {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"), + ar_mining_stats:solution(rejected); + {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 +514,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 +529,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,7 +543,8 @@ prepare_solution(steps, Candidate, Solution) -> PrevStepNumber, StepNumber, PrevNextSeed, PrevNextVDFDifficulty), case Steps of not_found -> - ?LOG_WARNING([{event, found_solution_but_failed_to_find_checkpoints}, + ?LOG_WARNING([{event, solution_rejected}, + {reason, failed_to_find_checkpoints}, {start_step_number, PrevStepNumber}, {next_step_number, StepNumber}, {next_seed, ar_util:safe_encode(PrevNextSeed)}, @@ -523,21 +552,24 @@ prepare_solution(steps, Candidate, Solution) -> 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_stats:solution(rejected), 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}]), + ?LOG_WARNING([{event, solution_rejected}, + {reason, stale_step_number}, + {start_step_number, PrevStepNumber}, + {next_step_number, StepNumber}, + {next_seed, ar_util:safe_encode(PrevNextSeed)}, + {next_vdf_difficulty, PrevNextVDFDifficulty}]), + ar_mining_stats:solution(rejected), 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,19 +578,21 @@ prepare_solution(proofs, Candidate, Solution) -> PartitionUpperBound), case { H1, H2 } of {not_set, not_set} -> - ?LOG_WARNING([{event, found_solution_but_h1_h2_not_set}]), + ?LOG_WARNING([{event, solution_rejected}, + {reason, h1_h2_not_set}]), + ar_mining_stats:solution(rejected), 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, @@ -582,7 +616,8 @@ prepare_solution(poa1, Candidate, not_found -> {RecallRange1Start, _RecallRange2Start} = ar_block:get_recall_range(H0, PartitionNumber, PartitionUpperBound), - ?LOG_WARNING([{event, mined_block_but_failed_to_read_chunk_proofs}, + ?LOG_WARNING([{event, solution_rejected}, + {reason, failed_to_read_chunk_proofs}, {recall_byte1, RecallByte1}, {recall_range_start1, RecallRange1Start}, {nonce, Nonce}, @@ -591,21 +626,22 @@ prepare_solution(poa1, Candidate, 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"), + ar_mining_stats:solution(rejected), error; PoA1 -> Solution#mining_solution{ poa1 = PoA1#poa{ chunk = Chunk1 } } end - end; -prepare_solution(poa2, Candidate, + end. +prepare_solution_poa2(Candidate, #mining_solution{ poa2 = #poa{ chunk = <<>> } } = Solution) -> - #mining_solution{ mining_address = MiningAddress, partition_number = PartitionNumber, - recall_byte2 = RecallByte2 } = 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 }); + 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], @@ -619,7 +655,8 @@ prepare_solution(poa2, Candidate, not_found -> {_RecallRange1Start, RecallRange2Start} = ar_block:get_recall_range(H0, PartitionNumber, PartitionUpperBound), - ?LOG_ERROR([{event, mined_block_but_failed_to_read_chunk_proofs}, + ?LOG_ERROR([{event, solution_rejected}, + {reason, failed_to_read_chunk_proofs}, {tags, [solution_proofs]}, {recall_byte2, RecallByte2}, {recall_range_start2, RecallRange2Start}, @@ -629,83 +666,16 @@ prepare_solution(poa2, Candidate, 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"), + ar_mining_stats:solution(rejected), error; PoA2 -> - prepare_solution(poa1, Candidate, + prepare_solution_poa1(Candidate, Solution#mining_solution{ poa2 = PoA2#poa{ chunk = Chunk2 } }) end 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{}; diff --git a/apps/arweave/src/ar_mining_stats.erl b/apps/arweave/src/ar_mining_stats.erl index c31c2289b..ab03604e6 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, @@ -135,11 +135,8 @@ h2_sent_to_peer(Peer) -> 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 +362,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 +390,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 +511,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 +592,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 +602,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 +1264,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 +1323,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 516a60792..3b2ed3fa6 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 2a1f6b5c0..f155eb59c 100644 --- a/apps/arweave/src/ar_mining_worker.erl +++ b/apps/arweave/src/ar_mining_worker.erl @@ -355,24 +355,25 @@ 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(), + DiffCheck = h1_passes_diff_checks(H1, Candidate, State), + case DiffCheck of + false -> ok; + _ -> + ?LOG_INFO([{event, solution_found}, {worker, State#state.name}, + {h1, ar_util:encode(H1)}, + {difficulty, get_difficulty(State, Candidate)}, + {partial_difficulty, get_partial_difficulty(State, Candidate)}]), + ar_mining_stats:solution(found), + ar_mining_router:prepare_solution(Candidate) + end, + case DiffCheck of + network -> %% 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), {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} -> @@ -415,28 +416,21 @@ handle_task({computed_h2, Candidate}, State) -> 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)}]) + DiffCheck = h2_passes_diff_checks(H2, Candidate, State), + case DiffCheck of + false -> ok; + _ -> + ?LOG_INFO([{event, solution_found}, {worker, State#state.name}, + {h2, ar_util:encode(H2)}, + {difficulty, get_difficulty(State, Candidate)}, + {partial_difficulty, get_partial_difficulty(State, Candidate)}]), + ar_mining_stats:solution(found) end, - case {PassesDiffChecks, Peer} of + case {DiffCheck, Peer} of {false, _} -> ok; {_, not_set} -> - ar_mining_server:prepare_and_post_solution(Candidate); + ar_mining_router:prepare_solution(Candidate); _ -> {_RecallByte1, RecallByte2} = ar_mining_server:get_recall_bytes(H0, Partition1, Nonce, PartitionUpperBound), @@ -450,14 +444,15 @@ handle_task({computed_h2, Candidate}, State) -> "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)}]), + ?LOG_WARNING([{event, solution_rejected}, + {reason, failed_to_read_second_chunk_proof}, + {worker, State#state.name}, {h2, ar_util:encode(H2)}, + {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"), + ar_mining_stats:solution(rejected), not_found; PeerPoA2 -> PeerPoA2 @@ -522,11 +517,15 @@ h1_passes_diff_checks(H1, Candidate, State) -> h2_passes_diff_checks(H2, Candidate, State) -> passes_diff_checks(H2, false, Candidate, State). +%% @doc +%% If the solution passes the network difficulty: network +%% If the solution passes a pool's partial difficulty: partial +%% If the solution passes neither: false 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; + network; false -> case get_partial_difficulty(State, Candidate) of not_set -> @@ -782,7 +781,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 e9cc0b56e..0f855d2f5 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 c587e734e..bfce65b32 100644 --- a/apps/arweave/src/ar_node_worker.erl +++ b/apps/arweave/src/ar_node_worker.erl @@ -1890,8 +1890,9 @@ handle_found_solution(Args, PrevB, State) -> HaveSteps = case CorrectRebaseThreshold of {false, Reason5} -> - ?LOG_WARNING([{event, ignore_mining_solution}, + ?LOG_WARNING([{event, solution_rejected}, {reason, Reason5}, {solution, ar_util:encode(SolutionH)}]), + ar_mining_stats:solution(rejected), false; true -> ar_nonce_limiter:get_steps(PrevStepNumber, StepNumber, PrevNextSeed, @@ -1913,9 +1914,11 @@ handle_found_solution(Args, PrevB, State) -> not_found -> ar_events:send(solution, {rejected, #{ reason => vdf_not_found, source => Source }}), - ?LOG_WARNING([{event, did_not_find_steps_for_mined_block}, + ?LOG_WARNING([{event, solution_rejected}, + {reason, did_not_find_steps}, {seed, ar_util:encode(PrevNextSeed)}, {prev_step_number, PrevStepNumber}, {step_number, StepNumber}]), + ar_mining_stats:solution(rejected), {noreply, State}; [NonceLimiterOutput | _] = Steps -> {Seed, NextSeed, PartitionUpperBound, NextPartitionUpperBound, VDFDifficulty} @@ -2025,18 +2028,21 @@ handle_found_solution(Args, PrevB, State) -> undefined -> 1; _ -> 2 end}]), + ar_mining_stats:solution(accepted), ar_block_cache:add(block_cache, B), ar_events:send(solution, {accepted, #{ indep_hash => H, source => 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}, + ?LOG_ERROR([{event, solution_rejected}, + {reason, bad_steps}, {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)}]), + ar_mining_stats:solution(rejected), {noreply, State} end. diff --git a/apps/arweave/src/ar_pool.erl b/apps/arweave/src/ar_pool.erl index 9a35bde56..5f822e3e4 100644 --- a/apps/arweave/src/ar_pool.erl +++ b/apps/arweave/src/ar_pool.erl @@ -539,7 +539,7 @@ 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), + ar_mining_router:prepare_solution(Candidate), ar_mining_stats:h2_received_from_peer(pool); false -> ok diff --git a/apps/arweave/test/ar_coordinated_mining_tests.erl b/apps/arweave/test/ar_coordinated_mining_tests.erl index 387fd57c0..41c713353 100644 --- a/apps/arweave/test/ar_coordinated_mining_tests.erl +++ b/apps/arweave/test/ar_coordinated_mining_tests.erl @@ -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),