Skip to content
This repository has been archived by the owner on Mar 5, 2024. It is now read-only.

Commit

Permalink
Merge pull request #605 from helium/adt/quick-causality-check
Browse files Browse the repository at this point in the history
Improve performance of client side validation of state channels
  • Loading branch information
vihu authored Sep 3, 2020
2 parents ec90dcd + 5c02145 commit 5581ea6
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 4 deletions.
6 changes: 3 additions & 3 deletions src/state_channel/blockchain_state_channels_client.erl
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ send_packet_when_v1(Stream, Packet, Region,
-spec is_valid_sc(SC :: blockchain_state_channel_v1:state_channel(),
State :: state()) -> ok | {error, any()}.
is_valid_sc(SC, State) ->
case blockchain_state_channel_v1:validate(SC) of
case blockchain_state_channel_v1:quick_validate(SC, State#state.pubkey_bin) of
{error, Reason}=E ->
lager:error("invalid sc, reason: ~p", [Reason]),
E;
Expand Down Expand Up @@ -732,7 +732,7 @@ is_active_sc(SC, #state{chain=Chain}) ->

-spec is_causally_correct_sc(SC :: blockchain_state_channel_v1:state_channel(),
State :: state()) -> boolean().
is_causally_correct_sc(SC, State) ->
is_causally_correct_sc(SC, #state{pubkey_bin=PubkeyBin}=State) ->
SCID = blockchain_state_channel_v1:id(SC),

case get_state_channels(SCID, State) of
Expand All @@ -744,7 +744,7 @@ is_causally_correct_sc(SC, State) ->
false;
{ok, [KnownSC]} ->
%% Check if SC is causally correct
Check = (conflict /= blockchain_state_channel_v1:compare_causality(KnownSC, SC)),
Check = (conflict /= blockchain_state_channel_v1:quick_compare_causality(KnownSC, SC, PubkeyBin)),
lager:info("causality check: ~p, this sc_id: ~p, known_sc_id: ~p",
[Check, SCID, blockchain_state_channel_v1:id(KnownSC)]),
Check;
Expand Down
127 changes: 126 additions & 1 deletion src/state_channel/v1/blockchain_state_channel_v1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
root_hash/1, root_hash/2,
state/1, state/2,
expire_at_block/1, expire_at_block/2,
signature/1, sign/2, validate/1,
signature/1, sign/2, validate/1, quick_validate/2,
encode/1, decode/1,
save/3, fetch/2,
summaries/1, summaries/2, update_summary_for/3,
Expand All @@ -30,6 +30,7 @@
to_json/2,

compare_causality/2,
quick_compare_causality/3,
is_causally_newer/2,
merge/2
]).
Expand Down Expand Up @@ -239,6 +240,27 @@ validate_summaries([H|T]) ->
Error
end.

-spec quick_validate(state_channel(), libp2p_crypto:pubkey_bin()) -> ok | {error, any()}.
quick_validate(SC, PubkeyBin) ->
BaseSC = SC#blockchain_state_channel_v1_pb{signature = <<>>},
EncodedSC = ?MODULE:encode(BaseSC),
Signature = ?MODULE:signature(SC),
Owner = ?MODULE:owner(SC),
PubKey = libp2p_crypto:bin_to_pubkey(Owner),
case libp2p_crypto:verify(EncodedSC, Signature, PubKey) of
false -> {error, bad_signature};
true ->
case ?MODULE:get_summary(PubkeyBin, SC) of
{error, not_found} ->
ok;
{ok, Summary} ->
%% TODO we need to make sure the merge function handles SCs that are signed but
%% may have corrupt summaries for some actors (but our summary checks out)
blockchain_state_channel_summary_v1:validate(Summary)
end
end.


-spec encode(state_channel()) -> binary().
encode(#blockchain_state_channel_v1_pb{}=SC) ->
blockchain_state_channel_v1_pb:encode_msg(SC).
Expand Down Expand Up @@ -338,6 +360,84 @@ compare_causality(OlderSC, CurrentSC) ->
end,
Res.

%%--------------------------------------------------------------------
%% @doc
%% Get causal relation between older SC and current SC for a given pubkey.
%%
%% If SC1 -> SC2, return caused
%% If SC2 -> SC1, return effect_of
%% If SC1 == SC2, return equal
%% In all other scenarios, return conflict
%%
%% Important: The expected older state channel should be passed <b>first</b>.
%% @end
%%--------------------------------------------------------------------
-spec quick_compare_causality(OlderSC :: state_channel(),
CurrentSC :: state_channel(),
PubkeyBin :: libp2p_crypto:pubkey_bin()) -> temporal_relation().
quick_compare_causality(OlderSC, CurrentSC, PubkeyBin) ->
OlderNonce = ?MODULE:nonce(OlderSC),
CurrentNonce = ?MODULE:nonce(CurrentSC),

case {OlderNonce, CurrentNonce} of
{OlderNonce, OlderNonce} ->
%% nonces are equal, summaries must be equal
OlderSummaries = ?MODULE:summaries(OlderSC),
CurrentSummaries = ?MODULE:summaries(CurrentSC),
case OlderSummaries == CurrentSummaries of
false ->
%% If the nonces are the same but the summaries are not
%% then that's a conflict.
conflict;
true ->
equal
end;
{OlderNonce, CurrentNonce} when CurrentNonce > OlderNonce ->
case {?MODULE:get_summary(PubkeyBin, OlderSC), ?MODULE:get_summary(PubkeyBin, CurrentSC)} of
{{error, not_found}, {error, not_found}} ->
caused;
{{error, not_found}, {ok, _}} ->
caused;
{{ok, _}, {error, not_found}} ->
conflict;
{{ok, OlderSummary}, {ok, NewerSummary}} ->
OldNumDCs = blockchain_state_channel_summary_v1:num_dcs(OlderSummary),
OldNumPackets = blockchain_state_channel_summary_v1:num_packets(OlderSummary),
NewNumDCs = blockchain_state_channel_summary_v1:num_dcs(NewerSummary),
NewNumPackets = blockchain_state_channel_summary_v1:num_packets(NewerSummary),
case (NewNumPackets >= OldNumPackets) andalso (NewNumDCs >= OldNumDCs) of
true ->
caused;
false ->
conflict
end
end;
{OlderNonce, CurrentNonce} when CurrentNonce < OlderNonce ->
case {?MODULE:get_summary(PubkeyBin, OlderSC), ?MODULE:get_summary(PubkeyBin, CurrentSC)} of
{{error, not_found}, {error, not_found}} ->
%% only the nonce changes and current is less than old nonce
effect_of;
{{ok, _}, {error, not_found}} ->
%% older_sc has summary, current_sc does not, conflict
conflict;
{{error, not_found}, {ok, _}} ->
%% no summary in older_sc, summary in new_sc but new_sc has lower nonce
conflict;
{{ok, OlderSummary}, {ok, NewerSummary}} ->
OldNumDCs = blockchain_state_channel_summary_v1:num_dcs(OlderSummary),
OldNumPackets = blockchain_state_channel_summary_v1:num_packets(OlderSummary),
NewNumDCs = blockchain_state_channel_summary_v1:num_dcs(NewerSummary),
NewNumPackets = blockchain_state_channel_summary_v1:num_packets(NewerSummary),
case (NewNumPackets =< OldNumPackets) andalso (NewNumDCs =< OldNumDCs) of
true ->
%% new_sc has less packets, new_sc has less dcs, new_sc has lower nonce
%% thus, new_sc "caused" old_sc, a.k.a. effect_of
effect_of;
false ->
conflict
end
end
end.

merge(SCA, SCB) ->
lager:info("merging state channels"),
Expand Down Expand Up @@ -557,85 +657,110 @@ causality_test() ->
BaseSC1 = new(<<"1">>, <<"owner">>, 1),
BaseSC2 = new(<<"1">>, <<"owner">>, 1),
?assertEqual(equal, compare_causality(BaseSC1, BaseSC2)),
?assertEqual(equal, quick_compare_causality(BaseSC1, BaseSC2, PubKeyBin)),

%% no summary, increasing nonce
SC1 = new(<<"1">>, <<"owner">>, 1),
SC2 = nonce(1, new(<<"1">>, <<"owner">>, 1)),
?assertEqual(caused, compare_causality(SC1, SC2)),
?assertEqual(caused, quick_compare_causality(SC1, SC2, PubKeyBin)),
?assert(is_causally_newer(SC2, SC1)),
?assertEqual(effect_of, compare_causality(SC2, SC1)),
?assertEqual(effect_of, quick_compare_causality(SC2, SC1, PubKeyBin)),

%% 0 (same) nonce, with differing summary, conflict
SC3 = summaries([Summary2], new(<<"1">>, <<"owner">>, 1)),
SC4 = summaries([Summary1], new(<<"1">>, <<"owner">>, 1)),
?assertEqual(conflict, compare_causality(SC3, SC4)),
?assertEqual(conflict, quick_compare_causality(SC3, SC4, PubKeyBin)),
?assertEqual(conflict, compare_causality(SC4, SC3)),
?assertEqual(conflict, quick_compare_causality(SC4, SC3, PubKeyBin)),

%% SC6 is allowed after SC5
SC5 = new(<<"1">>, <<"owner">>, 1),
SC6 = summaries([Summary1], nonce(1, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(caused, compare_causality(SC5, SC6)),
?assertEqual(caused, quick_compare_causality(SC5, SC6, PubKeyBin)),
?assert(is_causally_newer(SC6, SC5)),
?assertEqual(effect_of, compare_causality(SC6, SC5)),
?assertEqual(effect_of, quick_compare_causality(SC6, SC5, PubKeyBin1)),

%% SC7 is allowed after SC5
%% NOTE: skipped a nonce here (should this actually be allowed?)
SC7 = summaries([Summary1], nonce(2, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(caused, compare_causality(SC5, SC7)),
?assertEqual(caused, quick_compare_causality(SC5, SC7, PubKeyBin)),
?assert(is_causally_newer(SC7, SC5)),
?assertEqual(effect_of, compare_causality(SC7, SC5)),
?assertEqual(effect_of, quick_compare_causality(SC7, SC5, PubKeyBin1)),

%% SC9 has higher nonce than SC8, however,
%% SC9 does not have summary which SC8 has, conflict
SC8 = summaries([Summary1], nonce(8, new(<<"1">>, <<"owner">>, 1))),
SC9 = nonce(9, new(<<"1">>, <<"owner">>, 1)),
?assertEqual(conflict, compare_causality(SC8, SC9)),
?assertEqual(conflict, quick_compare_causality(SC8, SC9, PubKeyBin)),
?assertEqual(conflict, compare_causality(SC9, SC8)),
?assertEqual(conflict, quick_compare_causality(SC9, SC8, PubKeyBin)),

%% same non-zero nonce, with differing summary, conflict
SC10 = nonce(10, new(<<"1">>, <<"owner">>, 1)),
SC11 = summaries([Summary1], nonce(10, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(conflict, compare_causality(SC10, SC11)),
?assertEqual(conflict, quick_compare_causality(SC10, SC11, PubKeyBin)),
?assertEqual(conflict, compare_causality(SC11, SC10)),
?assertEqual(conflict, quick_compare_causality(SC11, SC10, PubKeyBin)),

%% natural progression
SC12 = summaries([Summary1], nonce(10, new(<<"1">>, <<"owner">>, 1))),
SC13 = summaries([Summary2], nonce(11, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(caused, compare_causality(SC12, SC13)),
?assertEqual(caused, quick_compare_causality(SC12, SC13, PubKeyBin)),
?assert(is_causally_newer(SC13, SC12)),
?assertEqual(effect_of, compare_causality(SC13, SC12)),
?assertEqual(effect_of, quick_compare_causality(SC13, SC12, PubKeyBin)),

%% definite conflict, since summary for a client is missing in higher nonce sc
SC14 = summaries([Summary1], nonce(11, new(<<"1">>, <<"owner">>, 1))),
SC15 = summaries([Summary2], nonce(10, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(conflict, compare_causality(SC14, SC15)),
?assertEqual(conflict, quick_compare_causality(SC14, SC15, PubKeyBin)),
?assertEqual(conflict, compare_causality(SC15, SC14)),
?assertEqual(conflict, quick_compare_causality(SC15, SC14, PubKeyBin)),

%% another natural progression
SC16 = summaries([Summary1], nonce(10, new(<<"1">>, <<"owner">>, 1))),
SC17 = summaries([Summary2, Summary3], nonce(11, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(caused, compare_causality(SC16, SC17)),
?assertEqual(caused, quick_compare_causality(SC16, SC17, PubKeyBin)),
?assert(is_causally_newer(SC17, SC16)),
?assertEqual(effect_of, compare_causality(SC17, SC16)),
?assertEqual(effect_of, quick_compare_causality(SC17, SC16, PubKeyBin)),

%% another definite conflict, since higher nonce sc does not have previous summary
SC18 = summaries([Summary1, Summary3], nonce(10, new(<<"1">>, <<"owner">>, 1))),
SC19 = summaries([Summary2], nonce(11, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(conflict, compare_causality(SC18, SC19)),
?assertEqual(conflict, quick_compare_causality(SC18, SC19, PubKeyBin1)),
?assertEqual(conflict, compare_causality(SC19, SC18)),
?assertEqual(conflict, quick_compare_causality(SC19, SC18, PubKeyBin1)),

%% yet another conflict, since a higher nonce sc has an older client summary
SC20 = summaries([Summary2, Summary3], nonce(10, new(<<"1">>, <<"owner">>, 1))),
SC21 = summaries([Summary1, Summary3], nonce(11, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(conflict, compare_causality(SC20, SC21)),
?assertEqual(conflict, quick_compare_causality(SC20, SC21, PubKeyBin)),
?assertEqual(conflict, compare_causality(SC21, SC20)),
?assertEqual(conflict, quick_compare_causality(SC21, SC20, PubKeyBin)),

%% natural progression with nonce skip
SC22 = summaries([Summary1], nonce(10, new(<<"1">>, <<"owner">>, 1))),
SC23 = summaries([Summary2, Summary3], nonce(12, new(<<"1">>, <<"owner">>, 1))),
?assertEqual(caused, compare_causality(SC22, SC23)),
?assertEqual(caused, quick_compare_causality(SC22, SC23, PubKeyBin)),
?assert(is_causally_newer(SC23, SC22)),
?assertEqual(effect_of, compare_causality(SC23, SC22)),
?assertEqual(effect_of, quick_compare_causality(SC23, SC22, PubKeyBin)),

ok.

Expand Down

0 comments on commit 5581ea6

Please sign in to comment.