示例#1
0
namespace obelisk {

using namespace bc;

namespace posix_time = boost::posix_time;
using posix_time::seconds;
using posix_time::microsec_clock;

constexpr size_t request_retries = 3;
const posix_time::time_duration request_timeout_init = seconds(30);

backend_cluster::backend_cluster(threadpool& pool,
    zmq::context_t& context, const std::string& connection)
  : context_(context), socket_(context_, ZMQ_DEALER), strand_(pool)
{
    socket_.connect(connection.c_str());
    // Configure socket to not wait at close time.
    int linger = 0;
    socket_.setsockopt(ZMQ_LINGER, &linger, sizeof (linger));
}

void backend_cluster::request(
    const std::string& command, const data_chunk& data,
    response_handler handle, const worker_uuid& dest)
{
    request_container request{
        microsec_clock::universal_time(), request_timeout_init,
        request_retries, outgoing_message(dest, command, data)};
    auto insert_request = [this, handle, request]
    {
        handlers_[request.message.id()] = handle;
        retry_queue_[request.message.id()] = request;
        send(request.message);
    };
    strand_.randomly_queue(insert_request);
}
void backend_cluster::send(const outgoing_message& message)
{
    message.send(socket_);
}

void backend_cluster::update()
{
    //  Poll socket for a reply, with timeout
    zmq::pollitem_t items[] = { { socket_, 0, ZMQ_POLLIN, 0 } };
    zmq::poll(&items[0], 1, 0);
    //  If we got a reply, process it
    if (items[0].revents & ZMQ_POLLIN)
        receive_incoming();
    // Finally resend any expired requests that we didn't get
    // a response to yet.
    strand_.randomly_queue(
        &backend_cluster::resend_expired, this);
}

void backend_cluster::append_filter(
    const std::string& command, response_handler filter)
{
    strand_.randomly_queue(
        [this, command, filter]
        {
            filters_[command] = filter;
        });
}

void backend_cluster::receive_incoming()
{
    incoming_message response;
    if (!response.recv(socket_))
        return;
    strand_.randomly_queue(
        &backend_cluster::process, this, response);
}

void backend_cluster::process(const incoming_message& response)
{
    if (process_filters(response))
        return;
    if (process_as_reply(response))
        return;
}
bool backend_cluster::process_filters(const incoming_message& response)
{
    auto filter_it = filters_.find(response.command());
    if (filter_it == filters_.end())
        return false;
    filter_it->second(response.data(), response.origin());
    return true;
}
bool backend_cluster::process_as_reply(const incoming_message& response)
{
    auto handle_it = handlers_.find(response.id());
    // Unknown response. Not in our map.
    if (handle_it == handlers_.end())
        return false;
    handle_it->second(response.data(), response.origin());
    handlers_.erase(handle_it);
    size_t n_erased = retry_queue_.erase(response.id());
    BITCOIN_ASSERT(n_erased == 1);
    return true;
}

void backend_cluster::resend_expired()
{
    const posix_time::ptime now = microsec_clock::universal_time();
    for (auto& retry: retry_queue_)
    {
        request_container& request = retry.second;
        if (now - request.timestamp < request.timeout)
            continue;
        if (request.retries_left == 0)
        {
            // Unhandled... Appears there's a problem with the server.
            request.retries_left = request_retries;
            return;
        }
        request.timeout *= 2;
        --request.retries_left;
        // Resend request again.
        request.timestamp = now;
        send(request.message);
    }
}

} // namespace obelisk
示例#2
0
namespace obelisk {

using namespace bc;
using std::placeholders::_1;
using std::placeholders::_2;
namespace posix_time = boost::posix_time;
using posix_time::minutes;
using posix_time::second_clock;

const posix_time::time_duration sub_expiry = minutes(10);

void register_with_node(subscribe_manager& manager, node_impl& node)
{
    auto recv_blk = [&manager](size_t height, const block_type& blk)
    {
        const hash_digest blk_hash = hash_block_header(blk.header);
        for (const transaction_type& tx: blk.transactions)
            manager.submit(height, blk_hash, tx);
    };
    auto recv_tx = [&manager](const transaction_type& tx)
    {
        manager.submit(0, null_hash, tx);
    };
    node.subscribe_blocks(recv_blk);
    node.subscribe_transactions(recv_tx);
}

subscribe_manager::subscribe_manager(node_impl& node)
  : strand_(node.memory_related_threadpool())
{
    // subscribe to blocks and txs -> submit
    register_with_node(*this, node);
}

bool deserialize_address(payment_address& addr, const data_chunk& data)
{
    auto deserial = make_deserializer(data.begin(), data.end());
    try
    {
        uint8_t version_byte = deserial.read_byte();
        short_hash hash = deserial.read_short_hash();
        addr.set(version_byte, hash);
    }
    catch (end_of_stream)
    {
        return false;
    }
    if (deserial.iterator() != data.end())
        return false;
    return true;
}

void subscribe_manager::subscribe(
    const incoming_message& request, queue_send_callback queue_send)
{
    strand_.queue(
        &subscribe_manager::do_subscribe, this, request, queue_send);
}
std::error_code subscribe_manager::add_subscription(
    const incoming_message& request, queue_send_callback queue_send)
{
    payment_address addr_key;
    if (!deserialize_address(addr_key, request.data()))
    {
        log_warning(LOG_SUBSCRIBER) << "Incorrect format for subscribe data.";
        return error::bad_stream;
    }
    // Now create subscription.
    const posix_time::ptime now = second_clock::universal_time();
    // Limit absolute number of subscriptions to prevent exhaustion attacks.
    if (subs_.size() >= subscribe_limit_)
        return error::pool_filled;
    subs_.emplace(addr_key, subscription{
        now + sub_expiry, request.origin(), queue_send});
    return std::error_code();
}
void subscribe_manager::do_subscribe(
    const incoming_message& request, queue_send_callback queue_send)
{
    std::error_code ec = add_subscription(request, queue_send);
    // Send response.
    data_chunk result(4);
    auto serial = make_serializer(result.begin());
    write_error_code(serial, ec);
    outgoing_message response(request, result);
    queue_send(response);
}

void subscribe_manager::renew(
    const incoming_message& request, queue_send_callback queue_send)
{
    strand_.randomly_queue(
        &subscribe_manager::do_renew, this, request, queue_send);
}
void subscribe_manager::do_renew(
    const incoming_message& request, queue_send_callback queue_send)
{
    payment_address addr_key;
    if (!deserialize_address(addr_key, request.data()))
    {
        log_warning(LOG_SUBSCRIBER) << "Incorrect format for subscribe renew.";
        return;
    }
    const posix_time::ptime now = second_clock::universal_time();
    // Find entry and update expiry_time.
    auto range = subs_.equal_range(addr_key);
    for (auto it = range.first; it != range.second; ++it)
    {
        subscription& sub = it->second;
        // Only update subscriptions which were created by
        // the same client as this request originated from.
        if (sub.client_origin != request.origin())
            continue;
        // Future expiry time.
        sub.expiry_time = now + sub_expiry;
    }
    // Send response.
    data_chunk result(4);
    auto serial = make_serializer(result.begin());
    write_error_code(serial, std::error_code());
    outgoing_message response(request, result);
    queue_send(response);
}

void subscribe_manager::submit(
    size_t height, const bc::hash_digest& block_hash,
    const bc::transaction_type& tx)
{
    strand_.queue(
        &subscribe_manager::do_submit, this, height, block_hash, tx);
}
void subscribe_manager::do_submit(
    size_t height, const bc::hash_digest& block_hash,
    const bc::transaction_type& tx)
{
    for (const transaction_input_type& input: tx.inputs)
    {
        payment_address addr;
        if (!extract(addr, input.script))
            continue;
        post_updates(addr, height, block_hash, tx);
    }
    for (const transaction_output_type& output: tx.outputs)
    {
        payment_address addr;
        if (!extract(addr, output.script))
            continue;
        post_updates(addr, height, block_hash, tx);
    }
    // Periodicially sweep old expired entries.
    // Use the block 10 minute window as a periodic trigger.
    if (height)
        sweep_expired();
}
void subscribe_manager::post_updates(const payment_address& address,
    size_t height, const bc::hash_digest& block_hash,
    const bc::transaction_type& tx)
{
    auto range = subs_.equal_range(address);
    // Avoid expensive serialization if not needed.
    if (range.first == range.second)
        return;
    // [ addr,version ] (1 byte)
    // [ addr.hash ] (20 bytes)
    // [ height ] (4 bytes)
    // [ block_hash ] (32 bytes)
    // [ tx ]
    constexpr size_t info_size = 1 + short_hash_size + 4 + hash_size;
    data_chunk data(info_size + satoshi_raw_size(tx));
    auto serial = make_serializer(data.begin());
    serial.write_byte(address.version());
    serial.write_short_hash(address.hash());
    serial.write_4_bytes(height);
    serial.write_hash(block_hash);
    BITCOIN_ASSERT(serial.iterator() == data.begin() + info_size);
    // Now write the tx part.
    auto rawtx_end_it = satoshi_save(tx, serial.iterator());
    BITCOIN_ASSERT(rawtx_end_it == data.end());
    // Send the result to everyone interested.
    for (auto it = range.first; it != range.second; ++it)
    {
        const subscription& sub_detail = it->second;
        outgoing_message update(
            sub_detail.client_origin, "address.update", data);
        sub_detail.queue_send(update);
    }
}

void subscribe_manager::sweep_expired()
{
    // Delete entries that have expired.
    const posix_time::ptime now = second_clock::universal_time();
    for (auto it = subs_.begin(); it != subs_.end(); )
    {
        const subscription& sub_detail = it->second;
        // Already expired? If so, then erase.
        if (sub_detail.expiry_time < now)
        {
            log_debug(LOG_SUBSCRIBER) << "Deleting expired subscription: "
                << it->first.encoded() << " from " << sub_detail.client_origin;
            it = subs_.erase(it);
        }
        else
            ++it;
    }
}

} // namespace obelisk
示例#3
0
namespace obelisk {

using namespace bc;
using std::placeholders::_1;
namespace posix_time = boost::posix_time;
using posix_time::milliseconds;
using posix_time::seconds;
using posix_time::microsec_clock;

const posix_time::time_duration heartbeat_interval = milliseconds(4000);
// Milliseconds
constexpr long poll_sleep_interval = 1000;

auto now = []() { return microsec_clock::universal_time(); };

send_worker::send_worker(czmqpp::context& context)
  : context_(context)
{
}
void send_worker::queue_send(const outgoing_message& message)
{
    czmqpp::socket socket(context_, ZMQ_PUSH);
    BITCOIN_ASSERT(socket.self());
    int rc = socket.connect("inproc://trigger-send");
    BITCOIN_ASSERT(rc == 0);
    message.send(socket);
    socket.destroy(context_);
}

request_worker::request_worker()
  : socket_(context_, ZMQ_ROUTER),
    auth_(context_),
    wakeup_socket_(context_, ZMQ_PULL),
    heartbeat_socket_(context_, ZMQ_PUB),
    sender_(context_)
{
    BITCOIN_ASSERT(socket_.self());
    BITCOIN_ASSERT(wakeup_socket_.self());
    BITCOIN_ASSERT(heartbeat_socket_.self());
    int rc = wakeup_socket_.bind("inproc://trigger-send");
    BITCOIN_ASSERT(rc != -1);
}
bool request_worker::start(config_type& config)
{
    // Load config values.
    log_requests_ = config.log_requests;
    if (log_requests_)
        auth_.set_verbose(true);
    if (!config.whitelist.empty())
        whitelist(config.whitelist);
    if (config.certificate.empty())
        socket_.set_zap_domain("global");
    else
        enable_crypto(config);
    // Start ZeroMQ sockets.
    create_new_socket(config);
    log_debug(LOG_WORKER) << "Heartbeat: " << config.heartbeat;
    heartbeat_socket_.bind(config.heartbeat);
    // Timer stuff
    heartbeat_at_ = now() + heartbeat_interval;
    return true;
}
void request_worker::stop()
{
}

void request_worker::whitelist(config_type::ipaddress_list& addrs)
{
    for (const std::string& ip_address: addrs)
        auth_.allow(ip_address);
}
void request_worker::enable_crypto(config_type& config)
{
    if (config.client_allowed_certs == "ALLOW_ALL_CERTS")
        auth_.configure_curve("*", CURVE_ALLOW_ANY);
    else
        auth_.configure_curve("*", config.client_allowed_certs);
    cert_.reset(czmqpp::load_cert(config.certificate));
    cert_.apply(socket_);
    socket_.set_curve_server(1);
}
void request_worker::create_new_socket(config_type& config)
{
    log_debug(LOG_WORKER) << "Listening: " << config.service;
    // Set the socket identity name.
    if (!config.name.empty())
        socket_.set_identity(config.name.c_str());
    // Connect...
    socket_.bind(config.service);
    // Configure socket to not wait at close time
    socket_.set_linger(0);
    // Tell queue we're ready for work
    log_info(LOG_WORKER) << "worker ready";
}

void request_worker::attach(
    const std::string& command, command_handler handler)
{
    handlers_[command] = handler;
}

void request_worker::update()
{
    poll();
}

void request_worker::poll()
{
    // Poll for network updates.
    czmqpp::poller poller(socket_, wakeup_socket_);
    BITCOIN_ASSERT(poller.self());
    czmqpp::socket which = poller.wait(poll_sleep_interval);

    BITCOIN_ASSERT(socket_.self() && wakeup_socket_.self());
    if (which == socket_)
    {
        // Get message
        // 6-part envelope + content -> request
        incoming_message request;
        request.recv(socket_);

        auto it = handlers_.find(request.command());
        // Perform request if found.
        if (it != handlers_.end())
        {
            if (log_requests_)
                log_debug(LOG_REQUEST)
                    << request.command() << " from " << request.origin();
            it->second(request,
                std::bind(&send_worker::queue_send, &sender_, _1));
        }
        else
        {
            log_warning(LOG_WORKER)
                << "Unhandled request: " << request.command()
                << " from " << request.origin();
        }
    }
    else if (which == wakeup_socket_)
    {
        // Send queued message.
        czmqpp::message message;
        message.receive(wakeup_socket_);
        message.send(socket_);
    }

    // Publish heartbeat.
    if (now() > heartbeat_at_)
    {
        heartbeat_at_ = now() + heartbeat_interval;
        log_debug(LOG_WORKER) << "Sending heartbeat";
        publish_heartbeat();
    }
}

void request_worker::publish_heartbeat()
{
    static uint32_t counter = 0;
    czmqpp::message message;
    data_chunk raw_counter = to_data_chunk(to_little_endian(counter));
    message.append(raw_counter);
    message.send(heartbeat_socket_);
    ++counter;
}

} // namespace obelisk
示例#4
0
int
main() 
{
  using namespace boost;
  using namespace local_time;
  using namespace gregorian;
  using posix_time::time_duration;

  /***** custom_time_zone *****/
  
  // create the dependent objects for a custom_time_zone
  time_zone_names tzn("Eastern Standard Time", "EST",
                      "Eastern Daylight Time", "EDT");
  time_duration utc_offset(-5,0,0);
  dst_adjustment_offsets adj_offsets(time_duration(1,0,0), 
                                     time_duration(2,0,0), 
                                     time_duration(2,0,0));
  // rules for this zone are:
  // start on first Sunday of April at 2 am
  // end on last Sunday of October at 2 am
  // so we use a first_last_dst_rule
  first_day_of_the_week_in_month start_rule(Sunday, Apr);
  last_day_of_the_week_in_month    end_rule(Sunday, Oct);
  shared_ptr<dst_calc_rule> nyc_rules(new first_last_dst_rule(start_rule, 
                                                              end_rule));
  // create more dependent objects for a non-dst custom_time_zone
  time_zone_names tzn2("Mountain Standard Time", "MST",
                       "", ""); // no dst means empty dst strings
  time_duration utc_offset2(-7,0,0);
  dst_adjustment_offsets adj_offsets2(time_duration(0,0,0), 
                                      time_duration(0,0,0), 
                                      time_duration(0,0,0));
  // no dst means we need a null pointer to the rules
  shared_ptr<dst_calc_rule> phx_rules;

  // create the custom_time_zones
  time_zone_ptr nyc_1(new custom_time_zone(tzn, utc_offset, adj_offsets, nyc_rules));
  time_zone_ptr phx_1(new custom_time_zone(tzn2, utc_offset2, adj_offsets2, phx_rules));

  /***** posix_time_zone *****/

  // create posix_time_zones that are the duplicates of the 
  // custom_time_zones created above. See posix_time_zone documentation 
  // for details on full zone names.
  std::string nyc_string, phx_string;
  nyc_string = "EST-05:00:00EDT+01:00:00,M4.1.0/02:00:00,M10.5.0/02:00:00";
  // nyc_string = "EST-05EDT,M4.1.0,M10.5.0"; // shorter when defaults used
  phx_string = "MST-07"; // no-dst
  time_zone_ptr nyc_2(new posix_time_zone(nyc_string));
  time_zone_ptr phx_2(new posix_time_zone(phx_string));
 

  /***** show the sets are equal *****/

  std::cout << "The first zone is in daylight savings from:\n " 
    << nyc_1->dst_local_start_time(2004) << " through "
    << nyc_1->dst_local_end_time(2004) << std::endl;

  std::cout << "The second zone is in daylight savings from:\n " 
    << nyc_2->dst_local_start_time(2004) << " through "
    << nyc_2->dst_local_end_time(2004) << std::endl;

  std::cout << "The third zone (no daylight savings):\n " 
    << phx_1->std_zone_abbrev() << " and "
    << phx_1->base_utc_offset() << std::endl;

  std::cout << "The fourth zone (no daylight savings):\n " 
    << phx_2->std_zone_abbrev() << " and "
    << phx_2->base_utc_offset() << std::endl;

  return 0;
}
示例#5
0
namespace obelisk {

using namespace bc;
using std::placeholders::_1;
using std::placeholders::_2;
namespace posix_time = boost::posix_time;
using posix_time::minutes;
using posix_time::second_clock;

#define LOG_SUBSCRIBER "subscriber"

const posix_time::time_duration sub_renew = minutes(2);

subscriber_part::subscriber_part(czmqpp::context& context)
  : socket_block_(context, ZMQ_SUB), socket_tx_(context, ZMQ_SUB)
{
}

bool subscriber_part::setup_socket(
    const std::string& connection, czmqpp::socket socket)
{
    if (!socket.connect(connection))
    {
        log_warning(LOG_SUBSCRIBER)
            << "Subscriber failed to connect: " << connection;
        return false;
    }
    socket.set_subscribe("");
    return true;
}

bool subscriber_part::subscribe_blocks(const std::string& connection,
    block_notify_callback notify_block)
{
    if (!setup_socket(connection, socket_block_))
        return false;
    notify_block_ = notify_block;
    return true;
}

bool subscriber_part::subscribe_transactions(const std::string& connection,
    transaction_notify_callback notify_tx)
{
    if (!setup_socket(connection, socket_tx_))
        return false;
    notify_tx_ = notify_tx;
    return true;
}

void subscriber_part::update()
{
    czmqpp::poller poller;
    if (socket_tx_.self())
        poller.add(socket_tx_);
    if (socket_block_.self())
        poller.add(socket_block_);
    czmqpp::socket which = poller.wait(0);
    //  Poll socket for a reply, with timeout
    if (socket_tx_.self() && which == socket_tx_)
        recv_tx();
    if (socket_block_.self() && which == socket_block_)
        recv_block();
}

bool read_hash(hash_digest& hash, const data_chunk& raw_hash)
{
    if (raw_hash.size() != hash.size())
    {
        log_warning(LOG_SUBSCRIBER) << "Wrong size for hash. Dropping.";
        return false;
    }
    std::copy(raw_hash.begin(), raw_hash.end(), hash.begin());
    return true;
}

void subscriber_part::recv_tx()
{
    czmqpp::message message;
    bool success = message.receive(socket_tx_);
    BITCOIN_ASSERT(success);
    // [ tx hash ]
    // [ raw tx ]
    const data_stack& parts = message.parts();
    if (parts.size() != 2)
    {
        log_warning(LOG_SUBSCRIBER) << "Malformed tx response. Dropping.";
        return;
    }
    hash_digest tx_hash;
    if (!read_hash(tx_hash, parts[0]))
        return;
    const data_chunk& raw_tx = parts[1];
    transaction_type tx;
    satoshi_load(raw_tx.begin(), raw_tx.end(), tx);
    if (hash_transaction(tx) != tx_hash)
    {
        log_warning(LOG_SUBSCRIBER)
            << "Tx hash and actual tx unmatched. Dropping.";
        return;
    }
    // Everything OK!
    notify_tx_(tx);
}

void subscriber_part::recv_block()
{
    czmqpp::message message;
    bool success = message.receive(socket_block_);
    BITCOIN_ASSERT(success);
    // [ block hash ]
    // [ height ]
    // [ block data ]
    const data_stack& parts = message.parts();
    if (parts.size() != 3)
    {
        log_warning(LOG_SUBSCRIBER) << "Malformed block response. Dropping.";
        return;
    }
    hash_digest blk_hash;
    if (!read_hash(blk_hash, parts[0]))
        return;
    uint32_t height = cast_chunk<uint32_t>(parts[1]);
    const data_chunk& raw_blk = parts[2];
    block_type blk;
    satoshi_load(raw_blk.begin(), raw_blk.end(), blk);
    if (hash_block_header(blk.header) != blk_hash)
    {
        log_warning(LOG_SUBSCRIBER)
            << "Block hash and actual block unmatched. Dropping.";
        return;
    }
    // Everything OK!
    notify_block_(height, blk);
}

address_subscriber::address_subscriber(
    threadpool& pool, backend_cluster& backend)
  : backend_(backend), strand_(pool),
    last_renew_(second_clock::universal_time())
{
    backend_.append_filter("address.update",
        strand_.wrap(&address_subscriber::receive_update,
            this, _1, _2));
}

void address_subscriber::subscribe(const payment_address& address,
    update_handler handle_update, subscribe_handler handle_subscribe)
{
    data_chunk data(1 + short_hash_size);
    auto serial = make_serializer(data.begin());
    serial.write_byte(address.version());
    serial.write_short_hash(address.hash());
    BITCOIN_ASSERT(serial.iterator() == data.end());
    backend_.request("address.subscribe", data,
        strand_.wrap(&address_subscriber::receive_subscribe_result,
            this, _1, _2, address, handle_update, handle_subscribe));
}
void address_subscriber::receive_subscribe_result(
    const data_chunk& data, const worker_uuid& worker,
    const payment_address& address,
    update_handler handle_update, subscribe_handler handle_subscribe)
{
    // Insert listener into backend.
    subs_.emplace(address,
        subscription{worker, handle_update});
    // We will periodically send subscription
    // update messages with the Bitcoin address.
    // Decode std::error_code indicating success.
    decode_reply(data, worker, handle_subscribe);
}
void address_subscriber::decode_reply(
    const data_chunk& data, const worker_uuid& worker,
    subscribe_handler handle_subscribe)
{
    std::error_code ec;
    BITCOIN_ASSERT(data.size() == 4);
    auto deserial = make_deserializer(data.begin(), data.end());
    if (!read_error_code(deserial, data.size(), ec))
        return;
    BITCOIN_ASSERT(deserial.iterator() == data.end());
    handle_subscribe(ec, worker);
}

void address_subscriber::receive_update(
    const data_chunk& data, const worker_uuid& worker)
{
    // Deserialize data -> address, height, block hash, tx
    constexpr size_t info_size = 1 + short_hash_size + 4 + hash_digest_size;
    auto deserial = make_deserializer(data.begin(), data.begin() + info_size);
    // [ addr,version ] (1 byte)
    uint8_t version_byte = deserial.read_byte();
    // [ addr.hash ] (20 bytes)
    short_hash addr_hash = deserial.read_short_hash();
    payment_address address(version_byte, addr_hash);
    // [ height ] (4 bytes)
    uint32_t height = deserial.read_4_bytes();
    // [ block_hash ] (32 bytes)
    const hash_digest blk_hash = deserial.read_hash();
    // [ tx ]
    BITCOIN_ASSERT(deserial.iterator() == data.begin() + info_size);
    transaction_type tx;
    satoshi_load(deserial.iterator(), data.end(), tx);
    post_updates(address, worker, height, blk_hash, tx);
}
void address_subscriber::post_updates(
    const bc::payment_address& address, const worker_uuid& worker,
    size_t height, const bc::hash_digest& blk_hash,
    const bc::transaction_type& tx)
{
    auto it = subs_.find(address);
    if (it == subs_.end())
        return;
    const subscription& sub = it->second;
    if (sub.worker != worker)
    {
        log_error(LOG_SUBSCRIBER)
            << "Server sent update from a different worker than expected.";
        return;
    }
    sub.handle_update(std::error_code(), height, blk_hash, tx);
}

void address_subscriber::update()
{
    auto renewal_sent = [](const data_chunk&, const worker_uuid&) {};
    // Loop through subscriptions, send renew packets.
    auto send_renew = [this, renewal_sent](
        const payment_address& address, const worker_uuid& worker)
    {
        data_chunk data(1 + short_hash_size);
        auto serial = make_serializer(data.begin());
        serial.write_byte(address.version());
        serial.write_short_hash(address.hash());
        BITCOIN_ASSERT(serial.iterator() == data.end());
        backend_.request("address.renew", data, renewal_sent, worker);
    };
    auto loop_subs = [this, send_renew]
    {
        for (const auto& keyvalue_pair: subs_)
            send_renew(keyvalue_pair.first, keyvalue_pair.second.worker);
    };
    const posix_time::ptime now = second_clock::universal_time();
    // Send renews...
    if (now - last_renew_ > sub_renew)
    {
        strand_.randomly_queue(loop_subs);
        last_renew_ = now;
    }
}

void address_subscriber::fetch_history(const payment_address& address,
    blockchain::fetch_handler_history handle_fetch,
    size_t from_height, const worker_uuid& worker)
{
    data_chunk data;
    wrap_fetch_history_args(data, address, from_height);
    backend_.request("address.fetch_history", data,
        std::bind(receive_history_result, _1, handle_fetch), worker);
}

fullnode_interface::fullnode_interface(
    threadpool& pool, const std::string& connection,
    const std::string& cert_filename, const std::string& server_pubkey)
  : backend_(pool, context_, connection, cert_filename, server_pubkey),
    blockchain(backend_), transaction_pool(backend_),
    protocol(backend_), address(pool, backend_),
    subscriber_(context_)
{
}

void fullnode_interface::update()
{
    backend_.update();
    subscriber_.update();
    // Address subcomponent.
    address.update();
}

bool fullnode_interface::subscribe_blocks(const std::string& connection,
    subscriber_part::block_notify_callback notify_block)
{
    return subscriber_.subscribe_blocks(connection, notify_block);
}
bool fullnode_interface::subscribe_transactions(const std::string& connection,
    subscriber_part::transaction_notify_callback notify_tx)
{
    return subscriber_.subscribe_transactions(connection, notify_tx);
}

} // namespace obelisk