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_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 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_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 _update(self): current_block = self.web3.eth.blockNumber # reset unconfirmed channels in case of reorg if self.wait_sync_event.is_set(): # but not on first sync if current_block < self.cm.state.unconfirmed_head_number: self.log.info('chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]' % (self.cm.state.unconfirmed_head_number, self.web3.eth.blockNumber)) self.cm.reset_unconfirmed() try: # raises if hash doesn't exist (i.e. block has been replaced) self.web3.eth.getBlock(self.cm.state.unconfirmed_head_hash) except ValueError: self.log.info('chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]. ' '(getBlock() raised ValueError)' % (self.cm.state.unconfirmed_head_number, current_block)) self.cm.reset_unconfirmed() # in case of reorg longer than confirmation number fail try: self.web3.eth.getBlock(self.cm.state.confirmed_head_hash) except ValueError: self.log.critical('events considered confirmed have been reorganized') assert False # unreachable as long as confirmation level is set high enough if self.cm.state.confirmed_head_number is None: self.cm.state.update_sync_state(confirmed_head_number=self.sync_start_block) if self.cm.state.unconfirmed_head_number is None: self.cm.state.update_sync_state(unconfirmed_head_number=self.sync_start_block) new_unconfirmed_head_number = self.cm.state.unconfirmed_head_number + self.sync_chunk_size new_unconfirmed_head_number = min(new_unconfirmed_head_number, current_block) new_confirmed_head_number = max(new_unconfirmed_head_number - self.n_confirmations, 0) # return if blocks have already been processed if (self.cm.state.confirmed_head_number >= new_confirmed_head_number and self.cm.state.unconfirmed_head_number >= new_unconfirmed_head_number): return # filter for events after block_number filters_confirmed = { 'from_block': self.cm.state.confirmed_head_number + 1, 'to_block': new_confirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } filters_unconfirmed = { 'from_block': self.cm.state.unconfirmed_head_number + 1, 'to_block': new_unconfirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } self.log.debug( 'filtering for events u:%s-%s c:%s-%s @%d', filters_unconfirmed['from_block'], filters_unconfirmed['to_block'], filters_confirmed['from_block'], filters_confirmed['to_block'], current_block ) # unconfirmed channel created logs = get_logs( self.channel_manager_contract, 'ChannelCreated', **filters_unconfirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug( 'received unconfirmed ChannelCreated event (sender %s, block number %s)', sender, open_block_number ) self.cm.unconfirmed_event_channel_opened(sender, open_block_number, deposit) # channel created logs = get_logs( self.channel_manager_contract, 'ChannelCreated', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug('received ChannelOpened event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_opened(sender, open_block_number, deposit) # unconfirmed channel top ups logs = get_logs( self.channel_manager_contract, 'ChannelToppedUp', **filters_unconfirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, deposit %s)', sender, open_block_number, added_deposit ) self.cm.unconfirmed_event_channel_topup( sender, open_block_number, txhash, added_deposit ) # confirmed channel top ups logs = get_logs( self.channel_manager_contract, 'ChannelToppedUp', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, added deposit %s)', sender, open_block_number, added_deposit ) self.cm.event_channel_topup(sender, open_block_number, txhash, added_deposit) # channel settled event logs = get_logs( self.channel_manager_contract, 'ChannelSettled', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] self.log.debug('received ChannelSettled event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_settled(sender, open_block_number) # channel close requested logs = get_logs( self.channel_manager_contract, 'ChannelCloseRequested', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] if (sender, open_block_number) not in self.cm.channels: continue balance = log['args']['_balance'] try: timeout = self.channel_manager_contract.call().getChannelInfo( sender, self.cm.state.receiver, open_block_number )[2] except BadFunctionCallOutput: self.log.warning( 'received ChannelCloseRequested event for a channel that doesn\'t ' 'exist or has been closed already (sender=%s open_block_number=%d)' % (sender, open_block_number)) self.cm.force_close_channel(sender, open_block_number) continue self.log.debug('received ChannelCloseRequested event (sender %s, block number %s)', sender, open_block_number) try: self.cm.event_channel_close_requested(sender, open_block_number, balance, timeout) except InsufficientBalance: self.log.fatal('Insufficient ETH balance of the receiver. ' "Can't close the channel. " 'Will retry once the balance is sufficient') self.insufficient_balance = True # update head hash and number try: new_unconfirmed_head_hash = self.web3.eth.getBlock(new_unconfirmed_head_number).hash except AttributeError: self.log.info('chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]. ' % new_unconfirmed_head_number) self.cm.reset_unconfirmed() new_unconfirmed_head_number = self.cm.state.unconfirmed_head_number try: new_unconfirmed_head_hash = self.web3.eth.getBlock(new_unconfirmed_head_number).hash new_confirmed_head_hash = self.web3.eth.getBlock(new_confirmed_head_number).hash except AttributeError: self.log.critical("RPC endpoint didn't return proper info for an existing block " "(%d,%d)" % (new_unconfirmed_head_number, new_confirmed_head_number)) self.log.critical("It is possible that the blockchain isn't fully synced. " "This often happens when Parity is run with --fast or --warp sync.") self.log.critical("Can't continue - check status of the ethereum node.") sys.exit(1) self.cm.set_head( new_unconfirmed_head_number, new_unconfirmed_head_hash, new_confirmed_head_number, new_confirmed_head_hash ) if not self.wait_sync_event.is_set() and new_unconfirmed_head_number == current_block: self.log.info('Channel info (recever: {}) sync finished, continue listen ...'.format(self.cm.receiver)) self.wait_sync_event.set()
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) topup = get_logs(self.context.channel_manager, 'ChannelToppedUp', argument_filters=filters) close = get_logs(self.context.channel_manager, 'ChannelCloseRequested', argument_filters=filters) settle = get_logs(self.context.channel_manager, 'ChannelSettled', argument_filters=filters) 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 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 ) topup = get_logs( self.context.channel_manager, 'ChannelToppedUp', argument_filters=filters ) close = get_logs( self.context.channel_manager, 'ChannelCloseRequested', argument_filters=filters ) settle = get_logs( self.context.channel_manager, 'ChannelSettled', argument_filters=filters ) 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 _update(self): #print(bcolors.BOLD + 'monitor: block number %d' % (self.web3.eth.blockNumber) + bcolors.ENDC) current_block = self.web3.eth.blockNumber # reset unconfirmed channels in case of reorg if self.wait_sync_event.is_set(): # but not on first sync if current_block < self.cm.state.unconfirmed_head_number: self.log.info( 'chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]' % (self.cm.state.unconfirmed_head_number, self.web3.eth.blockNumber)) self.cm.reset_unconfirmed() try: # raises if hash doesn't exist (i.e. block has been replaced) self.web3.eth.getBlock(self.cm.state.unconfirmed_head_hash) except ValueError: self.log.info( 'chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]. ' '(getBlock() raised ValueError)' % (self.cm.state.unconfirmed_head_number, current_block)) self.cm.reset_unconfirmed() # in case of reorg longer than confirmation number fail try: self.web3.eth.getBlock(self.cm.state.confirmed_head_hash) except ValueError: self.log.critical( 'events considered confirmed have been reorganized') assert False # unreachable as long as confirmation level is set high enough if self.cm.state.confirmed_head_number is None: self.cm.state.update_sync_state( confirmed_head_number=self.sync_start_block) if self.cm.state.unconfirmed_head_number is None: self.cm.state.update_sync_state( unconfirmed_head_number=self.sync_start_block) new_unconfirmed_head_number = self.cm.state.unconfirmed_head_number + self.sync_chunk_size new_unconfirmed_head_number = min(new_unconfirmed_head_number, current_block) new_confirmed_head_number = max( new_unconfirmed_head_number - self.n_confirmations, 0) # return if blocks have already been processed if (self.cm.state.confirmed_head_number >= new_confirmed_head_number and self.cm.state.unconfirmed_head_number >= new_unconfirmed_head_number): return # Look for events in the state guardian contract with the customer # before the channel manager. If customer does something in between # monitor probably needs to handle close rather than responding to # customer channel. #for job in self.cm.jobs: for customer, sender, open_block_number in self.cm.jobs: # filter for events after block_number filters_confirmed = { 'from_block': self.cm.state.confirmed_head_number + 1, 'to_block': new_confirmed_head_number, 'argument_filters': { '_receiver_address': customer } } filters_unconfirmed = { 'from_block': self.cm.state.unconfirmed_head_number + 1, 'to_block': new_unconfirmed_head_number, 'argument_filters': { '_receiver_address': customer } } self.log.debug('filtering for channel events u:%s-%s c:%s-%s @%d', filters_unconfirmed['from_block'], filters_unconfirmed['to_block'], filters_confirmed['from_block'], filters_confirmed['to_block'], current_block) # channel close requested logs = get_logs(self.channel_manager_contract, 'ChannelCloseRequested', **filters_confirmed) for log in logs: self.log.debug( 'detected a channel close request in channel manager') #assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] receiver = log['args']['_receiver_address'] sender = to_checksum_address(sender) receiver = to_checksum_address(receiver) open_block_number = log['args']['_open_block_number'] balance = log['args']['_balance'] self.log.debug('sucessfully parsed the event information') self.log.debug('address of channel manager %s', self.channel_manager_contract.address) try: self.log.debug('params to get info: %s, %s, %d', sender, self.cm.state.receiver, open_block_number) mtimeout, timeout = self.channel_manager_contract.call( ).getChannelInfo(sender, self.cm.state.receiver, open_block_number)[2:4] except BadFunctionCallOutput: self.log.debug( 'BadFunctionCallOutput error caught when trying to get channel info' ) continue try: self.cm.event_channel_close_requested( sender, open_block_number, balance, timeout) except InsufficientBalance: self.log.fatal('Insufficient ETH balance of the receiver. ' "Can't close the channel. " 'Will retry once the balance is sufficient') self.insufficient_balance = True # TODO: recover # channel settled event logs = get_logs(self.channel_manager_contract, 'ChannelSettled', **filters_confirmed) for log in logs: #assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] self.log.debug( 'received ChannelSettled event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_settled(sender, open_block_number) logs = get_logs(self.channel_monitor_contract, 'Dispute', **filters_confirmed) for log in logs: customer = to_checksum_address( log['args']['_customer_address']) sender = to_checksum_address(log['args']['_sender_address']) open_block_number = log['args']['_open_block_number'] self.log.info( 'Customer triggered a dispute (customer %s, sender %s, open_block_number %d)\n', customer, sender, open_block_number) self.cm.event_customer_dispute(customer, sender, open_block_number) logs = get_logs(self.channel_monitor_contract, 'Resolve', **filters_unconfirmed) for log in logs: self.log.debug('\n\n saw resolve event happen \n\n') logs = get_logs(self.channel_monitor_contract, 'Withdraw', **filters_unconfirmed) for log in logs: self.log.debug( 'detected successful WITHDRAW in monitor contract') # update head hash and number try: new_unconfirmed_head_hash = self.web3.eth.getBlock( new_unconfirmed_head_number).hash new_confirmed_head_hash = self.web3.eth.getBlock( new_confirmed_head_number).hash except AttributeError: self.log.critical( "RPC endpoint didn't return proper info for an existing block " "(%d,%d)" % (new_unconfirmed_head_number, new_confirmed_head_number)) self.log.critical( "It is possible that the blockchain isn't fully synced. " "This often happens when Parity is run with --fast or --warp sync." ) self.log.critical( "Can't continue - check status of the ethereum node.") sys.exit(1) self.cm.set_head(new_unconfirmed_head_number, new_unconfirmed_head_hash, new_confirmed_head_number, new_confirmed_head_hash) if not self.wait_sync_event.is_set( ) and new_unconfirmed_head_number == current_block: self.wait_sync_event.set()
def _update(self): #print(bcolors.BOLD + 'manager: block number %d' % (self.web3.eth.blockNumber) + bcolors.ENDC) current_block = self.web3.eth.blockNumber # reset unconfirmed channels in case of reorg if self.wait_sync_event.is_set(): # but not on first sync if current_block < self.cm.state.unconfirmed_head_number: self.log.info( 'chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]' % (self.cm.state.unconfirmed_head_number, self.web3.eth.blockNumber)) self.cm.reset_unconfirmed() try: # raises if hash doesn't exist (i.e. block has been replaced) self.web3.eth.getBlock(self.cm.state.unconfirmed_head_hash) except ValueError: self.log.info( 'chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]. ' '(getBlock() raised ValueError)' % (self.cm.state.unconfirmed_head_number, current_block)) self.cm.reset_unconfirmed() # in case of reorg longer than confirmation number fail try: self.web3.eth.getBlock(self.cm.state.confirmed_head_hash) except ValueError: self.log.critical( 'events considered confirmed have been reorganized') assert False # unreachable as long as confirmation level is set high enough if self.cm.state.confirmed_head_number is None: self.cm.state.update_sync_state( confirmed_head_number=self.sync_start_block) if self.cm.state.unconfirmed_head_number is None: self.cm.state.update_sync_state( unconfirmed_head_number=self.sync_start_block) new_unconfirmed_head_number = self.cm.state.unconfirmed_head_number + self.sync_chunk_size new_unconfirmed_head_number = min(new_unconfirmed_head_number, current_block) new_confirmed_head_number = max( new_unconfirmed_head_number - self.n_confirmations, 0) # return if blocks have already been processed if (self.cm.state.confirmed_head_number >= new_confirmed_head_number and self.cm.state.unconfirmed_head_number >= new_unconfirmed_head_number): return # filter for events after block_number filters_confirmed = { 'from_block': self.cm.state.confirmed_head_number + 1, 'to_block': new_confirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } filters_unconfirmed = { 'from_block': self.cm.state.unconfirmed_head_number + 1, 'to_block': new_unconfirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } self.log.debug('filtering for events u:%s-%s c:%s-%s @%d', filters_unconfirmed['from_block'], filters_unconfirmed['to_block'], filters_confirmed['from_block'], filters_confirmed['to_block'], current_block) # unconfirmed channel created logs = get_logs(self.channel_manager_contract, 'ChannelCreated', **filters_unconfirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug( 'received unconfirmed ChannelCreated event (sender %s, block number %s)', sender, open_block_number) self.cm.unconfirmed_event_channel_opened(sender, open_block_number, deposit) # channel created logs = get_logs(self.channel_manager_contract, 'ChannelCreated', **filters_confirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug( 'received ChannelOpened event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_opened(sender, open_block_number, deposit) # unconfirmed channel top ups logs = get_logs(self.channel_manager_contract, 'ChannelToppedUp', **filters_unconfirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, deposit %s)', sender, open_block_number, added_deposit) self.cm.unconfirmed_event_channel_topup(sender, open_block_number, txhash, added_deposit) # confirmed channel top ups logs = get_logs(self.channel_manager_contract, 'ChannelToppedUp', **filters_confirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, added deposit %s)', sender, open_block_number, added_deposit) self.cm.event_channel_topup(sender, open_block_number, txhash, added_deposit) # channel settled event logs = get_logs(self.channel_manager_contract, 'ChannelSettled', **filters_confirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] self.log.debug( 'received ChannelSettled event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_settled(sender, open_block_number) # channel close requested logs = get_logs(self.channel_manager_contract, 'ChannelCloseRequested', **filters_confirmed) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] if (sender, open_block_number) not in self.cm.channels: continue balance = log['args']['_balance'] try: #self.log.info('params to get info: %s, %s, %d', sender, self.cm.state.receiver, open_block_number) mtimeout, timeout = self.channel_manager_contract.call( ).getChannelInfo(sender, self.cm.state.receiver, open_block_number)[2:4] print(bcolors.OKGREEN + 'Channel close request:' + '\n\tsender %s' % sender + '\n\tmonitor timeout %d' % mtimeout + '\n\tsettle timeout %d' % timeout + bcolors.ENDC) except BadFunctionCallOutput: self.log.warning( 'received ChannelCloseRequested event for a channel that doesn\'t ' 'exist or has been closed already (sender=%s open_block_number=%d)' % (sender, open_block_number)) self.cm.force_close_channel(sender, open_block_number) continue self.log.debug( 'received ChannelCloseRequested event (sender %s, block number %s)', sender, open_block_number) self.log.info( 'received ChannelCloseRequested event (blocknumber %s, monitor period %s, timeout %s)', self.cm.state.confirmed_head_number, mtimeout, timeout) self.wait_to_dispute[int(mtimeout)] = (sender, open_block_number, balance, mtimeout, timeout) #try: # self.cm.event_channel_close_requested(sender, open_block_number, balance, mtimeout, timeout) #except InsufficientBalance: # self.log.fatal('Insufficient ETH balance of the receiver. ' # "Can't close the channel. " # 'Will retry once the balance is sufficient') # self.insufficient_balance = True # TODO: recover logs = get_logs(self.channel_manager_contract, 'MonitorInterference', **filters_unconfirmed) for log in logs: sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['open_block_number'] if (sender, open_block_number) not in self.cm.channels: self.log.debug( "monitor interfered in channel that wasn't outsourced (sender %s open_block_number %s)", sender, open_block_number) self.cm.event_monitor_interference(sender, open_block_number) logs = get_logs(self.channel_monitor_contract, 'CustomerDeposit', **filters_unconfirmed) for log in logs: sender = to_checksum_address(log['args']['_sender_address']) deposit = log['args']['_sender_deposit'] if sender == self.cm.receiver: self.cm.event_deposit_unconfirmed(sender, deposit) logs = get_logs(self.channel_monitor_contract, 'CustomerDeposit', **filters_confirmed) for log in logs: sender = to_checksum_address(log['args']['_sender_address']) deposit = log['args']['_sender_deposit'] if sender == self.cm.receiver: self.cm.event_deposit(sender, deposit) logs = get_logs(self.channel_monitor_contract, 'RecourseResult', **filters_unconfirmed) for log in logs: # monitor_balance = log['args']['_monitor_balance'] # closing_balance = log['args']['_closing_balance'] round_number = int(log['args']['_round_number']) receipt_hash = log['args']['_receipt_hash'] cheated = log['args']['_cheated'] self.log.info( 'RECOURSE RESULT: detected unconfirmed recourse result (round_number %d, receipt hash %s), did he cheat? %r', round_number, receipt_hash, cheated) print( bcolors.OKBLUE, 'Recourse result detected', '\n\tCheated: %r' % cheated, '\n\tnew balance: %s' % int(self.web3.eth.getBalance(self.cm.receiver)), bcolors.ENDC) logs = get_logs(self.channel_manager_contract, 'ExtractHash', **filters_unconfirmed) for log in logs: message_hash = log['args']['message_hash'] state_hash = log['args']['state_hash'] signer = to_checksum_address(log['args']['signer']) self.log.info('\n\tmessage_hash %s\n\tstate_hash %s\n\tsigner %s', message_hash, state_hash, signer) logs = get_logs(self.channel_monitor_contract, 'Dispute', **filters_unconfirmed) for log in logs: customer = log['args']['_customer_address'] open_block_number = log['args']['_open_block_number'] sender = log['args']['_sender_address'] self.log.info( 'DETECTED UNCONFIMED DISPUTE from monitor contract (customer %s, sender %s, open_block_number %d)', customer, sender, open_block_number) logs = get_logs(self.channel_monitor_contract, 'Dispute', **filters_confirmed) for log in logs: customer = to_checksum_address(log['args']['_customer_address']) open_block_number = log['args']['_open_block_number'] sender = to_checksum_address(log['args']['_sender_address']) settle_timeout = int(log['data'], 16) self.log.info( 'DETECTED CONFIMED DISPUTE from monitor contract (customer %s, sender %s, open_block_number %d, settle timeout %d, current block %d)', customer, sender, open_block_number, settle_timeout, self.cm.blockchain.web3.eth.blockNumber) self.wait_to_resolve[settle_timeout + 1] = (customer, sender, open_block_number) self.resolve_timeouts[sender, open_block_number] = settle_timeout + 1 logs = get_logs(self.channel_monitor_contract, 'Resolve', **filters_confirmed) for log in logs: customer = to_checksum_address(log['args']['_sender_address']) self.log.info('successfully resolved channel (customer %s)', customer) print(bcolors.OKGREEN + 'Channel successfully resolved....' + bcolors.ENDC) print(bcolors.OKGREEN + '\n\tServer balance: %d' % (self.web3.eth.getBalance(self.cm.receiver))) logs = get_logs(self.channel_monitor_contract, 'Evidence', **filters_confirmed) for log in logs: customer = to_checksum_address(log['args']['_customer_address']) sender = to_checksum_address(log['args']['_sender_address']) pre_image = log['args']['_pre_image'] open_block_number = int(log['data'], 16) if (sender, open_block_number ) not in self.cm.channels or customer != self.cm.receiver: self.log.info('received SET STATE for unknown channel') else: self.log.info( 'Monitor called SET STATE (sender %s, open_block_number %d, pre_image %d)', sender, open_block_number, pre_image) self.cm.event_set_state(customer, sender, open_block_number, pre_image) logs = get_logs(self.channel_manager_contract, 'DebugVerify', **filters_unconfirmed) for log in logs: signer = to_checksum_address(log['args']['_sender']) self.log.info('Signer of customer evidence: %s', signer) logs = get_logs(self.channel_manager_contract, 'RevealHash', **filters_unconfirmed) for log in logs: evidence = encode_hex(log['args']['_evidence']) resolved = encode_hex(log['args']['_resolved']) balance = int(log['args']['_balance']) self.log.info( bcolors.BOLD + "Reveal (balance %s, evidence %s, resolved %s)" + bcolors.ENDC, balance, evidence, resolved) # See which close requests are ready to be responded to # and process them normally. disputed = [] for mtimeout in self.wait_to_dispute: if self.cm.state.confirmed_head_number > mtimeout: try: #print(self.cm.monitor_channels) sender, open_block_number = self.wait_to_dispute[mtimeout][ 0], self.wait_to_dispute[mtimeout][1] self.log.info( 'Processing close request at block %s, mtimeout %s', self.cm.state.confirmed_head_number, mtimeout) self.cm.reveal_monitor_submission( *self.wait_to_dispute[mtimeout]) self.cm.event_channel_close_requested( *self.wait_to_dispute[mtimeout]) except InsufficientBalance: self.log.fatal('Insufficient ETH balance of the receiver. ' "Can't close the channel. " 'Will retry once the balance is sufficient') self.insufficient_balance = True # TODO: recover disputed.append(mtimeout) for dispute in disputed: del self.wait_to_dispute[dispute] resolved = [] for stimeout in self.wait_to_resolve: if self.cm.blockchain.web3.eth.blockNumber > stimeout: self.log.info( 'Processing DISPUTE request at block %s, stimeout %s', self.cm.blockchain.web3.eth.blockNumber, stimeout) customer, sender, open_block_number = self.wait_to_resolve[ stimeout] self.cm.event_dispute(customer, sender, open_block_number) resolved.append(stimeout) for resolve in resolved: del self.wait_to_resolve[resolve] # update head hash and number try: new_unconfirmed_head_hash = self.web3.eth.getBlock( new_unconfirmed_head_number).hash new_confirmed_head_hash = self.web3.eth.getBlock( new_confirmed_head_number).hash except AttributeError: self.log.critical( "RPC endpoint didn't return proper info for an existing block " "(%d,%d)" % (new_unconfirmed_head_number, new_confirmed_head_number)) self.log.critical( "It is possible that the blockchain isn't fully synced. " "This often happens when Parity is run with --fast or --warp sync." ) self.log.critical( "Can't continue - check status of the ethereum node.") sys.exit(1) self.cm.set_head(new_unconfirmed_head_number, new_unconfirmed_head_hash, new_confirmed_head_number, new_confirmed_head_hash) if not self.wait_sync_event.is_set( ) and new_unconfirmed_head_number == current_block: self.wait_sync_event.set()
def _update(self): current_block = self.web3.eth.blockNumber # reset unconfirmed channels in case of reorg if self.wait_sync_event.is_set(): # but not on first sync if current_block < self.cm.state.unconfirmed_head_number: self.log.info('chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]' % (self.cm.state.unconfirmed_head_number, self.web3.eth.blockNumber)) self.cm.reset_unconfirmed() try: # raises if hash doesn't exist (i.e. block has been replaced) self.web3.eth.getBlock(self.cm.state.unconfirmed_head_hash) except ValueError: self.log.info('chain reorganization detected. ' 'Resyncing unconfirmed events (unconfirmed_head=%d) [@%d]. ' '(getBlock() raised ValueError)' % (self.cm.state.unconfirmed_head_number, current_block)) self.cm.reset_unconfirmed() # in case of reorg longer than confirmation number fail try: self.web3.eth.getBlock(self.cm.state.confirmed_head_hash) except ValueError: self.log.critical('events considered confirmed have been reorganized') assert False # unreachable as long as confirmation level is set high enough if self.cm.state.confirmed_head_number is None: self.cm.state.update_sync_state(confirmed_head_number=self.sync_start_block) if self.cm.state.unconfirmed_head_number is None: self.cm.state.update_sync_state(unconfirmed_head_number=self.sync_start_block) new_unconfirmed_head_number = self.cm.state.unconfirmed_head_number + self.sync_chunk_size new_unconfirmed_head_number = min(new_unconfirmed_head_number, current_block) new_confirmed_head_number = max(new_unconfirmed_head_number - self.n_confirmations, 0) # return if blocks have already been processed if (self.cm.state.confirmed_head_number >= new_confirmed_head_number and self.cm.state.unconfirmed_head_number >= new_unconfirmed_head_number): return # filter for events after block_number filters_confirmed = { 'from_block': self.cm.state.confirmed_head_number + 1, 'to_block': new_confirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } filters_unconfirmed = { 'from_block': self.cm.state.unconfirmed_head_number + 1, 'to_block': new_unconfirmed_head_number, 'argument_filters': { '_receiver_address': self.cm.state.receiver } } self.log.debug( 'filtering for events u:%s-%s c:%s-%s @%d', filters_unconfirmed['from_block'], filters_unconfirmed['to_block'], filters_confirmed['from_block'], filters_confirmed['to_block'], current_block ) # unconfirmed channel created logs = get_logs( self.channel_manager_contract, 'ChannelCreated', **filters_unconfirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug( 'received unconfirmed ChannelCreated event (sender %s, block number %s)', sender, open_block_number ) self.cm.unconfirmed_event_channel_opened(sender, open_block_number, deposit) # channel created logs = get_logs( self.channel_manager_contract, 'ChannelCreated', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) deposit = log['args']['_deposit'] open_block_number = log['blockNumber'] self.log.debug('received ChannelOpened event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_opened(sender, open_block_number, deposit) # unconfirmed channel top ups logs = get_logs( self.channel_manager_contract, 'ChannelToppedUp', **filters_unconfirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, deposit %s)', sender, open_block_number, added_deposit ) self.cm.unconfirmed_event_channel_topup( sender, open_block_number, txhash, added_deposit ) # confirmed channel top ups logs = get_logs( self.channel_manager_contract, 'ChannelToppedUp', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) txhash = log['transactionHash'] sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] added_deposit = log['args']['_added_deposit'] self.log.debug( 'received top up event (sender %s, block number %s, added deposit %s)', sender, open_block_number, added_deposit ) self.cm.event_channel_topup(sender, open_block_number, txhash, added_deposit) # channel settled event logs = get_logs( self.channel_manager_contract, 'ChannelSettled', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] self.log.debug('received ChannelSettled event (sender %s, block number %s)', sender, open_block_number) self.cm.event_channel_settled(sender, open_block_number) # channel close requested logs = get_logs( self.channel_manager_contract, 'ChannelCloseRequested', **filters_confirmed ) for log in logs: assert is_same_address(log['args']['_receiver_address'], self.cm.state.receiver) sender = log['args']['_sender_address'] sender = to_checksum_address(sender) open_block_number = log['args']['_open_block_number'] if (sender, open_block_number) not in self.cm.channels: continue balance = log['args']['_balance'] try: timeout = self.channel_manager_contract.call().getChannelInfo( sender, self.cm.state.receiver, open_block_number )[2] except BadFunctionCallOutput: self.log.warning( 'received ChannelCloseRequested event for a channel that doesn\'t ' 'exist or has been closed already (sender=%s open_block_number=%d)' % (sender, open_block_number)) self.cm.force_close_channel(sender, open_block_number) continue self.log.debug('received ChannelCloseRequested event (sender %s, block number %s)', sender, open_block_number) try: self.cm.event_channel_close_requested(sender, open_block_number, balance, timeout) except InsufficientBalance: self.log.fatal('Insufficient ETH balance of the receiver. ' "Can't close the channel. " 'Will retry once the balance is sufficient') self.insufficient_balance = True # TODO: recover # update head hash and number try: new_unconfirmed_head_hash = self.web3.eth.getBlock(new_unconfirmed_head_number).hash new_confirmed_head_hash = self.web3.eth.getBlock(new_confirmed_head_number).hash except AttributeError: self.log.critical("RPC endpoint didn't return proper info for an existing block " "(%d,%d)" % (new_unconfirmed_head_number, new_confirmed_head_number)) self.log.critical("It is possible that the blockchain isn't fully synced. " "This often happens when Parity is run with --fast or --warp sync.") self.log.critical("Can't continue - check status of the ethereum node.") sys.exit(1) self.cm.set_head( new_unconfirmed_head_number, new_unconfirmed_head_hash, new_confirmed_head_number, new_confirmed_head_hash ) if not self.wait_sync_event.is_set() and new_unconfirmed_head_number == current_block: self.wait_sync_event.set()