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)))