Beispiel #1
0
 def _create_bloom_filter(self):
     element_count = 1
     false_positive_probability = 0.00001
     filter_size = filter_size_required(element_count, false_positive_probability)
     hash_function_count = hash_function_count_required(filter_size, element_count)
     self._pool_filter = BloomFilter(filter_size, hash_function_count=hash_function_count, tweak=1)
     self._pool_filter.add_address('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa')
Beispiel #2
0
class P2PConnectionPool(BaseConnectionPool):
    async def on_peer_received_peers(self, peer, *a):
        Logger.p2p.debug('Received peers from peer: %s: (%s)', peer.hostname,
                         a)

    def __init__(self,
                 peers=list(),
                 network_checker=check_internet_connection,
                 delayer=async_delayed_task,
                 loop=asyncio.get_event_loop(),
                 proxy=False,
                 connections=3,
                 sleep_no_internet=30,
                 batcher=InvBatcher,
                 network=MAINNET,
                 batcher_timeout=20,
                 ipv6=False,
                 servers_storage=save_p2p_peers,
                 context=None,
                 enable_mempool=False):
        super().__init__(peers=peers,
                         network_checker=network_checker,
                         delayer=delayer,
                         ipv6=ipv6,
                         loop=loop,
                         proxy=proxy,
                         connections=connections,
                         sleep_no_internet=sleep_no_internet)

        self._pool_filter = None
        self._batcher_factory = batcher
        self._network = network
        self._batcher_timeout = batcher_timeout
        self._busy_peers = set()
        self.servers_storage = servers_storage
        self._storage_lock = asyncio.Lock()
        self._required_connections = connections
        self.best_header = None
        self._on_transaction_hash_callback = []
        self._on_transaction_callback = []
        self._on_block_callback = []
        self.context = context

        not enable_mempool and self._create_bloom_filter(
        )  # Mount a dummy filter to avoid receiving tx data

    @property
    def proxy(self):
        return self._proxy

    async def set_best_header(self, value):
        self.best_header = value

    def _create_bloom_filter(self):
        element_count = 1
        false_positive_probability = 0.00000000001
        filter_size = filter_size_required(element_count,
                                           false_positive_probability)
        hash_function_count = hash_function_count_required(
            filter_size, element_count)
        self._pool_filter = BloomFilter(
            filter_size, hash_function_count=hash_function_count, tweak=1)
        self._pool_filter.add_address('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa')

    def version_checker(self, peer_version):
        return self.context.get_network()['evaluate_peer_version'](
            peer_version)

    @property
    def required_connections(self):
        return self._required_connections

    @property
    def available(self):
        return len(self.connections) >= self._required_connections

    def add_peer(self, peer):
        self._peers.append(peer)

    def add_on_transaction_hash_callback(self, callback):
        self._on_transaction_hash_callback.append(callback)

    def add_on_transaction_callback(self, callback):
        self._on_transaction_callback.append(callback)

    def add_on_block_callback(self, callback):
        self._on_block_callback.append(callback)

    @property
    def connections(self):
        for connection in self._connections:
            if connection.failed:
                del connection
        return self._connections

    async def connect(self):
        await self._check_internet_connectivity()
        self._keepalive = True
        while not self._peers:
            Logger.p2p.warning('Peers not loaded yet')
            await asyncio.sleep(5)

        while self._keepalive:
            if not self.is_online():
                Logger.p2p.error(
                    'Looks like there is no internet connection available. '
                    'Sleeping the connection loop for %s',
                    self._sleep_on_no_internet_connectivity)
                await asyncio.sleep(self._sleep_on_no_internet_connectivity)
                await self._check_internet_connectivity()
                continue
            missings = self._required_connections - len(
                self.established_connections)
            if missings > 0:
                peers = self._pick_multiple_peers(missings)
                for peer in peers:
                    host, port = peer
                    self.loop.create_task(self._connect_peer(host, port))
            elif len(
                    self.established_connections) > self._required_connections:
                Logger.p2p.warning('Too many connections')
                connection = self._pick_connection()
                self.loop.create_task(connection.disconnect())
            #Logger.p2p.debug(
            #    'P2PConnectionPool: Sleeping %ss, connected to %s peers', 10, len(self.established_connections)
            #)
            for connection in self._connections:
                if connection.score <= 0:
                    self.loop.create_task(self._disconnect_peer(connection))
            await asyncio.sleep(10)

    async def _disconnect_peer(self, peer):
        await peer.disconnect()

    async def _connect_peer(self, host: str, port: int):
        Logger.p2p.debug('Allocating peer %s:%s', host, port)
        connection = P2PConnection(host,
                                   port,
                                   loop=self.loop,
                                   network=self._network,
                                   bloom_filter=self._pool_filter,
                                   best_header=self.best_header,
                                   version_checker=self.version_checker,
                                   proxy=self._proxy)
        if not await connection.connect():
            Logger.p2p.debug(
                'Connection to %s - %s failed. Connected to %s peers', host,
                port, len(self.established_connections))
            return
        self._connections.append(connection)
        connection.add_on_connect_callback(self.on_peer_connected)
        connection.add_on_header_callbacks(self.on_peer_received_header)
        connection.add_on_peers_callback(self.on_peer_received_peers)
        connection.add_on_error_callback(self.on_peer_error)
        connection.add_on_addr_callback(self.save_peers)
        for callback in self._on_transaction_hash_callback:
            connection.add_on_transaction_hash_callback(callback)
        for callback in self._on_transaction_callback:
            connection.add_on_transaction_callback(callback)
        for callback in self._on_block_callback:
            connection.add_on_blocks_callback(callback)

    async def get(self,
                  inv_item: InvItem,
                  peers=None,
                  timeout=None,
                  privileged=False):
        batcher = self._batcher_factory()
        connections = []
        s = time.time()
        Logger.p2p.debug('Fetching InvItem %s', inv_item)
        future = None
        try:
            async with async_timeout.timeout(
                    timeout if timeout is not None else self._batcher_timeout):
                connections = privileged and self._pick_privileged_connections(
                    peers if peers is not None else
                    (2 if len(self.established_connections) >= 2 else 1
                     )) or []
                connections = connections or self._pick_multiple_connections(
                    peers if peers is not None else (
                        2 if len(self.established_connections) >= 2 else 1))
                for connection in connections:
                    Logger.p2p.debug('Adding connection %s to batcher',
                                     connection.hostname)
                    await batcher.add_peer(connection.peer_event_handler)
                future = await batcher.inv_item_to_future(inv_item)
                response = await future
                Logger.p2p.debug('InvItem %s fetched in %ss', inv_item,
                                 round(time.time() - s, 4))
                for connection in connections:
                    connection.add_success()
                return response and response
        except asyncio.TimeoutError as error:
            for connection in connections:
                connection.add_error()
            Logger.p2p.debug(
                'Error in get InvItem %s, error: %s, failed in %ss from peers %s',
                inv_item, str(error), round(time.time() - s, 4), ', '.join([
                    '{} ({})'.format(x.hostname, len(x.errors))
                    for x in connections
                ]))
            future and future.cancel()
        finally:
            try:
                _ = [
                    self._busy_peers.remove(connection.hostname)
                    for connection in connections
                ]
                del connections
            except KeyError as e:
                Logger.p2p.debug('Peer %s already removed from busy peers',
                                 str(e))

            def del_batcher(_b):
                try:
                    _b.stop()
                    del _b._inv_item_future_queue
                    del _b._inv_item_hash_to_future[str(inv_item)]
                except:
                    del _b

            self.loop.run_in_executor(None, lambda: del_batcher(batcher))

    async def on_peer_connected(self, peer):
        Logger.p2p.debug('on_peer_connected: %s', peer.hostname)
        await peer.getaddr()

    async def save_peers(self, data):
        await self._storage_lock.acquire()
        try:
            self._peers = self.servers_storage(data)
        finally:
            self._storage_lock.release()

    async def get_from_connection(self, connection, inv_item):
        batcher = self._batcher_factory()
        await batcher.add_peer(connection.peer_event_handler)
        future = None
        try:
            future = await batcher.inv_item_to_future(inv_item)
            response = await future
            return response
        finally:
            future and future.cancel()