def __init__( self, web3: Web3, contract_manager: ContractManager, registry_address: Address, sync_start_block: int = 0, required_confirmations: int = 8, poll_interval: int = 10, ): """ Creates a new pathfinding service Args: contract_manager: A contract manager token_network_listener: A blockchain listener object token_network_registry_listener: A blockchain listener object for the network registry chain_id: The id of the chain the PFS runs on """ super().__init__() self.web3 = web3 self.contract_manager = contract_manager self.registry_address = registry_address self.sync_start_block = sync_start_block self.required_confirmations = required_confirmations self.poll_interval = poll_interval self.chain_id = int(web3.net.version) self.is_running = gevent.event.Event() self.token_networks: Dict[Address, TokenNetwork] = {} self.token_network_listeners: List[BlockchainListener] = [] self.is_running = gevent.event.Event() log.info('Starting TokenNetworkRegistry Listener (required confirmations: {})...'.format( self.required_confirmations, )) self.token_network_registry_listener = BlockchainListener( web3=web3, contract_manager=self.contract_manager, contract_name=CONTRACT_TOKEN_NETWORK_REGISTRY, contract_address=self.registry_address, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=self.sync_start_block, ) log.info( f'Listening to token network registry @ {registry_address} ' f'from block {sync_start_block}', ) self._setup_token_networks()
def blockchain_listener(web3, contracts_manager, token_network): blockchain_listener = BlockchainListener( web3=web3, contract_manager=contracts_manager, contract_name=CONTRACT_TOKEN_NETWORK, contract_address=token_network.address, poll_interval=0, ) return blockchain_listener
def create_token_network_for_address( self, token_network_address: Address, token_address: Address, block_number: int = 0, ): token_network = TokenNetwork(token_network_address, token_address) self.token_networks[token_network_address] = token_network log.debug('Creating token network model', token_network_address=token_network_address) token_network_listener = BlockchainListener( web3=self.web3, contract_manager=self.contract_manager, contract_address=token_network_address, contract_name=CONTRACT_TOKEN_NETWORK, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=block_number, ) # subscribe to event notifications from blockchain listener token_network_listener.add_confirmed_listener( create_channel_event_topics(), self.handle_channel_event, ) token_network_listener.start() self.token_network_listeners.append(token_network_listener)
def __init__( self, web3: Web3, contract_manager: ContractManager, registry_address: Address, private_key: str, db_filename: str, user_deposit_contract_address: Address, sync_start_block: int = 0, required_confirmations: int = 8, poll_interval: int = 10, service_fee: int = 0, ): """ Creates a new pathfinding service Args: contract_manager: A contract manager token_network_listener: A blockchain listener object token_network_registry_listener: A blockchain listener object for the network registry chain_id: The id of the chain the PFS runs on """ super().__init__() self.web3 = web3 self.contract_manager = contract_manager self.registry_address = registry_address self.sync_start_block = sync_start_block self.required_confirmations = required_confirmations self.poll_interval = poll_interval self.chain_id = int(web3.net.version) self.private_key = private_key self.address = private_key_to_address(private_key) self.service_fee = service_fee self.is_running = gevent.event.Event() self.token_networks: Dict[Address, TokenNetwork] = {} self.token_network_listeners: List[BlockchainListener] = [] self.database = PFSDatabase( filename=db_filename, pfs_address=self.address, ) self.user_deposit_contract = web3.eth.contract( abi=self.contract_manager.get_contract_abi( CONTRACT_USER_DEPOSIT, ), address=user_deposit_contract_address, ) log.info( 'Starting TokenNetworkRegistry Listener', required_confirmations=self.required_confirmations, ) self.token_network_registry_listener = BlockchainListener( web3=web3, contract_manager=self.contract_manager, contract_name=CONTRACT_TOKEN_NETWORK_REGISTRY, contract_address=self.registry_address, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=self.sync_start_block, ) log.info( 'Listening to token network registry', registry_address=registry_address, start_block=sync_start_block, ) self._setup_token_networks() try: self.matrix_listener = MatrixListener( private_key=private_key, chain_id=self.chain_id, callback=self.handle_message, service_room_suffix=PATH_FINDING_BROADCASTING_ROOM, ) except ConnectionError as e: log.critical( 'Could not connect to broadcasting system.', exc=e, ) sys.exit(1)
class PathfindingService(gevent.Greenlet): def __init__( self, web3: Web3, contract_manager: ContractManager, registry_address: Address, private_key: str, db_filename: str, user_deposit_contract_address: Address, sync_start_block: int = 0, required_confirmations: int = 8, poll_interval: int = 10, service_fee: int = 0, ): """ Creates a new pathfinding service Args: contract_manager: A contract manager token_network_listener: A blockchain listener object token_network_registry_listener: A blockchain listener object for the network registry chain_id: The id of the chain the PFS runs on """ super().__init__() self.web3 = web3 self.contract_manager = contract_manager self.registry_address = registry_address self.sync_start_block = sync_start_block self.required_confirmations = required_confirmations self.poll_interval = poll_interval self.chain_id = int(web3.net.version) self.private_key = private_key self.address = private_key_to_address(private_key) self.service_fee = service_fee self.is_running = gevent.event.Event() self.token_networks: Dict[Address, TokenNetwork] = {} self.token_network_listeners: List[BlockchainListener] = [] self.database = PFSDatabase( filename=db_filename, pfs_address=self.address, ) self.user_deposit_contract = web3.eth.contract( abi=self.contract_manager.get_contract_abi( CONTRACT_USER_DEPOSIT, ), address=user_deposit_contract_address, ) log.info( 'Starting TokenNetworkRegistry Listener', required_confirmations=self.required_confirmations, ) self.token_network_registry_listener = BlockchainListener( web3=web3, contract_manager=self.contract_manager, contract_name=CONTRACT_TOKEN_NETWORK_REGISTRY, contract_address=self.registry_address, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=self.sync_start_block, ) log.info( 'Listening to token network registry', registry_address=registry_address, start_block=sync_start_block, ) self._setup_token_networks() try: self.matrix_listener = MatrixListener( private_key=private_key, chain_id=self.chain_id, callback=self.handle_message, service_room_suffix=PATH_FINDING_BROADCASTING_ROOM, ) except ConnectionError as e: log.critical( 'Could not connect to broadcasting system.', exc=e, ) sys.exit(1) def _setup_token_networks(self): self.token_network_registry_listener.add_confirmed_listener( create_registry_event_topics(self.contract_manager), self.handle_token_network_created, ) def _run(self): register_error_handler(error_handler) self.matrix_listener.start() self.token_network_registry_listener.start() self.is_running.wait() def stop(self): self.token_network_registry_listener.stop() for task in self.token_network_listeners: task.stop() self.matrix_listener.stop() self.is_running.set() self.matrix_listener.join() def follows_token_network(self, token_network_address: Address) -> bool: """ Checks if a token network is followed by the pathfinding service. """ assert is_checksum_address(token_network_address) return token_network_address in self.token_networks.keys() def _get_token_network(self, token_network_address: Address) -> Optional[TokenNetwork]: """ Returns the `TokenNetwork` for the given address or `None` for unknown networks. """ assert is_checksum_address(token_network_address) if not self.follows_token_network(token_network_address): return None else: return self.token_networks[token_network_address] def handle_channel_event(self, event: Dict): event_name = event['event'] if event_name == ChannelEvent.OPENED: self.handle_channel_opened(event) elif event_name == ChannelEvent.DEPOSIT: self.handle_channel_new_deposit(event) elif event_name == ChannelEvent.CLOSED: self.handle_channel_closed(event) else: log.debug('Unhandled event', evt=event) def handle_channel_opened(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return channel_identifier = event['args']['channel_identifier'] participant1 = event['args']['participant1'] participant2 = event['args']['participant2'] settle_timeout = event['args']['settle_timeout'] log.info( 'Received ChannelOpened event', token_network_address=token_network.address, channel_identifier=channel_identifier, participant1=participant1, participant2=participant2, settle_timeout=settle_timeout, ) token_network.handle_channel_opened_event( channel_identifier, participant1, participant2, settle_timeout, ) def handle_channel_new_deposit(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return channel_identifier = event['args']['channel_identifier'] participant_address = event['args']['participant'] total_deposit = event['args']['total_deposit'] log.info( 'Received ChannelNewDeposit event', token_network_address=token_network.address, channel_identifier=channel_identifier, participant=participant_address, total_deposit=total_deposit, ) token_network.handle_channel_new_deposit_event( channel_identifier, participant_address, total_deposit, ) def handle_channel_closed(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return channel_identifier = event['args']['channel_identifier'] log.info( 'Received ChannelClosed event', token_network_address=token_network.address, channel_identifier=channel_identifier, ) token_network.handle_channel_closed_event(channel_identifier) def handle_token_network_created(self, event): token_network_address = event['args']['token_network_address'] token_address = event['args']['token_address'] event_block_number = event['blockNumber'] assert is_checksum_address(token_network_address) assert is_checksum_address(token_address) if not self.follows_token_network(token_network_address): log.info( 'Found new token network', token_address=token_address, token_network_address=token_network_address, ) self.create_token_network_for_address( token_network_address, token_address, event_block_number, ) def create_token_network_for_address( self, token_network_address: Address, token_address: Address, block_number: int = 0, ): token_network = TokenNetwork(token_network_address, token_address) self.token_networks[token_network_address] = token_network log.debug('Creating token network model', token_network_address=token_network_address) token_network_listener = BlockchainListener( web3=self.web3, contract_manager=self.contract_manager, contract_address=token_network_address, contract_name=CONTRACT_TOKEN_NETWORK, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=block_number, ) # subscribe to event notifications from blockchain listener token_network_listener.add_confirmed_listener( create_channel_event_topics(), self.handle_channel_event, ) token_network_listener.start() self.token_network_listeners.append(token_network_listener) def handle_message(self, message: SignedMessage): if isinstance(message, UpdatePFS): try: self.on_pfs_update(message) except InvalidCapacityUpdate as x: log.info( str(x), chain_id=message.canonical_identifier.chain_identifier, token_network_address=message.canonical_identifier.token_network_address, channel_identifier=message.canonical_identifier.channel_identifier, updating_capacity=message.updating_capacity, other_capacity=message.updating_capacity, ) else: log.info('Ignoring unknown message type') def on_pfs_update(self, message: UpdatePFS): token_network_address = to_checksum_address( message.canonical_identifier.token_network_address, ) log.info( 'Received Capacity Update', token_network_address=token_network_address, channel_identifier=message.canonical_identifier.channel_identifier, ) assert is_checksum_address(message.updating_participant) assert is_checksum_address(message.other_participant) # check if chain_id matches if message.canonical_identifier.chain_identifier != self.chain_id: raise InvalidCapacityUpdate('Received Capacity Update with unknown chain identifier') # check if token network exists token_network = self._get_token_network(token_network_address) if token_network is None: raise InvalidCapacityUpdate('Received Capacity Update with unknown token network') # check if channel exists channel_identifier = message.canonical_identifier.channel_identifier if channel_identifier not in token_network.channel_id_to_addresses: raise InvalidCapacityUpdate( 'Received Capacity Update with unknown channel identifier in token network', ) # TODO: check signature of message # check values < max int 256 if message.updating_capacity > UINT256_MAX: raise InvalidCapacityUpdate( 'Received Capacity Update with impossible updating_capacity', ) if message.other_capacity > UINT256_MAX: raise InvalidCapacityUpdate( 'Received Capacity Update with impossible other_capacity', ) # check if participants fit to channel id participants = token_network.channel_id_to_addresses[channel_identifier] if message.updating_participant not in participants: raise InvalidCapacityUpdate( 'Sender of Capacity Update does not match the internal channel', ) if message.other_participant not in participants: raise InvalidCapacityUpdate( 'Other Participant of Capacity Update does not match the internal channel', ) # check if nonce is higher than current nonce view_to_partner, view_from_partner = token_network.get_channel_views_for_partner( channel_identifier=channel_identifier, updating_participant=message.updating_participant, other_participant=message.other_participant, ) valid_nonces = ( message.updating_nonce <= view_to_partner.update_nonce and message.other_nonce <= view_from_partner.update_nonce ) if valid_nonces: raise InvalidCapacityUpdate('Capacity Update already received') token_network.handle_channel_balance_update_message( channel_identifier=message.canonical_identifier.channel_identifier, updating_participant=message.updating_participant, other_participant=message.other_participant, updating_nonce=message.updating_nonce, other_nonce=message.other_nonce, updating_capacity=message.updating_capacity, other_capacity=message.other_capacity, reveal_timeout=message.reveal_timeout, )
def test_blockchain_listener_nonexistant_contract( web3: Web3, wait_for_blocks, generate_raiden_clients, blockchain_listener: BlockchainListener, ethereum_tester, ): blockchain_listener.required_confirmations = 4 blockchain_listener.contract_address = '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa' blockchain_listener.start() blockchain_listener.wait_sync() unconfirmed_channel_open_events: List[Dict] = [] confirmed_channel_open_events: List[Dict] = [] blockchain_listener.add_unconfirmed_listener( create_channel_event_topics(), unconfirmed_channel_open_events.append, ) blockchain_listener.add_confirmed_listener( create_channel_event_topics(), confirmed_channel_open_events.append, ) # create unconfirmed channel c1, c2 = generate_raiden_clients(2) c1.open_channel(c2.address) # no unconfirmed event should be available wait_for_blocks(0) assert len(unconfirmed_channel_open_events) == 0 # no confirmed event should be available after 4 more blocks wait_for_blocks(4) assert len(confirmed_channel_open_events) == 0 blockchain_listener.stop()
def test_blockchain_listener( web3: Web3, wait_for_blocks, generate_raiden_clients, blockchain_listener: BlockchainListener, ethereum_tester, ): blockchain_listener.required_confirmations = 4 blockchain_listener.start() blockchain_listener.wait_sync() unconfirmed_channel_open_events: List[Dict] = [] confirmed_channel_open_events: List[Dict] = [] blockchain_listener.add_unconfirmed_listener( create_channel_event_topics(), unconfirmed_channel_open_events.append, ) blockchain_listener.add_confirmed_listener( create_channel_event_topics(), confirmed_channel_open_events.append, ) # create unconfirmed channel c1, c2 = generate_raiden_clients(2) c1.open_channel(c2.address) # the unconfirmed event should be available directly wait_for_blocks(0) assert len(unconfirmed_channel_open_events) == 1 assert unconfirmed_channel_open_events[0]['args'][ 'participant1'] == c1.address assert unconfirmed_channel_open_events[0]['args'][ 'participant2'] == c2.address # settle_timeout acc. to mock client = 15 assert unconfirmed_channel_open_events[0]['args']['settle_timeout'] == 15 # the confirmed event should be available after 4 more blocks as set above assert len(confirmed_channel_open_events) == 0 wait_for_blocks(4) assert len(confirmed_channel_open_events) == 1 assert confirmed_channel_open_events[0]['args'][ 'participant1'] == c1.address assert confirmed_channel_open_events[0]['args'][ 'participant2'] == c2.address # settle_timeout acc. to mock client = 15 assert confirmed_channel_open_events[0]['args']['settle_timeout'] == 15 blockchain_listener.stop()
def test_reorg( web3: Web3, wait_for_blocks, generate_raiden_clients, blockchain_listener: BlockchainListener, ethereum_tester, ): blockchain_listener.required_confirmations = 5 blockchain_listener.start() blockchain_listener.wait_sync() unconfirmed_channel_open_events: List[Dict] = [] blockchain_listener.add_unconfirmed_listener( create_channel_event_topics(), unconfirmed_channel_open_events.append, ) c1, c2 = generate_raiden_clients(2) snapshot_id = web3.testing.snapshot() # create unconfirmed channel c1.open_channel(c2.address) wait_for_blocks(0) assert len(unconfirmed_channel_open_events) == 1 assert unconfirmed_channel_open_events[0]['args'][ 'participant1'] == c1.address assert unconfirmed_channel_open_events[0]['args'][ 'participant2'] == c2.address # remove unconfirmed channel opening with reorg web3.testing.revert(snapshot_id) # run the BlockchainListener again, it should have a lower head_number now old_head_number = blockchain_listener.unconfirmed_head_number wait_for_blocks(0) new_head_number = blockchain_listener.unconfirmed_head_number assert old_head_number > new_head_number # test that a chain reorg of one block is handled # this created a channel first, then reverts and creates a different channel unconfirmed_channel_open_events.clear() c1.open_channel(c2.address) wait_for_blocks(0) assert len(unconfirmed_channel_open_events) == 1 assert unconfirmed_channel_open_events[0]['args'][ 'participant1'] == c1.address assert unconfirmed_channel_open_events[0]['args'][ 'participant2'] == c2.address web3.testing.revert(snapshot_id) c2.open_channel(c1.address) wait_for_blocks(0) assert len(unconfirmed_channel_open_events) == 2 assert unconfirmed_channel_open_events[1]['args'][ 'participant1'] == c2.address assert unconfirmed_channel_open_events[1]['args'][ 'participant2'] == c1.address web3.testing.revert(snapshot_id) # test a big chain reorg (> required_confirmations) confirmed_channel_open_events: List[Dict] = [] blockchain_listener.add_confirmed_listener( create_channel_event_topics(), confirmed_channel_open_events.append, ) c1.open_channel(c2.address) # create a new event and wait till it's confirmed wait_for_blocks(5) assert len(confirmed_channel_open_events) == 1 # revert the chain, this should kill the process web3.testing.revert(snapshot_id) with pytest.raises(SystemExit): wait_for_blocks(0) blockchain_listener.stop()
class PathfindingService(gevent.Greenlet): def __init__( self, web3: Web3, contract_manager: ContractManager, registry_address: Address, sync_start_block: int = 0, required_confirmations: int = 8, poll_interval: int = 10, ) -> None: """ Creates a new pathfinding service Args: contract_manager: A contract manager token_network_listener: A blockchain listener object token_network_registry_listener: A blockchain listener object for the network registry chain_id: The id of the chain the PFS runs on """ super().__init__() self.web3 = web3 self.contract_manager = contract_manager self.registry_address = registry_address self.sync_start_block = sync_start_block self.required_confirmations = required_confirmations self.poll_interval = poll_interval self.chain_id = int(web3.net.version) self.is_running = gevent.event.Event() self.token_networks: Dict[Address, TokenNetwork] = {} self.token_network_listeners: List[BlockchainListener] = [] self.is_running = gevent.event.Event() log.info( 'Starting TokenNetworkRegistry Listener (required confirmations: {})...' .format(self.required_confirmations, )) self.token_network_registry_listener = BlockchainListener( web3=web3, contract_manager=self.contract_manager, contract_name=CONTRACT_TOKEN_NETWORK_REGISTRY, contract_address=self.registry_address, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=self.sync_start_block, ) log.info( f'Listening to token network registry @ {registry_address} ' f'from block {sync_start_block}', ) self._setup_token_networks() def _setup_token_networks(self): self.token_network_registry_listener.add_confirmed_listener( create_registry_event_topics(self.contract_manager), self.handle_token_network_created, ) def _run(self): register_error_handler(error_handler) self.token_network_registry_listener.start() self.is_running.wait() def stop(self): self.token_network_registry_listener.stop() for task in self.token_network_listeners: task.stop() self.is_running.set() def follows_token_network(self, token_network_address: Address) -> bool: """ Checks if a token network is followed by the pathfinding service. """ assert is_checksum_address(token_network_address) return token_network_address in self.token_networks.keys() def _get_token_network( self, token_network_address: Address) -> Optional[TokenNetwork]: """ Returns the `TokenNetwork` for the given address or `None` for unknown networks. """ assert is_checksum_address(token_network_address) if not self.follows_token_network(token_network_address): return None else: return self.token_networks[token_network_address] def _check_chain_id(self, received_chain_id: int): if not received_chain_id == self.chain_id: raise ValueError('Chain id does not match') def handle_channel_event(self, event: Dict): event_name = event['event'] if event_name == ChannelEvent.OPENED: self.handle_channel_opened(event) elif event_name == ChannelEvent.DEPOSIT: self.handle_channel_new_deposit(event) elif event_name == ChannelEvent.CLOSED: self.handle_channel_closed(event) else: log.info('Unhandled event: %s', event_name) def handle_channel_opened(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return log.debug('Received ChannelOpened event for token network {}'.format( token_network.address, )) channel_identifier = event['args']['channel_identifier'] participant1 = event['args']['participant1'] participant2 = event['args']['participant2'] token_network.handle_channel_opened_event( channel_identifier, participant1, participant2, ) def handle_channel_new_deposit(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return log.debug( 'Received ChannelNewDeposit event for token network {}'.format( token_network.address, )) channel_identifier = event['args']['channel_identifier'] participant_address = event['args']['participant'] total_deposit = event['args']['total_deposit'] token_network.handle_channel_new_deposit_event( channel_identifier, participant_address, total_deposit, ) def handle_channel_closed(self, event: Dict): token_network = self._get_token_network(event['address']) if token_network is None: return log.debug('Received ChannelClosed event for token network {}'.format( token_network.address, )) channel_identifier = event['args']['channel_identifier'] token_network.handle_channel_closed_event(channel_identifier) def handle_token_network_created(self, event): token_network_address = event['args']['token_network_address'] token_address = event['args']['token_address'] event_block_number = event['blockNumber'] assert is_checksum_address(token_network_address) assert is_checksum_address(token_address) if not self.follows_token_network(token_network_address): log.info( f'Found token network for token {token_address} @ {token_network_address}' ) self.create_token_network_for_address( token_network_address, token_address, event_block_number, ) def create_token_network_for_address( self, token_network_address: Address, token_address: Address, block_number: int = 0, ): token_network = TokenNetwork(token_network_address, token_address) self.token_networks[token_network_address] = token_network log.info('Creating token network for %s', token_network_address) token_network_listener = BlockchainListener( web3=self.web3, contract_manager=self.contract_manager, contract_address=token_network_address, contract_name=CONTRACT_TOKEN_NETWORK, required_confirmations=self.required_confirmations, poll_interval=self.poll_interval, sync_start_block=block_number, ) # subscribe to event notifications from blockchain listener token_network_listener.add_confirmed_listener( create_channel_event_topics(), self.handle_channel_event, ) token_network_listener.start() self.token_network_listeners.append(token_network_listener)