Esempio n. 1
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
Esempio n. 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;

#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