Skip to content

Heartbeat Service

James Chiang edited this page Aug 20, 2018 · 2 revisions

The Libbitcoin Server provides a dedicated endpoint to serve a heartbeat to subscribing clients. The heartbeat is a dedicated publishing service and separate from the query and other subscription services: It therefore provides an efficient way to monitor server availability, yet cannot guarantee that other separate services are running as expected.

The heartbeat subscription service is provided with a ZMQ publisher socket, which allows for multiple subscribing clients. The client application connects to the heartbeat service with a ZMQ SUB or XSUB socket, which can also subscribe to multiple subscription services simultaneously.

requester-request-response

Note: If subscribed to multiple subscription services directly or indirectly through a proxy, it is not possible to uniquely correlate a received message and its publisher. In this case, it is helpful to use a proxy in between which adds an server-specific identifier message frame.

Subscribing with a ZMQ XSUB socket: Unlike with the ZMQ SUB socket type, subscribing to the publishing socket with a XSUB socket will require the XSUB socket to actively "subscribe", by sending a single-frame message to the publishing socket:

[0x01][filter]

The single "1" byte indicates a subscription to the ZMQ PUB socket. The filter is matched against the beginning of the first frame of the published messages, so that the subscriber only receives messages of interest.

For the our script example below, we will simply use the ZMQ SUB socket type, which requires no active subscription message sent to the publisher.

Heartbeat Service Messages

Heartbeat service messages consist of 2 message frames, both encoded in little endian.

[--- 2-Byte sequence ----]  
[-- 8-Byte blockheight --]

Note: The message sequence increments for each published heartbeat, and wraps around once the maximum value is reached. Missing sequence values may indicate reconnects in the socket connection, as it would otherwise mean messages queuing beyond the high water mark at the subscriber, which is unlikely given low frequency of heartbeats.

Heartbeat Subscription Example

Subscribing to the heartbeat subscription service with a ZMQ SUB socket merely requires connecting the socket object to the service endpoint. The subscription process is hidden from the application.

The zmq subscriber socket from the bc::protocol::zmq wrapper is initialised with the subscription filter set to subscribe_all by default.

#include <iostream>
#include <bitcoin/protocol.hpp> // Version 3.
#include <bitcoin/bitcoin.hpp>  // Version 3.

int main() {

    bc::protocol::zmq::context my_context(true);

    bc::protocol::zmq::socket my_subscriber(
        my_context,
        bc::protocol::zmq::socket::role::subscriber
    );


    bc::config::endpoint public_endpoint("tcp://testnet2.libbitcoin.net:19092");
    bc::code ec;
    ec = my_subscriber.connect(public_endpoint);

    bc::protocol::zmq::poller my_poller;
    my_poller.add(my_subscriber);
    bc::protocol::zmq::identifier my_subscriber_id = my_subscriber.id();
    bc::protocol::zmq::identifiers socket_ids;

    while (true)
    {
        socket_ids = my_poller.wait(100);
        if (socket_ids.contains(my_subscriber_id))
        {

            bc::protocol::zmq::message heartbeat_message;
            heartbeat_message.receive(my_subscriber);
            std::cout << "------------------" << std::endl;
            std::cout << "Heartbeat received" << std::endl;

            bc::data_chunk sequence_chunk;
            heartbeat_message.dequeue(sequence_chunk);
            uint16_t sequence = sequence_chunk[0] | (sequence_chunk[1] << 8);
            std::cout << "sequence: " << sequence << std::endl;

            bc::data_chunk height_chunk;
            heartbeat_message.dequeue(height_chunk);
            bc::data_source height_byte_stream(height_chunk);
            bc::istream_reader height_byte_stream_reader(height_byte_stream);
            auto height = height_byte_stream_reader.read_8_bytes_little_endian();
            std::cout << "latest height: " << height << std::endl;

        }
    }

    return 0;
}

Console output:

------------------
Heartbeat received
sequence: 2388
latest height: 1384502
------------------
Heartbeat received
sequence: 2389
latest height: 1384502
------------------
Heartbeat received
sequence: 2390
latest height: 1384502
Clone this wiki locally