Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
09f7a0d
[GH-491] SnapshotSoloTx implemented. Tests
gorbak25 Aug 10, 2018
a6fee0d
Merge remote-tracking branch 'origin/master' into GH-491
gorbak25 Aug 11, 2018
7bb42f1
[GH-491] More tests
gorbak25 Aug 11, 2018
78ac4bc
[GH-491] Changed comment for new_tx_mined handling of ChannelSnapshot…
gorbak25 Aug 13, 2018
fc11b32
[GH-491] More relevant error messages
gorbak25 Aug 13, 2018
d64bb11
[GH-491] Worker replies with pool error. Consistency fixes.
gorbak25 Aug 13, 2018
083daca
[GH-491] Consistency fix of filename
gorbak25 Aug 13, 2018
26454eb
[GH-491] Fixed channels test setup
cytadela8 Aug 13, 2018
2fdccc2
[GH-491] Removed changes to mix.exs and mix.lock
cytadela8 Aug 13, 2018
796ebf6
Updated README, implemented transaction blacklists
gorbak25 Aug 13, 2018
e16afee
[GH-491] Small fix
gorbak25 Aug 13, 2018
1a36b28
[GH-491] Removed transaction blacklist
gorbak25 Aug 14, 2018
de77959
[GH-491] Added ChannelSnapshotSoloTx to allowed transactions
gorbak25 Aug 14, 2018
8aa153a
Merge pull request #525 from aeternity/GH-491
thepiwo Aug 15, 2018
82efbd4
Merge remote-tracking branch 'origin/master-channels' into GH-759
gorbak25 Nov 15, 2018
6ba26cf
[GH-759] mix format
gorbak25 Nov 15, 2018
9aebc2f
[GH-759] Adjusted ChannelSnapshotSoloTx
gorbak25 Nov 15, 2018
d09837c
[GH-759] Adjusted ChannelStateOnChain, small Withdraw/deposit refacto…
gorbak25 Nov 15, 2018
968a735
[GH-759] Adjusted ChannelStatePeer and Worker
gorbak25 Nov 15, 2018
6417a5b
[GH-759] Adjusted tests, more tests
gorbak25 Nov 16, 2018
4e571ba
[GH-759] Spellcheck
gorbak25 Nov 16, 2018
a15f34d
[GH-759] Mix credo
gorbak25 Nov 16, 2018
a4f29ce
Merge branch 'GH-759' of https://github.com/aeternity/elixir-node int…
gorbak25 Nov 16, 2018
e52b8b5
[GH-759] Dialyzer fix
gorbak25 Nov 16, 2018
8b63df0
[GH-759] Small changes
gorbak25 Nov 20, 2018
f519f76
Merge remote-tracking branch 'origin/master' into GH-759
gorbak25 Nov 20, 2018
1f8baa0
[GH-759] Solo close with an snapshoted state
gorbak25 Nov 20, 2018
1a5c469
Merge remote-tracking branch 'origin/master' into GH-759
gorbak25 Nov 21, 2018
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
87 changes: 63 additions & 24 deletions apps/aecore/lib/aecore/channel/channel_state_on_chain.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule Aecore.Channel.ChannelStateOnChain do
# Parameters
- initiator_pubkey
- responder_pubkey
- delegates - list of delegates alowed to perform certain operations
- delegates - list of delegates allowed to perform certain operations
- total_amount - the total amount of tokens in the channel
- initiator_amount - amount deposited by initiator in create_tx or from poi
- responder_amount - amount deposited by responder in create_tx or from poi
Expand Down Expand Up @@ -147,6 +147,31 @@ defmodule Aecore.Channel.ChannelStateOnChain do
{initiator_pubkey, responder_pubkey}
end

@spec is_peer?(ChannelStateOnChain.t(), Keys.pubkey()) :: boolean()
def is_peer?(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"is_" is not needed. Boolean-ness is represented by "?". This follows the convention we have in our code. (see: active?, settled?)

%ChannelStateOnChain{
initiator_pubkey: initiator_pubkey,
responder_pubkey: responder_pubkey
},
pubkey
)
when is_binary(pubkey) do
pubkey in [initiator_pubkey, responder_pubkey]
end

@spec is_peer_or_delegate?(ChannelStateOnChain.t(), Keys.pubkey()) :: boolean()
def is_peer_or_delegate?(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also remove "is_"

%ChannelStateOnChain{
initiator_pubkey: initiator_pubkey,
responder_pubkey: responder_pubkey,
delegates: delegates
},
pubkey
)
when is_binary(pubkey) do
pubkey in [initiator_pubkey, responder_pubkey | delegates]
end

@doc """
Returns true if the channel wasn't slashed. (Closed channels should be removed from the Channels state tree)
"""
Expand Down Expand Up @@ -248,7 +273,7 @@ defmodule Aecore.Channel.ChannelStateOnChain do

channel.sequence >= offchain_tx.sequence ->
{:error,
"#{__MODULE__}: OffChain state is too old, expected newer than #{channel.sequence}, got #{
"#{__MODULE__}: OffChainTx is too old, expected newer than #{channel.sequence}, got #{
offchain_tx.sequence
}"}

Expand Down Expand Up @@ -334,32 +359,22 @@ defmodule Aecore.Channel.ChannelStateOnChain do

@spec validate_withdraw(
ChannelStateOnChain.t(),
Keys.pubkey(),
non_neg_integer(),
non_neg_integer()
) :: :ok | error()
def validate_withdraw(
%ChannelStateOnChain{
initiator_pubkey: initiator_pubkey,
responder_pubkey: responder_pubkey,
total_amount: total_amount,
sequence: sequence,
channel_reserve: channel_reserve
},
tx_account,
tx_amount,
tx_sequence
) do
cond do
sequence >= tx_sequence ->
{:error, "Too old state - latest known sequence is #{sequence} but we got #{tx_sequence}"}

tx_account != initiator_pubkey and tx_account != responder_pubkey ->
{:error,
"Withdraw destination must be a party of this channel. Tried to withdraw from #{
tx_account
} but the parties are #{initiator_pubkey} and #{responder_pubkey}"}

tx_amount < 0 ->
{:error, "Withdraw of negative amount(#{tx_amount}) is forbidden"}

Expand Down Expand Up @@ -397,34 +412,23 @@ defmodule Aecore.Channel.ChannelStateOnChain do

@spec validate_deposit(
ChannelStateOnChain.t(),
Keys.pubkey(),
non_neg_integer(),
non_neg_integer()
) :: :ok | error()
def validate_deposit(
%ChannelStateOnChain{
initiator_pubkey: initiator_pubkey,
responder_pubkey: responder_pubkey,
sequence: sequence
},
tx_account,
tx_amount,
tx_sequence
)
when is_binary(tx_account) do
) do
cond do
sequence >= tx_sequence ->
{:error, "Too old state - latest known sequence is #{sequence} but we got #{tx_sequence}"}

tx_amount < 0 ->
{:error, "Deposit of negative amount(#{tx_amount}) is forbidden"}

tx_account != initiator_pubkey and tx_account != responder_pubkey ->
{:error,
"Deposit destination must be a party of this channel. Tried to deposit tokens to #{
tx_account
} but the parties are #{initiator_pubkey} and #{responder_pubkey}"}

true ->
:ok
end
Expand All @@ -451,6 +455,41 @@ defmodule Aecore.Channel.ChannelStateOnChain do
}
end

@doc """
Validates the payload of ChannelSnapshotSoloTx.
"""
@spec validate_snapshot(ChannelStateOnChain.t(), ChannelOffChainTx.t()) ::
:ok | {:error, binary()}
def validate_snapshot(
%ChannelStateOnChain{} = channel,
%ChannelOffChainTx{} = offchain_tx
) do
if channel.sequence < offchain_tx.sequence do
ChannelOffChainTx.verify_signatures(offchain_tx, pubkeys(channel))
else
{:error,
"#{__MODULE__}: OffChainTx is too old, expected newer than #{channel.sequence}, got #{
offchain_tx.sequence
}"}
end
end

@doc """
Applies the provided payload of ChannelSnapshotSoloTx to the state of the channel. The contents should be validated by &validate_snapshot/2
"""
@spec apply_snapshot(ChannelStateOnChain.t(), ChannelOffChainTx.t()) :: ChannelStateOnChain.t()
def apply_snapshot(%ChannelStateOnChain{} = channel, %ChannelOffChainTx{
sequence: sequence,
state_hash: state_hash
}) do
%ChannelStateOnChain{
channel
| sequence: sequence,

This comment was marked as resolved.

state_hash: state_hash,
solo_sequence: 0
}
end

@spec encode_to_list(ChannelStateOnChain.t()) :: list(binary())
def encode_to_list(%ChannelStateOnChain{} = channel) do
[
Expand Down
90 changes: 83 additions & 7 deletions apps/aecore/lib/aecore/channel/channel_state_peer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ defmodule Aecore.Channel.ChannelStatePeer do
ChannelSlashTx,
ChannelSettleTx,
ChannelWithdrawTx,
ChannelDepositTx
ChannelDepositTx,
ChannelSnapshotSoloTx
}

alias Aecore.Keys
Expand All @@ -44,7 +45,8 @@ defmodule Aecore.Channel.ChannelStatePeer do
mutually_signed_tx: list(ChannelOffChainTx.t()),
highest_half_signed_tx: ChannelOffChainTx.t() | nil,
channel_reserve: non_neg_integer(),
offchain_chainstate: Chainstate.t() | nil
offchain_chainstate: Chainstate.t() | nil,
snapshot_sequence: non_neg_integer()
}

@typedoc "Type of the errors returned by functions in this module"
Expand All @@ -60,7 +62,8 @@ defmodule Aecore.Channel.ChannelStatePeer do
:channel_reserve,
mutually_signed_tx: [],
highest_half_signed_tx: nil,
offchain_chainstate: nil
offchain_chainstate: nil,
snapshot_sequence: 0
]

require Logger
Expand Down Expand Up @@ -992,21 +995,31 @@ defmodule Aecore.Channel.ChannelStatePeer do
Keys.sign_priv_key()
) :: {:ok, ChannelStatePeer.t(), SignedTx.t()} | error()
def solo_close(
%ChannelStatePeer{channel_id: channel_id, mutually_signed_tx: [most_recent_tx | _]} =
peer_state,
%ChannelStatePeer{
channel_id: channel_id,
mutually_signed_tx: [most_recent_tx | _],
snapshot_sequence: snapshot_sequence
} = peer_state,
fee,
nonce,
priv_key
) do
new_peer_state = %ChannelStatePeer{peer_state | fsm_state: :closing}
# If we are closing with a recently snapshoted state we MUST use the state saved on chain
offchain_tx =
if snapshot_sequence == ChannelStatePeer.sequence(peer_state) do
:empty
else
ChannelTransaction.dispute_payload(most_recent_tx)
end

data =
DataTx.init(
ChannelCloseSoloTx,
%{
channel_id: channel_id,
poi: dispute_poi_for_latest_state(peer_state),
offchain_tx: ChannelTransaction.dispute_payload(most_recent_tx)
offchain_tx: offchain_tx
},
our_pubkey(peer_state),
fee,
Expand Down Expand Up @@ -1049,7 +1062,7 @@ defmodule Aecore.Channel.ChannelStatePeer do
end

@doc """
Handles mined ChnanelSlashTx and ChannelCloseSoloTx. Provided fee and nonce are for potentially created SlashTx. Pubkey and Privkey don't have to match any of channel parties. Returns altered ChannelPeerState and ChannelSlashTx if we have higher signed state.
Handles mined ChannelSlashTx and ChannelCloseSoloTx. Provided fee and nonce are for potentially created SlashTx. Pubkey and Privkey don't have to match any of channel parties. Returns altered ChannelPeerState and ChannelSlashTx if we have higher signed state.
"""
@spec slashed(
ChannelStatePeer.t(),
Expand Down Expand Up @@ -1091,6 +1104,69 @@ defmodule Aecore.Channel.ChannelStatePeer do
end
end

@doc """
Creates a snapshot transaction from the most recent mutually signed transaction. This transaction must be a ChannelOffChainTx, otherwise the onchain state contains the most recent offchain state hash and a snapshot is unnecessary.
Can be called anytime after the channel was opened and before the channel is closed.
"""
@spec snapshot(ChannelStatePeer.t(), non_neg_integer(), non_neg_integer(), Keys.sign_priv_key()) ::
{:ok, SignedTx.t()} | error()
def snapshot(
%ChannelStatePeer{
fsm_state: state,
channel_id: channel_id,
mutually_signed_tx: [%ChannelOffChainTx{} = offchain_tx | _],
snapshot_sequence: snapshot_sequence
} = peer_state,
fee,
nonce,
priv_key
)
when state in [:awaiting_full_tx, :awaiting_tx_confirmed, :open] do
if snapshot_sequence < ChannelStatePeer.sequence(peer_state) do
data =
DataTx.init(
ChannelSnapshotSoloTx,
%{channel_id: channel_id, offchain_tx: offchain_tx},
our_pubkey(peer_state),
fee,
nonce
)

SignedTx.sign_tx(data, priv_key)
else
{:error, "#{__MODULE__}: We cannot snapshot an already snapshoted state."}
end
end

def snapshot(
%ChannelStatePeer{
fsm_state: state
},
_,
_,
_
)
when state in [:awaiting_full_tx, :awaiting_tx_confirmed, :open] do
{:error, "#{__MODULE__}: We can only snapshot with ChannelOffChainTx."}
end

def snapshot(%ChannelStatePeer{fsm_state: state}, _, _, _) do
{:error, "#{__MODULE__}: Invalid peer state: #{state}"}
end

@doc """
Notifies the channel that a snapshot tx was mined. MUST only be called when ChannelSnapshotSoloTx was mined.
"""
@spec snapshot_mined(ChannelStatePeer.t(), SignedTx.t()) :: ChannelStatePeer.t()
def snapshot_mined(%ChannelStatePeer{} = peer_state, %SignedTx{
data: %DataTx{
type: ChannelSnapshotSoloTx,
payload: %ChannelSnapshotSoloTx{offchain_tx: %ChannelOffChainTx{sequence: sequence}}
}
}) do
%ChannelStatePeer{peer_state | snapshot_sequence: sequence}
end

@doc """
Creates channel settle tx.
"""
Expand Down
8 changes: 7 additions & 1 deletion apps/aecore/lib/aecore/channel/tx/channel_deposit_tx.ex
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,14 @@ defmodule Aecore.Channel.Tx.ChannelDepositTx do
!ChannelStateOnChain.active?(channel) ->
{:error, "#{__MODULE__}: Can't deposit from inactive channel."}

!ChannelStateOnChain.is_peer?(channel, depositing_account) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you move this check from ChannelStateOnChain.validate_deposit to transaction?

Copy link
Contributor Author

@gorbak25 gorbak25 Nov 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to avoid passing the sender to the on chain channel code as it is used only in one place.

{:error,
"Deposit destination must be a party of this channel. Tried to deposit tokens to #{
depositing_account
} but the parties are #{channel.initiator_pubkey} and #{channel.responder_pubkey}"}

true ->
ChannelStateOnChain.validate_deposit(channel, depositing_account, amount, sequence)
ChannelStateOnChain.validate_deposit(channel, amount, sequence)
end
end

Expand Down
Loading