Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make closeChannel callable by anybody with sigs #1124

Merged
merged 5 commits into from
Jul 15, 2019
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Documents changes that result in:

## Unreleased

- TokenNetwork's closeChannel() can be called by anybody on behalf of the closing participant.
- TokenNetwork's ChannelClosed and NonClosingBalanceProofUpdated events contain balance_hash from the submitted balance proofs.

## [0.26.0](https://github.com/raiden-network/raiden-contracts/releases/tag/v0.26.0) - 2019-07-11
Expand Down
430 changes: 231 additions & 199 deletions raiden_contracts/data/contracts.json

Large diffs are not rendered by default.

55 changes: 37 additions & 18 deletions raiden_contracts/data/source/raiden/TokenNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -470,66 +470,84 @@ contract TokenNetwork is Utils {

}

/// @notice Close the channel defined by the two participant addresses. Only
/// a participant may close the channel, providing a balance proof signed by
/// its partner. Callable only once.
/// @notice Close the channel defined by the two participant addresses.
/// Anybody can call this function on behalf of a participant (called
/// the closing participant), providing a balance proof signed by
/// both parties. Callable only once.
/// @param channel_identifier Identifier for the channel on which this
/// operation takes place.
/// @param partner Channel partner of the `msg.sender`, who provided the
/// signature.
/// @param closing_participant Channel participant who closes the channel.
/// @param non_closing_participant Channel partner of the `closing_participant`,
/// who provided the balance proof.
/// @param balance_hash Hash of (transferred_amount, locked_amount,
/// locksroot).
/// @param additional_hash Computed from the message. Used for message
/// authentication.
/// @param nonce Strictly monotonic value used to order transfers.
/// @param signature Partner's signature of the balance proof data.
/// @param non_closing_signature Non-closing participant's signature of the balance proof data.
/// @param closing_signature Closing participant's signature of the balance
/// proof data.
function closeChannel(
uint256 channel_identifier,
address partner,
address non_closing_participant,
address closing_participant,
// The next four arguments form a balance proof.
bytes32 balance_hash,
uint256 nonce,
bytes32 additional_hash,
bytes memory signature
bytes memory non_closing_signature,
bytes memory closing_signature
)
public
isOpen(channel_identifier)
{
require(channel_identifier == getChannelIdentifier(msg.sender, partner));
require(channel_identifier == getChannelIdentifier(closing_participant, non_closing_participant));

address recovered_partner_address;
address recovered_non_closing_participant_address;

Channel storage channel = channels[channel_identifier];

channel.state = ChannelState.Closed;
channel.participants[msg.sender].is_the_closer = true;
channel.participants[closing_participant].is_the_closer = true;

// This is the block number at which the channel can be settled.
channel.settle_block_number += uint256(block.number);

// The closing participant must have signed the balance proof.
address recovered_closing_participant_address = recoverAddressFromBalanceProofCounterSignature(
Copy link
Contributor

Choose a reason for hiding this comment

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

@pirapira: This uses MessageTypeId.BalanceProofUpdate. Shouldn't we use MessageTypeId.BalanceProof, instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right because these are two different messages. I'll file a PR (it's a bug fix, not a new feature).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

channel_identifier,
balance_hash,
nonce,
additional_hash,
non_closing_signature,
closing_signature
);
require(closing_participant == recovered_closing_participant_address);

// Nonce 0 means that the closer never received a transfer, therefore
// never received a balance proof, or he is intentionally not providing
// the latest transfer, in which case the closing party is going to
// lose the tokens that were transferred to him.
if (nonce > 0) {
recovered_partner_address = recoverAddressFromBalanceProof(
recovered_non_closing_participant_address = recoverAddressFromBalanceProof(
channel_identifier,
balance_hash,
nonce,
additional_hash,
signature
non_closing_signature
);
// Signature must be from the channel partner
require(partner == recovered_partner_address);
require(non_closing_participant == recovered_non_closing_participant_address);

updateBalanceProofData(
channel,
recovered_partner_address,
recovered_non_closing_participant_address,
nonce,
balance_hash
);
}

emit ChannelClosed(channel_identifier, msg.sender, nonce, balance_hash);
emit ChannelClosed(channel_identifier, closing_participant, nonce, balance_hash);
}

/// @notice Called on a closed channel, the function allows the non-closing
Expand All @@ -553,6 +571,7 @@ contract TokenNetwork is Utils {
uint256 channel_identifier,
address closing_participant,
address non_closing_participant,
// The next four arguments form a balance proof
bytes32 balance_hash,
uint256 nonce,
bytes32 additional_hash,
Expand Down Expand Up @@ -599,7 +618,7 @@ contract TokenNetwork is Utils {

// We need the signature from the non-closing participant to allow
// anyone to make this transaction. E.g. a monitoring service.
recovered_non_closing_participant = recoverAddressFromBalanceProofUpdateMessage(
recovered_non_closing_participant = recoverAddressFromBalanceProofCounterSignature(
channel_identifier,
balance_hash,
nonce,
Expand Down Expand Up @@ -1484,7 +1503,7 @@ contract TokenNetwork is Utils {
signature_address = ECVerify.ecverify(message_hash, signature);
}

function recoverAddressFromBalanceProofUpdateMessage(
function recoverAddressFromBalanceProofCounterSignature(
uint256 channel_identifier,
bytes32 balance_hash,
uint256 nonce,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ contract TokenNetworkSignatureTest is TokenNetwork {
);
}

function recoverAddressFromBalanceProofUpdateMessagePublic(
function recoverAddressFromBalanceProofCounterSignaturePublic(
uint256 channel_identifier,
bytes32 balance_hash,
uint256 nonce,
Expand All @@ -195,7 +195,7 @@ contract TokenNetworkSignatureTest is TokenNetwork {
view
returns (address signature_address)
{
return recoverAddressFromBalanceProofUpdateMessage(
return recoverAddressFromBalanceProofCounterSignature(
channel_identifier,
balance_hash,
nonce,
Expand Down
51 changes: 45 additions & 6 deletions raiden_contracts/tests/fixtures/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

from raiden_contracts.constants import TEST_SETTLE_TIMEOUT_MIN, ChannelState
from raiden_contracts.tests.utils import (
EMPTY_ADDITIONAL_HASH,
EMPTY_BALANCE_HASH,
EMPTY_SIGNATURE,
LOCKSROOT_OF_NO_LOCKS,
NONEXISTENT_LOCKSROOT,
ChannelValues,
Expand Down Expand Up @@ -167,7 +170,7 @@ def get(
def close_and_update_channel(
token_network: Contract,
create_balance_proof: Callable,
create_balance_proof_update_signature: Callable,
create_balance_proof_countersignature: Callable,
) -> Callable:
def get(
channel_identifier: int,
Expand Down Expand Up @@ -199,12 +202,19 @@ def get(
participant2_values.locksroot,
additional_hash2,
)
balance_proof_update_signature_2 = create_balance_proof_update_signature(
balance_proof_close_signature_1 = create_balance_proof_countersignature(
participant1, channel_identifier, *balance_proof_2
)
balance_proof_update_signature_2 = create_balance_proof_countersignature(
participant2, channel_identifier, *balance_proof_1
)

token_network.functions.closeChannel(
channel_identifier, participant2, *balance_proof_2
channel_identifier,
participant2,
participant1,
*balance_proof_2,
balance_proof_close_signature_1,
).call_and_transact({"from": participant1})

token_network.functions.updateNonClosingBalanceProof(
Expand Down Expand Up @@ -648,7 +658,7 @@ def get(


@pytest.fixture(scope="session")
def create_balance_proof_update_signature(
def create_balance_proof_countersignature(
token_network: Contract, get_private_key: Callable
) -> Callable:
def get(
Expand All @@ -657,7 +667,7 @@ def get(
balance_hash: bytes,
nonce: int,
additional_hash: bytes,
closing_signature: bytes,
original_signature: bytes,
v: int = 27,
other_token_network: Optional[Contract] = None,
) -> bytes:
Expand All @@ -672,7 +682,36 @@ def get(
balance_hash,
nonce,
additional_hash,
closing_signature,
original_signature,
v,
)
return non_closing_signature

return get


@pytest.fixture(scope="session")
def create_close_signature_for_no_balance_proof(
token_network: Contract, get_private_key: Callable
) -> Callable:
def get(
participant: HexAddress,
channel_identifier: int,
v: int = 27,
other_token_network: Optional[Contract] = None,
) -> bytes:
_token_network = other_token_network or token_network
private_key = get_private_key(participant)

non_closing_signature = sign_balance_proof_update_message(
private_key,
_token_network.address,
int(_token_network.functions.chain_id().call()),
channel_identifier,
EMPTY_BALANCE_HASH,
0,
EMPTY_ADDITIONAL_HASH,
EMPTY_SIGNATURE,
v,
)
return non_closing_signature
Expand Down
14 changes: 13 additions & 1 deletion raiden_contracts/tests/property/test_tokennetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,60 +271,72 @@ def contract_close(self, transfer, closer_pkey, partner_pkey):
with transaction_must_fail(msg):
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature, # A part of the transfer data
closer_signature, # The closer wants to close it
).transact()

elif transfer.sender == closer_address:
with transaction_must_fail("close with self signed transfer didnt fail"):
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature, # The closer wants to close it
).transact()

elif channel_state == 2:
with transaction_must_fail("close called twice didnt fail"):
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature,
).transact()

elif not self.is_participant(closer_address):
with transaction_must_fail("close called by a non participant didnt fail"):
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature,
).transact()

elif transfer.channel != to_canonical_address(self.token_network.address):
msg = "close called with a transfer for a different channe didnt fail"
with transaction_must_fail(msg):
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature,
).transact()

else:
self.token_network.functions.closeChannel(
partner_address,
closer_address,
transfer.balance_hash(),
transfer.nonce,
transfer_hash,
closer_signature,
closer_signature,
).transact()

self.closing_address = closer_address
Expand Down
Loading