Skip to content

Transaction Service

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

The Libbitcoin Server provides a dedicated service which publishes new transactions accepted in the transaction pool of the Bitcoin node. Once the same transaction is confirmed in a strong chain block, it is broadcast again.

The ZMQ socket connection setup for subscribing to this service follows that of the heartbeat service.

Transaction Service Messages

Published transaction service messages consist of 2 message frames. The sequence is little endian encoded, and the serialised transaction data is encoded in the Bitcoin over-the-wire format.

[----- 2-byte sequence ------]  
[-- serialised transaction --]

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, or dropped messages at the publisher or subscriber socket, if the high water mark is reached on either side.

Example: Parsing Newly Accepted Transactions

In the following example, we will subscribe to all newly accepted transactions to the transaction pool, and parse the individual transaction output scripts for common script patterns.

We will subscribe with a ZMQ SUB socket ⑴, which in the bc::protocol::zmq library is initialised with an empty filter and will not filter any messages sent by the publisher.

The subscribing socket is connected to the publishing endpoint ⑵, and subsequently can begin receiving ⑶ messages. Please consider the expected rate of published messages, as a message queue can build up if the subscribing socket is receiving at a slower rate, eventually leading to dropped messages if the high water mark is reached. This can be observed in gaps between received sequence ⑷ values.

Parsing the received transaction data ⑸ is easily done with the bc::chain::transaction class from the libbitcoin-system library, which is directly intialized with the transaction data in the over-the-wire format. We then extract the output list and iterate through each one ⑹, where the script operations ⑺ are referenced to a series of bc::chain::script methods, each checking for common output script patterns.

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

int main() {

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

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


    // (2)
    bc::config::endpoint public_endpoint("tcp://mainnet2.libbitcoin.net:9094");    
    bc::code ec;                     
    ec = my_subscriber.connect(public_endpoint);
    std::cout << ec.message() << std::endl;


    while (true)
    {
        // (3)
        bc::protocol::zmq::message transaction_message;
        transaction_message.receive(my_subscriber);                     

        // (4)
        bc::data_chunk sequence_chunk;
        transaction_message.dequeue(sequence_chunk);
        uint16_t sequence = sequence_chunk[0] | (sequence_chunk[1] << 8);

        bc::data_chunk transaction_chunk;
        transaction_message.dequeue(transaction_chunk);
        bc::chain::transaction published_transaction;

        // (5)
        published_transaction.from_data(transaction_chunk,true,true);
        auto outputs = published_transaction.outputs();

        std::cout << "--------------------" << std::endl;
        std::cout << "[" << sequence << "]" << std::endl;

        std::cout << "Output Script Patterns: " << std::endl;

        // (6)
        for (const bc::chain::output& output : outputs)                 
        {   
            // (7)
            auto script_ops = output.script().operations();

            if (bc::chain::script::is_witness_program_pattern(script_ops))
            {

                if (bc::chain::script::is_pay_witness_script_hash_pattern(
                      script_ops))
                {
                    std::cout <<  "  p2wsh" << std::endl;
                }

                else
                {
                    // Assume witness-public-key-hash-pattern.
                    // (Assumption can be broken with future consensus updates)
                    std::cout <<  "  p2wpkh" << std::endl;
                }
            }

            else if(bc::chain::script::is_pay_key_hash_pattern(script_ops))
            {
                std::cout <<  "  p2pkh" << std::endl;
            }

            else if(bc::chain::script::is_pay_script_hash_pattern(script_ops))
            {
                std::cout <<  "  p2sh" << std::endl;
            }

            else if(bc::chain::script::is_pay_multisig_pattern(script_ops))
            {
                std::cout <<  "  p2sh" << std::endl;
            }

            else if(bc::chain::script::is_null_data_pattern(script_ops))
            {
                std::cout <<  "  op_return" << std::endl;
            }

            else
            {
                std::cout <<  "  other" << std::endl;
            }

        }

    }

    return 0;
}

Console output:

--------------------
[13821]
Output Script Patterns:
  p2pkh
  p2pkh
--------------------
[13822]
Output Script Patterns:
  p2pkh
  p2sh
  op_return
--------------------
[13823]
Output Script Patterns:
  p2sh
  p2sh
--------------------
[13824]
Output Script Patterns:
  p2wpkh
  p2sh
--------------------
[13825]
Output Script Patterns:
  p2pkh
  p2pkh
  op_return
--------------------
[13826]
Output Script Patterns:
  p2sh
  p2pkh
Clone this wiki locally