Network IO and the DiscoveryStrategy

This document assumes you have a basic understanding of asyncio tasks, as documented in the tasks tutorial. You will learn how to use the IPv8’s DiscoveryStrategy class to avoid network congestion.

The DiscoveryStrategy

IPv8 only manages one socket (Endpoint), which is most likely using the UDP protocol. If every Community starts sending at the exact same time and overpowers the UDP socket, this causes packet drops. To counter this, IPv8 has the DiscoveryStrategy class.

An IPv8 instance will call each of its registered DiscoveryStrategy instances sequentially to avoid network I/O clashes. If you have an interval task in your TaskManager that leads to network I/O, you should consider converting it to a DiscoveryStrategy. You can make your own subclass as follows:

class MyDiscoveryStrategy(DiscoveryStrategy):

    def take_step(self) -> None:
        with self.walk_lock:
            # Insert your logic here. For example:
            if self.overlay.get_peers():
                peer = choice(self.overlay.get_peers())
                self.overlay.send_introduction_request(peer)

Note that a DiscoveryStrategy should be thread-safe. You can use the walk_lock for thread safety.

Using a DiscoveryStrategy

You can register your DiscoveryStrategy with a running IPv8 instance as follows:

def main(ipv8_instance: IPv8) -> None:
    overlay = ipv8_instance.get_overlay(MyCommunity)
    target_peers = -1
    ipv8_instance.add_strategy(overlay,
                               MyDiscoveryStrategy(overlay),
                               target_peers)

Note that we specify a target_peers argument. This argument specifies the amount of peers after which the DiscoveryStrategy should no longer be called. Calls will be resumed when the amount of peers in your Community dips below this value again. For example, the built-in RandomWalk strategy can be configured to stop finding new peers after if an overlay already has 20 or more peers. In this example we have used the magic value -1, which causes IPv8 to never stop calling this strategy.

You can also load your strategy through the configuration or loader. First, an example of how to do this with the configuration:

class MyCommunity(Community):
    community_id = os.urandom(20)

    def get_available_strategies(self) -> dict[str, type[DiscoveryStrategy]]:
        return {"MyDiscoveryStrategy": MyDiscoveryStrategy}


definition = {
    'strategy': "MyDiscoveryStrategy",
    'peers': -1,
    'init': {}
}

config = get_default_configuration()
config['overlays'] = [{
    'class': 'MyCommunity',
    'key': "anonymous id",
    'walkers': [definition],
    'bootstrappers': [DISPERSY_BOOTSTRAPPER.copy()],
    'initialize': {},
    'on_start': []
}]

Note that you can add as many strategies as you want to an overlay. Also note that for IPv8 to link the name "MyDiscoveryStrategy" to a class, you need to define it in your Community’s get_available_strategies() dictionary.

Lastly, alternatively, the way to add your custom MyDiscoveryStrategy class to a CommunityLauncher is as follows:

@overlay('my_module.some_submodule', 'MyCommunity')
@walk_strategy(MyDiscoveryStrategy)
class MyLauncher(CommunityLauncher):
    pass

This is the shortest way.