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

Dance moves disputed? Get off the Dance Floor! #1229

Merged
merged 3 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion include/blockchain_vars.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,10 @@
%% The "new" behavior (i.e., `true') is to count state channels
%% that are only in the open state and ignore closed channels.
-define(sc_only_count_open_active, sc_only_count_open_active).

%% How to handle rewards when a state channel close is disputed.
%% - 0 :: reconcile as much as possible
%% - 1 :: accept first dispute, drop all DC from opener, no rewards
-define(sc_dispute_strategy_version, sc_dispute_strategy_version).

%% ------------------------------------------------------------------
%% snapshot vars
Expand Down
92 changes: 50 additions & 42 deletions src/transactions/v1/blockchain_txn_state_channel_close_v1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ is_valid(Txn, Chain) ->
_ ->
0
end,
SCDisputeStrategy = case blockchain:config(?sc_dispute_strategy_version, Ledger) of
{ok, V2} -> V2;
_ -> 0
end,
%% first check if it's time to expire
case SCVersion == 0 orelse
(LedgerHeight >= ExpiresAt andalso
Expand All @@ -176,50 +180,54 @@ is_valid(Txn, Chain) ->
{error, _Reason} ->
{error, state_channel_not_open};
{ok, LedgerSC} ->
case Owner == Closer of
%% check the owner's close conditions
%% the owner is not allowed to update if the channel is in dispute
%% and must provide a causally newer version of the channel if there's already a close on file
true ->
case blockchain_ledger_state_channel_v2:is_v2(LedgerSC) of
false ->
ok;
CloseState = blockchain_ledger_state_channel_v2:close_state(LedgerSC),
case {CloseState, SCDisputeStrategy} of
{dispute, Ver} when Ver >= 1 -> {error, already_disputed};
_ ->
case Owner == Closer of
%% check the owner's close conditions
%% the owner is not allowed to update if the channel is in dispute
%% and must provide a causally newer version of the channel if there's already a close on file
true ->
LSC = blockchain_ledger_state_channel_v2:state_channel(LedgerSC),
CloseState = blockchain_ledger_state_channel_v2:close_state(LedgerSC),
lager:info("close state was ~p", [CloseState]),
%% check this new SC is newer than the current one, if any
case LSC == undefined orelse (CloseState /= dispute andalso blockchain_state_channel_v1:compare_causality(LSC, SC) == caused) of
true ->
ok;
case blockchain_ledger_state_channel_v2:is_v2(LedgerSC) of
false ->
{error, redundant}
end
end;
false ->
case blockchain_state_channel_v1:get_summary(Closer, SC) of
{error, _Reason}=E ->
E;
{ok, _Summary} ->
case check_close_updates(LedgerSC, Txn, Ledger) of
ok ->
%% This closer was part of the state channel
%% Is therefore allowed to close said state channel
%% Verify they can afford the fee

AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger),
TxnFee = ?MODULE:fee(Txn),
%% NOTE: TMP removing fee check as SC close fees are hardcoded to zero atm and the check breaks dialyzer
%% ExpectedTxnFee = ?MODULE:calculate_fee(Txn, Chain),
%% case ExpectedTxnFee =< TxnFee orelse not AreFeesEnabled of
%% false ->
%% {error, {wrong_txn_fee, ExpectedTxnFee, TxnFee}};
%% true ->
%% blockchain_ledger_v1:check_dc_or_hnt_balance(Closer, TxnFee, Ledger, AreFeesEnabled)
%% end
blockchain_ledger_v1:check_dc_or_hnt_balance(Closer, TxnFee, Ledger, AreFeesEnabled);
E ->
E
ok;
true ->
LSC = blockchain_ledger_state_channel_v2:state_channel(LedgerSC),
lager:info("close state was ~p", [CloseState]),
%% check this new SC is newer than the current one, if any
case LSC == undefined orelse (CloseState /= dispute andalso blockchain_state_channel_v1:compare_causality(LSC, SC) == caused) of
true ->
ok;
false ->
{error, redundant}
end
end;
false ->
case blockchain_state_channel_v1:get_summary(Closer, SC) of
{error, _Reason}=E ->
E;
{ok, _Summary} ->
case check_close_updates(LedgerSC, Txn, Ledger) of
ok ->
%% This closer was part of the state channel
%% Is therefore allowed to close said state channel
%% Verify they can afford the fee

AreFeesEnabled = blockchain_ledger_v1:txn_fees_active(Ledger),
TxnFee = ?MODULE:fee(Txn),
%% NOTE: TMP removing fee check as SC close fees are hardcoded to zero atm and the check breaks dialyzer
%% ExpectedTxnFee = ?MODULE:calculate_fee(Txn, Chain),
%% case ExpectedTxnFee =< TxnFee orelse not AreFeesEnabled of
%% false ->
%% {error, {wrong_txn_fee, ExpectedTxnFee, TxnFee}};
%% true ->
%% blockchain_ledger_v1:check_dc_or_hnt_balance(Closer, TxnFee, Ledger, AreFeesEnabled)
%% end
blockchain_ledger_v1:check_dc_or_hnt_balance(Closer, TxnFee, Ledger, AreFeesEnabled);
E ->
E
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions src/transactions/v1/blockchain_txn_vars_v1.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,8 @@ validate_var(?sc_only_count_open_active, Value) ->
false -> ok;
Other -> throw({error, {invalid_sc_only_count_open_active_value, Other}})
end;
validate_var(?sc_dispute_strategy_version, Value) ->
validate_int(Value, "sc_dispute_strategy_version", 0, 1, false);

%% txn snapshot vars
validate_var(?snapshot_version, Value) ->
Expand Down
107 changes: 87 additions & 20 deletions src/transactions/v2/blockchain_txn_rewards_v2.erl
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,10 @@ get_reward_vars(Start, End, Ledger) ->
_ ->
1
end,
SCDisputeStrategyVersion = case blockchain:config(?sc_dispute_strategy_version, Ledger) of
{ok, SCDV} -> SCDV;
_ -> 0
end,
POCVersion = case blockchain:config(?poc_version, Ledger) of
{ok, V} -> V;
_ -> 1
Expand Down Expand Up @@ -746,6 +750,7 @@ get_reward_vars(Start, End, Ledger) ->
dc_percent => DCPercent,
sc_grace_blocks => SCGrace,
sc_version => SCVersion,
sc_dispute_strategy_version => SCDisputeStrategyVersion,
poc_version => POCVersion,
reward_version => RewardVersion,
witness_redundancy => WitnessRedundancy,
Expand Down Expand Up @@ -1377,26 +1382,39 @@ dc_reward(Txn, End, AccIn, Ledger, #{ sc_grace_blocks := GraceBlocks,
%% pull out the final version of the state channel
FinalSC = blockchain_ledger_state_channel_v2:state_channel(SC),
RewardVersion = maps:get(reward_version, Vars, 1),

Summaries = case RewardVersion > 3 of
%% reward version 4 normalizes payouts
true -> blockchain_state_channel_v1:summaries(blockchain_state_channel_v1:normalize(FinalSC));
false -> blockchain_state_channel_v1:summaries(FinalSC)
end,
%% check the dispute status
Bonus = case blockchain_ledger_state_channel_v2:close_state(SC) of
%% Reward version 4 or higher just slashes overcommit
dispute when RewardVersion < 4 ->
%% the owner of the state channel
%% did a naughty thing, divide
%% their overcommit between the
%% participants
OverCommit = blockchain_ledger_state_channel_v2:amount(SC)
- blockchain_ledger_state_channel_v2:original(SC),
OverCommit div length(Summaries);
_ ->
0
end,
CloseState = blockchain_ledger_state_channel_v2:close_state(SC),
SCDisputeStrategy = maps:get(?sc_dispute_strategy_version, Vars, 0),
{Summaries, Bonus} =
case {SCDisputeStrategy, CloseState} of
{Ver, dispute} when Ver >= 1 ->
%% When sc_dispute_strategy_version is 1 we want to zero out as much as possible.
%% No Bonuses, no summaries. All slashed.
{[], 0};
{_, _} ->

InnerSummaries = case RewardVersion > 3 of
%% reward version 4 normalizes payouts
true -> blockchain_state_channel_v1:summaries(blockchain_state_channel_v1:normalize(FinalSC));
false -> blockchain_state_channel_v1:summaries(FinalSC)
end,

%% check the dispute status
InnerBonus = case blockchain_ledger_state_channel_v2:close_state(SC) of
%% Reward version 4 or higher just slashes overcommit
dispute when RewardVersion < 4 ->
%% the owner of the state channel
%% did a naughty thing, divide
%% their overcommit between the
%% participants

OverCommit = blockchain_ledger_state_channel_v2:amount(SC)
- blockchain_ledger_state_channel_v2:original(SC),
OverCommit div length(InnerSummaries);
_ ->
0
end,
{InnerSummaries, InnerBonus}
end,

lists:foldl(fun(Summary, A) ->
Key = blockchain_state_channel_summary_v1:client_pubkeybin(Summary),
Expand Down Expand Up @@ -1768,6 +1786,54 @@ poc_witnesses_rewards_test() ->
?assertEqual(Rewards, normalize_witness_rewards(WitnessShares, EpochVars)),
test_utils:cleanup_tmp_dir(BaseDir).

dc_rewards_sc_dispute_strategy_test() ->
BaseDir = test_utils:tmp_dir("dc_rewards_dispute_sc_test"),
Ledger = blockchain_ledger_v1:new(BaseDir),
Ledger1 = blockchain_ledger_v1:new_context(Ledger),

Vars = #{
epoch_reward => 100000,
dc_percent => 0.3,
consensus_percent => 0.06 + 0.025,
poc_challengees_percent => 0.18,
poc_challengers_percent => 0.0095,
poc_witnesses_percent => 0.0855,
securities_percent => 0.34,
sc_version => 2,
sc_grace_blocks => 5,
reward_version => 4,
oracle_price => 100000000, %% 1 dollar
consensus_members => [<<"c">>, <<"d">>],
%% This is the important part of what's being tested here.
sc_dispute_strategy_version => 1
},

LedgerVars = maps:merge(#{?poc_version => 5, ?sc_version => 2, ?sc_grace_blocks => 5}, common_poc_vars()),
ok = blockchain_ledger_v1:vars(LedgerVars, [], Ledger1),

{SC0, _} = blockchain_state_channel_v1:new(<<"id">>, <<"owner">>, 100, <<"blockhash">>, 10),
SCValid = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 1, 1), blockchain_state_channel_summary_v1:new(<<"b">>, 2, 2)], SC0),
SCDispute = blockchain_state_channel_v1:summaries([blockchain_state_channel_summary_v1:new(<<"a">>, 2, 2), blockchain_state_channel_summary_v1:new(<<"b">>, 3, 3)], SC0),

ok = blockchain_ledger_v1:add_state_channel(<<"id">>, <<"owner">>, 10, 1, 100, 200, Ledger1),
{ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1),

ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"owner">>, SCValid, <<"id">>, false, Ledger1),
{ok, _} = blockchain_ledger_v1:find_state_channel(<<"id">>, <<"owner">>, Ledger1),

ok = blockchain_ledger_v1:close_state_channel(<<"owner">>, <<"a">>, SCDispute, <<"id">>, true, Ledger1),

SCClose = blockchain_txn_state_channel_close_v1:new(SCValid, <<"owner">>),
{ok, _DCsInEpochAsHNT} = blockchain_ledger_v1:dc_to_hnt(3, 100000000), %% 3 DCs burned at HNT price of 1 dollar

DCShares = dc_reward(SCClose, 100, #{}, Ledger1, Vars),

%% We only care that no rewards are generated when sc_dispute_strategy_version is active.
{Res, M} = normalize_dc_rewards(DCShares, Vars),
?assertEqual(#{}, M, "no summaries in rewards map"),
?assertEqual(30000, Res),
test_utils:cleanup_tmp_dir(BaseDir).

dc_rewards_v3_test() ->
BaseDir = test_utils:tmp_dir("dc_rewards_v3_test"),
Ledger = blockchain_ledger_v1:new(BaseDir),
Expand All @@ -1783,6 +1849,7 @@ dc_rewards_v3_test() ->
securities_percent => 0.34,
sc_version => 2,
sc_grace_blocks => 5,
sc_dispute_strategy_version => 0,
reward_version => 3,
oracle_price => 100000000, %% 1 dollar
consensus_members => [<<"c">>, <<"d">>]
Expand Down
Loading