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

Use nonce ranges #287

Merged
merged 9 commits into from
Dec 21, 2016
Merged
2 changes: 2 additions & 0 deletions raiden/assetmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,12 @@ def register_channel(self, netting_channel, reveal_timeout):
our_state = ChannelEndState(
channel_details['our_address'],
channel_details['our_balance'],
netting_channel.opened,
)
partner_state = ChannelEndState(
channel_details['partner_address'],
channel_details['partner_balance'],
netting_channel.opened,
)

external_state = ChannelExternalState(
Expand Down
10 changes: 7 additions & 3 deletions raiden/channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ def compute_proof_for_lock(self, secret, lock):
class ChannelEndState(object):
""" Tracks the state of one of the participants in a channel. """

def __init__(self, participant_address, participant_balance):
def __init__(self, participant_address, participant_balance, get_block_number):
# since ethereum only uses integral values we cannot use float/Decimal
if not isinstance(participant_balance, (int, long)):
raise ValueError('participant_balance must be an integer.')
Expand All @@ -281,8 +281,12 @@ def __init__(self, participant_address, participant_balance):

# sequential nonce, current value has not been used.
# 0 is used in the netting contract to represent the lack of a
# transfer, so this value must start at 1
self.nonce = 1
# the nonce value must be inside the netting channel allowed range
# that is defined in terms of the opened block
if isinstance(get_block_number, int):
self.nonce = 1 * (get_block_number * (2 ** 32))
else:
self.nonce = 1 * (get_block_number() * (2 ** 32))

# contains the last known message with a valid signature and
# transferred_amount, the secrets revealed since that transfer, and the
Expand Down
28 changes: 24 additions & 4 deletions raiden/smart_contracts/NettingChannelLibrary.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ library NettingChannelLibrary {
_;
}

modifier inNonceRange(Data storage self, bytes message) {
uint64 nonce;
nonce = getNonce(message);
if (nonce < self.opened * (2 ** 32) || nonce >= (self.opened + 1) * (2 ** 32))
throw;
_;
}

/// @notice deposit(uint) to deposit amount to channel.
/// @dev Deposit an amount to the channel. At least one of the participants
/// must deposit before the channel is opened.
Expand Down Expand Up @@ -198,7 +206,7 @@ library NettingChannelLibrary {
}

// else we are closing a channel that has received transfers
their_sender = processTransfer(node1, node2, their_transfer);
their_sender = processTransfer(self, node1, node2, their_transfer);
if (their_sender == caller_address) {
// the sender of "their" transaction can't be ourselves
throw;
Expand All @@ -207,15 +215,19 @@ library NettingChannelLibrary {
if (our_transfer.length != 0) {
address our_sender;
// we also provided a courtesy update of our own latest transfer
our_sender = processTransfer(node1, node2, our_transfer);
our_sender = processTransfer(self, node1, node2, our_transfer);
if (our_sender != caller_address) {
// we have to be the sender of our own transaction
throw;
}
}
}

function processTransfer(Participant storage node1, Participant storage node2, bytes transfer) internal returns (address) {
function processTransfer(Data storage self, Participant storage node1, Participant storage node2, bytes transfer)
inNonceRange(self, transfer)
internal
returns (address)
{
bytes memory transfer_raw;
address transfer_address;

Expand Down Expand Up @@ -267,7 +279,7 @@ library NettingChannelLibrary {
Participant storage node1 = participants[0];
Participant storage node2 = participants[1];

processTransfer(node1, node2, their_transfer);
processTransfer(self, node1, node2, their_transfer);

// TODO check if tampered and penalize
// TODO check if outdated and penalize
Expand Down Expand Up @@ -572,6 +584,14 @@ library NettingChannelLibrary {
}
}

// Get nonce from a message
function getNonce(bytes message) private returns (uint64 nonce) {
// don't care about length of message since nonce is always at a fixed position
assembly {
nonce := mload(add(message, 12))
}
}

function signatureSplit(bytes signature) private returns (bytes32 r, bytes32 s, uint8 v) {
// The signature format is a compact form of:
// {bytes32 r}{bytes32 s}{uint8 v}
Expand Down
106 changes: 99 additions & 7 deletions raiden/tests/smart_contracts/test_channel_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@

from ethereum import tester
from ethereum.utils import encode_hex, sha3
from raiden.utils import get_contract_path
from raiden.utils import get_contract_path, privatekey_to_address
from raiden.encoding.signing import GLOBAL_CTX
from ethereum.tester import ABIContract, ContractTranslator, TransactionFailed
from secp256k1 import PrivateKey

from raiden.tests.utils.tester import new_channelmanager


def test_channelnew_event(settle_timeout, tester_state, tester_events,
tester_registry, tester_token):
def test_channelnew_event(
settle_timeout,
tester_state,
tester_events,
tester_registry,
tester_token):

privatekey0 = tester.DEFAULT_KEY
address0 = tester.DEFAULT_ACCOUNT
Expand Down Expand Up @@ -40,10 +46,13 @@ def test_channelnew_event(settle_timeout, tester_state, tester_events,
}


def test_channelmanager(tester_state, tester_token, tester_events,
tester_channelmanager_library_address, settle_timeout,
netting_channel_abi):
# pylint: disable=too-many-locals,too-many-statements
def test_channelmanager(
tester_state,
tester_token,
tester_events,
tester_channelmanager_library_address,
settle_timeout,
netting_channel_abi): # pylint: disable=too-many-locals,too-many-statements

address0 = tester.DEFAULT_ACCOUNT
address1 = tester.a1
Expand Down Expand Up @@ -146,3 +155,86 @@ def test_channelmanager(tester_state, tester_token, tester_events,
# assert k1 == sha3(vs[0] + vs[1])
# with pytest.raises(TransactionFailed):
# channel_manager.key(sha3('address1')[:20], sha3('address1')[:20])

@pytest.mark.xfail
def test_reopen_channel(
tester_state,
tester_channelmanager,
tester_channels,
settle_timeout,
netting_channel_abi):

privatekey0_raw, privatekey1_raw, nettingchannel, channel0, _ = tester_channels[0]

privatekey0 = PrivateKey(privatekey0_raw, ctx=GLOBAL_CTX, raw=True)
address0 = privatekey_to_address(privatekey0_raw)
address1 = privatekey_to_address(privatekey1_raw)
address2 = tester.a2

# We need to close the channel before it can be deleted, to do so we need
# one transfer to call closeSingleTransfer()
transfer_amount = 10
identifier = 1
direct_transfer = channel0.create_directtransfer(
transfer_amount,
identifier,
)
direct_transfer.sign(privatekey0, address0)
direct_transfer_data = str(direct_transfer.packed().data)

should_be_nonce = nettingchannel.opened(sender=privatekey0_raw) * (2**32)
should_be_nonce_plus_one = (nettingchannel.opened(sender=privatekey0_raw) + 1) * (2**32)
assert should_be_nonce <= direct_transfer.nonce < should_be_nonce_plus_one

# settle the channel should not change the channel manager state
nettingchannel.closeSingleTransfer(
direct_transfer_data,
sender=privatekey1_raw,
)
tester_state.mine(number_of_blocks=settle_timeout + 1)

# deleting the channel needs to update the manager's state
number_of_channels = len(tester_channelmanager.getChannelsAddresses(sender=privatekey0_raw))

nettingchannel.settle(sender=privatekey0_raw)

tester_state.mine(1)

# now a single new channel can be opened
# if channel with address is settled a new can be opened
# old entry will be deleted when calling newChannel
netting_channel_address1_hex = tester_channelmanager.newChannel(
address1,
settle_timeout,
sender=privatekey0_raw,
)

netting_channel_translator = ContractTranslator(netting_channel_abi)

netting_contract_proxy1 = ABIContract(
tester_state,
netting_channel_translator,
netting_channel_address1_hex,
)

# transfer not in nonce range
with pytest.raises(TransactionFailed):
netting_contract_proxy1.closeSingleTransfer(
direct_transfer_data,
sender=privatekey0_raw,
)

# channel already exists
with pytest.raises(TransactionFailed):
tester_channelmanager.newChannel(
address1,
settle_timeout,
sender=privatekey0_raw,
)

# opening a new channel that did not exist before
netting_channel_address2_hex = tester_channelmanager.newChannel(
address2,
settle_timeout,
sender=privatekey0_raw,
)
15 changes: 9 additions & 6 deletions raiden/tests/unit/test_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def make_external_state():


def test_end_state():
netting_channel = NettingChannelMock()
asset_address = make_address()
privkey1, address1 = make_privkey_address()
address2 = make_address()
Expand All @@ -55,8 +56,8 @@ def test_end_state():
lock_expiration = 10
lock_hashlock = sha3(lock_secret)

state1 = ChannelEndState(address1, balance1)
state2 = ChannelEndState(address2, balance2)
state1 = ChannelEndState(address1, balance1, netting_channel.opened)
state2 = ChannelEndState(address2, balance2, netting_channel.opened)

assert state1.contract_balance == balance1
assert state2.contract_balance == balance2
Expand Down Expand Up @@ -187,6 +188,7 @@ def test_end_state():


def test_invalid_timeouts():
netting_channel = NettingChannelMock()
asset_address = make_address()
reveal_timeout = 5
settle_timeout = 15
Expand All @@ -196,8 +198,8 @@ def test_invalid_timeouts():
balance1 = 10
balance2 = 10

our_state = ChannelEndState(address1, balance1)
partner_state = ChannelEndState(address2, balance2)
our_state = ChannelEndState(address1, balance1, netting_channel.opened)
partner_state = ChannelEndState(address2, balance2, netting_channel.opened)
external_state = make_external_state()

# do not allow a reveal timeout larger than the settle timeout
Expand Down Expand Up @@ -237,6 +239,7 @@ def test_invalid_timeouts():


def test_python_channel():
netting_channel = NettingChannelMock()
asset_address = make_address()
privkey1, address1 = make_privkey_address()
address2 = make_address()
Expand All @@ -247,8 +250,8 @@ def test_python_channel():
reveal_timeout = 5
settle_timeout = 15

our_state = ChannelEndState(address1, balance1)
partner_state = ChannelEndState(address2, balance2)
our_state = ChannelEndState(address1, balance1, netting_channel.opened)
partner_state = ChannelEndState(address2, balance2, netting_channel.opened)
external_state = make_external_state()

test_channel = Channel(
Expand Down
2 changes: 2 additions & 0 deletions raiden/tests/utils/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,12 @@ def channel_from_nettingcontract(our_key, netting_contract, external_state, reve
our_state = ChannelEndState(
our_address,
our_balance,
external_state.opened_block,
)
partner_state = ChannelEndState(
partner_address,
partner_balance,
external_state.opened_block,
)

channel = Channel(
Expand Down
1 change: 1 addition & 0 deletions raiden/tests/utils/tester_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def __init__(self, tester_state, private_key, address):
def get_block_number(self):
return self.tester_state.block.number

@property
def opened_block(self):
return self.proxy.opened()

Expand Down