diff --git a/builds/gnu/Makefile.am b/builds/gnu/Makefile.am index 8daa8051..753dc613 100644 --- a/builds/gnu/Makefile.am +++ b/builds/gnu/Makefile.am @@ -77,6 +77,7 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/protocols/protocol_block_in_106.cpp \ ${srcdir}/../../src/protocols/protocol_block_in_31800.cpp \ ${srcdir}/../../src/protocols/protocol_block_out_106.cpp \ + ${srcdir}/../../src/protocols/protocol_block_out_70001.cpp \ ${srcdir}/../../src/protocols/protocol_block_out_70012.cpp \ ${srcdir}/../../src/protocols/protocol_filter_out_70015.cpp \ ${srcdir}/../../src/protocols/protocol_header_in_31800.cpp \ @@ -88,6 +89,7 @@ src_libbitcoin_node_la_SOURCES = \ ${srcdir}/../../src/protocols/protocol_performer.cpp \ ${srcdir}/../../src/protocols/protocol_transaction_in_106.cpp \ ${srcdir}/../../src/protocols/protocol_transaction_out_106.cpp \ + ${srcdir}/../../src/protocols/protocol_transaction_out_70001.cpp \ ${srcdir}/../../src/sessions/session.cpp \ ${srcdir}/../../src/sessions/session_inbound.cpp \ ${srcdir}/../../src/sessions/session_manual.cpp \ @@ -162,6 +164,7 @@ include_bitcoin_node_protocols_HEADERS = \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_block_in_106.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_block_in_31800.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_block_out_106.hpp \ + ${srcdir}/../../include/bitcoin/node/protocols/protocol_block_out_70001.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_block_out_70012.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_filter_out_70015.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_header_in_31800.hpp \ @@ -173,6 +176,7 @@ include_bitcoin_node_protocols_HEADERS = \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_performer.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_in_106.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_out_106.hpp \ + ${srcdir}/../../include/bitcoin/node/protocols/protocol_transaction_out_70001.hpp \ ${srcdir}/../../include/bitcoin/node/protocols/protocols.hpp include_bitcoin_node_sessionsdir = \ diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj index c355ac35..750d08b1 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj @@ -146,6 +146,7 @@ + @@ -157,6 +158,7 @@ + @@ -196,6 +198,7 @@ + @@ -207,6 +210,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters index a6dbdb30..10876c36 100644 --- a/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -132,6 +132,9 @@ src\protocols + + src\protocols + src\protocols @@ -165,6 +168,9 @@ src\protocols + + src\protocols + src\sessions @@ -278,6 +284,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols @@ -311,6 +320,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj index 2d05d8d6..c43c94a9 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj @@ -146,6 +146,7 @@ + @@ -157,6 +158,7 @@ + @@ -196,6 +198,7 @@ + @@ -207,6 +210,7 @@ + diff --git a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters index a6dbdb30..10876c36 100644 --- a/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-node/libbitcoin-node.vcxproj.filters @@ -132,6 +132,9 @@ src\protocols + + src\protocols + src\protocols @@ -165,6 +168,9 @@ src\protocols + + src\protocols + src\sessions @@ -278,6 +284,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols @@ -311,6 +320,9 @@ include\bitcoin\node\protocols + + include\bitcoin\node\protocols + include\bitcoin\node\protocols diff --git a/include/bitcoin/node.hpp b/include/bitcoin/node.hpp index b7d31740..7ce08dfe 100644 --- a/include/bitcoin/node.hpp +++ b/include/bitcoin/node.hpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/node/impl/sessions/session_peer.ipp b/include/bitcoin/node/impl/sessions/session_peer.ipp index fde9fa10..d766ee94 100644 --- a/include/bitcoin/node/impl/sessions/session_peer.ipp +++ b/include/bitcoin/node/impl/sessions/session_peer.ipp @@ -107,8 +107,7 @@ inline void CLASS::attach_protocols(const channel_ptr& channel) NOEXCEPT return; // Ready to relay transactions. - const auto txs_in_out = relay && peer->is_negotiated(level::bip37) && - (!delay || is_current_chain(true)); + const auto txs_in_out = relay && (!delay || is_current_chain(true)); // Peer advertises chain (blocks in). if (peer->is_peer_service(service::node_network)) @@ -140,21 +139,24 @@ inline void CLASS::attach_protocols(const channel_ptr& channel) NOEXCEPT channel->attach(self)->start(); channel->attach(self)->start(); } - else if (headers && peer->is_negotiated(level::headers_protocol)) - { - channel->attach(self)->start(); - channel->attach(self)->start(); - } else { - channel->attach(self)->start(); + if (headers && peer->is_negotiated(level::headers_protocol)) + channel->attach(self)->start(); + + if (peer->is_negotiated(level::bip37)) + channel->attach(self)->start(); + else + channel->attach(self)->start(); } } // Relay is configured, active, and txs are ready (txs in/out). - if (txs_in_out) + if (txs_in_out && peer->peer_version()->relay) { - if (peer->peer_version()->relay) + if (peer->is_negotiated(level::bip37)) + channel->attach(self)->start(); + else channel->attach(self)->start(); } } diff --git a/include/bitcoin/node/protocols/protocol_block_out_106.hpp b/include/bitcoin/node/protocols/protocol_block_out_106.hpp index 7c254ce7..7e3c8f96 100644 --- a/include/bitcoin/node/protocols/protocol_block_out_106.hpp +++ b/include/bitcoin/node/protocols/protocol_block_out_106.hpp @@ -54,6 +54,8 @@ class BCN_API protocol_block_out_106 protected: using get_data = network::messages::peer::get_data; using get_blocks = network::messages::peer::get_blocks; + using inventory_item = network::messages::peer::inventory_item; + using inventory_items = network::messages::peer::inventory_items; /// Block announcements are superseded by send_headers. virtual bool superseded() const NOEXCEPT; @@ -71,11 +73,17 @@ class BCN_API protocol_block_out_106 const get_data::cptr& message) NOEXCEPT; virtual void send_block(const code& ec) NOEXCEPT; + /// The item cannot be served, stops the channel and returns false. + virtual bool handle_unservable(const inventory_item& item) NOEXCEPT; + + /// Send any unservable items accumulated above, false if none. + virtual bool report_unservable() NOEXCEPT; + private: using inventory = network::messages::peer::inventory; - using inventory_item = network::messages::peer::inventory_item; - using inventory_items = network::messages::peer::inventory_items; + bool is_servable(const inventory_item& item, + const database::header_link& link) NOEXCEPT; bool is_under_checkpoint(const database::header_link& link) NOEXCEPT; inventory create_inventory(const get_blocks& locator) const NOEXCEPT; void merge_inventory(const inventory_items& items) NOEXCEPT; diff --git a/include/bitcoin/node/protocols/protocol_block_out_70001.hpp b/include/bitcoin/node/protocols/protocol_block_out_70001.hpp new file mode 100644 index 00000000..09104759 --- /dev/null +++ b/include/bitcoin/node/protocols/protocol_block_out_70001.hpp @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_OUT_70001_HPP +#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_OUT_70001_HPP + +#include +#include + +namespace libbitcoin { +namespace node { + +class BCN_API protocol_block_out_70001 + : public protocol_block_out_106, + protected network::tracker +{ +public: + typedef std::shared_ptr ptr; + + protocol_block_out_70001(const auto& session, + const network::channel::ptr& channel) NOEXCEPT + : protocol_block_out_106(session, channel), + enable_not_found_(session->network_settings().enable_not_found), + network::tracker(session->log) + { + } + +protected: + /// The item cannot be served, accumulates it for the not_found reply. + bool handle_unservable(const inventory_item& item) NOEXCEPT override; + + /// Replies not_found with the accumulated items, false if none. + bool report_unservable() NOEXCEPT override; + +private: + // This is thread safe. + const bool enable_not_found_; + + // This is protected by strand. + inventory_items unservable_{}; +}; + +} // namespace node +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/node/protocols/protocol_block_out_70012.hpp b/include/bitcoin/node/protocols/protocol_block_out_70012.hpp index 4cbe70e3..41265988 100644 --- a/include/bitcoin/node/protocols/protocol_block_out_70012.hpp +++ b/include/bitcoin/node/protocols/protocol_block_out_70012.hpp @@ -20,13 +20,13 @@ #define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_BLOCK_OUT_70012_HPP #include -#include +#include namespace libbitcoin { namespace node { class BCN_API protocol_block_out_70012 - : public protocol_block_out_106, + : public protocol_block_out_70001, protected network::tracker { public: @@ -34,7 +34,7 @@ class BCN_API protocol_block_out_70012 protocol_block_out_70012(const auto& session, const network::channel::ptr& channel) NOEXCEPT - : protocol_block_out_106(session, channel), + : protocol_block_out_70001(session, channel), network::tracker(session->log) { } diff --git a/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp b/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp index 23aa4c44..05dcf659 100644 --- a/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp +++ b/include/bitcoin/node/protocols/protocol_transaction_out_106.hpp @@ -63,6 +63,11 @@ class BCN_API protocol_transaction_out_106 virtual void send_transaction(const code& ec, size_t index, const network::messages::peer::get_data::cptr& message) NOEXCEPT; + /// The requested item cannot be served, stops the channel. + virtual void handle_unservable( + const network::messages::peer::inventory_item& item, size_t index, + const network::messages::peer::get_data::cptr& message) NOEXCEPT; + virtual bool announce(const system::hash_digest& hash) NOEXCEPT; private: diff --git a/include/bitcoin/node/protocols/protocol_transaction_out_70001.hpp b/include/bitcoin/node/protocols/protocol_transaction_out_70001.hpp new file mode 100644 index 00000000..d37d2960 --- /dev/null +++ b/include/bitcoin/node/protocols/protocol_transaction_out_70001.hpp @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_TRANSACTION_OUT_70001_HPP +#define LIBBITCOIN_NODE_PROTOCOLS_PROTOCOL_TRANSACTION_OUT_70001_HPP + +#include +#include + +namespace libbitcoin { +namespace node { + +class BCN_API protocol_transaction_out_70001 + : public protocol_transaction_out_106, + protected network::tracker +{ +public: + typedef std::shared_ptr ptr; + + protocol_transaction_out_70001(const auto& session, + const network::channel::ptr& channel) NOEXCEPT + : protocol_transaction_out_106(session, channel), + enable_not_found_(session->network_settings().enable_not_found), + network::tracker(session->log) + { + } + +protected: + /// The requested item cannot be served, replies not_found. + void handle_unservable( + const network::messages::peer::inventory_item& item, size_t index, + const network::messages::peer::get_data::cptr& message) NOEXCEPT + override; + +private: + // This is thread safe. + const bool enable_not_found_; +}; + +} // namespace node +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/node/protocols/protocols.hpp b/include/bitcoin/node/protocols/protocols.hpp index 21ded064..9ff2dc25 100644 --- a/include/bitcoin/node/protocols/protocols.hpp +++ b/include/bitcoin/node/protocols/protocols.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -34,5 +35,6 @@ #include #include #include +#include #endif diff --git a/src/protocols/protocol_block_out_106.cpp b/src/protocols/protocol_block_out_106.cpp index 51495482..aaad09c7 100644 --- a/src/protocols/protocol_block_out_106.cpp +++ b/src/protocols/protocol_block_out_106.cpp @@ -204,39 +204,55 @@ void protocol_block_out_106::send_block(const code& ec) NOEXCEPT if (stopped(ec)) return; - if (backlog_.empty()) return; - const auto& item = backlog_.front(); - const auto witness = item.is_witness_type(); - if (witness && !node_witness_) + const auto& query = archive(); + + // Drain unservable items from the front of the backlog. The derived + // protocol accumulates them if it reports them, and otherwise stops the + // channel on the first. Copied because each is handled after the pop. + database::header_link link{}; + while (!backlog_.empty()) { - LOGR("Unsupported witness get_data from [" << opposite() << "]."); - stop(network::error::protocol_violation); - return; + const auto item = backlog_.front(); + if (item.is_witness_type() && !node_witness_) + { + LOGR("Unsupported witness get_data from [" << opposite() << "]."); + stop(network::error::protocol_violation); + return; + } + + link = query.to_header(item.hash); + if (is_servable(item, link)) + break; + + backlog_.pop_front(); + if (!handle_unservable(item)) + return; } - const auto& query = archive(); - const auto link = query.to_header(item.hash); - if (node_pruned_ && (is_under_checkpoint(link) || query.is_milestone(link))) - { - LOGR("Requested pruned block " << encode_hash(item.hash) - << " from [" << opposite() << "]."); - stop(system::error::not_found); + // The report resumes this loop on completion, so it precedes the block. + if (report_unservable()) return; - } + if (backlog_.empty()) return; + + const auto item = backlog_.front(); + const auto witness = item.is_witness_type(); const auto start = logger::now(); messages::peer::block out { { query.get_wire_block(link, witness), witness } }; + + // Association is verified above, so this is not ordinary peer input. if (!out.block.is_valid()) { - LOGR("Requested block " << encode_hash(item.hash) << " from [" - << opposite() << "] not found."); + LOGV("Requested block " << encode_hash(item.hash) << " from [" + << opposite() << "] not obtained."); + + backlog_.pop_front(); + if (handle_unservable(item)) + report_unservable(); - // This block could not have been advertised to the peer. - // TODO: send not_found message in protocol override. - stop(system::error::not_found); return; } @@ -245,6 +261,59 @@ void protocol_block_out_106::send_block(const code& ec) NOEXCEPT SEND(std::move(out), send_block, _1); } +// The checkpoint, milestone and association queries assume an archived header. +bool protocol_block_out_106::is_servable(const inventory_item& LOG_ONLY(item), + const database::header_link& link) NOEXCEPT +{ + BC_ASSERT(stranded()); + + // A hash that resolves to no header is ordinary peer input. + if (link.is_terminal()) + { + LOGV("Requested block " << encode_hash(item.hash) << " from [" + << opposite() << "] not stored."); + return false; + } + + const auto& query = archive(); + if (node_pruned_ && (is_under_checkpoint(link) || query.is_milestone(link))) + { + LOGV("Requested pruned block " << encode_hash(item.hash) + << " from [" << opposite() << "]."); + return false; + } + + // This block could not have been advertised to the peer. + if (!query.is_associated(link)) + { + LOGV("Requested block " << encode_hash(item.hash) << " from [" + << opposite() << "] not found."); + return false; + } + + return true; +} + +// not_found is undefined below bip37, so the channel is stopped instead. +bool protocol_block_out_106::handle_unservable( + const inventory_item& LOG_ONLY(item)) NOEXCEPT +{ + BC_ASSERT(stranded()); + + LOGR("Unservable block " << encode_hash(item.hash) << " from [" + << opposite() << "], stopping."); + + stop(system::error::not_found); + return false; +} + +// There is nothing to report below bip37, the channel is stopped above. +bool protocol_block_out_106::report_unservable() NOEXCEPT +{ + BC_ASSERT(stranded()); + return false; +} + // utilities // ---------------------------------------------------------------------------- diff --git a/src/protocols/protocol_block_out_70001.cpp b/src/protocols/protocol_block_out_70001.cpp new file mode 100644 index 00000000..06bc39b0 --- /dev/null +++ b/src/protocols/protocol_block_out_70001.cpp @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include + +namespace libbitcoin { +namespace node { + +#define CLASS protocol_block_out_70001 + +using namespace system; +using namespace network; +using namespace network::messages::peer; +using namespace std::placeholders; + +// Shared pointers required for lifetime in handler parameters. +BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) + +// Outbound (not_found). +// ---------------------------------------------------------------------------- + +// Accumulate the run, so that it is reported by one message. +bool protocol_block_out_70001::handle_unservable( + const inventory_item& item) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (!enable_not_found_) + return protocol_block_out_106::handle_unservable(item); + + unservable_.push_back(item); + return true; +} + +// The items are answered and the send loop resumed, as with a block, so +// nothing is produced until the prior write completes. +bool protocol_block_out_70001::report_unservable() NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (unservable_.empty()) + return false; + + auto items = std::move(unservable_); + unservable_.clear(); + + SEND(not_found{ std::move(items) }, send_block, _1); + return true; +} + +BC_POP_WARNING() +BC_POP_WARNING() + +} // namespace node +} // namespace libbitcoin diff --git a/src/protocols/protocol_block_out_70012.cpp b/src/protocols/protocol_block_out_70012.cpp index ccd750cb..f341d534 100644 --- a/src/protocols/protocol_block_out_70012.cpp +++ b/src/protocols/protocol_block_out_70012.cpp @@ -44,7 +44,7 @@ void protocol_block_out_70012::start() NOEXCEPT return; SUBSCRIBE_CHANNEL(send_headers, handle_receive_send_headers, _1, _2); - protocol_block_out_106::start(); + protocol_block_out_70001::start(); } // Inbound (send_headers). diff --git a/src/protocols/protocol_transaction_out_106.cpp b/src/protocols/protocol_transaction_out_106.cpp index 9f2423f3..7d0d8e57 100644 --- a/src/protocols/protocol_transaction_out_106.cpp +++ b/src/protocols/protocol_transaction_out_106.cpp @@ -199,17 +199,30 @@ void protocol_transaction_out_106::send_transaction(const code& ec, const auto ptr = query.get_transaction(query.to_tx(item.hash), witness); if (!ptr) { - LOGR("Requested tx " << encode_hash(item.hash) + LOGV("Requested tx " << encode_hash(item.hash) << " from [" << opposite() << "] not found."); // This tx could not have been advertised to the peer. - stop(system::error::not_found); + handle_unservable(item, index, message); return; } SEND(transaction{ ptr }, send_transaction, _1, add1(index), message); } +// not_found is undefined below bip37, so the channel is stopped instead. +void protocol_transaction_out_106::handle_unservable( + const inventory_item& LOG_ONLY(item), size_t, + const get_data::cptr&) NOEXCEPT +{ + BC_ASSERT(stranded()); + + LOGR("Unservable tx " << encode_hash(item.hash) << " from [" + << opposite() << "], stopping."); + + stop(system::error::not_found); +} + BC_POP_WARNING() BC_POP_WARNING() diff --git a/src/protocols/protocol_transaction_out_70001.cpp b/src/protocols/protocol_transaction_out_70001.cpp new file mode 100644 index 00000000..76008da8 --- /dev/null +++ b/src/protocols/protocol_transaction_out_70001.cpp @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include + +namespace libbitcoin { +namespace node { + +#define CLASS protocol_transaction_out_70001 + +using namespace system; +using namespace network::messages::peer; +using namespace std::placeholders; + +// Shared pointers required for lifetime in handler parameters. +BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) + +// Outbound (not_found). +// ---------------------------------------------------------------------------- + +// The item is answered and the send loop resumed, as with a transaction, so +// nothing is produced until the prior write completes. +void protocol_transaction_out_70001::handle_unservable( + const inventory_item& item, size_t index, + const get_data::cptr& message) NOEXCEPT +{ + BC_ASSERT(stranded()); + + if (!enable_not_found_) + { + protocol_transaction_out_106::handle_unservable(item, index, message); + return; + } + + SEND(not_found{ { item } }, send_transaction, _1, add1(index), message); +} + +BC_POP_WARNING() +BC_POP_WARNING() + +} // namespace node +} // namespace libbitcoin diff --git a/test/functional/p2p.cpp b/test/functional/p2p.cpp index 61fffefa..b56fe1cc 100644 --- a/test/functional/p2p.cpp +++ b/test/functional/p2p.cpp @@ -58,4 +58,154 @@ BOOST_AUTO_TEST_CASE(functional_p2p__get_data__genesis_block__expected_bytes) BOOST_REQUIRE(payload == expected); } +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_block__not_found, + p2p_not_found_setup_fixture) +{ + BOOST_REQUIRE(handshake()); + + const get_data get{ { { inventory_item::type_id::block, system::one_hash } } }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); + BOOST_REQUIRE(message->items.front().hash == system::one_hash); +} + +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__pruned_block__not_found, + p2p_limited_setup_fixture) +{ + BOOST_REQUIRE(handshake()); + + const system::chain::block& genesis = config_.bitcoin.genesis_block; + const get_data get{ { { inventory_item::type_id::block, genesis.hash() } } }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); +} + +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unassociated_block__not_found, + p2p_unassociated_setup_fixture) +{ + BOOST_REQUIRE(handshake()); + + const auto hash = unassociated().hash(); + const get_data get{ { { inventory_item::type_id::block, hash } } }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); + BOOST_REQUIRE(message->items.front().hash == hash); +} + +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_transaction__not_found, + p2p_relay_setup_fixture) +{ + BOOST_REQUIRE(handshake(0, level::maximum_protocol, true)); + + const get_data get{ { { inventory_item::type_id::transaction, system::one_hash } } }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); + BOOST_REQUIRE(message->items.front().hash == system::one_hash); +} + +// Below bip130 the 70001 protocol is attached directly (not as 70012 base). +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_block_70001__not_found, + p2p_not_found_setup_fixture) +{ + BOOST_REQUIRE(handshake(0, level::bip61)); + + const get_data get{ { { inventory_item::type_id::block, system::one_hash } } }; + send(get, level::bip61); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(level::bip61, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); + BOOST_REQUIRE(message->items.front().hash == system::one_hash); +} + +// A run of unservable items is answered by one message. +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_blocks__one_not_found, + p2p_not_found_setup_fixture) +{ + BOOST_REQUIRE(handshake()); + + const get_data get + { + { + { inventory_item::type_id::block, system::one_hash }, + { inventory_item::type_id::block, system::null_hash } + } + }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), two); + BOOST_REQUIRE(message->items.front().hash == system::one_hash); + BOOST_REQUIRE(message->items.back().hash == system::null_hash); +} + +// The unservable run is flushed before the send loop resumes. +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_then_genesis__not_found_then_block, + p2p_not_found_setup_fixture) +{ + BOOST_REQUIRE(handshake()); + + const system::chain::block& genesis = config_.bitcoin.genesis_block; + const get_data get + { + { + { inventory_item::type_id::block, system::one_hash }, + { inventory_item::type_id::block, genesis.hash() } + } + }; + send(get, node_version->value); + + const auto payload = receive(not_found::command); + const auto message = not_found::deserialize(node_version->value, payload); + BOOST_REQUIRE(message); + BOOST_REQUIRE_EQUAL(message->items.size(), one); + BOOST_REQUIRE(message->items.front().hash == system::one_hash); + + // The send loop resumes and serves the item that follows the run. + BOOST_REQUIRE_EQUAL(receive(block::command).size(), genesis.to_data(true).size()); +} + +// not_found is undefined below bip37, so the channel is stopped instead. +BOOST_FIXTURE_TEST_CASE(functional_p2p__get_data__unknown_block_106__stopped, + p2p_not_found_setup_fixture) +{ + BOOST_REQUIRE(handshake(0, level::bip35)); + + const get_data get{ { { inventory_item::type_id::block, system::one_hash } } }; + send(get, level::bip35); + + // The channel is stopped, so the socket closes without a not_found. + BOOST_REQUIRE_THROW(receive(not_found::command), boost::system::system_error); +} + +// The option is off by default, so the channel is stopped instead. +BOOST_AUTO_TEST_CASE(functional_p2p__get_data__unknown_block_disabled__stopped) +{ + BOOST_REQUIRE(handshake()); + + const get_data get{ { { inventory_item::type_id::block, system::one_hash } } }; + send(get, node_version->value); + + BOOST_REQUIRE_THROW(receive(not_found::command), boost::system::system_error); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/test/functional/p2p_setup_fixture.cpp b/test/functional/p2p_setup_fixture.cpp index a4acf10d..ddfddb1f 100644 --- a/test/functional/p2p_setup_fixture.cpp +++ b/test/functional/p2p_setup_fixture.cpp @@ -134,7 +134,8 @@ data_chunk p2p_setup_fixture::receive(const std::string& command) } } -bool p2p_setup_fixture::handshake(uint64_t services, uint32_t value) +bool p2p_setup_fixture::handshake(uint64_t services, uint32_t value, + bool relay) { version out{}; out.value = value; @@ -143,7 +144,7 @@ bool p2p_setup_fixture::handshake(uint64_t services, uint32_t value) out.nonce = 42424242; out.user_agent = "/test/"; out.start_height = 0; - out.relay = false; + out.relay = relay; send(out, value); // The node sends its version upon attach and verack upon our version. @@ -170,4 +171,18 @@ bool p2p_setup_fixture::handshake(uint64_t services, uint32_t value) return true; } +system::chain::header p2p_unassociated_setup_fixture::unassociated() NOEXCEPT +{ + const system::settings bitcoin{ chain::selection::mainnet }; + return + { + 1u, + bitcoin.genesis_block.hash(), + system::null_hash, + 0u, + 0u, + 0u + }; +} + BC_POP_WARNING() diff --git a/test/functional/p2p_setup_fixture.hpp b/test/functional/p2p_setup_fixture.hpp index b4a7f80c..ae12f910 100644 --- a/test/functional/p2p_setup_fixture.hpp +++ b/test/functional/p2p_setup_fixture.hpp @@ -55,7 +55,8 @@ struct p2p_setup_fixture /// Perform the version handshake, retains the node's version message. bool handshake(uint64_t services=0, - uint32_t version=network::messages::peer::level::maximum_protocol); + uint32_t version=network::messages::peer::level::maximum_protocol, + bool relay=false); /// The node's version message (set by handshake). network::messages::peer::version::cptr node_version{}; @@ -72,4 +73,65 @@ struct p2p_setup_fixture boost::asio::ip::tcp::socket socket_{ io_ }; }; +// A node configured to reply not_found. +struct p2p_not_found_setup_fixture + : p2p_setup_fixture +{ + inline p2p_not_found_setup_fixture() + : p2p_setup_fixture({}, [](configuration& config) + { + config.network.enable_not_found = true; + }) + { + } +}; + +// A node that relays transactions and replies not_found. +struct p2p_relay_setup_fixture + : p2p_setup_fixture +{ + inline p2p_relay_setup_fixture() + : p2p_setup_fixture({}, [](configuration& config) + { + config.network.enable_relay = true; + config.network.enable_not_found = true; + }) + { + } +}; + +// A node that does not store the blocks it has pruned. +struct p2p_limited_setup_fixture + : p2p_setup_fixture +{ + inline p2p_limited_setup_fixture() + : p2p_setup_fixture({}, [](configuration& config) + { + config.node.limited_blocks = true; + config.network.enable_not_found = true; + }) + { + } +}; + +// A header is archived before its block is associated, so the header link +// resolves while the block remains absent from the archive. This is the +// steady state of headers-first synchronization. +struct p2p_unassociated_setup_fixture + : p2p_setup_fixture +{ + static system::chain::header unassociated() NOEXCEPT; + + inline p2p_unassociated_setup_fixture() + : p2p_setup_fixture([](node::query& query) + { + return query.set(unassociated(), database::context{}, false); + }, [](configuration& config) + { + config.network.enable_not_found = true; + }) + { + } +}; + #endif