def test_cooperative( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, web3: Web3, token_contract: Contract, wait_for_blocks, sender_address: str ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig1 = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig1) receiver_sig = channel_manager.sign_close(sender_address, confirmed_open_channel.block, 5) channel_rec = channel_manager.channels[channel_id] assert channel_rec.is_closed is True block_before = web3.eth.blockNumber confirmed_open_channel.close_cooperatively(receiver_sig) wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) logs = get_logs(token_contract, 'Transfer', from_block=block_before - 1) assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 5]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 5]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def test_balances( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks, sender_address: str, use_tester: bool ): blockchain = channel_manager.blockchain initial_liquid_balance = channel_manager.get_liquid_balance() initial_locked_balance = channel_manager.get_locked_balance() if use_tester: assert initial_liquid_balance == 0 assert initial_locked_balance == 0 sig = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig) assert channel_manager.get_liquid_balance() == initial_liquid_balance assert channel_manager.get_locked_balance() == 5 receiver_sig = channel_manager.sign_close(sender_address, confirmed_open_channel.block, 5) confirmed_open_channel.close_cooperatively(receiver_sig) wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_manager.get_liquid_balance() == initial_liquid_balance + 5 assert channel_manager.get_locked_balance() == initial_locked_balance
def test_topup( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks ): blockchain = channel_manager.blockchain channel_manager.wait_sync() channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) confirmed_open_channel.topup(5) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] topup_txs = channel_rec.unconfirmed_topups assert len(topup_txs) == 1 and list(topup_txs.values())[0] == 5 wait_for_blocks(channel_manager.blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] topup_txs = channel_rec.unconfirmed_topups assert len(topup_txs) == 0 assert channel_rec.deposit == 15
def test_channel_settled_event( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks, web3: Web3, use_tester: bool ): if not use_tester: pytest.skip('This test takes several hours on real blockchains.') blockchain = channel_manager.blockchain channel_manager.wait_sync() channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] wait_for_blocks(channel_rec.settle_timeout - web3.eth.blockNumber) gevent.sleep(blockchain.poll_interval) assert web3.eth.blockNumber >= channel_rec.settle_timeout assert channel_id in channel_manager.channels confirmed_open_channel.settle() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def close_channel_cooperatively( channel: Channel, privkey_receiver: str, contract_address: str, balance: int=None ): if balance is not None: channel.balance = balance closing_sig = sign_close(privkey_receiver, channel.balance_sig) assert channel.close_cooperatively(closing_sig)
def test_multiple_topups( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] # first unconfirmed topup assert channel_rec.deposit == 10 confirmed_open_channel.topup(5) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) == 1 assert list(channel_rec.unconfirmed_topups.values()) == [5] assert channel_rec.deposit == 10 # second unconfirmed_topups confirmed_open_channel.topup(10) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) >= 1 # equality if first is confirmed assert 10 in channel_rec.unconfirmed_topups.values() assert channel_rec.deposit in [10, 15] # depends if first topup is confirmed or not # wait for confirmations wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) == 0 assert channel_rec.deposit == 25
def test_topup( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks ): blockchain = channel_manager.blockchain channel_manager.wait_sync() channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) confirmed_open_channel.topup(5) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] topup_txs = channel_rec.unconfirmed_topups assert len(topup_txs) == 1 and list(topup_txs.values())[0] == 5 wait_for_blocks(channel_manager.blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] topup_txs = channel_rec.unconfirmed_topups assert len(topup_txs) == 0 assert channel_rec.deposit == 15
def test_cooperative( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, web3: Web3, token_contract: Contract, wait_for_blocks, sender_address: str ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig1 = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig1) receiver_sig = channel_manager.sign_close(sender_address, confirmed_open_channel.block, 5) channel_rec = channel_manager.channels[channel_id] assert channel_rec.is_closed is True block_before = web3.eth.blockNumber confirmed_open_channel.close_cooperatively(receiver_sig) wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) logs = get_logs(token_contract, 'Transfer', from_block=block_before - 1) assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 5]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 5]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def test_balances( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks, sender_address: str, use_tester: bool ): blockchain = channel_manager.blockchain initial_liquid_balance = channel_manager.get_liquid_balance() initial_locked_balance = channel_manager.get_locked_balance() if use_tester: assert initial_liquid_balance == 0 assert initial_locked_balance == 0 sig = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig) assert channel_manager.get_liquid_balance() == initial_liquid_balance assert channel_manager.get_locked_balance() == 5 receiver_sig = channel_manager.sign_close(sender_address, confirmed_open_channel.block, 5) confirmed_open_channel.close_cooperatively(receiver_sig) wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_manager.get_liquid_balance() == initial_liquid_balance + 5 assert channel_manager.get_locked_balance() == initial_locked_balance
def close_channel_cooperatively(channel: Channel, privkey_receiver: str, balance: int = None): if balance is not None: channel.update_balance(balance) closing_sig = sign_close(privkey_receiver, channel.balance_sig) assert channel.close_cooperatively(closing_sig)
def test_multiple_topups( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] # first unconfirmed topup assert channel_rec.deposit == 10 confirmed_open_channel.topup(5) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) == 1 assert list(channel_rec.unconfirmed_topups.values()) == [5] assert channel_rec.deposit == 10 # second unconfirmed_topups confirmed_open_channel.topup(10) wait_for_blocks(1) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) >= 1 # equality if first is confirmed assert 10 in channel_rec.unconfirmed_topups.values() assert channel_rec.deposit in [10, 15] # depends if first topup is confirmed or not # wait for confirmations wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] assert len(channel_rec.unconfirmed_topups) == 0 assert channel_rec.deposit == 25
def test_settlement( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, wait_for_blocks, web3: Web3, token_contract: Contract, sender_address: str ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig = encode_hex(confirmed_open_channel.create_transfer(2)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 2, sig) confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) block_before = web3.eth.blockNumber channel_rec = channel_manager.channels[channel_id] wait_for_blocks(channel_rec.settle_timeout - block_before) confirmed_open_channel.settle() logs = get_logs(token_contract, 'Transfer', from_block=block_before - 1) assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 2]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 8]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def close_channel_cooperatively(channel: Channel, privkey_receiver: str, contract_address: str, balance: int = None): if balance is not None: channel.update_balance(balance) closing_sig = sign_close(privkey_receiver, channel.sender, channel.block, channel.balance, contract_address) assert channel.close_cooperatively(closing_sig)
def close_channel(self, channel: Channel): log.info( 'Requesting closing signature from server for balance {} on channel {}/{}/{}.' .format(channel.balance, channel.sender, channel.sender, channel.block) ) url = self.make_url('api/1/channels/{}/{}'.format(channel.sender, channel.block)) response = requests.delete(url, data={'balance': channel.balance}) if response.status_code == requests.codes.OK: closing_sig = json.loads(response.content.decode())['close_signature'] channel.close_cooperatively(decode_hex(closing_sig)) else: body = response.content.decode() if response.content else None log.error('No closing signature received: {}'.format(body))
def close_channel_cooperatively( channel: Channel, privkey_receiver: str, contract_address: str, balance: int=None ): if balance is not None: channel.update_balance(balance) closing_sig = sign_close( privkey_receiver, channel.sender, channel.block, channel.balance, contract_address ) assert channel.close_cooperatively(closing_sig)
def close_channel(self, endpoint_url: str, channel: Channel): log.debug( 'Requesting closing signature from server for balance {} on channel {}/{}/{}.' .format(channel.balance, channel.sender, channel.sender, channel.block)) url = '{}/api/1/channels/{}/{}'.format(endpoint_url, channel.sender, channel.block) response = requests.delete(url, data={'balance': channel.balance}) if response.status_code == requests.codes.OK: closing_sig = response.json()['close_signature'] channel.close_cooperatively(decode_hex(closing_sig)) else: self.on_cooperative_close_denied(endpoint_url, channel, response)
def test_cooperative_wrong_balance_proof( channel_manager: ChannelManager, confirmed_open_channel: Channel, sender_address: str ): channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] sig1 = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig1) sig2 = encode_hex(confirmed_open_channel.create_transfer(1)) with pytest.raises(InvalidBalanceProof): channel_manager.sign_close(sender_address, confirmed_open_channel.block, sig2) assert channel_rec.is_closed is False
def test_cooperative_wrong_balance_proof( channel_manager: ChannelManager, confirmed_open_channel: Channel, sender_address: str ): channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] sig1 = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig1) sig2 = encode_hex(confirmed_open_channel.create_transfer(1)) with pytest.raises(InvalidBalanceProof): channel_manager.sign_close(sender_address, confirmed_open_channel.block, sig2) assert channel_rec.is_closed is False
def test_close_confirmed_event(channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks): blockchain = channel_manager.blockchain channel_manager.wait_sync() confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] assert channel_rec.is_closed is True settle_block = channel_manager.channel_manager_contract.call( ).getChannelInfo(channel_rec.sender, channel_rec.receiver, channel_rec.open_block_number)[2] assert channel_rec.settle_timeout == settle_block
def test_channel_settled_event(channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks, web3: Web3, use_tester: bool): if not use_tester: pytest.skip('This test takes several hours on real blockchains.') blockchain = channel_manager.blockchain channel_manager.wait_sync() channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] wait_for_blocks(channel_rec.settle_timeout - web3.eth.blockNumber) gevent.sleep(blockchain.poll_interval) assert web3.eth.blockNumber >= channel_rec.settle_timeout assert channel_id in channel_manager.channels confirmed_open_channel.settle() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def test_channel_settled_event( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks, web3: Web3 ): blockchain = channel_manager.blockchain channel_manager.wait_sync() channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_rec = channel_manager.channels[channel_id] wait_for_blocks(channel_rec.settle_timeout - web3.eth.blockNumber) gevent.sleep(blockchain.poll_interval) assert web3.eth.blockNumber >= channel_rec.settle_timeout assert channel_id in channel_manager.channels confirmed_open_channel.settle() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def test_close_confirmed_event( channel_manager: ChannelManager, confirmed_open_channel: Channel, wait_for_blocks ): blockchain = channel_manager.blockchain channel_manager.wait_sync() confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) channel_rec = channel_manager.channels[channel_id] assert channel_rec.is_closed is True settle_block = channel_manager.channel_manager_contract.call().getChannelInfo( channel_rec.sender, channel_rec.receiver, channel_rec.open_block_number )[2] assert channel_rec.settle_timeout == settle_block
def test_settlement( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, wait_for_blocks, web3: Web3, token_contract: Contract, sender_address: str, use_tester: bool ): if not use_tester: pytest.skip('This test takes several hours on real blockchains.') blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig = encode_hex(confirmed_open_channel.create_transfer(2)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 2, sig) confirmed_open_channel.close() wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) block_before = web3.eth.blockNumber channel_rec = channel_manager.channels[channel_id] wait_for_blocks(channel_rec.settle_timeout - block_before) confirmed_open_channel.settle() logs = get_logs(token_contract, 'Transfer', from_block=block_before - 1) assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 2]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 8]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels
def test_challenge( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, sender_address: str, wait_for_blocks, web3: Web3, client: Client ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig) # hack channel to decrease balance confirmed_open_channel.update_balance(0) sig = confirmed_open_channel.create_transfer(3) block_before = web3.eth.blockNumber confirmed_open_channel.close() # should challenge and immediately settle for waited_blocks in count(): logs = get_logs(client.context.token, 'Transfer', from_block=block_before - 1) if logs: break wait_for_blocks(1) assert waited_blocks < 10 assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 5]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 5]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels # update channel state so that it will not be closed twice client.sync_channels() new_state = None for channel in client.channels: if all(channel.sender == confirmed_open_channel.sender, channel.receiver == confirmed_open_channel.receiver, channel.block == confirmed_open_channel.block): new_state = channel.state if new_state is None: confirmed_open_channel.state = confirmed_open_channel.State.closed else: confirmed_open_channel.state = new_state
def test_challenge( channel_manager: ChannelManager, confirmed_open_channel: Channel, receiver_address: str, sender_address: str, wait_for_blocks, web3: Web3, client: Client ): blockchain = channel_manager.blockchain channel_id = (confirmed_open_channel.sender, confirmed_open_channel.block) sig = encode_hex(confirmed_open_channel.create_transfer(5)) channel_manager.register_payment(sender_address, confirmed_open_channel.block, 5, sig) # hack channel to decrease balance confirmed_open_channel.update_balance(0) sig = confirmed_open_channel.create_transfer(3) block_before = web3.eth.blockNumber confirmed_open_channel.close() # should challenge and immediately settle for waited_blocks in count(): logs = get_logs(client.context.token, 'Transfer', from_block=block_before - 1) if logs: break wait_for_blocks(1) assert waited_blocks < 10 assert len([l for l in logs if is_same_address(l['args']['_to'], receiver_address) and l['args']['_value'] == 5]) == 1 assert len([l for l in logs if is_same_address(l['args']['_to'], sender_address) and l['args']['_value'] == 5]) == 1 wait_for_blocks(blockchain.n_confirmations) gevent.sleep(blockchain.poll_interval) assert channel_id not in channel_manager.channels # update channel state so that it will not be closed twice client.sync_channels() new_state = None for channel in client.channels: if all(channel.sender == confirmed_open_channel.sender, channel.receiver == confirmed_open_channel.receiver, channel.block == confirmed_open_channel.block): new_state = channel.state if new_state is None: confirmed_open_channel.state = confirmed_open_channel.State.closed else: confirmed_open_channel.state = new_state
def sync_channels(self): """ Merges locally available channel information, including their current balance signatures, with channel information available on the blockchain to make up for local data loss. Naturally, balance signatures cannot be recovered from the blockchain. """ filters = {'_sender_address': self.context.address} create = get_logs(self.context.channel_manager, 'ChannelCreated', argument_filters=filters, from_block=start_sync_block, to_block='latest') topup = get_logs(self.context.channel_manager, 'ChannelToppedUp', argument_filters=filters, from_block=start_sync_block, to_block='latest') close = get_logs(self.context.channel_manager, 'ChannelCloseRequested', argument_filters=filters, from_block=start_sync_block, to_block='latest') settle = get_logs(self.context.channel_manager, 'ChannelSettled', argument_filters=filters, from_block=start_sync_block, to_block='latest') channel_key_to_channel = {} def get_channel(event) -> Channel: sender = to_checksum_address(event['args']['_sender_address']) receiver = to_checksum_address(event['args']['_receiver_address']) block = event['args'].get('_open_block_number', event['blockNumber']) assert is_same_address(sender, self.context.address) return channel_key_to_channel.get((sender, receiver, block), None) for c in self.channels: channel_key_to_channel[(c.sender, c.receiver, c.block)] = c for e in create: c = get_channel(e) if c: c.deposit = e['args']['_deposit'] else: c = Channel( self.context, to_checksum_address(e['args']['_sender_address']), to_checksum_address(e['args']['_receiver_address']), e['blockNumber'], e['args']['_deposit'], on_settle=lambda channel: self.channels.remove(channel)) assert is_same_address(c.sender, self.context.address) channel_key_to_channel[(c.sender, c.receiver, c.block)] = c for e in topup: c = get_channel(e) c.deposit += e['args']['_added_deposit'] for e in close: # Requested closed, not actual closed. c = get_channel(e) c.update_balance(e['args']['_balance']) c.state = Channel.State.settling for e in settle: c = get_channel(e) c.state = Channel.State.closed # Forget closed channels. self.channels = [ c for c in channel_key_to_channel.values() if c.state != Channel.State.closed ] log.debug('Synced a total of {} channels.'.format(len(self.channels)))
def on_cooperative_close_denied(self, endpoint_url: str, channel: Channel, response: Response): log.warning( 'No closing signature received. Closing noncooperatively on a balance of 0.' ) channel.close(0)