diff --git a/include/bitcoin/node/chasers/chaser_organize.hpp b/include/bitcoin/node/chasers/chaser_organize.hpp index 63cf8808..e7ee531e 100644 --- a/include/bitcoin/node/chasers/chaser_organize.hpp +++ b/include/bitcoin/node/chasers/chaser_organize.hpp @@ -59,6 +59,9 @@ class chaser_organize using block_tree = std::unordered_map; using header_links = std::vector; + /// Pure Virtual + /// ----------------------------------------------------------------------- + /// Get header from Block instance. virtual const system::chain::header& get_header( const Block& block) const NOEXCEPT = 0; @@ -75,12 +78,18 @@ class chaser_organize virtual bool is_storable(const Block& block, const chain_state& state) const NOEXCEPT = 0; + /// Properties + /// ----------------------------------------------------------------------- + /// Constant access to Block tree. virtual const block_tree& tree() const NOEXCEPT; /// System configuration settings. virtual const system::settings& settings() const NOEXCEPT; + /// Methods + /// ----------------------------------------------------------------------- + /// Handle chaser events. virtual void handle_event(const code&, chase event_, link value) NOEXCEPT; @@ -92,6 +101,12 @@ class chaser_organize const organize_handler& handler) NOEXCEPT; private: + static constexpr size_t fork_bits = to_bits(sizeof(system::chain::forks)); + static constexpr bool is_block() NOEXCEPT + { + return is_same_type; + } + // Store Block into logical tree cache. void cache(const typename Block::cptr& block_ptr, const chain_state::ptr& state) NOEXCEPT; @@ -127,6 +142,15 @@ class chaser_organize } // namespace node } // namespace libbitcoin +#define TEMPLATE template +#define CLASS chaser_organize + +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + #include +BC_POP_WARNING() + +#undef CLASS + #endif diff --git a/include/bitcoin/node/impl/chasers/chaser_organize.ipp b/include/bitcoin/node/impl/chasers/chaser_organize.ipp index 8994d9ee..ceca75ea 100644 --- a/include/bitcoin/node/impl/chasers/chaser_organize.ipp +++ b/include/bitcoin/node/impl/chasers/chaser_organize.ipp @@ -26,13 +26,7 @@ namespace libbitcoin { namespace node { -#define TEMPLATE template -#define CLASS chaser_organize - -BC_PUSH_WARNING(NO_NEW_OR_DELETE) -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - -// public +// Public // ---------------------------------------------------------------------------- TEMPLATE @@ -49,17 +43,17 @@ code CLASS::start() NOEXCEPT using namespace system; using namespace std::placeholders; + const auto& query = archive(); // Initialize cache of top candidate chain state. // Spans full chain to obtain cumulative work. This can be optimized by // storing it with each header, though the scan is fast. The same occurs // when a block first branches below the current chain top. Chain work // is a questionable DoS protection scheme only, so could also toss it. - state_ = archive().get_candidate_chain_state(settings_, - archive().get_top_candidate()); + state_ = query.get_candidate_chain_state(settings_, + query.get_top_candidate()); - // Avoid double logging. - if constexpr (is_same_type) + if constexpr (!is_block()) { LOGN("Candidate top [" << encode_hash(state_->hash()) << ":" << state_->height() << "]."); @@ -75,7 +69,7 @@ void CLASS::organize(const typename Block::cptr& block_ptr, POST(do_organize, block_ptr, std::move(handler)); } -// protected +// Properties // ---------------------------------------------------------------------------- TEMPLATE @@ -90,10 +84,12 @@ const typename CLASS::block_tree& CLASS::tree() const NOEXCEPT return tree_; } +// Methods +// ---------------------------------------------------------------------------- + TEMPLATE void CLASS::handle_event(const code&, chase event_, link value) NOEXCEPT { - // Block chaser doesn't need to capture unchecked/unconnected (but okay). if (event_ == chase::unchecked || event_ == chase::unconnected || event_ == chase::unconfirmed) @@ -110,7 +106,7 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT using namespace system; // Skip already reorganized out, get height. - // ------------------------------------------------------------------------ + // ........................................................................ // Upon restart candidate chain validation will hit unconfirmable block. if (closed()) @@ -137,7 +133,7 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT } // Mark candidates above and pop at/above height. - // ------------------------------------------------------------------------ + // ........................................................................ // Pop from top down to and including header marking each as unconfirmable. // Unconfirmability isn't necessary for validation but adds query context. @@ -166,7 +162,7 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT } // Reset top chain state cache to fork point. - // ------------------------------------------------------------------------ + // ........................................................................ const auto top_candidate = state_->height(); const auto prev_forks = state_->forks(); @@ -182,7 +178,6 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT const auto next_forks = state_->forks(); if (prev_forks != next_forks) { - constexpr auto fork_bits = to_bits(sizeof(chain::forks)); const binary prev{ fork_bits, to_big_endian(prev_forks) }; const binary next{ fork_bits, to_big_endian(next_forks) }; LOGN("Forks reverted from [" @@ -204,7 +199,7 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT } // Copy candidates from above fork point to top into header tree. - // ------------------------------------------------------------------------ + // ........................................................................ auto state = state_; for (auto index = add1(fork_point); index <= top_candidate; ++index) @@ -216,12 +211,14 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT return; } + BC_PUSH_WARNING(NO_NEW_OR_DELETE) state.reset(new chain::chain_state{ *state, *save, settings_ }); + BC_POP_WARNING() cache(save, state); } // Pop candidates from top to above fork point. - // ------------------------------------------------------------------------ + // ........................................................................ for (auto index = top_candidate; index > fork_point; --index) { LOGN("Deorganizing candidate [" << index << "]."); @@ -234,7 +231,7 @@ void CLASS::do_disorganize(header_t header) NOEXCEPT } // Push confirmed headers from above fork point onto candidate chain. - // ------------------------------------------------------------------------ + // ........................................................................ const auto top_confirmed = query.get_top_confirmed(); for (auto index = add1(fork_point); index <= top_confirmed; ++index) { @@ -250,242 +247,243 @@ TEMPLATE void CLASS::do_organize(typename Block::cptr& block_ptr, const organize_handler& handler) NOEXCEPT { - BC_ASSERT(stranded()); + BC_ASSERT(stranded()); + + using namespace system; + const auto& block = *block_ptr; + const auto hash = block.hash(); + const auto header = get_header(block); + auto& query = archive(); + + // Skip existing/orphan, get state. + // ........................................................................ - using namespace system; - auto& query = archive(); - const auto& block = *block_ptr; - const auto hash = block.hash(); - const auto header = get_header(block); + if (closed()) + { + handler(network::error::service_stopped, {}); + return; + } + + const auto it = tree_.find(hash); + if (it != tree_.end()) + { + const auto height = it->second.state->height(); + if constexpr (is_block()) + handler(error::duplicate_block, height); + else + handler(error::duplicate_header, height); - // Skip existing/orphan, get state. - // ------------------------------------------------------------------------ + return; + } - if (closed()) + // If exists test for prior invalidity. + const auto link = query.to_header(hash); + if (!link.is_terminal()) + { + size_t height{}; + if (!query.get_height(height, link)) { - handler(network::error::service_stopped, {}); + handler(error::store_integrity, {}); + close(error::store_integrity); return; } - const auto it = tree_.find(hash); - if (it != tree_.end()) + const auto ec = query.get_header_state(link); + if (ec == database::error::block_unconfirmable) { - const auto height = it->second.state->height(); - if constexpr (is_same_type) - handler(error::duplicate_block, height); - else - handler(error::duplicate_header, height); - + handler(ec, height); return; } - // If exists test for prior invalidity. - const auto link = query.to_header(hash); - if (!link.is_terminal()) + if constexpr (is_block()) { - size_t height{}; - if (!query.get_height(height, link)) + // Blocks are only duplicates if txs are associated. + if (ec != database::error::unassociated) { - handler(error::store_integrity, {}); - close(error::store_integrity); - return; - } - - const auto ec = query.get_header_state(link); - if (ec == database::error::block_unconfirmable) - { - handler(ec, height); - return; - } - - if constexpr (is_same_type) - { - // Blocks are only duplicates if txs are associated. - if (ec != database::error::unassociated) - { - handler(error::duplicate_block, height); - return; - } - } - else - { - handler(error::duplicate_header, height); + handler(error::duplicate_block, height); return; } } - - // Obtains from state_, tree, or store as applicable. - auto state = get_chain_state(header.previous_block_hash()); - if (!state) + else { - if constexpr (is_same_type) - handler(error::orphan_block, {}); - else - handler(error::orphan_header, {}); - + handler(error::duplicate_header, height); return; } + } - // Roll chain state forward from previous to current header. - // ------------------------------------------------------------------------ + // Obtains from state_, tree, or store as applicable. + auto state = get_chain_state(header.previous_block_hash()); + if (!state) + { + if constexpr (is_block()) + handler(error::orphan_block, {}); + else + handler(error::orphan_header, {}); - const auto prev_forks = state->forks(); - const auto prev_version = state->minimum_block_version(); + return; + } - // Do not use block parameter here as that override is for tx pool. - state.reset(new chain::chain_state{ *state, header, settings_ }); - const auto height = state->height(); + // Roll chain state forward from previous to current header. + // ........................................................................ - // TODO: this could be moved to confirmation. - const auto next_forks = state->forks(); - if (prev_forks != next_forks) - { - constexpr auto fork_bits = to_bits(sizeof(chain::forks)); - const binary prev{ fork_bits, to_big_endian(prev_forks) }; - const binary next{ fork_bits, to_big_endian(next_forks) }; - LOGN("Forked from [" - << prev << "] to [" - << next << "] at [" - << height << ":" << encode_hash(hash) << "]."); - } + const auto prev_forks = state->forks(); + const auto prev_version = state->minimum_block_version(); - // TODO: this could be moved to confirmation. - const auto next_version = state->minimum_block_version(); - if (prev_version != next_version) - { - LOGN("Minimum block version [" - << prev_version << "] changed to [" - << next_version << "] at [" - << height << ":" << encode_hash(hash) << "]."); - } + BC_PUSH_WARNING(NO_NEW_OR_DELETE) + // Do not use block parameter here as that override is for tx pool. + state.reset(new chain::chain_state{ *state, header, settings_ }); + BC_POP_WARNING() + const auto height = state->height(); - // Validation and currency. - // ------------------------------------------------------------------------ + // TODO: this could be moved to confirmation. + const auto next_forks = state->forks(); + if (prev_forks != next_forks) + { + const binary prev{ fork_bits, to_big_endian(prev_forks) }; + const binary next{ fork_bits, to_big_endian(next_forks) }; + LOGN("Forked from [" + << prev << "] to [" + << next << "] at [" + << height << ":" << encode_hash(hash) << "]."); + } - if (chain::checkpoint::is_conflict(settings_.checkpoints, hash, height)) - { - handler(system::error::checkpoint_conflict, height); - return; - }; + // TODO: this could be moved to confirmation. + const auto next_version = state->minimum_block_version(); + if (prev_version != next_version) + { + LOGN("Minimum block version [" + << prev_version << "] changed to [" + << next_version << "] at [" + << height << ":" << encode_hash(hash) << "]."); + } - if (const auto ec = validate(block, *state)) - { - handler(ec, height); - return; - } + // Validation and currency. + // ........................................................................ - if (!is_storable(block, *state)) - { - cache(block_ptr, state); - handler(error::success, height); - return; - } + if (chain::checkpoint::is_conflict(settings_.checkpoints, hash, height)) + { + handler(system::error::checkpoint_conflict, height); + return; + }; - // Compute relative work. - // ------------------------------------------------------------------------ + if (const auto ec = validate(block, *state)) + { + handler(ec, height); + return; + } - uint256_t work{}; - hashes tree_branch{}; - size_t branch_point{}; - header_links store_branch{}; - if (!get_branch_work(work, branch_point, tree_branch, store_branch, header)) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } + if (!is_storable(block, *state)) + { + cache(block_ptr, state); + handler(error::success, height); + return; + } - bool strong{}; - if (!get_is_strong(strong, work, branch_point)) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } + // Compute relative work. + // ........................................................................ - if (!strong) - { - // New top of current weak branch. - cache(block_ptr, state); - handler(error::success, height); - return; - } + uint256_t work{}; + hashes tree_branch{}; + size_t branch_point{}; + header_links store_branch{}; + if (!get_branch_work(work, branch_point, tree_branch, store_branch, header)) + { + handler(error::store_integrity, height); + close(error::store_integrity); + return; + } - // Reorganize candidate chain. - // ------------------------------------------------------------------------ + bool strong{}; + if (!get_is_strong(strong, work, branch_point)) + { + handler(error::store_integrity, height); + close(error::store_integrity); + return; + } - auto top = state_->height(); - if (top < branch_point) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } + if (!strong) + { + // New top of current weak branch. + cache(block_ptr, state); + handler(error::success, height); + return; + } - // Pop down to the branch point. - while (top-- > branch_point) - { - LOGN("Reorganizing candidate [" << add1(top) << "]."); + // Reorganize candidate chain. + // ........................................................................ - if (!query.pop_candidate()) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } - } + auto top = state_->height(); + if (top < branch_point) + { + handler(error::store_integrity, height); + close(error::store_integrity); + return; + } - // Push stored strong headers to candidate chain. - for (const auto& id: views_reverse(store_branch)) + // Pop down to the branch point. + while (top-- > branch_point) + { + LOGN("Reorganizing candidate [" << add1(top) << "]."); + + if (!query.pop_candidate()) { - if (!query.push_candidate(id)) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } + handler(error::store_integrity, height); + close(error::store_integrity); + return; } + } - // Store strong tree headers and push to candidate chain. - for (const auto& key: views_reverse(tree_branch)) + // Push stored strong headers to candidate chain. + for (const auto& id: views_reverse(store_branch)) + { + if (!query.push_candidate(id)) { - if (!push(key)) - { - handler(error::store_integrity, height); - close(error::store_integrity); - return; - } + handler(error::store_integrity, height); + close(error::store_integrity); + return; } + } - // Push new header as top of candidate chain. - if (push(block_ptr, state->context()).is_terminal()) + // Store strong tree headers and push to candidate chain. + for (const auto& key: views_reverse(tree_branch)) + { + if (!push(key)) { handler(error::store_integrity, height); close(error::store_integrity); return; } + } - // Reset top chain state cache and notify. - // ------------------------------------------------------------------------ + // Push new header as top of candidate chain. + if (push(block_ptr, state->context()).is_terminal()) + { + handler(error::store_integrity, height); + close(error::store_integrity); + return; + } - const auto point = possible_narrow_cast(branch_point); + // Reset top chain state cache and notify. + // ........................................................................ - if constexpr (is_same_type) - { - notify(error::success, chase::block, point); - } - else - { - // Delay so headers can get current before block download starts. - if (is_current(header.timestamp())) - notify(error::success, chase::header, point); - } + const auto point = possible_narrow_cast(branch_point); - state_ = state; - handler(error::success, height); + if constexpr (is_block()) + { + notify(error::success, chase::block, point); + } + else + { + // Delay so headers can get current before block download starts. + if (is_current(header.timestamp())) + notify(error::success, chase::header, point); } -// private + state_ = state; + handler(error::success, height); +} + +// Private // ---------------------------------------------------------------------------- TEMPLATE @@ -614,11 +612,6 @@ bool CLASS::push(const system::hash_digest& key) NOEXCEPT return query.push_candidate(link); } -#undef CLASS - -BC_POP_WARNING() -BC_POP_WARNING() - } // namespace node } // namespace libbitcoin diff --git a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp index 5fb0a117..df2ecf88 100644 --- a/include/bitcoin/node/protocols/protocol_header_in_31800.hpp +++ b/include/bitcoin/node/protocols/protocol_header_in_31800.hpp @@ -47,9 +47,9 @@ class BCN_API protocol_header_in_31800 protected: virtual bool handle_receive_headers(const code& ec, const network::messages::headers::cptr& message) NOEXCEPT; - virtual void complete() NOEXCEPT; virtual void handle_organize(const code& ec, size_t height, const system::chain::header::cptr& header_ptr) NOEXCEPT; + virtual void complete() NOEXCEPT; private: network::messages::get_headers create_get_headers() const NOEXCEPT; diff --git a/src/chasers/chaser_block.cpp b/src/chasers/chaser_block.cpp index 6c654e71..61924bda 100644 --- a/src/chasers/chaser_block.cpp +++ b/src/chasers/chaser_block.cpp @@ -71,7 +71,8 @@ code chaser_block::validate(const system::chain::block& block, return network::error::protocol_violation; // Requires only prevout population. - if ((ec = block.accept(state.context(), settings().subsidy_interval_blocks, + if ((ec = block.accept(state.context(), + settings().subsidy_interval_blocks, settings().initial_subsidy()))) return ec; diff --git a/src/protocols/protocol_block_in.cpp b/src/protocols/protocol_block_in.cpp index 55c57dfb..1a6a206e 100644 --- a/src/protocols/protocol_block_in.cpp +++ b/src/protocols/protocol_block_in.cpp @@ -85,7 +85,7 @@ bool protocol_block_in::handle_receive_inventory(const code& ec, // Work on only one block inventory at a time. if (!tracker_.ids.empty()) { - LOGP("Received unrequested (" << block_count + LOGP("Unrequested (" << block_count << ") block inventory from [" << authority() << "] with (" << tracker_.ids.size() << ") pending."); return true; @@ -99,22 +99,20 @@ bool protocol_block_in::handle_receive_inventory(const code& ec, // If getter is empty it may be because we have them all. if (getter.items.empty()) { - // Send assumes create_get_inventory back item is block hash. // The inventory response to get_blocks is limited to max_get_blocks. if (block_count == max_get_blocks) { - LOGP("Get inventory [" << authority() << "] (empty maximal)."); - SEND(create_get_inventory(message->items.back().hash), - handle_send, _1); + const auto& last = message->items.back().hash; + SEND(create_get_inventory(last), handle_send, _1); } // A non-maximal inventory has no new blocks, assume complete. // Inventory completeness assumes empty response if caught up at 500. - LOGP("Complete inventory [" << authority() << "]."); + LOGP("Completed block inventory from [" << authority() << "]."); return true; } - LOGP("Requesting (" << getter.items.size() << ") blocks from [" + LOGP("Requested (" << getter.items.size() << ") blocks from [" << authority() << "]."); // Track inventory and request blocks (to_hashes order is reversed). @@ -142,7 +140,7 @@ bool protocol_block_in::handle_receive_block(const code& ec, // Unrequested block, may not have been announced via inventory. if (tracker_.ids.find(block_ptr->hash()) == tracker_.ids.end()) { - LOGP("Received unrequested block [" << encode_hash(block_ptr->hash()) + LOGP("Unrequested block [" << encode_hash(block_ptr->hash()) << "] from [" << authority() << "]."); return true; } @@ -156,17 +154,14 @@ bool protocol_block_in::handle_receive_block(const code& ec, void protocol_block_in::handle_organize(const code& ec, size_t height, const chain::block::cptr& block_ptr) NOEXCEPT { + // Chaser may be stopped before protocol. if (stopped() || ec == network::error::service_stopped) return; - // This ignores order as that is enforced by organize, unordered is faster. - if (is_zero(tracker_.ids.erase(block_ptr->hash()))) - { - LOGF("Unexpected block from organizer."); - return; - } + // Order is enforced by organize. + tracker_.ids.erase(block_ptr->hash()); - // Must erase (above). + // Must erase duplicates, handled above. if (ec == error::duplicate_block) return; @@ -191,7 +186,7 @@ void protocol_block_in::handle_organize(const code& ec, size_t height, } LOGP("Block [" << encode_hash(block_ptr->hash()) << ":" << height - << "] from [" << authority() << "] " << ec.message()); + << "] from [" << authority() << "]."); // Completion of tracked inventory. if (tracker_.ids.empty()) @@ -199,14 +194,13 @@ void protocol_block_in::handle_organize(const code& ec, size_t height, // Protocol presumes max_get_blocks unless complete. if (tracker_.announced == max_get_blocks) { - LOGP("Get inventory [" << authority() << "] (exhausted maximal)."); SEND(create_get_inventory(tracker_.last), handle_send, _1); } else { // Completeness stalls if on 500 as empty message is ambiguous. // This is ok, since complete is not used for anything essential. - LOGP("Complete blocks [" << authority() << "] with (" + LOGP("Completed blocks from [" << authority() << "] with (" << tracker_.announced << ") announced."); } } @@ -222,8 +216,8 @@ get_blocks protocol_block_in::create_get_inventory() const NOEXCEPT // This will bypass all blocks with candidate headers, resulting in block // orphans if headers-first is run followed by a restart and blocks-first. const auto& query = archive(); - return create_get_inventory(query.get_candidate_hashes(get_blocks::heights( - query.get_top_candidate()))); + const auto index = get_blocks::heights(query.get_top_candidate()); + return create_get_inventory(query.get_candidate_hashes(index)); } get_blocks protocol_block_in::create_get_inventory( @@ -235,11 +229,19 @@ get_blocks protocol_block_in::create_get_inventory( get_blocks protocol_block_in::create_get_inventory( hashes&& hashes) const NOEXCEPT { - if (!hashes.empty()) + if (hashes.empty()) + return {}; + + if (hashes.size() == one) { - LOGP("Request blocks after [" << encode_hash(hashes.front()) + LOGP("Request block inventory after [" << encode_hash(hashes.back()) << "] from [" << authority() << "]."); } + else + { + LOGP("Request block inventory (" << hashes.size() << ") after [" + << encode_hash(hashes.back()) << "] from [" << authority() << "]."); + } return { std::move(hashes) }; } diff --git a/src/protocols/protocol_header_in_31800.cpp b/src/protocols/protocol_header_in_31800.cpp index 7ca27237..0eb80d89 100644 --- a/src/protocols/protocol_header_in_31800.cpp +++ b/src/protocols/protocol_header_in_31800.cpp @@ -84,27 +84,20 @@ bool protocol_header_in_31800::handle_receive_headers(const code& ec, // The headers response to get_headers is limited to max_get_headers. if (message->header_ptrs.size() == max_get_headers) { - SEND(create_get_headers(message->header_ptrs.back()->hash()), - handle_send, _1); + const auto last = message->header_ptrs.back()->hash(); + SEND(create_get_headers(last), handle_send, _1); } else { // Protocol presumes max_get_headers unless complete. // Completeness assumes empty response from peer if caught up at 2000. + LOGP("Completed headers from [" << authority() << "]."); complete(); } return true; } -// This could be the end of a catch-up sequence, or a singleton announcement. -// The distinction is ultimately arbitrary, but this signals peer completeness. -void protocol_header_in_31800::complete() NOEXCEPT -{ - BC_ASSERT(stranded()); - LOGP("Headers from [" << authority() << "] exhausted."); -} - void protocol_header_in_31800::handle_organize(const code& ec, size_t height, const chain::header::cptr& LOG_ONLY(header_ptr)) NOEXCEPT { @@ -135,6 +128,13 @@ void protocol_header_in_31800::handle_organize(const code& ec, << "] from [" << authority() << "] " << ec.message()); } +// This could be the end of a catch-up sequence, or a singleton announcement. +// The distinction is ultimately arbitrary, but this signals peer completeness. +void protocol_header_in_31800::complete() NOEXCEPT +{ + BC_ASSERT(stranded()); +} + // utilities // ---------------------------------------------------------------------------- @@ -144,8 +144,8 @@ get_headers protocol_header_in_31800::create_get_headers() const NOEXCEPT // Until the header tree is current the candidate chain remains empty. // So all channels will fully sync from the top candidate at their startup. const auto& query = archive(); - return create_get_headers(query.get_candidate_hashes(get_headers::heights( - query.get_top_candidate()))); + const auto index = get_headers::heights(query.get_top_candidate()); + return create_get_headers(query.get_candidate_hashes(index)); } get_headers protocol_header_in_31800::create_get_headers( @@ -157,11 +157,19 @@ get_headers protocol_header_in_31800::create_get_headers( get_headers protocol_header_in_31800::create_get_headers( hashes&& hashes) const NOEXCEPT { - if (!hashes.empty()) + if (hashes.empty()) + return {}; + + if (hashes.size() == one) { - LOGP("Request headers after [" << encode_hash(hashes.front()) + LOGP("Request headers after [" << encode_hash(hashes.back()) << "] from [" << authority() << "]."); } + else + { + LOGP("Request headers (" << hashes.size() << ") after [" + << encode_hash(hashes.back()) << "] from [" << authority() << "]."); + } return { std::move(hashes) }; } diff --git a/src/protocols/protocol_header_in_70012.cpp b/src/protocols/protocol_header_in_70012.cpp index 76cf1d38..02d53eed 100644 --- a/src/protocols/protocol_header_in_70012.cpp +++ b/src/protocols/protocol_header_in_70012.cpp @@ -38,7 +38,7 @@ void protocol_header_in_70012::complete() NOEXCEPT if (!sent_) { SEND(send_headers{}, handle_send, _1); - LOGP("Request header announcements from [" << authority() << "]."); + LOGP("Requested header announcements from [" << authority() << "]."); sent_ = true; } }