Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/bitcoin/node/protocols/protocol_block_out_106.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class BCN_API protocol_block_out_106
node_pruned_(session->node_settings().limited_blocks),
node_witness_(session->node_settings().provide_witness),
allow_overlapped_(session->node_settings().allow_overlapped),
not_found_allowed_(negotiated_version() >=
network::messages::peer::not_found::version_minimum),
network::tracker<protocol_block_out_106>(session->log)
{
}
Expand Down Expand Up @@ -73,10 +75,12 @@ class BCN_API protocol_block_out_106

private:
using inventory = network::messages::peer::inventory;
using not_found = network::messages::peer::not_found;
using inventory_item = network::messages::peer::inventory_item;
using inventory_items = network::messages::peer::inventory_items;

bool is_under_checkpoint(const database::header_link& link) NOEXCEPT;
void send_not_found() NOEXCEPT;
inventory create_inventory(const get_blocks& locator) const NOEXCEPT;
void merge_inventory(const inventory_items& items) NOEXCEPT;

Expand All @@ -85,6 +89,7 @@ class BCN_API protocol_block_out_106
const bool node_pruned_;
const bool node_witness_;
const bool allow_overlapped_;
const bool not_found_allowed_;

// This is protected by strand.
std::deque<inventory_item> backlog_{};
Expand Down
41 changes: 36 additions & 5 deletions src/protocols/protocol_block_out_106.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,13 @@ bool protocol_block_out_106::handle_receive_get_data(const code& ec,
void protocol_block_out_106::send_block(const code& ec) NOEXCEPT
{
BC_ASSERT(stranded());

if (stopped(ec))
return;

if (backlog_.empty()) return;
if (backlog_.empty())
return;

const auto& item = backlog_.front();
const auto witness = item.is_witness_type();
if (witness && !node_witness_)
Expand All @@ -216,11 +219,23 @@ void protocol_block_out_106::send_block(const code& ec) NOEXCEPT

const auto& query = archive();
const auto link = query.to_header(item.hash);

// The checkpoint height query faults the store on a terminal link.
if (link.is_terminal())
{
LOGR("Requested block " << encode_hash(item.hash) << " from ["
<< opposite() << "] not stored.");

send_not_found();
return;
}

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);

send_not_found();
return;
}

Expand All @@ -229,14 +244,13 @@ void protocol_block_out_106::send_block(const code& ec) NOEXCEPT
{
{ query.get_wire_block(link, witness), witness }
};

if (!out.block.is_valid())
{
LOGR("Requested block " << encode_hash(item.hash) << " from ["
<< opposite() << "] not found.");

// This block could not have been advertised to the peer.
// TODO: send not_found message in protocol override.
stop(system::error::not_found);
send_not_found();
return;
}

Expand All @@ -248,6 +262,23 @@ void protocol_block_out_106::send_block(const code& ec) NOEXCEPT
// utilities
// ----------------------------------------------------------------------------

// This protocol is also attached below bip130, and when headers-first is
// disabled, so the peer may be below bip37, where not_found is undefined.
void protocol_block_out_106::send_not_found() NOEXCEPT
{
BC_ASSERT(stranded());

if (!not_found_allowed_)
{
stop(system::error::not_found);
return;
}

not_found out{ { backlog_.front() } };
backlog_.pop_front();
SEND(std::move(out), send_block, _1);
}

void protocol_block_out_106::merge_inventory(
const inventory_items& items) NOEXCEPT
{
Expand Down
5 changes: 3 additions & 2 deletions src/protocols/protocol_transaction_out_106.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,9 @@ void protocol_transaction_out_106::send_transaction(const code& ec,
LOGR("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);
// The protocol is attached above bip37, where not_found is defined.
BC_ASSERT(negotiated_version() >= level::bip37);
SEND(not_found{ { item } }, send_transaction, _1, add1(index), message);
return;
}

Expand Down
98 changes: 98 additions & 0 deletions test/functional/p2p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,102 @@ BOOST_AUTO_TEST_CASE(functional_p2p__get_data__genesis_block__expected_bytes)
BOOST_REQUIRE(payload == expected);
}

BOOST_AUTO_TEST_CASE(functional_p2p__get_data__unknown_block__not_found)
{
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);
}

// A limited node answers for a block it has pruned, and remains unfaulted.
struct p2p_limited_setup_fixture
: p2p_setup_fixture
{
p2p_limited_setup_fixture() NOEXCEPT
: p2p_setup_fixture({}, [](configuration& config) NOEXCEPT
{
config.node.limited_blocks = true;
})
{
}
};

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);
}

// 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 sync.
struct p2p_unassociated_setup_fixture
: p2p_setup_fixture
{
static system::chain::header unassociated() NOEXCEPT
{
const system::settings bitcoin{ system::chain::selection::mainnet };
return
{
1u,
bitcoin.genesis_block.hash(),
system::null_hash,
0u,
0u,
0u
};
}

p2p_unassociated_setup_fixture() NOEXCEPT
: p2p_setup_fixture([](node::query& query) NOEXCEPT
{
return query.set(unassociated(), database::context{}, false);
})
{
}
};

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);
}

// not_found is undefined below bip37, so the channel is stopped instead.
BOOST_AUTO_TEST_CASE(functional_p2p__get_data__unknown_block_below_bip37__stopped)
{
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);
}

BOOST_AUTO_TEST_SUITE_END()
Loading