Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,13 @@ jobs:
## Long-running tests. Put these first to limit the overall runtime of the
## test suite
ar_coordinated_mining_tests,
ar_data_sync_tests,
ar_data_sync_recovers_from_corruption_test,
ar_data_sync_syncs_data_test,
ar_data_sync_syncs_after_joining_test,
ar_data_sync_mines_off_only_last_chunks_test,
ar_data_sync_mines_off_only_second_last_chunks_test,
ar_data_sync_disk_pool_rotation_test,
ar_data_sync_enqueue_intervals_test,
ar_fork_recovery_tests,
ar_tx,
ar_packing_tests,
Expand Down Expand Up @@ -251,6 +257,7 @@ jobs:
ar_node,
ar_node_utils,
ar_nonce_limiter,
ar_node_worker,
# ar_p3,
# ar_p3_config,
# ar_p3_db,
Expand Down
13 changes: 11 additions & 2 deletions apps/arweave/src/ar_block.erl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
get_max_nonce/1, get_recall_range_size/1, get_recall_byte/3,
get_sub_chunk_size/1, get_nonces_per_chunk/1, get_nonces_per_recall_range/1,
get_sub_chunk_index/2,
get_chunk_padded_offset/1]).
get_chunk_padded_offset/1, get_double_signing_condition/4]).

-include("../include/ar.hrl").
-include("../include/ar_consensus.hrl").
Expand Down Expand Up @@ -666,7 +666,16 @@ get_chunk_padded_offset(Offset) ->
Offset
end.


%% @doc Return true if the given cumulative difficulty - previous cumulative difficulty
%% pairs satisfy the double signing condition.
-spec get_double_signing_condition(
CDiff1 :: non_neg_integer(),
PrevCDiff1 :: non_neg_integer(),
CDiff2 :: non_neg_integer(),
PrevCDiff2 :: non_neg_integer()
) -> boolean().
get_double_signing_condition(CDiff1, PrevCDiff1, CDiff2, PrevCDiff2) ->
CDiff1 == CDiff2 orelse (CDiff1 > PrevCDiff2 andalso CDiff2 > PrevCDiff1).

%%%===================================================================
%%% Private functions.
Expand Down
251 changes: 250 additions & 1 deletion apps/arweave/src/ar_block_cache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
get_longest_chain_cache/1,
get_block_and_status/2, remove/2, get_checkpoint_block/1, prune/2,
get_by_solution_hash/5, is_known_solution_hash/2,
get_siblings/2, get_fork_blocks/2, update_timestamp/3]).
get_siblings/2, get_fork_blocks/2, update_timestamp/3,
get_validated_front/1, get_blocks_by_miner/2]).

-include_lib("arweave/include/ar.hrl").
-include_lib("eunit/include/eunit.hrl").
Expand Down Expand Up @@ -480,6 +481,36 @@ update_timestamp(Tab, H, ReceiveTimestamp) ->
end
end.

%% @doc Return the list of top validated blocks of the longest chains (the largest
%% cumulative difficulty blocks).
get_validated_front(Tab) ->
case ets:lookup(Tab, links) of
[] ->
[];
[{_, Set}] ->
get_validated_front(Tab, Set)
end.

%% @doc Return all blocks from the cache mined by the given address.
get_blocks_by_miner(Tab, MinerAddr) ->
case ets:lookup(Tab, links) of
[{links, Set}] ->
gb_sets:fold(
fun({_Height, H}, Acc) ->
case ets:lookup(Tab, {block, H}) of
[{_, {B, _Status, _Timestamp, _Children}}] when B#block.reward_addr == MinerAddr ->
[B | Acc];
_ ->
Acc
end
end,
[],
Set
);
_ ->
[]
end.

%%%===================================================================
%%% Private functions.
%%%===================================================================
Expand Down Expand Up @@ -720,6 +751,31 @@ update_longest_chain_cache(Tab) ->
end,
Result.

get_validated_front(Tab, Set) ->
{_MaxCDiff, Blocks} = gb_sets:fold(
fun({_Height, H}, {CurrentMaxCDiff, CurrentBlocks}) ->
case ets:lookup(Tab, {block, H}) of
[{_, {B, Status, _Timestamp, _Children}}]
when Status == validated;
Status == on_chain ->
CDiff = B#block.cumulative_diff,
case CDiff > CurrentMaxCDiff of
true ->
{CDiff, [B]};
false when CDiff == CurrentMaxCDiff ->
{CurrentMaxCDiff, [B | CurrentBlocks]};
false ->
{CurrentMaxCDiff, CurrentBlocks}
end;
_ ->
{CurrentMaxCDiff, CurrentBlocks}
end
end,
{0, []},
Set
),
Blocks.

%%%===================================================================
%%% Tests.
%%%===================================================================
Expand Down Expand Up @@ -1760,3 +1816,196 @@ block_id(#block{ indep_hash = H }) ->
on_top(B, PrevB) ->
B#block{ previous_block = PrevB#block.indep_hash, height = PrevB#block.height + 1,
previous_cumulative_diff = PrevB#block.cumulative_diff }.

get_validated_front_test() ->
ets:new(bcache_test, [set, named_table]),

%% Test empty cache
%%
%% Height Block/Status
%%
%% (empty)
?assertEqual([], get_validated_front(bcache_test)),

%% Test with a single on-chain block
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
new(bcache_test, B1 = random_block(0)),
?assertEqual([B1], get_validated_front(bcache_test)),

%% Test with a single validated block
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
add_validated(bcache_test, B2 = on_top(random_block(1), B1)),
?assertEqual([B2], get_validated_front(bcache_test)),

%% Test with multiple validated blocks at different heights
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
add_validated(bcache_test, B3 = on_top(random_block(2), B2)),
add_validated(bcache_test, B4 = on_top(random_block(3), B3)),
?assertEqual([B4], get_validated_front(bcache_test)),

%% Test with multiple blocks at the same highest cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
add_validated(bcache_test, B5 = on_top(random_block(3), B2)),
?assertEqual(lists:sort([B4, B5]),
lists:sort(get_validated_front(bcache_test))),

%% Test with non-validated blocks having higher cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/not_validated (cdiff=4)
add(bcache_test, B6 = on_top(random_block(4), B4)),
?assertEqual(lists:sort([B4, B5]),
lists:sort(get_validated_front(bcache_test))),

%% Test validating a block with higher cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/validated (cdiff=4)
add_validated(bcache_test, B6),
?assertEqual([B6], get_validated_front(bcache_test)),

%% Test with multiple forks at the same height and cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/validated (cdiff=4)
%% 4 B7/validated (cdiff=4)
%% 4 B8/validated (cdiff=4)
add_validated(bcache_test, B7 = on_top(random_block(4), B3)),
add_validated(bcache_test, B8 = on_top(random_block(4), B5)),
?assertEqual(lists:sort([B6, B7, B8]),
lists:sort(get_validated_front(bcache_test))),

%% Test with a mix of validated and non-validated blocks at different heights
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/validated (cdiff=4)
%% 4 B7/validated (cdiff=4)
%% 4 B8/validated (cdiff=4)
%% 5 B9/not_validated (cdiff=5)
%% 5 B10/not_validated (cdiff=5)
add(bcache_test, B9 = on_top(random_block(5), B6)),
add(bcache_test, B10 = on_top(random_block(5), B7)),
?assertEqual(lists:sort([B6, B7, B8]),
lists:sort(get_validated_front(bcache_test))),

%% Test validating blocks with higher cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/validated (cdiff=4)
%% 4 B7/validated (cdiff=4)
%% 4 B8/validated (cdiff=4)
%% 5 B9/validated (cdiff=5)
%% 5 B10/validated (cdiff=5)
add_validated(bcache_test, B9),
add_validated(bcache_test, B10),
?assertEqual(lists:sort([B9, B10]),
lists:sort(get_validated_front(bcache_test))),

%% Test with on_chain blocks at the highest cumulative difficulty
%%
%% Height Block/Status
%%
%% 0 B1/on_chain
%% 1 B2/validated (cdiff=1)
%% 2 B3/validated (cdiff=2)
%% 3 B4/validated (cdiff=3)
%% 3 B5/validated (cdiff=3)
%% 4 B6/validated (cdiff=4)
%% 4 B7/validated (cdiff=4)
%% 4 B8/validated (cdiff=4)
%% 5 B9/validated (cdiff=5)
%% 5 B10/validated (cdiff=5)
%% 6 B11/validated (cdiff=6)
%% 6 B12/on_chain (cdiff=7)
add(bcache_test, B11 = on_top(random_block(6), B9)),
mark_tip(bcache_test, block_id(B11)),
?assertEqual([B11], get_validated_front(bcache_test)),
add(bcache_test, B12 = on_top(random_block(6), B9)),
mark_tip(bcache_test, block_id(B12)),
?assertEqual(lists:sort([B11, B12]),
lists:sort(get_validated_front(bcache_test))),

ets:delete(bcache_test).

%% @doc Test that get_blocks_by_miner returns the correct blocks for a given miner.
get_blocks_by_miner_test() ->
ets:new(bcache_test, [set, named_table]),
new(bcache_test, B0 = random_block(0)),
Tab = bcache_test,
?assertEqual([], get_blocks_by_miner(Tab, <<"miner1">>)),
% Create some test blocks
B1 = #block{ indep_hash = <<"hash1">>, reward_addr = <<"miner1">> },
B2 = #block{ indep_hash = <<"hash2">>, reward_addr = <<"miner2">> },
B3 = #block{ indep_hash = <<"hash3">>, reward_addr = <<"miner1">> },
% Add blocks to cache
add(Tab, on_top(B1, B0)),
add(Tab, on_top(B2, B0)),
add(Tab, on_top(B3, B0)),
B1_1 = B1#block{
height = 1,
previous_block = B0#block.indep_hash,
previous_cumulative_diff = B0#block.cumulative_diff },
B2_1 = B2#block{
height = 1,
previous_block = B0#block.indep_hash,
previous_cumulative_diff = B0#block.cumulative_diff },
B3_1 = B3#block{
height = 1,
previous_block = B0#block.indep_hash,
previous_cumulative_diff = B0#block.cumulative_diff },

% Test getting blocks by miner
?assertEqual([B1_1, B3_1], lists:sort(fun(A, B) -> A#block.indep_hash < B#block.indep_hash end, get_blocks_by_miner(Tab, <<"miner1">>))),
?assertEqual([B2_1], get_blocks_by_miner(Tab, <<"miner2">>)),
?assertEqual([], get_blocks_by_miner(Tab, <<"miner3">>)),
ets:delete(Tab).
16 changes: 12 additions & 4 deletions apps/arweave/src/ar_chain_stats.erl
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,22 @@ record_fork_depth([], _ForkRootB, N) ->
prometheus_histogram:observe(fork_recovery_depth, N),
ok;
record_fork_depth([H | Orphans], ForkRootB, N) ->
?LOG_INFO([
SolutionHashInfo =
case ar_block_cache:get(block_cache, H) of
not_found ->
%% Should never happen, by construction.
?LOG_ERROR([{event, block_not_found_in_cache}, {h, ar_util:encode(H)}]),
[];
#block{ hash = SolutionH } ->
[{solution_hash, ar_util:encode(SolutionH)}]
end,
LogInfo = [
{event, orphaning_block}, {block, ar_util:encode(H)}, {depth, N},
{fork_root, ar_util:encode(ForkRootB#block.indep_hash)},
{fork_height, ForkRootB#block.height + 1}
]),
{fork_height, ForkRootB#block.height + 1} | SolutionHashInfo],
?LOG_INFO(LogInfo),
record_fork_depth(Orphans, ForkRootB, N + 1).


%%%===================================================================
%%% Tests.
%%%===================================================================
Expand Down
Loading
Loading