Skip to content

New Channel Subscriptions

James edited this page Oct 28, 2018 · 2 revisions

New channel subscriptions can be performed on a Libbitcoin p2p object by calling the p2p::connection_subscription(connect_handler handler) method. Connect handlers are then invoked whenever a new channel has been negotiated with a peer.

The overview below illustrates when the handler is invoked during the different sessions along the lifetime of a p2p peer object.

handler invocation during sessions

The p2p::connection_subscription(handler) method can be called anytime after p2p.start() has been called. The internal channel subscriber is started together with the p2p object, and is subsequently ready to register subscriptions.

New channel notifications begin after seed session

Note that the seed session is constructed with new channel notifications set to false. Therefore, even though channels are created and closed during the seeding session, no new channel notifications are sent to the channel subscriber.

Once the seed session is completed successfully, the p2p object will initiate manual peer connections and in-/outbound sessions. Newly created channels after this point onwards will cause connect handlers to be invoked.

New channel notification occurs after handshake

A closer look at the individual channel lifetime reveals when exactly new channel notification is created.

handler invocation during channel lifetime

The channel first negotiates the highest possible channel version during the version protocol with its peer. Upon successful completion of the handshake protocol, the channel subscriber is notified, and the channel is then attached to the ping, reject and address protocols, which maintain the channel until it is closed locally or remotely by the peer.

Let us review the connect_handler type:

typedef std::function<bool(const code&, channel::ptr)> connect_handler;

When a connect handler is called, the new channel pointer is passed in as an argument, allowing for channel methods to be called. In the following example, we subscribe to all received messages of type message_type on the newly created channel.

channel->subscribe<message_type>(handler)

Our subscription handler above is of the Libbitcoin type message_handler<message_type>, which is defined as the following in the network library.

template <class Message>
using message_handler =
    std::function<bool(const code&, std::shared_ptr<const Message>)>;

If you wish to subscribe to channel specific messages from within the new channel subscriber handler, remember that no notifications are triggered until after the successful completion of the version protocol. Subscriptions to handshake messages version or verack are therefore never notified.

New Channel Subscription Example

In the following example, we subscribe to newly created channels from a p2p object, and attempt to fetch headers from each new peer.

  • Subscribe to new channels.
    • For each new channel:
      • Subscribe to headers message.
        • For each headers message received:
          • Print # of headers.
      • Send get_headers message.

Note that the p2p::connection_subscription() interface is not suited for defining a custom channel messaging protocol. Please refer to the custom protocol documentation article in this case.

The example below illustrates how to initiate a subscription to messages of a certain type on each active channel.

#include <bitcoin/network.hpp>

BC_USE_LIBBITCOIN_MAIN

int bc::main(int argc, char* argv[])
{
    // Create P2P object.
    // -------------------------------------------------------------------------

    // Single Outbound Channel.
    bc::network::settings settings(bc::config::settings::mainnet);
    settings.seeds.clear();
    settings.seeds.push_back({"mainnet1.libbitcoin.net",8333});
    settings.seeds.push_back({"mainnet2.libbitcoin.net",8333});
    settings.seeds.push_back({"mainnet3.libbitcoin.net",8333});
    settings.inbound_connections = 0; // default setting = 0.
    settings.outbound_connections = 1;  // default setting = 8.
    settings.host_pool_capacity = 100; // default setting = 0.
    settings.threads = 3; // default setting = 0.
    settings.services = bc::message::version::service::none; // default none.

    // Create P2P object.
    bc::network::p2p my_p2p(settings);

    // Promise which signals completion to main loop (below).
    std::promise<bc::code> promise;

    // Handlers
    // -------------------------------------------------------------------------

    const auto send_handler = [](const bc::code& ec)
        {
            bc::cout << std::string("[My App] Get headers message sent: ")
                .append(ec.message()) << std::endl;
        };

    const auto headers_subscription_handler = [](const bc::code& ec,
        bc::message::headers::const_ptr message)
    {
        bc::cout << std::string("[My App] Headers message received with ")
            .append(std::to_string(message->elements().size()))
            .append(" headers") << std::endl;

        // Note: We do not attempt to request further peer headers
        // from this subscription_handler, since its channel pointer is
        // not accessible.

        return true; // Resubscribe
    };

    const auto connect_handler = [&headers_subscription_handler, &send_handler](
        const bc::code& ec, bc::network::channel::ptr channel)
    {
        // Resubscribe if connect error occurs.
        if (ec)
            return true;

        // Get negotiated version.
        bc::cout << std::string("[My App] New channel with version: ")
            .append(std::to_string(channel->peer_version()->value()))
            << std::endl;

        // Depending on version, get_blocks or get_headers.
        if (channel->peer_version()->value() >=
            bc::message::version::level::headers)
        {

            // Subscribe to headers message.
            channel->subscribe<bc::message::headers>(
                  headers_subscription_handler);

            // Send get_headers message.
            const bc::hash_list
                genesis_hashlist({bc::chain::block::genesis_testnet().hash()});
            bc::message::get_headers get_headers_msg(genesis_hashlist, {});
            channel->send(get_headers_msg, send_handler);
        }

        else
        {
            // We reject peer that do not support headers first sync.
            channel->stop(bc::error::channel_stopped);
        }

        // Resubscribe
        return true;
    };

    const auto run_handler = [&my_p2p](const bc::code& ec)
    {
        // Omitted.
    };

    const auto start_handler = [&my_p2p, &promise, &connect_handler,
        &run_handler](const bc::code& ec)
    {
        if (ec && ec != bc::error::peer_throttling)
        {
            promise.set_value(ec);
        }

        // Network run.
        my_p2p.subscribe_connection(connect_handler);
        my_p2p.run(run_handler);
    };

    // Start P2P object.
    // -------------------------------------------------------------------------

    my_p2p.start(start_handler);

    std::cout << promise.get_future().get().message() << std::endl;

    return 0;
}

This results in the following command line output:

[2018-10-24 20:04:40.470252] [0x00007fffb5a373c0] [info]    Starting manual session.
[2018-10-24 20:04:40.470671] [0x0000700001d62000] [info]    Contacting seed [mainnet1.libbitcoin.net:8333]
[2018-10-24 20:04:40.470775] [0x0000700001d62000] [info]    Contacting seed [mainnet2.libbitcoin.net:8333]
[2018-10-24 20:04:40.470798] [0x0000700001d62000] [info]    Contacting seed [mainnet3.libbitcoin.net:8333]
...
[2018-10-24 20:04:41.621146] [0x0000700001cdf000] [info]    Seed channel stopped: success
...
[My App] New channel with version: 70015
...
[My App] Get headers message sent: success
...
[My App] Headers message received with 2000 headers
...