Skip to content

Commit

Permalink
Pt 167961470 stop off-chain updates on-chain Lima on (#2666)
Browse files Browse the repository at this point in the history
Refuse old off-chain serialization from Lima on
  • Loading branch information
velzevur committed Aug 22, 2019
1 parent b3056d6 commit 860dd70
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 89 deletions.
10 changes: 0 additions & 10 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,16 +813,6 @@ jobs:
- *install_otp
- *install_libsodium
- *restore_machine_build_cache
# Git based deps are problematic for rebar3 - even running "./rebar3 as system_test upgrade" doesn't do the job of ensuring these deps are up to date
# Just delete the problematic deps from the cache as rebuilding those deps is faster than invalidating the entire outdated cache
- run:
name: Get rid of problematic cached deps
command: |
rm -rf _build/system_test/lib/aesophia;
rm -rf _build/system_test/lib/aesophia_cli;
rm -rf _build/system_test+test/lib/aesophia;
rm -rf _build/system_test+test/lib/aesophia_cli;
# keep user preparation step after cache restore because of perms
- *prepare_ubuntu_user
- *install_system_test_deps
- run:
Expand Down
Empty file added Compiling
Empty file.
1 change: 1 addition & 0 deletions apps/aechannel/src/aesc_offchain_tx.erl
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ version(#channel_offchain_tx{updates = Updates}) ->
-spec valid_at_protocol(aec_hard_forks:protocol_vsn(), tx()) -> boolean().
valid_at_protocol(Protocol, Tx) ->
case version(Tx) of
?INITIAL_VSN when Protocol >= ?LIMA_PROTOCOL_VSN -> false;
?INITIAL_VSN -> true;
?NO_UPDATES_VSN when Protocol >= ?FORTUNA_PROTOCOL_VSN -> true;
_ -> false
Expand Down
17 changes: 5 additions & 12 deletions apps/aechannel/test/aesc_test_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -395,23 +395,16 @@ state_tx(ChannelPubKey, Initiator, Responder, Spec0) ->
aec_trees:hash(Trees);
V -> V
end,
OffChainSpec0
= #{channel_id => aeser_id:create(channel, ChannelPubKey),
updates => maps:get(updates, Spec, []),
state_hash => StateHash,
round => maps:get(round, Spec)},
OffChainSpec =
case maps:get(block_hash, Spec0, none) of
none -> OffChainSpec0;
Val -> OffChainSpec0#{block_hash => Val}
end,
maps:merge(#{ channel_id => aeser_id:create(channel, ChannelPubKey)
, state_hash => StateHash
, round => maps:get(round, Spec)},
maps:with([block_hash, updates], Spec0)),
{ok, StateTx} = aesc_offchain_tx:new(OffChainSpec),
StateTx.

state_tx_spec() ->
#{initiator_amount => 3,
responder_amount => 4,
state => <<"state..">>,
#{state => <<"state..">>,
round => 11}.

payload(ChannelId, Initiator, Responder, SignersPrivKeys, Spec) ->
Expand Down
162 changes: 97 additions & 65 deletions apps/aechannel/test/aesc_txs_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@

% fork related tests
-export([ fp_sophia_versions/1,
close_mutual_already_closing/1
close_mutual_already_closing/1,
reject_old_offchain_tx_vsn/1
]).

-include_lib("common_test/include/ct.hrl").
Expand Down Expand Up @@ -399,7 +400,8 @@ groups() ->
]},
{fork_awareness, [sequence],
[ fp_sophia_versions,
close_mutual_already_closing
close_mutual_already_closing,
reject_old_offchain_tx_vsn
]}
].

Expand Down Expand Up @@ -913,6 +915,7 @@ close_mutual_already_closing(Cfg) ->
run(#{cfg => Cfg, initiator_amount => StartIAmt, responder_amount => StartRAmt},
[positive(fun create_channel_/2),
set_from(Closer),
set_prop(height, FortunaHeight),
positive(fun close_solo_/2),
fun(#{channel_pubkey := ChannelPubKey, state := S} = Props) ->
% make sure the channel is not active any more
Expand All @@ -923,7 +926,6 @@ close_mutual_already_closing(Cfg) ->
%% prepare balances and a fee..
prepare_balances_for_mutual_close(),
% Before Lima fork this should fail
set_prop(height, FortunaHeight),
negative(fun close_mutual_/2, {error, channel_not_active}),
% After Lima fork this should succeed
set_prop(height, LimaHeight),
Expand All @@ -932,6 +934,36 @@ close_mutual_already_closing(Cfg) ->
[Test(Role) || Role <- ?ROLES],
ok.

reject_old_offchain_tx_vsn(Cfg) ->
StartIAmt = 100000 * aec_test_utils:min_gas_price(),
StartRAmt = 100000 * aec_test_utils:min_gas_price(),

RomaHeight = 123,
FortunaHeight = 12345,
LimaHeight = 123456,

% ensure assumptions regarding heights
true = RomaHeight < ?MINERVA_FORK_HEIGHT,
true = LimaHeight > FortunaHeight,
true = FortunaHeight >= ?FORTUNA_FORK_HEIGHT,
true = LimaHeight >= ?LIMA_FORK_HEIGHT,

Test =
fun(Closer) ->
run(#{cfg => Cfg, initiator_amount => StartIAmt, responder_amount => StartRAmt},
[positive(fun create_channel_/2),
set_from(Closer),
%% produce an off-chain transaction with the old structure
set_prop(height, RomaHeight),
create_payload(),
set_prop(height, LimaHeight),
%% it fails after Lima
negative(fun close_solo_/2, {error, invalid_at_height})
])
end,
[Test(Role) || Role <- ?ROLES],
ok.

%%%===================================================================
%%% Slash
%%%===================================================================
Expand Down Expand Up @@ -1166,11 +1198,10 @@ slash_payload_not_co_signed(Cfg) ->
responder_amount := RAmt,
from_pubkey := FromPubKey,
state := S,
height := Height}) ->
height := Height} = Props) ->
lists:foreach(
fun(PrivKeys) ->
PayloadSpec = #{initiator_amount => IAmt,
responder_amount => RAmt},
PayloadSpec = create_payload_spec(Props),
PayloadMissingS = aesc_test_utils:payload(ChannelPubKey, I, R,
PrivKeys, PayloadSpec),
PoI = aesc_test_utils:proof_of_inclusion([{I, IAmt},
Expand Down Expand Up @@ -4408,33 +4439,57 @@ get_onchain_balance(Pubkey, Key) ->
create_payload() ->
create_payload(payload).

reuse_or_create_payload(Key, Props) ->
case maps:get(Key, Props, not_specified) of
not_specified ->
CreateFun = create_payload(Key),
Props1 = CreateFun(Props),
maps:get(Key, Props1);
Payload -> Payload
end.

create_payload(Key) ->
fun(#{channel_pubkey := ChannelPubKey,
initiator_amount := IAmt,
responder_amount := RAmt,
initiator_pubkey := IPubkey,
responder_pubkey := RPubkey,
initiator_privkey := IPrivkey,
responder_privkey := RPrivkey} = Props) ->

PayloadSpec0 = #{initiator_amount => IAmt,
responder_amount => RAmt,
round => maps:get(round, Props, 11)},
PayloadSpec =
lists:foldl(
fun({PropsKey, OffChainKey}, Accum) ->
case maps:get(PropsKey, Props, none) of
none -> Accum;
V -> maps:put(OffChainKey, V, Accum)
end
end,
PayloadSpec0,
[{state_hash, state_hash}]),
PayloadSpec = create_payload_spec(Props),
Payload = aesc_test_utils:payload(ChannelPubKey, IPubkey, RPubkey,
[IPrivkey, RPrivkey], PayloadSpec),
Props#{Key => Payload}
end.

create_payload_spec(#{initiator_amount := IAmt,
responder_amount := RAmt} = Props) ->
PayloadSpec0 = #{initiator_amount => IAmt,
responder_amount => RAmt,
round => maps:get(round, Props, 11)},
Protocol =
case maps:get(height, Props, use_no_updates_vsn) of
use_no_updates_vsn ->
aect_test_utils:latest_protocol_version();
ChainHeight ->
aec_hard_forks:protocol_effective_at_height(ChainHeight)
end,
PayloadSpec01 =
case Protocol of
_P when _P >= ?FORTUNA_PROTOCOL_VSN -> %% no updates
PayloadSpec0;
_ ->
PayloadSpec0#{updates => []} %% this is some updates
end,
lists:foldl(
fun({PropsKey, OffChainKey}, Accum) ->
case maps:get(PropsKey, Props, none) of
none -> Accum;
V -> maps:put(OffChainKey, V, Accum)
end
end,
PayloadSpec01,
[{state_hash, state_hash}]).

create_contract_call_payload(ContractId, ContractName, Fun, Args, Amount) ->
create_contract_call_payload(solo_payload, ContractId, ContractName, Fun, Args, Amount).

Expand Down Expand Up @@ -4697,16 +4752,10 @@ close_solo_(#{channel_pubkey := ChannelPubKey,
responder_amount := RAmt,
initiator_pubkey := IPubkey,
responder_pubkey := RPubkey,
state := S,
initiator_privkey := IPrivkey,
responder_privkey := RPrivkey} = Props, Expected) ->
state := S} = Props, Expected) ->

Fee = maps:get(fee, Props, 50000 * aec_test_utils:min_gas_price()),
%% Create close_solo tx and apply it on state trees
Round = maps:get(round, Props, 10),
PayloadSpec0 = #{initiator_amount => IAmt,
responder_amount => RAmt,
round => Round},
PoI = maps:get(poi, Props,
aesc_test_utils:proof_of_inclusion([{IPubkey, IAmt},
{RPubkey, RAmt}])),
Expand All @@ -4715,10 +4764,8 @@ close_solo_(#{channel_pubkey := ChannelPubKey,
not_passed -> aec_trees:poi_hash(PoI);
Hash -> Hash
end,
PayloadSpec = PayloadSpec0#{state_hash => StateHash},
Payload = maps:get(payload, Props,
aesc_test_utils:payload(ChannelPubKey, IPubkey, RPubkey,
[IPrivkey, RPrivkey], PayloadSpec)),
Payload = reuse_or_create_payload(payload, Props#{state_hash => StateHash}),
ct:log("Close is based on payload: ~p", [aesc_utils:deserialize_payload(Payload)]),
Spec =
case Props of
#{nonce := Nonce} -> #{fee => Fee, nonce => Nonce};
Expand All @@ -4739,27 +4786,19 @@ slash_(#{channel_pubkey := ChannelPubKey,
initiator_pubkey := IPubkey,
responder_pubkey := RPubkey,
fee := Fee,
state := S,
initiator_privkey := IPrivkey,
responder_privkey := RPrivkey} = Props, Expected) ->
state := S} = Props, Expected) ->

%% Create slash tx and apply it on state trees
Round = maps:get(round, Props, 10),
PayloadSpec0 = #{initiator_amount => IAmt,
responder_amount => RAmt,
round => Round},
PayloadSpec =
case maps:get(state_hash, Props, not_passed) of
not_passed -> PayloadSpec0;
Hash -> PayloadSpec0#{state_hash => Hash}
end,
Payload = maps:get(payload, Props,
aesc_test_utils:payload(ChannelPubKey, IPubkey, RPubkey,
[IPrivkey, RPrivkey], PayloadSpec)),
PoI = maps:get(poi, Props, aesc_test_utils:proof_of_inclusion([{IPubkey,
IAmt},
{RPubkey,
RAmt}])),
StateHash =
case maps:get(state_hash, Props, not_passed) of
not_passed -> aec_trees:poi_hash(PoI);
Hash -> Hash
end,
Payload = reuse_or_create_payload(payload, Props#{state_hash => StateHash}),
Spec =
case Props of
#{nonce := Nonce} -> #{fee => Fee, nonce => Nonce};
Expand Down Expand Up @@ -4798,24 +4837,11 @@ close_mutual_(#{channel_pubkey := ChannelPubKey,
snapshot_solo_(#{ channel_pubkey := ChannelPubKey,
from_pubkey := FromPubKey,
from_privkey := FromPrivkey,
initiator_amount := IAmt,
responder_amount := RAmt,
initiator_pubkey := IPubkey,
responder_pubkey := RPubkey,
fee := Fee,
state := S,
initiator_privkey := IPrivkey,
responder_privkey := RPrivkey} = Props, Expected) ->
Round = maps:get(round, Props, 42),
state := S} = Props, Expected) ->
StateHashSize = aeser_api_encoder:byte_size_for_type(state),
StateHash = maps:get(state_hash, Props, <<42:StateHashSize/unit:8>>),
PayloadSpec = #{initiator_amount => IAmt,
responder_amount => RAmt,
state_hash => StateHash,
round => Round},
Payload = maps:get(payload, Props,
aesc_test_utils:payload(ChannelPubKey, IPubkey, RPubkey,
[IPrivkey, RPrivkey], PayloadSpec)),
Payload = reuse_or_create_payload(payload, Props#{state_hash => StateHash}),

SnapshotTxSpec = aesc_test_utils:snapshot_solo_tx_spec(ChannelPubKey, FromPubKey,
Payload, #{fee => Fee},S),
Expand Down Expand Up @@ -5012,11 +5038,10 @@ test_payload_not_both_signed(Cfg, SpecFun, CreateTxFun) ->
responder_amount := RAmt,
from_pubkey := FromPubKey,
state := S,
height := Height}) ->
height := Height} = Props) ->
lists:foreach(
fun(PrivKeys) ->
PayloadSpec = #{initiator_amount => IAmt,
responder_amount => RAmt},
PayloadSpec = create_payload_spec(Props),
PayloadMissingS = aesc_test_utils:payload(ChannelPubKey, I, R,
PrivKeys, PayloadSpec),
PoI = aesc_test_utils:proof_of_inclusion([{I, IAmt},
Expand Down Expand Up @@ -5413,6 +5438,7 @@ fp_sophia_versions(Cfg) ->
_ContractCreateRound = 10,
_ContractOwner = Forcer),
set_from(Forcer),
set_prop(height, RomaHeight),
set_prop(round, Round),
fun(#{contract_id := ContractId, contract_file := CName} = Props) ->
(create_contract_call_payload(ContractId, CName, <<"main">>,
Expand All @@ -5430,6 +5456,12 @@ fp_sophia_versions(Cfg) ->
TestAtHeight(RomaHeight, RomaRes),
TestAtHeight(MinervaHeight, MinervaRes),
TestAtHeight(FortunaHeight, FortunaRes),
%% recreate the payload to be the new vsn
set_prop(height, LimaHeight),
set_prop(round, Round - 1),
delete_prop(payload), % new off-chain vsn with correct round
create_payload(),
set_prop(round, Round),
TestAtHeight(LimaHeight, LimaRes)
])
end,
Expand Down
5 changes: 3 additions & 2 deletions apps/aega/test/aega_test_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,12 @@ add_poi([{account, PK} | What], Trees, PoI) ->


payload(CId, S = #{round := Rnd, sign := Sigs}, S0) ->
%% use only aesc_offchain_tx without updates since GAs are introduced in
%% Fortuna
{ok, OffTx} = aesc_offchain_tx:new(
#{ channel_id => aeser_id:create(channel, CId)
, state_hash => state_hash(S)
, round => Rnd + 1
, updates => [] }),
, round => Rnd + 1}),
SOffTx = sign_tx(aetx_sign:new(OffTx, []), Sigs, S0),
aetx_sign:serialize_to_binary(SOffTx).

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
* Obsoletes the old State Channel off-chain transaction type which contained
the list of updates that produced the latest `state_hash`. The new
transaction type is available since Fortuna hard fork and the FSM is
producing such transactions ever since. This detaches the off-chain protocol
from the on-chain one and allows development of unique off-chain protocols
that don't need their updates to be serializable on-chain.

* For existing state channels where the latest co-authenticated state is an
off-chain transaction based on the old version, it is suggested to make a
new co-authenticated transaction which is using the new serialization. For
currently existing state channels which latest co-authenticated state is an
old version off-chain transaction is suggested to make a new
co-authenticated transaction that is using the new serialization. If the
other party refuses to authenticate a new round or is simply missing, one
can use the solo closing sequence instead.

0 comments on commit 860dd70

Please sign in to comment.